Changeset 3517303
- Timestamp:
- 04/28/2026 11:19:33 AM (6 months ago)
- Location:
- moonrank-integration/trunk
- Files:
-
- 4 edited
-
includes/class-moonrank-publisher.php (modified) (2 diffs)
-
includes/class-moonrank-seo.php (modified) (1 diff)
-
moonrank-integration.php (modified) (3 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
moonrank-integration/trunk/includes/class-moonrank-publisher.php
r3491281 r3517303 74 74 // Set SEO meta, taxonomy, and featured image. 75 75 Moonrank_Seo::set_meta( $post_id, $article ); 76 Moonrank_Seo::save_schema_jsonld( $post_id, $article ); 76 77 self::set_categories( $post_id, $article ); 77 78 self::set_tags( $post_id, $article ); … … 161 162 162 163 Moonrank_Seo::set_meta( $post_id, $article ); 164 Moonrank_Seo::save_schema_jsonld( $post_id, $article ); 163 165 self::set_categories( $post_id, $article ); 164 166 self::set_tags( $post_id, $article ); -
moonrank-integration/trunk/includes/class-moonrank-seo.php
r3473129 r3517303 119 119 120 120 /** 121 * Persist JSON-LD schema blocks from the webhook payload as post meta. 122 * Each block is validated as a well-formed <script type="application/ld+json"> 123 * tag containing valid JSON; anything else is dropped silently. The 124 * meta is removed when the payload field is empty so the publisher 125 * can also clear stale schema (e.g. if includeSchema is later turned off). 126 * 127 * @param int $post_id WordPress post ID. 128 * @param array $article Article data from webhook payload. 129 */ 130 public static function save_schema_jsonld( $post_id, $article ) { 131 $raw = isset( $article['schema'] ) ? (string) $article['schema'] : ''; 132 133 // Hard cap to prevent pathological inputs from bloating wp_postmeta. 134 if ( strlen( $raw ) > 65535 ) { 135 $raw = ''; 136 } 137 138 $sanitized = self::sanitize_schema_jsonld( $raw ); 139 140 if ( $sanitized === '' ) { 141 delete_post_meta( $post_id, MOONRANK_META_SCHEMA_JSONLD ); 142 return; 143 } 144 145 update_post_meta( $post_id, MOONRANK_META_SCHEMA_JSONLD, wp_slash( $sanitized ) ); 146 } 147 148 /** 149 * Extract and validate JSON-LD blocks from a raw HTML fragment. 150 * Returns the cleaned, newline-joined script blocks, or an empty 151 * string if nothing valid was found. 152 * 153 * @param string $raw Raw fragment from the webhook payload. 154 * @return string 155 */ 156 private static function sanitize_schema_jsonld( $raw ) { 157 if ( '' === trim( $raw ) ) { 158 return ''; 159 } 160 161 $pattern = '#<script\s+type=["\']application/ld\+json["\'][^>]*>([\s\S]*?)</script>#i'; 162 if ( ! preg_match_all( $pattern, $raw, $matches ) ) { 163 return ''; 164 } 165 166 $clean_blocks = array(); 167 foreach ( $matches[1] as $json ) { 168 $json = trim( $json ); 169 if ( '' === $json ) { 170 continue; 171 } 172 // Reject anything that isn't valid JSON. json_decode returns 173 // null both for invalid JSON and for the literal "null"; the 174 // latter we don't care about anyway. 175 $decoded = json_decode( $json, true ); 176 if ( null === $decoded || ( ! is_array( $decoded ) && ! is_object( $decoded ) ) ) { 177 continue; 178 } 179 // Re-encode to drop comments/whitespace and guarantee no 180 // </script> sneaks back in via tricky escaping. 181 $reencoded = wp_json_encode( $decoded, JSON_UNESCAPED_SLASHES ); 182 if ( false === $reencoded ) { 183 continue; 184 } 185 $clean_blocks[] = '<script type="application/ld+json">' . $reencoded . '</script>'; 186 } 187 188 return implode( "\n", $clean_blocks ); 189 } 190 191 /** 192 * Print the JSON-LD schema saved for the current singular post in <head>. 193 * Hooked from moonrank-integration.php at wp_head priority 30. 194 */ 195 public static function output_schema_jsonld() { 196 if ( ! is_singular() ) { 197 return; 198 } 199 200 $post_id = get_queried_object_id(); 201 if ( ! $post_id ) { 202 return; 203 } 204 205 $schema = get_post_meta( $post_id, MOONRANK_META_SCHEMA_JSONLD, true ); 206 if ( empty( $schema ) ) { 207 return; 208 } 209 210 // Output verbatim — already validated in save_schema_jsonld(), and 211 // each block is a controlled <script type="application/ld+json">…</script> 212 // wrapping JSON re-encoded by wp_json_encode. 213 echo "\n" . $schema . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 214 } 215 216 /** 121 217 * Check if Yoast SEO is active. 122 218 */ -
moonrank-integration/trunk/moonrank-integration.php
r3491281 r3517303 4 4 * Plugin URI: https://www.moonrank.ai/integrations/wordpress 5 5 * Description: Connects your WordPress site to Moonrank for automated SEO article publishing via a secure webhook endpoint. 6 * Version: 1.0. 56 * Version: 1.0.7 7 7 * Author: Moonrank 8 8 * Author URI: https://www.moonrank.ai … … 19 19 } 20 20 21 define( 'MOONRANK_VERSION', '1.0. 5' );21 define( 'MOONRANK_VERSION', '1.0.7' ); 22 22 define( 'MOONRANK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 23 23 define( 'MOONRANK_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 24 24 define( 'MOONRANK_OPTION_KEY_HASH', 'moonrank_integration_key_hash' ); 25 25 define( 'MOONRANK_META_ARTICLE_ID', '_moonrank_article_id' ); 26 define( 'MOONRANK_META_SCHEMA_JSONLD', '_moonrank_schema_jsonld' ); 27 28 // IndexNow shared key (Moonrank-wide, hex chars a-f/0-9, 8-128 long). 29 // MUST match the INDEXNOW_KEY env var on Moonrank's backend — mismatches cause 30 // search engines to reject the ping during key verification. 31 define( 'MOONRANK_INDEXNOW_KEY', '859046e1f60224a563c1fe2c4c88e7b0' ); 26 32 27 33 // Load class files. … … 33 39 // Register REST API routes. 34 40 add_action( 'rest_api_init', array( 'Moonrank_Api', 'register_routes' ) ); 41 42 // Emit JSON-LD schema (saved by the publisher as post meta) on singular 43 // post views. Priority 30 places it after Yoast/Rank Math's own schema. 44 add_action( 'wp_head', array( 'Moonrank_Seo', 'output_schema_jsonld' ), 30 ); 45 46 // Serve the IndexNow key file at https://<site>/<key>.txt. Intercepted via 47 // parse_request so no rewrite rules (and no flush) are needed. Search engines 48 // fetch this URL unauthenticated to verify that we own the domain before 49 // accepting IndexNow submissions, so the response must be plain text = the key. 50 add_action( 'parse_request', 'moonrank_serve_indexnow_key' ); 51 52 function moonrank_serve_indexnow_key( $wp ) { 53 $key = MOONRANK_INDEXNOW_KEY; 54 if ( empty( $key ) || '__REPLACE_WITH_INDEXNOW_KEY__' === $key ) { 55 return; 56 } 57 if ( ! isset( $wp->request ) || $wp->request !== $key . '.txt' ) { 58 return; 59 } 60 status_header( 200 ); 61 header( 'Content-Type: text/plain; charset=utf-8' ); 62 header( 'Cache-Control: public, max-age=86400' ); 63 echo $key; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- hardcoded hex constant. 64 exit; 65 } 35 66 36 67 // Register admin menu. -
moonrank-integration/trunk/readme.txt
r3491959 r3517303 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 57 Stable tag: 1.0.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 68 68 == Changelog == 69 69 70 = 1.0.5 = 71 * Fix: Add verify action for post-failure status checking 70 = 1.0.7 = 71 * Feature: JSON-LD schema markup is now emitted in `<head>` via wp_head from a dedicated post meta field. Previously, Article/FAQPage/HowTo schema sent by Moonrank was being stripped by wp_kses_post() because it was embedded in the post content. Articles republished after upgrading to 1.0.7 will gain rich-snippet eligibility (FAQ accordions, HowTo cards) on top of any schema your SEO plugin already provides. 72 73 = 1.0.6 = 74 * Feature: Serve IndexNow key file at /<key>.txt so Moonrank's backend can notify Bing, Yandex, and other participating search engines (including the indexes that power ChatGPT Search, Perplexity, and Copilot) the moment an article is published. 72 75 73 76 = 1.0.4 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)