'metaforge_group', 'posts_per_page' => -1, 'post_status' => 'publish', ) ); foreach ( $posts as $post ) { $group = get_post_meta( $post->ID, '_metaforge_group_config', true ); if ( is_array( $group ) && ! empty( $group['fields'] ) ) { metaforge_build_flat_map_recursive( $group['fields'], $flat_map ); } } set_transient( 'metaforge_field_flat_map', $flat_map, HOUR_IN_SECONDS * 24 ); } $result = $flat_map[ $field_name ] ?? null; return $cache[ $field_name ] = $result; } function metaforge_build_flat_map_recursive( array $fields, array &$flat_map ) { foreach ( $fields as $field ) { if ( ! is_array( $field ) ) { continue; } if ( ! empty( $field['name'] ) ) { $flat_map[ $field['name'] ] = $field; } if ( ! empty( $field['sub_fields'] ) && is_array( $field['sub_fields'] ) ) { metaforge_build_flat_map_recursive( $field['sub_fields'], $flat_map ); } if ( ! empty( $field['layouts'] ) && is_array( $field['layouts'] ) ) { foreach ( $field['layouts'] as $fc_layout ) { if ( ! empty( $fc_layout['sub_fields'] ) && is_array( $fc_layout['sub_fields'] ) ) { metaforge_build_flat_map_recursive( $fc_layout['sub_fields'], $flat_map ); } } } } } // ┌─────────────────────────────────────────────────────────────────────────┐ // │ INTERNAL — Per-type value formatter │ // └─────────────────────────────────────────────────────────────────────────┘ /** * Central formatter — converts any raw DB value into the correct PHP value * based on field type. Recurses into repeaters and groups automatically. * * @param mixed $value Raw value from DB. * @param array $field Field config from metaforge_find_field_config(). * @return mixed */ function metaforge_format_value( $value, array $field ) { $type = $field['type'] ?? 'text'; if ( $type === 'repeater' ) { return metaforge_format_repeater_rows( $value, $field['sub_fields'] ?? array() ); } if ( $type === 'group' ) { return metaforge_format_group_value( $value, $field['sub_fields'] ?? array() ); } if ( class_exists( 'MetaForge_Field_Registry' ) ) { $registry_instance = MetaForge_Field_Registry::get_field_type( $type ); if ( $registry_instance ) { $reflector = new ReflectionMethod( $registry_instance, 'format_value' ); if ( $reflector->getDeclaringClass()->getName() !== 'MetaForge_Field_Type' ) { return $registry_instance->format_value( $value, 0, $field ); } } } switch ( $type ) { case 'textarea': $new_lines = isset( $field['new_lines'] ) ? $field['new_lines'] : 'br'; if ( is_string( $value ) ) { if ( $new_lines === 'wpautop' ) { return ( strpos( $value, '
' ) !== false || strpos( $value, '
(string) $value, 'title' => '', 'text' => '', 'target' => '', ); case 'date_time': return $value; case 'oembed': if ( empty( $value ) ) { return ''; } if ( is_string( $value ) && strpos( $value, ''; } return wp_oembed_get( $value ) ?: $value; case 'relationship': if ( empty( $value ) ) { return array(); } $ids = is_array( $value ) ? $value : explode( ',', (string) $value ); $ids = array_filter( array_map( 'intval', $ids ) ); if ( empty( $ids ) ) { return array(); } $fetched_posts = get_posts( array( 'post__in' => $ids, 'posts_per_page' => -1, 'post_type' => 'any', 'orderby' => 'post__in', ) ); if ( ! empty( $fetched_posts ) ) { metaforge_prime_post_cache( wp_list_pluck( $fetched_posts, 'ID' ) ); } return $fetched_posts; case 'gallery': if ( empty( $value ) ) { return array(); } $ids = is_array( $value ) ? $value : explode( ',', (string) $value ); $ids = array_filter( array_map( 'intval', $ids ) ); $format = $field['image_return_format'] ?? 'array'; $gallery = array(); foreach ( $ids as $id ) { $img = metaforge_format_image( $id, $format ); if ( $img ) { $gallery[] = $img; } } return $gallery; case 'user': if ( class_exists( 'MetaForge_UserField' ) ) { $uf = new MetaForge_UserField(); return $uf->format_value( $value, 0, $field ); } return $value; case 'flexible_content': if ( class_exists( 'MetaForge_FlexibleContentField' ) ) { $fcf = new MetaForge_FlexibleContentField(); return $fcf->format_value( $value, 0, $field ); } return metaforge_format_repeater_rows( $value, $field['sub_fields'] ?? array() ); case 'wysiwyg': return wpautop( $value ); default: return $value; } } /** * Image: attachment ID → array / url / id * * 'array' returns: * [ * 'ID' => 18, * 'id' => 18, * 'url' => 'https://example.com/.../photo.jpg', * 'alt' => 'Alt text', * 'title' => 'Photo Title', * 'caption' => '', * 'filename' => 'photo.jpg', * 'width' => 1920, * 'height' => 1080, * 'mime_type'=> 'image/jpeg', * 'sizes' => [ 'thumbnail' => ['url'=>..., 'width'=>150, 'height'=>150], ... ] * ] */ function metaforge_format_image( $attachment_id, $format = 'array' ) { if ( is_array( $attachment_id ) ) { if ( $format === 'url' ) { return $attachment_id['url'] ?? ''; } if ( $format === 'id' ) { return $attachment_id['ID'] ?? ( $attachment_id['id'] ?? 0 ); } return $attachment_id; } $attachment_id = intval( $attachment_id ); if ( ! $attachment_id ) { return ( $format === 'id' ) ? 0 : ( ( $format === 'url' ) ? '' : null ); } if ( $format === 'id' ) { return (int) $attachment_id; } if ( $format === 'url' ) { return wp_get_attachment_url( $attachment_id ) ?: ''; } // format === 'array' $post = get_post( $attachment_id ); $meta = wp_get_attachment_metadata( $attachment_id ); $full_src = wp_get_attachment_image_src( $attachment_id, 'full' ); $img_url = $full_src ? $full_src[0] : ( wp_get_attachment_url( $attachment_id ) ?: '' ); if ( ! $post || empty( $img_url ) ) { return null; } $mime = get_post_mime_type( $attachment_id ); $alt = (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); $sizes = array(); if ( ! empty( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) { $upload_dir = wp_upload_dir(); $base_url = trailingslashit( $upload_dir['baseurl'] ); $dir = isset( $meta['file'] ) ? trailingslashit( dirname( $meta['file'] ) ) : ''; foreach ( $meta['sizes'] as $size_name => $data ) { if ( ! empty( $data['file'] ) ) { $sizes[ $size_name ] = array( 'url' => $base_url . $dir . $data['file'], 'width' => (int) ( $data['width'] ?? 0 ), 'height' => (int) ( $data['height'] ?? 0 ), 'mime' => $data['mime-type'] ?? $mime, ); } } } $file_path = (string) get_attached_file( $attachment_id ); return array( 'ID' => $attachment_id, 'id' => $attachment_id, 'title' => get_the_title( $post ), 'filename' => ! empty( $file_path ) ? basename( $file_path ) : '', 'url' => $img_url, 'alt' => $alt, 'caption' => $post->post_excerpt, 'description' => $post->post_content, 'mime_type' => $mime, 'width' => ! empty( $full_src[1] ) ? (int) $full_src[1] : ( ! empty( $meta['width'] ) ? (int) $meta['width'] : 0 ), 'height' => ! empty( $full_src[2] ) ? (int) $full_src[2] : ( ! empty( $meta['height'] ) ? (int) $meta['height'] : 0 ), 'sizes' => $sizes, ); } /** File: attachment ID → structured array */ function metaforge_format_file( $attachment_id ) { if ( is_array( $attachment_id ) ) { return $attachment_id; } $attachment_id = intval( $attachment_id ); if ( ! $attachment_id ) { return null; } $post = get_post( $attachment_id ); return array( 'ID' => $attachment_id, 'id' => $attachment_id, 'title' => $post ? get_the_title( $post ) : 'Download File', 'filename' => basename( (string) get_attached_file( $attachment_id ) ), 'url' => wp_get_attachment_url( $attachment_id ) ?: '', 'mime_type' => get_post_mime_type( $attachment_id ) ?: '', 'link' => get_attachment_link( $attachment_id ) ?: '', ); } /** * Repeater: format every sub-field in every row using each sub-field's own config. * Recursion: if a sub-field is itself a group or repeater, it formats those too. */ function metaforge_format_repeater_rows( $rows, array $sub_field_configs ) { if ( ! is_array( $rows ) || empty( $rows ) ) { return array(); } $config_map = array(); foreach ( $sub_field_configs as $cfg ) { if ( ! empty( $cfg['name'] ) ) { $config_map[ $cfg['name'] ] = $cfg; } } $formatted = array(); foreach ( array_values( $rows ) as $row ) { if ( ! is_array( $row ) ) { continue; } $formatted_row = array(); foreach ( $row as $key => $val ) { if ( isset( $config_map[ $key ] ) ) { $formatted_row[ $key ] = metaforge_format_value( $val, $config_map[ $key ] ); } else { $field_cfg = metaforge_find_field_config( $key ); $formatted_row[ $key ] = $field_cfg ? metaforge_format_value( $val, $field_cfg ) : $val; } } $formatted[] = $formatted_row; } return $formatted; } /** * Group: format every sub-field using its own config. * Recursion: nested repeaters/groups inside this group are also formatted. */ function metaforge_format_group_value( $value, array $sub_field_configs ) { if ( ! is_array( $value ) ) { return $value; } $config_map = array(); foreach ( $sub_field_configs as $cfg ) { if ( ! empty( $cfg['name'] ) ) { $config_map[ $cfg['name'] ] = $cfg; } } $formatted = array(); foreach ( $value as $key => $val ) { if ( isset( $config_map[ $key ] ) ) { $formatted[ $key ] = metaforge_format_value( $val, $config_map[ $key ] ); } else { $field_cfg = metaforge_find_field_config( $key ); $formatted[ $key ] = $field_cfg ? metaforge_format_value( $val, $field_cfg ) : $val; } } return $formatted; } if ( ! function_exists( 'metaforge_format_field' ) ) { /** * Format any raw field, group, or subfield value using its registered configuration. * * @param string $field_name Field name. * @param mixed $value Raw value from postmeta or layout row. * @return mixed Formatted value (e.g. image array, formatted group, etc.) */ function metaforge_format_field( $field_name, $value = null ) { $field = metaforge_find_field_config( $field_name ); if ( ! $field ) { return $value; } return metaforge_format_value( $value, $field ); } } // ┌─────────────────────────────────────────────────────────────────────────┐ // │ PUBLIC API │ // └─────────────────────────────────────────────────────────────────────────┘ /** * Internal: Resolve raw database value from post, term, user, comment, or options store. * Supports string entity formats such as 'term_12', 'taxonomy_term_12', 'user_4', 'comment_9', 'options'. * * @param string $field_name Field name. * @param mixed $post_id Post ID or object identifier string. * @return mixed */ function metaforge_resolve_raw_meta( $field_name, $post_id = null ) { // 1. Check if we are inside a layout template with global $layout if ( isset( $GLOBALS['layout'] ) && is_array( $GLOBALS['layout'] ) && array_key_exists( $field_name, $GLOBALS['layout'] ) ) { return $GLOBALS['layout'][ $field_name ]; } // 2. Check if we are in an active metaforge loop stack (metaforge_have_rows) global $metaforge_loop_stack; if ( ! empty( $metaforge_loop_stack ) && is_array( $metaforge_loop_stack ) ) { $i = count( $metaforge_loop_stack ) - 1; $idx = $metaforge_loop_stack[ $i ]['index'] ?? -1; if ( $idx >= 0 && isset( $metaforge_loop_stack[ $i ]['rows'][ $idx ][ $field_name ] ) ) { return $metaforge_loop_stack[ $i ]['rows'][ $idx ][ $field_name ]; } } if ( $post_id === null || $post_id === false || $post_id === '' ) { $post_id = get_the_ID(); } if ( ! $post_id ) { return null; } // Handle string identifier formats if ( is_string( $post_id ) ) { $post_id_trimmed = trim( $post_id ); $post_id_lower = strtolower( $post_id_trimmed ); if ( $post_id_lower === 'options' || $post_id_lower === 'option' ) { $opt_posts = get_posts( array( 'post_type' => 'metaforge_options', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ) ); if ( ! empty( $opt_posts ) ) { foreach ( $opt_posts as $oid ) { $v = get_post_meta( $oid, $field_name, true ); if ( $v !== '' && $v !== null ) { return $v; } } } return null; } if ( strpos( $post_id_lower, 'term_' ) === 0 ) { $term_id = absint( substr( $post_id_lower, 5 ) ); return $term_id ? get_term_meta( $term_id, $field_name, true ) : null; } if ( strpos( $post_id_lower, 'taxonomy_term_' ) === 0 ) { $term_id = absint( substr( $post_id_lower, 14 ) ); return $term_id ? get_term_meta( $term_id, $field_name, true ) : null; } if ( strpos( $post_id_lower, 'user_' ) === 0 ) { $user_id = absint( substr( $post_id_lower, 5 ) ); return $user_id ? get_user_meta( $user_id, $field_name, true ) : null; } if ( strpos( $post_id_lower, 'comment_' ) === 0 ) { $comment_id = absint( substr( $post_id_lower, 8 ) ); return $comment_id ? get_comment_meta( $comment_id, $field_name, true ) : null; } } $numeric_id = absint( $post_id ); if ( ! $numeric_id ) { return null; } return get_post_meta( $numeric_id, $field_name, true ); } if ( ! function_exists( 'metaforge_get_field' ) ) { /** * metaforge_get_field( $field_name, $post_id ) * * Get any single field value — auto-formatted by type. * Supports post IDs, 'term_12', 'user_5', 'comment_3', or 'options'. * * metaforge_get_field('hero_image') → image array (or url/id per builder setting) * metaforge_get_field('category_icon', 'term_15') → formatted field for term * metaforge_get_field('bio_link', 'user_2') → formatted field for user * metaforge_get_field('layouts') → fully formatted repeater rows * metaforge_get_field('page_title') → plain string */ function metaforge_get_field( $field_name, $post_id = null ) { $value = metaforge_resolve_raw_meta( $field_name, $post_id ); $field = metaforge_find_field_config( $field_name ); return $field ? metaforge_format_value( $value, $field ) : $value; } } if ( ! function_exists( 'metaforge_the_field' ) ) { /** * metaforge_the_field( $field_name, $post_id ) * * Directly echo a formatted field value. */ function metaforge_the_field( $field_name, $post_id = null ) { $value = metaforge_get_field( $field_name, $post_id ); if ( is_null( $value ) || false === $value || '' === $value ) { return; } if ( is_array( $value ) || is_object( $value ) ) { echo esc_html( (string) wp_json_encode( $value ) ); } else { if ( strpos( (string) $value, '