Plugin Directory

Changeset 3479820


Ignore:
Timestamp:
03/11/2026 08:25:04 AM (4 weeks ago)
Author:
vinsmach
Message:

Tag version 1.5.0

Location:
mescio-for-agents
Files:
3 edited
7 copied

Legend:

Unmodified
Added
Removed
  • mescio-for-agents/tags/1.5.0/includes/class-markdown-generator.php

    r3479326 r3479820  
    8080                    }
    8181                }
     82            }
     83        }
     84
     85        // Custom fields (ACF or plain post meta)
     86        $custom = self::get_custom_meta( $post );
     87        if ( ! empty( $custom ) ) {
     88            $lines[] = 'custom_fields:';
     89            foreach ( $custom as $key => $value ) {
     90                $lines[] = '  ' . esc_html( $key ) . ': ' . self::yaml_value( $value );
    8291            }
    8392        }
     
    260269        return str_replace( [ '\\', '"', "\n", "\r" ], [ '\\\\', '\\"', '\n', '' ], $value );
    261270    }
     271
     272    // ── Custom meta ───────────────────────────────────────────────────────────
     273
     274    /**
     275     * Meta key prefixes / exact keys produced by common plugins that carry no
     276     * semantic value for an AI agent and should be silently skipped.
     277     *
     278     * The list is intentionally conservative: we only block keys we *know* are
     279     * structural/internal. Unknown user-defined keys are always exposed.
     280     */
     281    const BLOCKED_META_PREFIXES = [
     282        // WordPress internals
     283        '_edit_', '_wp_', '_encloseme', '_pingme', '_thumbnail_id',
     284        // Our own plugin
     285        '_mescio_',
     286        // Elementor
     287        '_elementor_', 'elementor_',
     288        // Yoast SEO
     289        '_yoast_wpseo_', '_yst_',
     290        // RankMath
     291        'rank_math_',
     292        // All in One SEO
     293        '_aioseop_',
     294        // WooCommerce internals (product data is already in its own block)
     295        '_wc_', '_product_', 'total_sales', '_price', '_regular_price',
     296        '_sale_price', '_sku', '_stock', '_weight', '_length', '_width',
     297        '_height', '_upsell_ids', '_crosssell_ids', '_purchase_note',
     298        '_wc_average_rating', '_wc_rating_count', '_wc_review_count',
     299        '_downloadable', '_virtual', '_manage_stock', '_backorders',
     300        '_sold_individually', '_tax_status', '_tax_class',
     301        // WPML
     302        'wpml_', '_icl_',
     303        // Polylang
     304        '_pll_',
     305        // Jetpack
     306        '_jetpack_', 'jetpack_',
     307        // Post thumbnail (already surfaceable via featured image in body)
     308        '_thumbnail_id',
     309        // Revolution Slider / WPBakery
     310        '_rs_', 'vc_',
     311    ];
     312
     313    /**
     314     * Return public custom fields for a post, preferring ACF field labels.
     315     *
     316     * Priority:
     317     *   1. If ACF is active: use get_fields() — returns label-keyed, typed values.
     318     *   2. Otherwise: read raw post meta, skip internal/plugin keys and
     319     *      serialized values, return whatever is left.
     320     *
     321     * Developers can add or remove entries via the filter:
     322     *   add_filter( 'mescio_custom_meta', function( $fields, $post ) { ... }, 10, 2 );
     323     *
     324     * @return array<string, scalar>  key => scalar value (strings, ints, floats, bools)
     325     */
     326    private static function get_custom_meta( WP_Post $post ): array {
     327        // ── ACF path ──────────────────────────────────────────────────────────
     328        if ( function_exists( 'get_fields' ) ) {
     329            $acf = get_fields( $post->ID );
     330            if ( is_array( $acf ) && ! empty( $acf ) ) {
     331                $flat = self::flatten_acf( $acf );
     332                /** @var array<string,scalar> $flat */
     333                return apply_filters( 'mescio_custom_meta', $flat, $post );
     334            }
     335        }
     336
     337        // ── Plain post meta path ──────────────────────────────────────────────
     338        $all = get_post_meta( $post->ID );
     339        if ( ! is_array( $all ) ) {
     340            return [];
     341        }
     342
     343        $result = [];
     344        foreach ( $all as $key => $values ) {
     345            // Skip private / internal keys (start with _)
     346            if ( str_starts_with( $key, '_' ) ) {
     347                continue;
     348            }
     349            // Skip keys matching known plugin prefixes
     350            $blocked = false;
     351            foreach ( self::BLOCKED_META_PREFIXES as $prefix ) {
     352                if ( str_starts_with( $key, $prefix ) ) {
     353                    $blocked = true;
     354                    break;
     355                }
     356            }
     357            if ( $blocked ) {
     358                continue;
     359            }
     360
     361            $raw = $values[0] ?? '';
     362
     363            // Skip serialized / JSON blobs — not human-readable
     364            if ( is_serialized( $raw ) ) continue;
     365            $decoded = json_decode( $raw, true );
     366            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) continue;
     367
     368            // Skip empty strings
     369            $scalar = wp_strip_all_tags( (string) $raw );
     370            if ( $scalar === '' ) continue;
     371
     372            $result[ $key ] = $scalar;
     373        }
     374
     375        /** @var array<string,scalar> $result */
     376        return apply_filters( 'mescio_custom_meta', $result, $post );
     377    }
     378
     379    /**
     380     * Recursively flatten an ACF field array into dot-notation keys with scalar leaves.
     381     * Arrays of scalars are joined with ", ". Nested groups use "parent.child" keys.
     382     *
     383     * @param  array<string,mixed>  $fields
     384     * @param  string               $prefix
     385     * @return array<string,scalar>
     386     */
     387    private static function flatten_acf( array $fields, string $prefix = '' ): array {
     388        $out = [];
     389        foreach ( $fields as $key => $value ) {
     390            $full_key = $prefix ? $prefix . '.' . $key : $key;
     391
     392            if ( is_array( $value ) ) {
     393                // Nested group or repeater
     394                $nested = self::flatten_acf( $value, $full_key );
     395                if ( ! empty( $nested ) ) {
     396                    $out = array_merge( $out, $nested );
     397                }
     398            } elseif ( is_scalar( $value ) ) {
     399                $str = wp_strip_all_tags( (string) $value );
     400                if ( $str !== '' ) {
     401                    $out[ $full_key ] = $str;
     402                }
     403            }
     404        }
     405        return $out;
     406    }
     407
     408    /**
     409     * Render a scalar value as an inline YAML value.
     410     * Booleans → true/false, numerics → unquoted, strings → double-quoted.
     411     */
     412    private static function yaml_value( $value ): string {
     413        if ( is_bool( $value ) ) return $value ? 'true' : 'false';
     414        if ( is_int( $value ) || is_float( $value ) ) return (string) $value;
     415        $str = (string) $value;
     416        // Numeric strings stay unquoted
     417        if ( is_numeric( $str ) ) return $str;
     418        return '"' . self::yaml_escape( $str ) . '"';
     419    }
    262420}
  • mescio-for-agents/tags/1.5.0/mescio-for-agents.php

    r3479326 r3479820  
    44 * Plugin URI:        https://wordpress.org/plugins/mescio-for-agents/
    55 * Description:       Mescio for Agents serves your posts, pages and WooCommerce products as clean Markdown to AI agents and GPT crawlers — using HTTP content negotiation (Accept: text/markdown). Human visitors never notice a thing.
    6  * Version:           1.4.0
     6 * Version:           1.5.0
    77 * Requires at least: 6.0
    88 * Requires PHP:      8.0
     
    5151
    5252    /** Plugin version — must match the Version header above. */
    53     const VERSION = '1.4.0';
     53    const VERSION = '1.5.0';
    5454
    5555    /** Post types served by default (filterable via mescio_enabled_post_types). */
  • mescio-for-agents/tags/1.5.0/readme.txt

    r3479326 r3479820  
    55Tested up to: 6.9
    66Requires PHP: 8.0
    7 Stable tag: 1.4.0
     7Stable tag: 1.5.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
  • mescio-for-agents/trunk/includes/class-markdown-generator.php

    r3479326 r3479820  
    8080                    }
    8181                }
     82            }
     83        }
     84
     85        // Custom fields (ACF or plain post meta)
     86        $custom = self::get_custom_meta( $post );
     87        if ( ! empty( $custom ) ) {
     88            $lines[] = 'custom_fields:';
     89            foreach ( $custom as $key => $value ) {
     90                $lines[] = '  ' . esc_html( $key ) . ': ' . self::yaml_value( $value );
    8291            }
    8392        }
     
    260269        return str_replace( [ '\\', '"', "\n", "\r" ], [ '\\\\', '\\"', '\n', '' ], $value );
    261270    }
     271
     272    // ── Custom meta ───────────────────────────────────────────────────────────
     273
     274    /**
     275     * Meta key prefixes / exact keys produced by common plugins that carry no
     276     * semantic value for an AI agent and should be silently skipped.
     277     *
     278     * The list is intentionally conservative: we only block keys we *know* are
     279     * structural/internal. Unknown user-defined keys are always exposed.
     280     */
     281    const BLOCKED_META_PREFIXES = [
     282        // WordPress internals
     283        '_edit_', '_wp_', '_encloseme', '_pingme', '_thumbnail_id',
     284        // Our own plugin
     285        '_mescio_',
     286        // Elementor
     287        '_elementor_', 'elementor_',
     288        // Yoast SEO
     289        '_yoast_wpseo_', '_yst_',
     290        // RankMath
     291        'rank_math_',
     292        // All in One SEO
     293        '_aioseop_',
     294        // WooCommerce internals (product data is already in its own block)
     295        '_wc_', '_product_', 'total_sales', '_price', '_regular_price',
     296        '_sale_price', '_sku', '_stock', '_weight', '_length', '_width',
     297        '_height', '_upsell_ids', '_crosssell_ids', '_purchase_note',
     298        '_wc_average_rating', '_wc_rating_count', '_wc_review_count',
     299        '_downloadable', '_virtual', '_manage_stock', '_backorders',
     300        '_sold_individually', '_tax_status', '_tax_class',
     301        // WPML
     302        'wpml_', '_icl_',
     303        // Polylang
     304        '_pll_',
     305        // Jetpack
     306        '_jetpack_', 'jetpack_',
     307        // Post thumbnail (already surfaceable via featured image in body)
     308        '_thumbnail_id',
     309        // Revolution Slider / WPBakery
     310        '_rs_', 'vc_',
     311    ];
     312
     313    /**
     314     * Return public custom fields for a post, preferring ACF field labels.
     315     *
     316     * Priority:
     317     *   1. If ACF is active: use get_fields() — returns label-keyed, typed values.
     318     *   2. Otherwise: read raw post meta, skip internal/plugin keys and
     319     *      serialized values, return whatever is left.
     320     *
     321     * Developers can add or remove entries via the filter:
     322     *   add_filter( 'mescio_custom_meta', function( $fields, $post ) { ... }, 10, 2 );
     323     *
     324     * @return array<string, scalar>  key => scalar value (strings, ints, floats, bools)
     325     */
     326    private static function get_custom_meta( WP_Post $post ): array {
     327        // ── ACF path ──────────────────────────────────────────────────────────
     328        if ( function_exists( 'get_fields' ) ) {
     329            $acf = get_fields( $post->ID );
     330            if ( is_array( $acf ) && ! empty( $acf ) ) {
     331                $flat = self::flatten_acf( $acf );
     332                /** @var array<string,scalar> $flat */
     333                return apply_filters( 'mescio_custom_meta', $flat, $post );
     334            }
     335        }
     336
     337        // ── Plain post meta path ──────────────────────────────────────────────
     338        $all = get_post_meta( $post->ID );
     339        if ( ! is_array( $all ) ) {
     340            return [];
     341        }
     342
     343        $result = [];
     344        foreach ( $all as $key => $values ) {
     345            // Skip private / internal keys (start with _)
     346            if ( str_starts_with( $key, '_' ) ) {
     347                continue;
     348            }
     349            // Skip keys matching known plugin prefixes
     350            $blocked = false;
     351            foreach ( self::BLOCKED_META_PREFIXES as $prefix ) {
     352                if ( str_starts_with( $key, $prefix ) ) {
     353                    $blocked = true;
     354                    break;
     355                }
     356            }
     357            if ( $blocked ) {
     358                continue;
     359            }
     360
     361            $raw = $values[0] ?? '';
     362
     363            // Skip serialized / JSON blobs — not human-readable
     364            if ( is_serialized( $raw ) ) continue;
     365            $decoded = json_decode( $raw, true );
     366            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) continue;
     367
     368            // Skip empty strings
     369            $scalar = wp_strip_all_tags( (string) $raw );
     370            if ( $scalar === '' ) continue;
     371
     372            $result[ $key ] = $scalar;
     373        }
     374
     375        /** @var array<string,scalar> $result */
     376        return apply_filters( 'mescio_custom_meta', $result, $post );
     377    }
     378
     379    /**
     380     * Recursively flatten an ACF field array into dot-notation keys with scalar leaves.
     381     * Arrays of scalars are joined with ", ". Nested groups use "parent.child" keys.
     382     *
     383     * @param  array<string,mixed>  $fields
     384     * @param  string               $prefix
     385     * @return array<string,scalar>
     386     */
     387    private static function flatten_acf( array $fields, string $prefix = '' ): array {
     388        $out = [];
     389        foreach ( $fields as $key => $value ) {
     390            $full_key = $prefix ? $prefix . '.' . $key : $key;
     391
     392            if ( is_array( $value ) ) {
     393                // Nested group or repeater
     394                $nested = self::flatten_acf( $value, $full_key );
     395                if ( ! empty( $nested ) ) {
     396                    $out = array_merge( $out, $nested );
     397                }
     398            } elseif ( is_scalar( $value ) ) {
     399                $str = wp_strip_all_tags( (string) $value );
     400                if ( $str !== '' ) {
     401                    $out[ $full_key ] = $str;
     402                }
     403            }
     404        }
     405        return $out;
     406    }
     407
     408    /**
     409     * Render a scalar value as an inline YAML value.
     410     * Booleans → true/false, numerics → unquoted, strings → double-quoted.
     411     */
     412    private static function yaml_value( $value ): string {
     413        if ( is_bool( $value ) ) return $value ? 'true' : 'false';
     414        if ( is_int( $value ) || is_float( $value ) ) return (string) $value;
     415        $str = (string) $value;
     416        // Numeric strings stay unquoted
     417        if ( is_numeric( $str ) ) return $str;
     418        return '"' . self::yaml_escape( $str ) . '"';
     419    }
    262420}
  • mescio-for-agents/trunk/mescio-for-agents.php

    r3479326 r3479820  
    44 * Plugin URI:        https://wordpress.org/plugins/mescio-for-agents/
    55 * Description:       Mescio for Agents serves your posts, pages and WooCommerce products as clean Markdown to AI agents and GPT crawlers — using HTTP content negotiation (Accept: text/markdown). Human visitors never notice a thing.
    6  * Version:           1.4.0
     6 * Version:           1.5.0
    77 * Requires at least: 6.0
    88 * Requires PHP:      8.0
     
    5151
    5252    /** Plugin version — must match the Version header above. */
    53     const VERSION = '1.4.0';
     53    const VERSION = '1.5.0';
    5454
    5555    /** Post types served by default (filterable via mescio_enabled_post_types). */
  • mescio-for-agents/trunk/readme.txt

    r3479326 r3479820  
    55Tested up to: 6.9
    66Requires PHP: 8.0
    7 Stable tag: 1.4.0
     7Stable tag: 1.5.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
Note: See TracChangeset for help on using the changeset viewer.