array( 'src' => true, 'height' => true, 'width' => true, 'frameborder' => true, 'allowfullscreen' => true, 'style' => true, 'loading' => true, 'referrerpolicy' => true, 'title' => true, 'allow' => true, ), ) ); // 1. Raw iframe embed snippet (e.g. Google Maps or custom iframe embed) if ( strpos( $raw_input, ''; wp_send_json_success( $map_html ); } } // 2. Google Maps URL direct embed if ( strpos( $raw_input, 'google.com/maps' ) !== false || strpos( $raw_input, 'maps.google.com' ) !== false ) { $escaped_src = esc_url( $raw_input ); $map_html = ''; wp_send_json_success( $map_html ); } // 3. Standard oEmbed provider (YouTube, Vimeo, Spotify, etc.) $url = esc_url_raw( $raw_input ); if ( ! empty( $url ) ) { $html = wp_oembed_get( $url ); if ( $html ) { wp_send_json_success( wp_kses( $html, $allowed_embed ) ); } } wp_send_json_error(); } /** * AJAX: Search posts/pages for relationship fields. */ public function ajax_relationship_search() { check_ajax_referer( 'metaforge_relationship_search_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( 'Forbidden' ); } $search = sanitize_text_field( wp_unslash( $_POST['search'] ?? '' ) ); $post_types = isset( $_POST['post_types'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['post_types'] ) ) : array( 'post', 'page' ); $args = array( 'post_type' => $post_types, 'posts_per_page' => 20, 's' => $search, 'post_status' => 'publish', ); $posts = get_posts( $args ); $results = array(); foreach ( $posts as $p ) { $results[] = array( 'id' => $p->ID, 'title' => get_the_title( $p ), 'post_type' => get_post_type_object( $p->post_type )->labels->singular_name, ); } wp_send_json_success( $results ); } /** * AJAX: Return taxonomy terms grouped by taxonomy for a given post type slug. * Used by the dynamic taxonomy field feature in field.js. */ public function ajax_get_dynamic_taxonomies() { check_ajax_referer( 'metaforge_dynamic_tax_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( 'Forbidden' ); } $post_type = sanitize_key( $_POST['post_type'] ?? '' ); if ( empty( $post_type ) ) { wp_send_json_error( array( 'message' => 'No post type provided.' ) ); } // Get all taxonomies linked to this post type $taxonomy_slugs = get_object_taxonomies( $post_type, 'objects' ); if ( empty( $taxonomy_slugs ) ) { wp_send_json_success( array( 'groups' => array() ) ); } $groups = array(); foreach ( $taxonomy_slugs as $tax_slug => $tax_obj ) { $terms = get_terms( array( 'taxonomy' => $tax_slug, 'hide_empty' => false, ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { continue; } $term_list = array(); foreach ( $terms as $term ) { $term_list[] = array( 'id' => $term->term_id, 'name' => $term->name, ); } $groups[] = array( 'slug' => $tax_slug, 'label' => $tax_obj->label, 'terms' => $term_list, ); } wp_send_json_success( array( 'groups' => $groups ) ); } public function get_groups_by_context( array $context ) { $db_groups = get_transient( 'metaforge_all_field_groups_cache' ); if ( false === $db_groups ) { $posts = get_posts( array( 'post_type' => 'metaforge_group', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'menu_order', 'order' => 'ASC', ) ); $db_groups = array(); foreach ( $posts as $post ) { $config = get_post_meta( $post->ID, '_metaforge_group_config', true ); if ( is_array( $config ) ) { $db_groups[] = $config; } } set_transient( 'metaforge_all_field_groups_cache', $db_groups, HOUR_IN_SECONDS * 24 ); } global $metaforge_local_field_groups; $local_groups = is_array( $metaforge_local_field_groups ) ? array_values( $metaforge_local_field_groups ) : array(); $groups = array_merge( $db_groups, $local_groups ); return array_filter( $groups, function ( $group ) use ( $context ) { if ( ! empty( $group['location_rules'] ) && is_array( $group['location_rules'] ) ) { $group_match = false; foreach ( $group['location_rules'] as $rule_group ) { if ( ! is_array( $rule_group ) || empty( $rule_group ) ) { continue; } $rule_group_match = true; foreach ( $rule_group as $rule ) { $param = $rule['param'] ?? ''; $operator = $rule['operator'] ?? '=='; $value = $rule['value'] ?? ''; $match = false; if ( $param === 'post_type' && isset( $context['post_type'] ) ) { $match = ( $value === $context['post_type'] ); } elseif ( $param === 'page_template' && isset( $context['post_type'] ) ) { if ( $context['post_type'] !== 'page' && ! post_type_supports( $context['post_type'], 'page-attributes' ) ) { $match = false; } else { $current_post_id = absint( $context['post_id'] ?? 0 ); if ( $current_post_id ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only inspection of core WordPress page_template during meta box location rule matching. $posted_template = ! empty( $_POST['page_template'] ) ? sanitize_text_field( wp_unslash( $_POST['page_template'] ) ) : ''; $current_template = $posted_template ?: get_page_template_slug( $current_post_id ); if ( ! $current_template ) { $current_template = get_post_meta( $current_post_id, '_wp_page_template', true ); } if ( ! $current_template ) { $current_template = 'default'; } $val_clean = str_replace( '\\', '/', trim( (string) $value ) ); $cur_clean = str_replace( '\\', '/', trim( (string) $current_template ) ); if ( $val_clean === $cur_clean ) { $match = true; } elseif ( $val_clean === 'default' || $cur_clean === 'default' ) { $match = false; } else { // 1. Compare basenames (e.g. "page-directory-listing.php" vs "templates/page-directory-listing.php") if ( basename( $val_clean ) === basename( $cur_clean ) ) { $match = true; } elseif ( sanitize_title( basename( $val_clean, '.php' ) ) === sanitize_title( basename( $cur_clean, '.php' ) ) ) { $match = true; } else { // 2. Cross-reference with theme's registered page templates $theme_templates = wp_get_theme()->get_page_templates(); foreach ( $theme_templates as $t_file => $t_name ) { $t_file_clean = str_replace( '\\', '/', trim( (string) $t_file ) ); $is_val = ( $val_clean === $t_name || $val_clean === $t_file_clean || basename( $val_clean ) === basename( $t_file_clean ) || sanitize_title( $val_clean ) === sanitize_title( $t_name ) ); $is_cur = ( $cur_clean === $t_name || $cur_clean === $t_file_clean || basename( $cur_clean ) === basename( $t_file_clean ) || sanitize_title( $cur_clean ) === sanitize_title( $t_name ) ); if ( $is_val && $is_cur ) { $match = true; break; } } } } } else { $match = true; } } } elseif ( $param === 'post_status' ) { $current_post_id = absint( $context['post_id'] ?? 0 ); if ( $current_post_id ) { $current_status = get_post_status( $current_post_id ); $match = ( $value === $current_status ); } else { $match = true; } } elseif ( $param === 'page_type' ) { $current_post_id = absint( $context['post_id'] ?? 0 ); $front_page_id = absint( get_option( 'page_on_front' ) ); $posts_page_id = absint( get_option( 'page_for_posts' ) ); if ( $value === 'front_page' ) { $match = ( $current_post_id && $current_post_id === $front_page_id ); } elseif ( $value === 'posts_page' ) { $match = ( $current_post_id && $current_post_id === $posts_page_id ); } elseif ( $value === 'top_level' ) { $parent_id = $current_post_id ? wp_get_post_parent_id( $current_post_id ) : 0; $match = ( $parent_id === 0 ); } elseif ( $value === 'parent' ) { $children = $current_post_id ? get_children( array( 'post_parent' => $current_post_id, 'post_type' => 'page', ) ) : array(); $match = ! empty( $children ); } elseif ( $value === 'child' ) { $parent_id = $current_post_id ? wp_get_post_parent_id( $current_post_id ) : 0; $match = ( $parent_id > 0 ); } } elseif ( $param === 'page' || $param === 'post' ) { $current_post_id = absint( $context['post_id'] ?? 0 ); $match = ( $current_post_id === absint( $value ) ); } elseif ( $param === 'taxonomy' && isset( $context['taxonomy'] ) ) { $match = ( $value === $context['taxonomy'] ); } elseif ( $param === 'user_role' && isset( $context['user_role'] ) ) { $match = ( $value === $context['user_role'] ); } elseif ( $param === 'comment' && isset( $context['comment'] ) ) { $match = ( $value === $context['comment'] ); } elseif ( $param === 'options_page' && isset( $context['options_page'] ) ) { $match = ( $value === $context['options_page'] ); } if ( $operator === '!=' ) { $match = ! $match; } if ( ! $match ) { $rule_group_match = false; break; } } if ( $rule_group_match ) { $group_match = true; break; } } return $group_match; } if ( isset( $context['post_type'] ) ) { return ( $group['post_type'] ?? '' ) === $context['post_type']; } return false; } ); } public function get_groups_by_post_type( $post_type, $post_id = 0 ) { if ( ! $post_id ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing -- Read-only post_id context determination for field group matching. $post_id = absint( wp_unslash( $_GET['post'] ?? 0 ) ) ?: absint( wp_unslash( $_POST['post_ID'] ?? 0 ) ); } return $this->get_groups_by_context( array( 'post_type' => $post_type, 'post_id' => $post_id, ) ); } public static function render_field( $field, $value = '', $custom_name = null ) { $name = $custom_name ?: "metaforge[{$field['name']}]"; $conditions = ( ! empty( $field['conditional_logic'] ) && ! empty( $field['conditions'] ) ) ? $field['conditions'] : array(); $cond_attr = ''; $cond_class = ''; if ( ! empty( $conditions ) ) { $cond_attr = " data-conditions='" . esc_attr( wp_json_encode( $conditions ) ) . "'"; $cond_class = ' mfe-condition-hidden'; } $width = isset( $field['width'] ) ? intval( $field['width'] ) : 100; if ( ! in_array( $width, array( 25, 33, 50, 66, 75, 100 ), true ) ) { $width = 100; } $width_class = " mfe-width-{$width}"; echo '
'; $type = $field['type'] ?? 'text'; $registry_instance = MetaForge_Field_Registry::get_field_type( $type ); if ( $registry_instance ) { $registry_instance->render( $field, $value, $name ); } else { $default_types = array( 'text' => 'MetaForge_TextField', 'textarea' => 'MetaForge_TextareaField', 'number' => 'MetaForge_NumberField', 'select' => 'MetaForge_SelectField', 'image' => 'MetaForge_ImageField', 'file' => 'MetaForge_FileField', 'wysiwyg' => 'MetaForge_WysiwygField', 'checkbox' => 'MetaForge_CheckboxField', 'radio' => 'MetaForge_RadioField', 'true_false' => 'MetaForge_TrueFalseField', 'link' => 'MetaForge_LinkField', 'post_type_selector' => 'MetaForge_PostTypeSelectorField', 'taxonomy' => 'MetaForge_TaxonomyField', 'group' => 'MetaForge_GroupField', 'color_picker' => 'MetaForge_ColorPickerField', 'date_time' => 'MetaForge_DateTimeField', 'user' => 'MetaForge_UserField', 'tab' => 'MetaForge_TabField', 'range' => 'MetaForge_RangeField', 'button' => 'MetaForge_ButtonField', ); $field_types = apply_filters( 'metaforge/registered_field_types', $default_types ); if ( isset( $field_types[ $type ] ) && class_exists( $field_types[ $type ] ) ) { $renderer = new $field_types[ $type ](); $renderer->render( $field, $value, $name ); } elseif ( in_array( $type, array( 'flexible_content', 'repeater', 'gallery', 'relationship', 'icon_picker', 'code_editor', 'accordion', 'oembed' ), true ) ) { // Graceful Pro Inactive Display: Do NOT render as TextField/Embed box, and protect existing data! $label = ! empty( $field['label'] ) ? $field['label'] : ucwords( str_replace( '_', ' ', $type ) ); $type_name = ucwords( str_replace( '_', ' ', $type ) ); echo '
'; echo '
'; echo '' . esc_html( $label ) . ' ' . esc_html( $type_name ) . ' ' . esc_html__( '(Pro)', 'metaforge-engine' ) . ''; echo ''; echo '
'; echo '

' . esc_html__( 'This field is powered by MetaForge Pro. Activate the MetaForge Pro plugin to view and edit this content. Existing layout data is safely preserved.', 'metaforge-engine' ) . '

'; echo '
'; } else { $renderer = new MetaForge_TextField(); $renderer->render( $field, $value, $name ); } } echo '
'; } }