Changeset 3481458
- Timestamp:
- 03/12/2026 07:52:40 PM (33 hours ago)
- Location:
- vectoron/trunk
- Files:
-
- 6 edited
-
includes/settings-page.php (modified) (1 diff)
-
integrations/elementor.php (modified) (1 diff)
-
integrations/schema-sync.php (modified) (5 diffs)
-
readme.txt (modified) (2 diffs)
-
vectoron-api.php (modified) (1 diff)
-
vectoron.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
vectoron/trunk/includes/settings-page.php
r3451062 r3481458 595 595 vectoron_debug( 'update_post_meta(_elementor_edit_mode): ' . vectoron_debug_export( $result_mode ) ); 596 596 vectoron_debug( 'update_post_meta(_elementor_version): ' . vectoron_debug_export( $result_version ) ); 597 598 // Remove hide_title from page settings — preserve other user-configured settings. 599 $page_settings = get_post_meta( $test_post_id, '_elementor_page_settings', true ); 600 if ( is_array( $page_settings ) && isset( $page_settings['hide_title'] ) ) { 601 unset( $page_settings['hide_title'] ); 602 if ( empty( $page_settings ) ) { 603 delete_post_meta( $test_post_id, '_elementor_page_settings' ); 604 } else { 605 update_post_meta( $test_post_id, '_elementor_page_settings', $page_settings ); 606 } 607 } 597 608 598 609 // Clear Elementor CSS cache. -
vectoron/trunk/integrations/elementor.php
r3462938 r3481458 293 293 } 294 294 295 // Set page settings to hide title (matches working posts 687, 904). 296 // Without this, pages show with default WordPress title styling ("unstyled"). 297 // Value is serialized PHP array: array('hide_title' => 'yes') 298 $page_settings = array( 'hide_title' => 'yes' ); 299 update_post_meta( $post_id, '_elementor_page_settings', $page_settings ); 295 // Remove hide_title from page settings — let the theme handle title rendering. 296 // Setting hide_title=yes suppresses the title entirely on themes that don't 297 // render it outside of Elementor. Use selective unset to preserve any other 298 // user-configured page settings (custom_css, padding, background_overlay, etc.). 299 $page_settings = get_post_meta( $post_id, '_elementor_page_settings', true ); 300 if ( is_array( $page_settings ) && isset( $page_settings['hide_title'] ) ) { 301 unset( $page_settings['hide_title'] ); 302 if ( empty( $page_settings ) ) { 303 delete_post_meta( $post_id, '_elementor_page_settings' ); 304 } else { 305 update_post_meta( $post_id, '_elementor_page_settings', $page_settings ); 306 } 307 } 300 308 301 309 // Clear Elementor CSS cache for this post. -
vectoron/trunk/integrations/schema-sync.php
r3459361 r3481458 58 58 // Try JSON first (Tier 1: Vectoron plugin API, Tier 2: Native WP REST API). 59 59 $data = json_decode( $request_body, true ); 60 if ( json_last_error() !== JSON_ERROR_NONE ) { 61 $data = null; // Will fall through to Tier 3 form-encoded fallback below. 62 } 60 63 61 64 // Fall back to form-encoded (Tier 3: AJAX proxy sends rest_body as form field). … … 70 73 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 71 74 $data = json_decode( wp_unslash( $_POST['rest_body'] ), true ); 75 if ( json_last_error() !== JSON_ERROR_NONE ) { 76 $data = null; 77 } 72 78 } 73 79 … … 89 95 // even if re-decode fails downstream. 90 96 $clean_json = wp_json_encode( $decoded, JSON_UNESCAPED_UNICODE ); 97 if ( false === $clean_json ) { 98 vectoron_debug( "[Schema Sync] wp_json_encode failed at storage for post {$post_id}" ); 99 return; 100 } 91 101 update_post_meta( $post_id, '_vectoron_schema_markup', $clean_json ); 92 102 vectoron_debug( "[Schema Sync] Stored schema_markup from API for post {$post_id} (" . strlen( $clean_json ) . ' chars)' ); … … 131 141 } 132 142 133 // Flexible regex: handles extra attributes in any order. 134 if ( preg_match( '/<script\b[^>]*\btype=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/s', $content, $matches ) ) { 135 $schema_json = trim( $matches[1] ); 136 $post_id = get_the_ID(); 137 138 // Validate JSON before storing. 139 $decoded = json_decode( $schema_json ); 140 if ( json_last_error() === JSON_ERROR_NONE && ! empty( $schema_json ) ) { 143 // Flexible regex: handles extra attributes in any order. Uses find_all equivalent (preg_match_all) 144 // to capture ALL JSON-LD tags — matches Python extract_json_ld_schema() behavior for multi-schema articles. 145 if ( preg_match_all( '/<script\b[^>]*\btype=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/s', $content, $matches ) ) { 146 $post_id = get_the_ID(); 147 $schemas = array(); 148 149 foreach ( $matches[1] as $raw_json ) { 150 $schema_json = trim( $raw_json ); 151 if ( empty( $schema_json ) ) { 152 continue; 153 } 154 $decoded_item = json_decode( $schema_json ); 155 if ( json_last_error() === JSON_ERROR_NONE ) { 156 $schemas[] = $decoded_item; 157 } 158 } 159 160 if ( ! empty( $schemas ) ) { 161 // Store as JSON array if multiple schemas, single object if one — matches Python behavior. 141 162 // SECURITY: Do NOT use JSON_UNESCAPED_SLASHES — matches output policy. 142 $clean_json = wp_json_encode( $decoded, JSON_UNESCAPED_UNICODE ); 143 update_post_meta( $post_id, '_vectoron_schema_markup', $clean_json ); 144 vectoron_debug( "[Schema Sync] Migrated inline schema for post {$post_id} (" . strlen( $clean_json ) . ' chars)' ); 145 } 146 147 // Strip ALL JSON-LD script tags from content (even if JSON invalid). 148 $content = preg_replace( '/<script\b[^>]*\btype=["\']application\/ld\+json["\'][^>]*>.*?<\/script>/s', '', $content ); 163 $to_store = count( $schemas ) === 1 ? $schemas[0] : $schemas; 164 $clean_json = wp_json_encode( $to_store, JSON_UNESCAPED_UNICODE ); 165 if ( false === $clean_json ) { 166 // Do NOT strip content — preserve inline schemas since storage failed. 167 vectoron_debug( '[Schema Sync] wp_json_encode failed at storage for post ' . $post_id ); 168 } else { 169 update_post_meta( $post_id, '_vectoron_schema_markup', $clean_json ); 170 vectoron_debug( '[Schema Sync] Migrated ' . count( $schemas ) . " inline schema(s) for post {$post_id} (" . strlen( $clean_json ) . ' chars)' ); 171 // Strip only after successful storage — prevents data loss if storage fails. 172 $content = preg_replace( '/<script\b[^>]*\btype=["\']application\/ld\+json["\'][^>]*>.*?<\/script>/s', '', $content ); 173 } 174 } else { 175 // All found schema tags failed JSON decode — preserve content unchanged to avoid data loss. 176 vectoron_debug( '[Schema Sync] WARNING: ' . count( $matches[1] ) . ' inline schema tag(s) found but none decoded successfully for post ' . get_the_ID() . '. Content preserved.' ); 177 } 149 178 } 150 179 … … 175 204 176 205 // Re-decode and re-encode to prevent XSS (sanitize stored JSON). 177 $decoded = json_decode( $schema ); 178 if ( json_last_error() === JSON_ERROR_NONE ) { 206 $decoded = json_decode( $schema, true ); 207 if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) || empty( $decoded ) ) { 208 return; 209 } 210 211 // Emit one <script> per schema object. 212 // Distinguish a JSON array of schema objects from a single schema object by checking 213 // whether the decoded PHP array is sequential (integer-keyed = JSON array of schemas) 214 // or associative (string-keyed = single schema object like {"@type":"FAQPage",...}). 215 // JSON-LD schema objects always use string keys (@type, @context, etc.) — never sequential 216 // integer keys — so this check is reliable and handles backward compat with legacy stored data. 217 $is_schema_list = ( array_keys( $decoded ) === range( 0, count( $decoded ) - 1 ) ); 218 $items = $is_schema_list ? $decoded : array( $decoded ); 219 foreach ( $items as $schema_item ) { 179 220 // SECURITY: Do NOT use JSON_UNESCAPED_SLASHES in HTML output context. 180 221 // json_encode() escapes "/" as "\/" by default, which prevents </script> 181 222 // tag injection (XSS) when JSON values contain attacker-controlled strings. 182 223 // The "\/" is valid JSON and parsed correctly by all JSON-LD consumers. 183 $safe_json = wp_json_encode( $decoded, JSON_UNESCAPED_UNICODE ); 184 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON is re-encoded via wp_json_encode() with slash escaping for XSS prevention. 224 $safe_json = wp_json_encode( $schema_item, JSON_UNESCAPED_UNICODE ); 225 if ( false === $safe_json ) { 226 vectoron_debug( '[Schema Sync] wp_json_encode failed for schema item on post ' . get_the_ID() ); 227 continue; 228 } 229 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode() re-encodes JSON with slash escaping (XSS prevention). false return guarded above. 185 230 echo '<script type="application/ld+json">' . $safe_json . '</script>' . "\n"; 186 231 } -
vectoron/trunk/readme.txt
r3469529 r3481458 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 2.11. 67 Stable tag: 2.11.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 135 135 136 136 == Changelog == 137 138 = 2.11.7 = 139 * Fixed: Multiple JSON-LD schema tags (e.g. FAQPage + ItemList for listicle articles) now all render in `<head>` 140 * Previously only the first schema tag was extracted; the second was left in the content body as visible text 141 * The `wp_head` output now emits one `<script type="application/ld+json">` per schema object 142 * Fixed: Elementor sync no longer sets `hide_title=yes` in page settings, which caused post titles to be hidden on themes that don't render titles outside Elementor 143 * On re-sync, existing `hide_title` is surgically removed while preserving other Elementor page settings 137 144 138 145 = 2.11.6 = -
vectoron/trunk/vectoron-api.php
r3469529 r3481458 1272 1272 $response = array( 1273 1273 'plugin' => 'Vectoron API', 1274 'version' => '2.11. 6',1274 'version' => '2.11.7', 1275 1275 'status' => 'active', 1276 1276 'endpoints' => array( -
vectoron/trunk/vectoron.php
r3469529 r3481458 3 3 * Plugin Name: Vectoron 4 4 * Description: Provides the [vectoron_article] shortcode to disable wpautop and load assets for custom content like the FAQ accordion and GA4 tracking. Includes REST API endpoints for external content management and ACF integration. 5 * Version: 2.11. 65 * Version: 2.11.7 6 6 * Author: Vectoron 7 7 * Author URI: https://vectoron.ai
Note: See TracChangeset
for help on using the changeset viewer.