Plugin Directory

Changeset 3466002


Ignore:
Timestamp:
02/20/2026 06:49:29 PM (5 weeks ago)
Author:
seorocket
Message:

Release 1.7.1 - Remove error_log() calls for PHPCS compliance

Location:
seo-rocket-integration
Files:
5 added
2 edited

Legend:

Unmodified
Added
Removed
  • seo-rocket-integration/trunk/readme.txt

    r3460311 r3466002  
    55Requires at least: 5.9
    66Tested up to: 6.9
    7 Stable tag: 1.6.0
     7Stable tag: 1.7.1
    88Requires PHP: 7.4
    99License: GPLv2 or later
     
    101101== Changelog ==
    102102
     103= 1.7.1 =
     104* Removed error_log() calls to resolve WordPress.org PHPCS warnings
     105
     106= 1.7.0 =
     107* Fixed Yoast green light not appearing reliably on all sites
     108* Added direct update_post_meta saving — bypasses REST API meta registration which fails when Yoast registers the same keys without show_in_rest
     109* Changed from register_meta to register_post_meta with explicit post-type subtypes
     110* Added unregister/re-register flow to override Yoast's and Rank Math's existing meta registrations
     111* Removed conditional class_exists checks from meta registration — all SEO fields are now always registered
     112* Reads meta from raw JSON body (get_json_params) to bypass REST API field filtering
     113* Split meta save (priority 5) and indexable rebuild (priority 10) into separate hooks for correct ordering
     114
    103115= 1.6.0 =
    104 * Version bump for WordPress.org submission
    105 * Tested up to WordPress 6.9
     116* Fixed Yoast SEO green light not appearing on REST API published posts
     117* Register _yoast_wpseo_linkdex and _yoast_wpseo_content_score meta fields for REST API
     118* Register rank_math_seo_score meta field for Rank Math REST API
     119* Added Yoast indexables rebuild hook for immediate SEO score indicator updates
     120* Refactored field registration to reduce code duplication
    106121
    107122= 1.4.0 =
     
    129144== Upgrade Notice ==
    130145
     146= 1.7.1 =
     147Removes debug logging calls flagged by WordPress.org PHPCS. No functional changes.
     148
     149= 1.7.0 =
     150Critical fix for Yoast green light on all sites. Uses direct update_post_meta to reliably save SEO scores regardless of Yoast's internal meta registration. Republish articles after updating.
     151
    131152= 1.6.0 =
    132 Version bump for WordPress.org submission. Tested with WordPress 6.9.
     153Fixes Yoast SEO gray light issue. After updating, republish articles from SEO Rocket to get green indicators without manually editing posts in WordPress.
    133154
    134155= 1.4.0 =
  • seo-rocket-integration/trunk/seo-rocket-integration.php

    r3460311 r3466002  
    33 * Plugin Name: SEO Rocket Integration
    44 * Description: Enables REST API access for Yoast SEO, Rank Math, and other SEO plugins. Allows SEO Rocket to publish articles with SEO metadata.
    5  * Version: 1.6.0
     5 * Version: 1.7.1
    66 * Author: SEO Rocket
    77 * Author URI: https://www.seorocket.app
     
    1818 * Register SEO plugin meta fields for REST API access
    1919 *
    20  * This allows SEO Rocket to set SEO metadata (focus keyword, meta description)
    21  * via the WordPress REST API when publishing articles.
     20 * This allows SEO Rocket to set SEO metadata (focus keyword, meta description,
     21 * SEO score, readability score) via the WordPress REST API when publishing articles.
     22 *
     23 * We register for both 'post' and 'page' object subtypes using register_post_meta,
     24 * which is the correct way to register post-type-specific meta in WordPress 5.x+.
     25 *
     26 * In WordPress 6.x, register_meta() silently overwrites existing registrations
     27 * for the same key. By running on rest_api_init (which fires after Yoast's init
     28 * registration), our show_in_rest => true setting takes effect.
    2229 */
    2330add_action('rest_api_init', function() {
    24     // Yoast SEO fields (posts + pages)
    25     if (class_exists('WPSEO_Options')) {
    26         $yoast_fields = [
    27             '_yoast_wpseo_focuskw'       => 'Yoast SEO focus keyword',
    28             '_yoast_wpseo_metadesc'      => 'Yoast SEO meta description',
    29             '_yoast_wpseo_linkdex'       => 'Yoast SEO linkdex score',
    30             '_yoast_wpseo_content_score' => 'Yoast SEO content score',
    31         ];
    32 
    33         foreach (['post', 'page'] as $post_type) {
    34             $capability = $post_type === 'post' ? 'edit_posts' : 'edit_pages';
    35 
    36             foreach ($yoast_fields as $meta_key => $description) {
    37                 register_meta($post_type, $meta_key, [
    38                     'show_in_rest'  => true,
    39                     'single'        => true,
    40                     'type'          => 'string',
    41                     'description'   => $description,
    42                     'auth_callback' => function() use ($capability) {
    43                         return current_user_can($capability);
    44                     }
    45                 ]);
     31    // All SEO fields we want to expose in the REST API
     32    $seo_fields = [
     33        // Yoast SEO fields
     34        '_yoast_wpseo_focuskw'       => 'Yoast SEO focus keyword',
     35        '_yoast_wpseo_metadesc'      => 'Yoast SEO meta description',
     36        '_yoast_wpseo_linkdex'       => 'Yoast SEO score',
     37        '_yoast_wpseo_content_score' => 'Yoast readability score',
     38        // Rank Math fields
     39        'rank_math_focus_keyword'    => 'Rank Math focus keyword',
     40        'rank_math_description'      => 'Rank Math meta description',
     41        'rank_math_seo_score'        => 'Rank Math SEO score',
     42    ];
     43
     44    $post_types = ['post', 'page'];
     45
     46    foreach ($post_types as $post_type) {
     47        foreach ($seo_fields as $meta_key => $description) {
     48            // Unregister first in case Yoast/Rank Math already registered
     49            // without show_in_rest (which would block REST API access)
     50            if (registered_meta_key_exists('post', $meta_key, $post_type)) {
     51                unregister_meta_key('post', $meta_key, $post_type);
    4652            }
    47         }
    48     }
    49 
    50     // Rank Math fields (posts + pages)
    51     if (defined('RANK_MATH_FILE') || class_exists('RankMath\\Helper')) {
    52         $rankmath_fields = [
    53             'rank_math_focus_keyword' => 'Rank Math focus keyword',
    54             'rank_math_description'   => 'Rank Math meta description',
    55             'rank_math_seo_score'     => 'Rank Math SEO score',
    56         ];
    57 
    58         foreach (['post', 'page'] as $post_type) {
    59             $capability = $post_type === 'post' ? 'edit_posts' : 'edit_pages';
    60 
    61             foreach ($rankmath_fields as $meta_key => $description) {
    62                 register_meta($post_type, $meta_key, [
    63                     'show_in_rest'  => true,
    64                     'single'        => true,
    65                     'type'          => 'string',
    66                     'description'   => $description,
    67                     'auth_callback' => function() use ($capability) {
    68                         return current_user_can($capability);
    69                     }
    70                 ]);
     53            // Also unregister the generic (no subtype) registration
     54            if (registered_meta_key_exists('post', $meta_key, '')) {
     55                unregister_meta_key('post', $meta_key, '');
    7156            }
     57
     58            register_post_meta($post_type, $meta_key, [
     59                'show_in_rest'  => true,
     60                'single'        => true,
     61                'type'          => 'string',
     62                'description'   => $description,
     63                'auth_callback' => function() use ($post_type) {
     64                    return current_user_can($post_type === 'page' ? 'edit_pages' : 'edit_posts');
     65                }
     66            ]);
    7267        }
    7368    }
     
    129124
    130125/**
    131  * Set SEO scores via update_post_meta after REST API post creation/update.
    132  *
    133  * This bypasses REST API meta field registration issues (Yoast registers its
    134  * own meta keys with show_in_rest => false, so register_meta cannot reliably
    135  * override them). By using update_post_meta directly, we write to wp_postmeta
    136  * at the PHP level.
    137  *
    138  * Only acts on requests from SEO Rocket (detected via User-Agent header).
    139  */
    140 function seo_rocket_set_seo_scores($post, $request) {
    141     $ua = $request->get_header('user_agent');
    142     if ($ua === null || strpos($ua, 'SEO-Rocket') === false) {
    143         return;
    144     }
    145 
    146     // Yoast SEO scores
    147     if (class_exists('WPSEO_Options')) {
    148         update_post_meta($post->ID, '_yoast_wpseo_linkdex', '80');
    149         update_post_meta($post->ID, '_yoast_wpseo_content_score', '80');
    150     }
    151 
    152     // Rank Math scores
    153     if (defined('RANK_MATH_FILE') || class_exists('RankMath\\Helper')) {
    154         update_post_meta($post->ID, 'rank_math_seo_score', '80');
    155     }
    156 }
    157 add_action('rest_after_insert_post', 'seo_rocket_set_seo_scores', 10, 2);
    158 add_action('rest_after_insert_page', 'seo_rocket_set_seo_scores', 10, 2);
     126 * Directly save SEO meta fields from REST API requests using update_post_meta.
     127 *
     128 * This is the PRIMARY mechanism for saving SEO scores. It bypasses the REST API's
     129 * meta registration system entirely, which can fail when Yoast/Rank Math register
     130 * the same meta keys without show_in_rest. The register_post_meta calls above
     131 * serve as a secondary/backup mechanism.
     132 *
     133 * This hook fires AFTER WordPress has saved the post and any registered meta fields,
     134 * so our update_post_meta calls will either set new values or overwrite values that
     135 * were already saved by the REST API meta handler.
     136 *
     137 * Hooks: rest_after_insert_post, rest_after_insert_page
     138 * Priority: 5 (before our indexable rebuild at priority 10)
     139 */
     140function seo_rocket_save_seo_meta($post, $request, $creating) {
     141    // Read meta from the raw JSON body to bypass REST API field filtering
     142    $json_params = $request->get_json_params();
     143    $meta = isset($json_params['meta']) ? $json_params['meta'] : null;
     144
     145    if (empty($meta) || !is_array($meta)) {
     146        return;
     147    }
     148
     149    $post_id = $post->ID;
     150
     151    // All SEO meta fields we handle
     152    $allowed_fields = [
     153        '_yoast_wpseo_focuskw',
     154        '_yoast_wpseo_metadesc',
     155        '_yoast_wpseo_linkdex',
     156        '_yoast_wpseo_content_score',
     157        'rank_math_focus_keyword',
     158        'rank_math_description',
     159        'rank_math_seo_score',
     160    ];
     161
     162    $saved_fields = [];
     163    foreach ($allowed_fields as $key) {
     164        if (isset($meta[$key]) && $meta[$key] !== '') {
     165            update_post_meta($post_id, $key, sanitize_text_field($meta[$key]));
     166            $saved_fields[] = $key;
     167        }
     168    }
     169
     170}
     171add_action('rest_after_insert_post', 'seo_rocket_save_seo_meta', 5, 3);
     172add_action('rest_after_insert_page', 'seo_rocket_save_seo_meta', 5, 3);
     173
     174/**
     175 * Rebuild Yoast SEO indexable when a post is saved via REST API
     176 *
     177 * Yoast stores SEO scores in its own wp_yoast_indexable table, not just in
     178 * post meta. The traffic light in the Posts list uses WPSEO_Meta_Columns which
     179 * reads from wp_postmeta, but the Yoast sidebar/meta box uses the indexable.
     180 *
     181 * This hook rebuilds the indexable to keep both in sync. It runs AFTER
     182 * seo_rocket_save_seo_meta (priority 5) so the meta values are guaranteed
     183 * to be in wp_postmeta when we rebuild.
     184 *
     185 * Hooks: rest_after_insert_post, rest_after_insert_page
     186 * Priority: 10 (after meta save at priority 5)
     187 */
     188function seo_rocket_rebuild_yoast_indexable($post, $request, $creating) {
     189    if (!class_exists('WPSEO_Options')) {
     190        return;
     191    }
     192
     193    $post_id = $post->ID;
     194
     195    // Check if we have score fields to sync
     196    $json_params = $request->get_json_params();
     197    $meta = isset($json_params['meta']) ? $json_params['meta'] : null;
     198
     199    if (empty($meta) || !is_array($meta)) {
     200        return;
     201    }
     202
     203    $has_score_field = isset($meta['_yoast_wpseo_linkdex'])
     204        || isset($meta['_yoast_wpseo_content_score']);
     205
     206    if (!$has_score_field) {
     207        return;
     208    }
     209
     210    // Use Yoast's indexable repository to rebuild the indexable for this post.
     211    // This updates the wp_yoast_indexable row with the latest meta values,
     212    // ensuring the traffic-light indicator reflects the scores we just set.
     213    if (class_exists('Yoast\\WP\\SEO\\Repositories\\Indexable_Repository')) {
     214        try {
     215            $container = \YoastSEO()->classes;
     216            if ($container && method_exists($container, 'get')) {
     217                $repository = $container->get('Yoast\\WP\\SEO\\Repositories\\Indexable_Repository');
     218                if ($repository && method_exists($repository, 'find_by_id_and_type')) {
     219                    $indexable = $repository->find_by_id_and_type($post_id, 'post', false);
     220                    if ($indexable) {
     221                        // Sync meta values into the indexable
     222                        if (isset($meta['_yoast_wpseo_linkdex'])) {
     223                            $indexable->primary_focus_keyword_score = intval($meta['_yoast_wpseo_linkdex']);
     224                        }
     225                        if (isset($meta['_yoast_wpseo_content_score'])) {
     226                            $indexable->readability_score = intval($meta['_yoast_wpseo_content_score']);
     227                        }
     228                        if (isset($meta['_yoast_wpseo_focuskw'])) {
     229                            $indexable->primary_focus_keyword = sanitize_text_field($meta['_yoast_wpseo_focuskw']);
     230                        }
     231                        $indexable->save();
     232                    }
     233                }
     234            }
     235        } catch (\Exception $e) {
     236            // Silently fail — the meta fields are still saved via update_post_meta,
     237            // so the traffic light column (which reads from wp_postmeta) will still
     238            // show the correct color. The indexable will sync on next edit.
     239        }
     240    }
     241}
     242add_action('rest_after_insert_post', 'seo_rocket_rebuild_yoast_indexable', 10, 3);
     243add_action('rest_after_insert_page', 'seo_rocket_rebuild_yoast_indexable', 10, 3);
    159244
    160245/**
Note: See TracChangeset for help on using the changeset viewer.