'', '_gb_overlay_display_condition_invert' => false, '_gb_overlay_trigger_type' => 'click', '_gb_overlay_type' => 'standard', '_gb_overlay_placement' => 'bottom-start', '_gb_overlay_backdrop' => true, '_gb_overlay_backdrop_color' => 'rgba(0, 0, 0, 0.5)', '_gb_overlay_backdrop_blur' => '', '_gb_overlay_animation_in' => '', '_gb_overlay_animation_out' => '', '_gb_overlay_animation_duration' => '', '_gb_overlay_animation_target' => '', '_gb_overlay_animation_distance' => '', '_gb_overlay_scroll_percent' => '', '_gb_overlay_time_delay' => '', '_gb_overlay_cookie_duration' => '', '_gb_overlay_close_on_esc' => true, '_gb_overlay_close_on_click_outside' => true, '_gb_overlay_disable_page_scroll' => false, '_gb_overlay_position' => 'center', '_gb_overlay_custom_event' => '', '_gb_overlay_hide_if_cookies_disabled' => false, '_gb_overlay_position_to_parent' => '', '_gb_overlay_hover_buffer' => '20', '_gb_overlay_width_mode' => '', ); } /** * Register our post meta. */ public function register_post_meta() { $defaults = self::get_meta_defaults(); $meta_fields = array( '_gb_overlay_display_condition' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_display_condition_invert' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_trigger_type' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { $allowed = [ 'click', 'hover', 'both', 'exit-intent', 'scroll', 'time', 'custom' ]; return in_array( $value, $allowed ) ? $value : 'click'; }, ), '_gb_overlay_type' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_placement' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_backdrop' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_backdrop_color' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_backdrop_blur' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_animation_in' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_animation_out' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_animation_duration' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { return '' === $value ? '' : absint( $value ); }, ), '_gb_overlay_animation_target' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_animation_distance' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_scroll_percent' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { return '' === $value ? '' : max( 0, min( 100, absint( $value ) ) ); }, ), '_gb_overlay_time_delay' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { return '' === $value ? '' : absint( $value ); }, ), '_gb_overlay_cookie_duration' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { return '' === $value ? '' : absint( $value ); }, ), '_gb_overlay_close_on_esc' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_close_on_click_outside' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_disable_page_scroll' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_position' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { $allowed = [ 'center', 'top-left', 'top-center', 'top-right', 'center-left', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right' ]; return in_array( $value, $allowed ) ? $value : 'center'; }, ), '_gb_overlay_custom_event' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_hide_if_cookies_disabled' => array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ), '_gb_overlay_position_to_parent' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), '_gb_overlay_hover_buffer' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { return '' === $value ? '' : max( 0, min( 100, absint( $value ) ) ); }, ), '_gb_overlay_width_mode' => array( 'type' => 'string', 'sanitize_callback' => function ( $value ) { $allowed = [ '', 'full' ]; return in_array( $value, $allowed ) ? $value : ''; }, ), ); foreach ( $meta_fields as $key => $args ) { register_post_meta( 'gblocks_overlay', $key, array( 'single' => true, 'type' => $args['type'], 'auth_callback' => static function() { return GenerateBlocks_Pro_Overlays::current_user_can_use_overlays( 'manage' ); }, 'sanitize_callback' => $args['sanitize_callback'], 'show_in_rest' => true, 'default' => $defaults[ $key ], ) ); } } /** * Get overlay meta with proper defaults. * This uses WordPress's built-in caching, so multiple calls are efficient. * * @param int $post_id Post ID. * @param string $meta_key Meta key. * @return mixed Meta value or default. */ public static function get_overlay_meta( $post_id, $meta_key ) { $defaults = self::get_meta_defaults(); // Check if meta exists in database. $meta_exists = metadata_exists( 'post', $post_id, $meta_key ); if ( ! $meta_exists && isset( $defaults[ $meta_key ] ) ) { // Meta doesn't exist, return default. return $defaults[ $meta_key ]; } // Meta exists, get the value. $value = get_post_meta( $post_id, $meta_key, true ); // For boolean fields, properly convert WordPress storage to boolean. // WordPress stores: true as '1', false as '' (empty string). if ( isset( $defaults[ $meta_key ] ) && is_bool( $defaults[ $meta_key ] ) ) { return '1' === $value; } // For string/number fields, if empty and has default, return default. if ( '' === $value && isset( $defaults[ $meta_key ] ) && ! is_bool( $defaults[ $meta_key ] ) ) { return $defaults[ $meta_key ]; } return $value; } /** * Get all overlay meta at once for better performance. * WordPress caches all meta after first query, but this can be more convenient. * * @param int $post_id Post ID. * @return array All overlay meta with defaults applied. */ public static function get_all_overlay_meta( $post_id ) { $defaults = self::get_meta_defaults(); $meta = array(); foreach ( $defaults as $key => $default ) { $meta[ $key ] = self::get_overlay_meta( $post_id, $key ); } return $meta; } /** * Register the REST field for the admin edit URL. * * @return void */ public function register_rest() { register_rest_field( 'gblocks_overlay', 'admin_edit_url', array( 'get_callback' => function ( $post_object ) { if ( current_user_can( 'edit_post', $post_object['id'] ) ) { return get_edit_post_link( $post_object['id'], 'raw' ); } return null; }, 'schema' => array( 'description' => 'Admin URL to edit the post.', 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit' ), ), ) ); register_rest_field( 'gblocks_overlay', 'trigger_info', array( 'get_callback' => function ( $post_object ) { $trigger_type = self::get_overlay_meta( $post_object['id'], '_gb_overlay_trigger_type' ); // Only return data for interactive overlays. if ( ! in_array( $trigger_type, array( 'click', 'hover', 'both' ), true ) ) { return null; } return array( 'trigger_type' => $trigger_type, 'is_interactive' => true, ); }, 'schema' => array( 'description' => 'Overlay trigger information for block editor.', 'type' => 'object', 'context' => array( 'view', 'edit' ), 'properties' => array( 'trigger_type' => array( 'type' => 'string', 'enum' => array( 'click', 'hover', 'both' ), ), 'is_interactive' => array( 'type' => 'boolean', ), ), ), ) ); add_filter( 'rest_gblocks_overlay_collection_params', array( $this, 'increase_overlay_collection_limit' ), 10, 1 ); } /** * Increase the per_page limit for overlay collections. * This is the proper WordPress way to handle this. * * @param array $params Collection parameters. * @return array Modified parameters. */ public function increase_overlay_collection_limit( $params ) { // Increase limit to reasonable amount (not unlimited). if ( isset( $params['per_page'] ) ) { $params['per_page']['maximum'] = 200; // Reasonable limit for overlays. $params['per_page']['default'] = 100; // Keep default reasonable. } return $params; } /** * Get the capability required for overlays. * * @since 2.4.0 * @param string $context The context: 'use' (select existing) or 'manage' (create/edit/dashboard). * @return string The capability required. */ public static function get_overlays_capability( $context = 'use' ) { // Default capabilities. if ( 'manage' === $context ) { // Can create/edit/access dashboard - default to manage_options. $capability = 'manage_options'; } else { // Can select/use existing overlays - anyone who can edit posts. $capability = 'edit_posts'; } /** * Filter the capability required for overlays. * * @since 2.4.0 * @param string $capability The capability required. * @param string $context The context: 'use' or 'manage'. */ return apply_filters( 'generateblocks_overlays_capability', $capability, $context ); } /** * Check if current user can use overlays. * * @since 2.4.0 * @param string $context The context: 'use' (select existing) or 'manage' (create/edit/dashboard). * @return bool */ public static function current_user_can_use_overlays( $context = 'use' ) { $capability = self::get_overlays_capability( $context ); return current_user_can( $capability ); } /** * Add the Site Editor menu item. * * @return void */ public function admin_menu() { // Get the required capability. $capability = self::get_overlays_capability( 'manage' ); // Only add menu if user has permission. if ( ! current_user_can( $capability ) ) { return; } add_submenu_page( 'generateblocks', __( 'Overlay Panels', 'generateblocks-pro' ), __( 'Overlay Panels', 'generateblocks-pro' ), $capability, 'generateblocks-overlay-panels', array( $this, 'render_page' ), 4 ); } /** * Render the Site Editor page. * * @return void */ public function render_page() { // Double-check permission before rendering. if ( ! self::current_user_can_use_overlays( 'manage' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to access this page.', 'generateblocks-pro' ) ); } ?>