Changeset 3479820
- Timestamp:
- 03/11/2026 08:25:04 AM (4 weeks ago)
- Location:
- mescio-for-agents
- Files:
-
- 3 edited
- 7 copied
-
tags/1.5.0 (copied) (copied from mescio-for-agents/trunk)
-
tags/1.5.0/includes/class-admin.php (copied) (copied from mescio-for-agents/trunk/includes/class-admin.php)
-
tags/1.5.0/includes/class-agents-txt.php (copied) (copied from mescio-for-agents/trunk/includes/class-agents-txt.php)
-
tags/1.5.0/includes/class-markdown-generator.php (copied) (copied from mescio-for-agents/trunk/includes/class-markdown-generator.php) (2 diffs)
-
tags/1.5.0/languages/mescio-for-agents-it_IT.po (copied) (copied from mescio-for-agents/trunk/languages/mescio-for-agents-it_IT.po)
-
tags/1.5.0/mescio-for-agents.php (copied) (copied from mescio-for-agents/trunk/mescio-for-agents.php) (2 diffs)
-
tags/1.5.0/readme.txt (copied) (copied from mescio-for-agents/trunk/readme.txt) (1 diff)
-
trunk/includes/class-markdown-generator.php (modified) (2 diffs)
-
trunk/mescio-for-agents.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
mescio-for-agents/tags/1.5.0/includes/class-markdown-generator.php
r3479326 r3479820 80 80 } 81 81 } 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 ); 82 91 } 83 92 } … … 260 269 return str_replace( [ '\\', '"', "\n", "\r" ], [ '\\\\', '\\"', '\n', '' ], $value ); 261 270 } 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 } 262 420 } -
mescio-for-agents/tags/1.5.0/mescio-for-agents.php
r3479326 r3479820 4 4 * Plugin URI: https://wordpress.org/plugins/mescio-for-agents/ 5 5 * 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.06 * Version: 1.5.0 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 51 51 52 52 /** Plugin version — must match the Version header above. */ 53 const VERSION = '1. 4.0';53 const VERSION = '1.5.0'; 54 54 55 55 /** Post types served by default (filterable via mescio_enabled_post_types). */ -
mescio-for-agents/tags/1.5.0/readme.txt
r3479326 r3479820 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1. 4.07 Stable tag: 1.5.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html -
mescio-for-agents/trunk/includes/class-markdown-generator.php
r3479326 r3479820 80 80 } 81 81 } 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 ); 82 91 } 83 92 } … … 260 269 return str_replace( [ '\\', '"', "\n", "\r" ], [ '\\\\', '\\"', '\n', '' ], $value ); 261 270 } 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 } 262 420 } -
mescio-for-agents/trunk/mescio-for-agents.php
r3479326 r3479820 4 4 * Plugin URI: https://wordpress.org/plugins/mescio-for-agents/ 5 5 * 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.06 * Version: 1.5.0 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 51 51 52 52 /** Plugin version — must match the Version header above. */ 53 const VERSION = '1. 4.0';53 const VERSION = '1.5.0'; 54 54 55 55 /** Post types served by default (filterable via mescio_enabled_post_types). */ -
mescio-for-agents/trunk/readme.txt
r3479326 r3479820 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1. 4.07 Stable tag: 1.5.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html
Note: See TracChangeset
for help on using the changeset viewer.