PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | includes/class-metasync-opengraph.php +29 -13 2.7.0 → 2.7.2 View file →
@@ -178,9 +178,9 @@
178 178 if ($value === '') {
179 179 return false;
180 180 }
181 181 # 'default' text domain, matching the __() call core itself uses.
182 - if ($value === 'Auto Draft' || $value === trim(__('Auto Draft'))) {
182 + if ($value === 'Auto Draft' || $value === trim(__('Auto Draft', 'default'))) { // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- resolves core's own 'Auto Draft' placeholder, which only exists in core's 'default' domain; the plugin text domain cannot reproduce it
183 183 return true;
184 184 }
185 185 # Registry arm: a variant captured in another locale's admin request.
186 186 # Without it the guard above is English-only on every non-admin request,
@@ -241,9 +241,9 @@
241 241 *
242 242 * @return void
243 243 */
244 244 public static function seed_auto_draft_placeholder() {
245 - self::remember_auto_draft_placeholder(__('Auto Draft'));
245 + self::remember_auto_draft_placeholder(__('Auto Draft', 'default')); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- resolves core's own 'Auto Draft' placeholder, which only exists in core's 'default' domain; the plugin text domain cannot reproduce it
246 246 }
247 247
248 248 /**
249 249 * Read a persisted social meta value, collapsing the "Auto Draft" placeholder
@@ -730,9 +730,9 @@
730 730
731 731 # LPS / custom-HTML pages bake their own OG/social tags into their HTML bundle,
732 732 # served before wp_head — so this box does nothing on them. Hide it; the SEO
733 733 # read-only notice covers the messaging.
734 - $lps_post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0);
734 + $lps_post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- read-only post ID to pick meta boxes
735 735 if (function_exists('metasync_is_custom_or_lps_page') && $lps_post_id > 0 && metasync_is_custom_or_lps_page($lps_post_id)) {
736 736 return;
737 737 }
738 738
@@ -742,8 +742,9 @@
742 742
743 743 foreach ($post_types as $post_type) {
744 744 add_meta_box(
745 745 self::META_BOX_ID,
746 + /* translators: %s: effective plugin name (whitelabel-aware). */
746 747 sprintf(esc_html__('Social Media & Open Graph by %s', 'metasync'), $plugin_name),
747 748 [$this, 'render_meta_box'],
748 749 $post_type,
749 750 'normal',
@@ -1044,9 +1045,9 @@
1044 1045 # remember_auto_draft_placeholder() already skips. Verified against
1045 1046 # __( 'Auto Draft' ), never trusted as a bare string, so a builder's
1046 1047 # custom-seeded auto-draft title is never captured.
1047 1048 if ($post->post_status === 'auto-draft'
1048 - && $title !== '' && $title === trim(__('Auto Draft'))) {
1049 + && $title !== '' && $title === trim(__('Auto Draft', 'default'))) { // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- resolves core's own 'Auto Draft' placeholder, which only exists in core's 'default' domain; the plugin text domain cannot reproduce it
1049 1050 self::remember_auto_draft_placeholder($title);
1050 1051 }
1051 1052
1052 1053 # Flag maintenance: recognition is context-independent thanks to the
@@ -1238,12 +1239,19 @@
1238 1239 $og_image = Metasync_Seo_Precedence::value($post->ID, Metasync_Seo_Precedence::FIELD_OG_IMAGE)
1239 1240 ?: $this->get_featured_image_url($post->ID);
1240 1241 $canonical_override = '';
1241 1242 if (class_exists('Metasync_Headless_Config') && Metasync_Headless_Config::is_active()) {
1242 - # The same two-key chain the canonical tag itself resolves, so the
1243 - # two cannot disagree. meta_canonical is not a legacy alias: the
1244 - # REST sync and the classic-editor Canonical box both still write
1245 - # it, and the GraphQL surface reads both keys as well.
1243 + # The two manual keys of the canonical chain, in the same order the
1244 + # canonical tag resolves them. OTTO's volatile staging tier is
1245 + # deliberately left out: headless rehosts derived WordPress URLs,
1246 + # and an OTTO suggestion is not the author's answer about where
1247 + # content lives — a staged cross-domain canonical must not flow
1248 + # into headless og:url. The two can therefore disagree with the
1249 + # WordPress-render canonical tag only when OTTO is staging a
1250 + # canonical and no manual value exists; that is the documented
1251 + # scope split. meta_canonical is not a legacy alias: the REST sync
1252 + # and the classic-editor Canonical box both still write it, and
1253 + # the GraphQL surface reads both keys as well.
1246 1254 foreach (array('_metasync_canonical_url', 'meta_canonical') as $canonical_key) {
1247 1255 $canonical_override = Metasync_Canonical_Sanitizer::sanitize(
1248 1256 get_post_meta($post->ID, $canonical_key, true)
1249 1257 );
@@ -1768,9 +1776,8 @@
1768 1776 $preview_html = $this->generate_preview_html($title, $description, $image, $url, $twitter_title, $twitter_description, $twitter_image);
1769 1777
1770 1778 if (empty($preview_html)) {
1771 1779 wp_send_json_error(['message' => 'Failed to generate preview HTML']);
1772 - return;
1773 1780 }
1774 1781
1775 1782 wp_send_json_success(['preview' => $preview_html]);
1776 1783
@@ -1785,9 +1792,9 @@
1785 1792 private function generate_preview_html($title, $description, $image, $url, $twitter_title = '', $twitter_description = '', $twitter_image = '') {
1786 1793 # Parse domain from URL
1787 1794 $domain = '';
1788 1795 if (!empty($url)) {
1789 - $parsed = parse_url($url);
1796 + $parsed = wp_parse_url($url);
1790 1797 $domain = $parsed['host'] ?? '';
1791 1798 }
1792 1799
1793 1800 # Fallback to site URL if no domain found
@@ -1792,9 +1799,9 @@
1792 1799
1793 1800 # Fallback to site URL if no domain found
1794 1801 if (empty($domain)) {
1795 1802 $site_url = get_site_url();
1796 - $parsed = parse_url($site_url);
1803 + $parsed = wp_parse_url($site_url);
1797 1804 $domain = $parsed['host'] ?? 'your-site.com';
1798 1805 }
1799 1806
1800 1807 # Provide fallbacks for empty values
@@ -2371,11 +2378,20 @@
2371 2378 /**
2372 2379 * Check if post slug changed via edit slug functionality
2373 2380 */
2374 2381 public function check_slug_change() {
2382 + # Fires on the same admin-ajax request as core's sample-permalink
2383 + # handler, but at priority 5 — before core validates the nonce and
2384 + # capability at its own priority-10 callback. Verify them ourselves
2385 + # before any post-meta write. Return (not die) so core still serves
2386 + # the preview request normally.
2387 + if (!check_ajax_referer('samplepermalink', 'samplepermalinknonce', false)) {
2388 + return;
2389 + }
2390 +
2375 2391 # Get the post ID from the request
2376 2392 $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
2377 - if (!$post_id) {
2393 + if (!$post_id || !current_user_can('edit_post', $post_id)) {
2378 2394 return;
2379 2395 }
2380 2396
2381 2397 # Only check for supported post types
@@ -2408,9 +2424,9 @@
2408 2424 # This means the og:url was set to the post permalink (not a custom URL)
2409 2425 if ($current_og_url !== $current_permalink && strpos($current_og_url, '?p=') === false) {
2410 2426 # Check if the og:url was the old permalink by comparing with a generated old permalink
2411 2427 $old_post = clone $post;
2412 - $old_slug = isset($_POST['new_slug']) ? sanitize_title($_POST['new_slug']) : $post->post_name;
2428 + $old_slug = isset($_POST['new_slug']) ? sanitize_title(wp_unslash($_POST['new_slug'])) : $post->post_name;
2413 2429
2414 2430 # If the og:url doesn't match the current permalink, it might be the old one
2415 2431 # We'll update it to the new permalink
2416 2432 if ($current_og_url !== $current_permalink) {