| Server IP : 146.59.209.152 / Your IP : 216.73.216.152 Web Server : Apache System : Linux webm009.cluster131.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64 User : monpetu ( 144298) PHP Version : 7.4.33 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/monpetu/www/ipprint/wp-content/plugins/surerank/inc/admin/ |
Upload File : |
<?php
/**
* Post Popup
*
* @since 1.0.0
* @package surerank
*/
namespace SureRank\Inc\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use SureRank\Inc\API\Migrations;
use SureRank\Inc\Frontend\Crawl_Optimization;
use SureRank\Inc\Frontend\Image_Seo;
use SureRank\Inc\Functions\Get;
use SureRank\Inc\Functions\Update;
use SureRank\Inc\GoogleSearchConsole\Auth as GoogleSearchConsoleAuth;
use SureRank\Inc\GoogleSearchConsole\Controller as GoogleSearchConsoleController;
use SureRank\Inc\GoogleSearchConsole\Url_Inspection;
use SureRank\Inc\Traits\Enqueue;
use SureRank\Inc\Traits\Get_Instance;
/**
* Post Popup
*
* @method void wp_enqueue_scripts()
* @since 1.0.0
*/
class Seo_Popup {
use Enqueue;
use Get_Instance;
/**
* Constructor
*
* @since 0.0.1
* @return void
*/
public function __construct() {
if ( ! apply_filters( 'surerank_content_setting_access', current_user_can( 'manage_options' ) ) ) {
return;
}
$this->enqueue_scripts_admin();
add_action( 'current_screen', [ $this, 'register_term_edit_trigger' ] );
add_action( 'show_user_profile', [ $this, 'add_user_meta_box_trigger' ] );
add_action( 'edit_user_profile', [ $this, 'add_user_meta_box_trigger' ] );
add_action( 'add_meta_boxes', [ $this, 'register_classic_sidebar_meta_box' ], 20, 2 );
// Pin the box to the second position (just below Publish). Runs last so all boxes are registered.
add_action( 'add_meta_boxes', [ $this, 'pin_classic_sidebar_meta_box' ], 9999, 1 );
add_action( 'created_category', [ $this, 'update_category_seo_values' ] );
add_action( 'edited_category', [ $this, 'update_category_seo_values' ] );
/* SEO menu on the admin bar (wp-admin + front end); gated in the callback. */
add_action( 'admin_bar_menu', [ $this, 'add_admin_bar_menu' ], 100 );
// For enqueue scripts on the frontend.
add_action( 'wp_enqueue_scripts', [ $this, 'frontend_enqueue_scripts' ] );
/* Live admin-bar status update when checks are ignored/restored/fixed. */
add_action( 'rest_api_init', [ $this, 'register_status_route' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_bar_live' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_admin_bar_live' ] );
}
/**
* Add tags
*
* @since 0.0.1
* @return void
*/
public function add_meta_box_trigger() {
echo '<span id="seo-popup" class="surerank-root"></span>';
}
/**
* Add SEO popup trigger on user profile edit screens.
*
* Fires inside the profile form via show_user_profile/edit_user_profile;
* the popup JS relocates the trigger next to the page heading.
*
* @param \WP_User $user The user being edited.
* @since 1.9.0
* @return void
*/
public function add_user_meta_box_trigger( $user ) {
if ( ! $user instanceof \WP_User || ! Seo_Bar::display_metabox( '', 'wp_users' ) ) {
return;
}
// profile.php is reachable by every role, so gate the trigger on the
// same capability chain the REST routes use plus edit_user on the
// target, otherwise subscribers see a button whose API calls 403.
if ( ! apply_filters( 'surerank_content_setting_access', current_user_can( 'manage_options' ) )
|| ! current_user_can( 'edit_user', $user->ID ) ) {
return;
}
echo '<span id="seo-popup" class="surerank-root"></span>';
}
/**
* Register the term edit form trigger for the current taxonomy screen.
*
* @param \WP_Screen $screen Current screen object.
* @return void
*/
public function register_term_edit_trigger( $screen ): void {
if ( ! $screen instanceof \WP_Screen || 'term' !== $screen->base || empty( $screen->taxonomy ) ) {
return;
}
if ( ! Seo_Bar::display_metabox( $screen->taxonomy, 'wp_terms' ) ) {
return;
}
add_action( "{$screen->taxonomy}_term_edit_form_top", [ $this, 'add_meta_box_trigger' ] );
}
/**
* Register the Classic Editor sidebar meta box for opening the SEO popup.
*
* @param string $post_type Current post type.
* @param \WP_Post $post Current post object.
* @return void
*/
public function register_classic_sidebar_meta_box( string $post_type, $post ): void {
if ( ! $post instanceof \WP_Post ) {
return;
}
$screen = $this->get_current_screen_safe();
if ( $screen && ! empty( $screen->is_block_editor ) ) {
return;
}
if ( ! Seo_Bar::display_metabox( $post_type, 'wp_posts' ) ) {
return;
}
$priority = apply_filters( 'surerank_seo_sidebar_box_priority', 'core', $post_type, $post );
if ( ! in_array( $priority, [ 'high', 'core', 'default', 'low' ], true ) ) {
$priority = 'core';
}
add_meta_box(
'surerank_classic_seo_box',
esc_html__( 'SureRank', 'surerank' ),
[ $this, 'render_classic_sidebar_meta_box' ],
$post_type,
'side',
$priority
);
}
/**
* Pin the SureRank Classic Editor meta box to the second position in the
* sidebar, directly below the Publish ("submitdiv") box.
*
* Runs after every meta box is registered. Only reorders when the box is in
* the default 'core' priority bucket, so a custom
* surerank_seo_sidebar_box_priority value is left untouched. A user's own
* drag-and-drop order still wins, since that is restored at render time.
*
* @param string $post_type Current post type / screen id.
* @since 1.9.2
* @return void
*/
public function pin_classic_sidebar_meta_box( string $post_type ): void {
global $wp_meta_boxes;
if ( empty( $wp_meta_boxes[ $post_type ]['side']['core'] ) ) {
return;
}
$core = $wp_meta_boxes[ $post_type ]['side']['core'];
if ( ! isset( $core['surerank_classic_seo_box'], $core['submitdiv'] ) ) {
return;
}
$surerank = $core['surerank_classic_seo_box'];
unset( $core['surerank_classic_seo_box'] );
$reordered = [];
foreach ( $core as $id => $box ) {
$reordered[ $id ] = $box;
if ( 'submitdiv' === $id ) {
$reordered['surerank_classic_seo_box'] = $surerank;
}
}
$wp_meta_boxes[ $post_type ]['side']['core'] = $reordered; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reordering registered meta boxes requires updating this global.
}
/**
* Render the Classic Editor sidebar meta box content.
*
* @param \WP_Post $post Current post object.
* @return void
*/
public function render_classic_sidebar_meta_box( $post ): void {
if ( ! $post instanceof \WP_Post ) {
return;
}
$box_title = apply_filters( 'surerank_seo_sidebar_box_title', __( 'Manage your SEO', 'surerank' ), $post );
$cta_label = apply_filters( 'surerank_seo_sidebar_cta_label', __( 'Click here', 'surerank' ), $post );
?>
<div class="surerank-classic-sidebar-box">
<p class="surerank-classic-sidebar-box-title"><?php echo esc_html( $box_title ); ?></p>
<div
id="surerank-classic-seo-popup-trigger"
class="surerank-root"
data-surerank-variant="sidebar"
data-surerank-cta-label="<?php echo esc_attr( $cta_label ); ?>"
></div>
</div>
<?php
}
/**
* Enqueue SEO metabox front-end scripts
*
* @since 1.6.2
* @return void
*/
public function frontend_enqueue_scripts() {
// Restrict to singular posts and taxonomy term archives — the only page
// types where SureRank manages SEO metadata.
if ( ! is_singular() && ! is_tax() && ! is_tag() && ! is_category() ) {
return;
}
// Skip shared-URL contexts that fire wp_enqueue_scripts but should not render UI.
if ( wp_doing_ajax()
|| ( defined( 'REST_REQUEST' ) && REST_REQUEST )
|| is_feed()
|| is_embed()
|| is_customize_preview()
|| is_preview() ) {
return;
}
// Skip third-party visual-builder previews that render on a public URL.
if ( null !== filter_input( INPUT_GET, 'elementor-preview', FILTER_VALIDATE_INT )
|| ( function_exists( 'bricks_is_builder' ) && bricks_is_builder() )
|| ( function_exists( 'et_fb_is_enabled' ) && et_fb_is_enabled() ) ) {
return;
}
// Same gate as the editor metabox and seo-bar: manage_options by
// default, Pro Role Manager grants surerank_content_setting via
// the surerank_content_setting_access filter.
$can_edit = apply_filters( 'surerank_content_setting_access', current_user_can( 'manage_options' ) );
$post_id = is_singular() ? (int) get_queried_object_id() : 0;
/**
* Filters whether the current user can open the frontend SEO metabox.
*
* Pro can hook here to apply license or custom-role restrictions on top
* of the default capability check. On singular pages $post_id is the post
* ID; on taxonomy archives it is 0.
*
* @since 1.9.0
* @param bool $can_edit Whether the user passes the default capability check.
* @param int $post_id Post ID for singular views; 0 for taxonomy archives.
*/
if ( ! is_user_logged_in() || ! apply_filters( 'surerank_frontend_metabox_access', $can_edit, $post_id ) ) {
return;
}
if ( ! is_admin_bar_showing() ) {
return;
}
do_action( 'surerank_seo_popup_frontend_enqueue_scripts' );
wp_enqueue_media();
Dashboard::get_instance()->site_seo_check_enqueue_scripts();
$context_data = $this->get_frontend_context_data();
if ( ! $context_data ) {
return;
}
// The seo-popup script bundle must never load on the frontend: its asset
// dependencies include wp-editor, which registers the core/editor data
// store in the browser. WooCommerce's Cart/Checkout blocks treat that
// store's presence as "running inside the editor" and render their
// preview products instead of the real cart. The frontend popup is
// mounted by the front-end-meta-box entry, which only needs the
// seo-popup localized data and stylesheet (cloned into its shadow root).
$this->enqueue_vendor_and_common_assets();
$this->enqueue_seo_popup_style();
$this->build_assets_operations(
'front-end-meta-box',
[
'hook' => 'front-end-meta-box',
'object_name' => 'front_end_meta_box',
'data' => [],
]
);
$this->localize_script(
'front-end-meta-box',
'seo_popup',
$this->get_localization_data( 'elementor', $context_data )
);
}
/**
* Enqueue admin scripts
*
* @since 0.0.1
* @return void
*/
public function admin_enqueue_scripts() {
wp_enqueue_media();
$screen = $this->get_current_screen_safe();
$editor_type = self::detect_editor_type( $screen );
if ( ! self::should_enqueue_scripts( $editor_type, $screen ) ) {
return;
}
$context_data = $this->get_context_data( $editor_type, $screen );
$this->enqueue_assets( $editor_type, $context_data );
}
/**
* Add admin bar menu
*
* @since 1.6.2
*
* @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance.
*
* @return void
*/
public function add_admin_bar_menu( $wp_admin_bar ) {
// On the frontend the popup is mounted by the front-end-meta-box entry;
// the seo-popup bundle itself is editor/admin-only (see frontend_enqueue_scripts).
$popup_available = wp_script_is( $this->enqueue_prefix . '-seo-popup', 'enqueued' )
|| wp_script_is( $this->enqueue_prefix . '-front-end-meta-box', 'enqueued' );
Seo_Toolbar::get_instance()->add_nodes(
$wp_admin_bar,
$this->get_admin_bar_object_id(),
$popup_available
);
}
/**
* Register the REST route that returns the admin bar's current issue counts.
*
* @since 1.9.2
* @return void
*/
public function register_status_route() {
register_rest_route(
'surerank/v1',
'/seo-bar-status',
[
'methods' => 'GET',
'callback' => [ $this, 'get_seo_bar_status' ],
'permission_callback' => static function () {
return apply_filters( 'surerank_content_setting_access', current_user_can( 'manage_options' ) );
},
'args' => [
'post_id' => [
'sanitize_callback' => 'absint',
'default' => 0,
],
],
]
);
}
/**
* REST callback: site + page issue counts for the admin bar.
*
* @param \WP_REST_Request<array<string, mixed>> $request Request.
* @since 1.9.2
* @return \WP_REST_Response
*/
public function get_seo_bar_status( $request ) {
return rest_ensure_response( Seo_Toolbar::get_instance()->get_status( (int) $request->get_param( 'post_id' ) ) );
}
/**
* Enqueue the tiny script that live-updates the admin-bar dots when checks change.
*
* @since 1.9.2
* @return void
*/
public function enqueue_admin_bar_live() {
if ( ! is_admin_bar_showing() ) {
return;
}
$handle = $this->enqueue_prefix . '-admin-bar-live';
wp_enqueue_script(
$handle,
SURERANK_URL . 'inc/admin/assets/admin-bar-live.js',
[ 'wp-api-fetch' ],
SURERANK_VERSION,
true
);
wp_localize_script(
$handle,
'surerank_admin_bar_live',
[ 'post_id' => $this->get_admin_bar_object_id() ]
);
}
/**
* Update seo values
*
* @param int $term_id Post ID.
*
* @since 0.0.1
* @return void
*/
public function update_category_seo_values( $term_id ) {
// Validate post ID.
if ( empty( $term_id ) || ! is_int( $term_id ) ) {
return;
}
// Update post seo values.
$result = Update::term_meta( $term_id, [], [] );
if ( is_wp_error( $result ) ) {
return;
}
do_action( 'surerank_after_update_category_seo_values', $term_id );
}
/**
* Get keyword checks configuration
*
* @since 1.0.0
* @return array<string>
*/
public function keyword_checks() {
return [
'keyword_in_title',
'keyword_in_description',
'keyword_in_url',
'keyword_in_content',
];
}
/**
* Get page checks configuration
*
* @since 1.0.0
* @return array<string>
*/
public function page_checks() {
return [
'h2_subheadings',
'image_alt_text',
'media_present',
'links_present',
'url_length',
'search_engine_title',
'search_engine_description',
'canonical_url',
'all_links',
'open_graph_tags',
'broken_links',
];
}
/**
* Detect the current editor type.
*
* @param \WP_Screen|null $screen Current screen object.
* @return string Editor type.
*/
public static function detect_editor_type( $screen ): string {
if ( class_exists( \Elementor\Plugin::class ) && \Elementor\Plugin::instance()->editor->is_edit_mode() ) {
return 'elementor';
}
if ( function_exists( 'bricks_is_builder_main' ) && bricks_is_builder_main() ) {
return 'bricks';
}
// Listing pages (post/taxonomy/user list tables) use a dedicated context.
if ( $screen && in_array( $screen->base, [ 'edit', 'edit-tags', 'users' ], true ) ) {
return 'listing';
}
// User profile edit screens use a dedicated context.
if ( $screen && in_array( $screen->base, [ 'profile', 'user-edit' ], true ) ) {
return 'user';
}
// Allow integrations (e.g. Divi BFB) to override before the block-editor check.
$filtered = apply_filters( 'surerank_detect_editor_type', 'classic', $screen );
if ( 'classic' !== $filtered ) {
return $filtered;
}
if ( $screen && $screen->is_block_editor ) {
return 'block';
}
return 'classic';
}
/**
* Check if scripts should be enqueued.
*
* @param string $editor_type Editor type.
* @param \WP_Screen|null $screen Current screen object.
* @return bool True if scripts should be enqueued.
*/
public static function should_enqueue_scripts( string $editor_type, $screen ): bool {
if ( $editor_type === 'bricks' ) {
return true;
}
if ( ! $screen || empty( $screen->base ) || ! in_array( $screen->base, [ 'post', 'term', 'edit', 'edit-tags', 'profile', 'user-edit', 'users' ], true ) ) {
return false;
}
if ( in_array( $screen->base, [ 'profile', 'user-edit', 'users' ], true ) ) {
if ( ! Seo_Bar::display_metabox( '', 'wp_users' ) ) {
return false;
}
// Don't ship the popup bundle to roles that can't use it (e.g.
// subscribers on their own profile.php). Per-user edit_user is
// still enforced per row in the users-list column and per request
// by the REST object guard.
return (bool) apply_filters( 'surerank_content_setting_access', current_user_can( 'manage_options' ) );
}
if ( 'post' === $screen->base && ! empty( $screen->post_type ) ) {
if ( ! Seo_Bar::display_metabox( $screen->post_type, 'wp_posts' ) ) {
return false;
}
}
if ( 'term' === $screen->base && ! empty( $screen->taxonomy ) ) {
if ( ! Seo_Bar::display_metabox( $screen->taxonomy, 'wp_terms' ) ) {
return false;
}
}
if ( 'edit' === $screen->base && ! empty( $screen->post_type ) ) {
if ( ! Seo_Bar::display_metabox( $screen->post_type, 'wp_posts' ) ) {
return false;
}
}
if ( 'edit-tags' === $screen->base && ! empty( $screen->taxonomy ) ) {
if ( ! Seo_Bar::display_metabox( $screen->taxonomy, 'wp_terms' ) ) {
return false;
}
}
return true;
}
/**
* Resolve the current object id for the admin bar in both contexts.
*
* @since 1.9.2
* @return int Post id on a singular front-end view or a post edit screen, else 0.
*/
private function get_admin_bar_object_id() {
if ( ! is_admin() ) {
return is_singular() ? (int) get_queried_object_id() : 0;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen instanceof \WP_Screen || 'post' !== $screen->base ) {
return 0;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen lookup, no state change.
return isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0;
}
/**
* Get current screen safely.
*
* @return \WP_Screen|null
*/
private function get_current_screen_safe() {
return function_exists( 'get_current_screen' ) ? get_current_screen() : null;
}
/**
* Get context data for the current page.
*
* @param string $editor_type Editor type.
* @param \WP_Screen|null $screen Current screen object.
* @return array{post_data: array<string, mixed>, term_data: array<string, mixed>, user_data: array<string, mixed>, post_type: string, is_taxonomy: bool, is_user: bool, is_frontend?: bool} Context data.
*/
private function get_context_data( string $editor_type, $screen ): array {
$post_data = $this->get_post_data( $editor_type, $screen );
$term_data = $this->get_term_data( $screen );
$user_data = $this->get_user_data( $screen );
return [
'post_data' => $post_data,
'term_data' => $term_data,
'user_data' => $user_data,
'post_type' => $this->get_post_type( $editor_type, $screen ),
'is_taxonomy' => $this->is_taxonomy( $editor_type, $screen ),
'is_user' => $this->is_user( $screen ),
];
}
/**
* Get post data if on post edit screen.
*
* @param string $editor_type Editor type.
* @param \WP_Screen|null $screen Current screen object.
* @return array<string, mixed> Post data.
*/
private function get_post_data( string $editor_type, $screen ): array {
if ( ( $screen && 'post' === $screen->base ) || $editor_type === 'bricks' ) {
$post_id = get_the_ID();
if ( ! $post_id ) {
return [];
}
return [
'post_id' => $post_id,
'editor_type' => $editor_type,
'link' => get_the_permalink( $post_id ),
];
}
return [];
}
/**
* Get term data if on term edit screen.
*
* @param \WP_Screen|null $screen Current screen object.
* @return array<string, mixed> Term data.
*/
private function get_term_data( $screen ): array {
if ( ! $screen || 'term' !== $screen->base ) {
return [];
}
global $tag_ID;
$final_link = get_term_link( (int) $tag_ID );
if ( is_wp_error( $final_link ) ) {
return [];
}
$final_link = $this->process_category_link( $final_link, $tag_ID, $screen );
return [
'term_id' => $tag_ID,
'link' => $final_link,
];
}
/**
* Get user data if on a user profile edit screen.
*
* @param \WP_Screen|null $screen Current screen object.
* @since 1.9.0
* @return array<string, mixed> User data.
*/
private function get_user_data( $screen ): array {
if ( ! $screen || ! in_array( $screen->base, [ 'profile', 'user-edit' ], true ) ) {
return [];
}
if ( 'user-edit' === $screen->base ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only context detection; capability enforced below and on save.
$user_id = isset( $_GET['user_id'] ) ? absint( wp_unslash( $_GET['user_id'] ) ) : 0;
} else {
$user_id = get_current_user_id();
}
if ( ! $user_id || ! current_user_can( 'edit_user', $user_id ) ) {
return [];
}
return [
'user_id' => $user_id,
'link' => get_author_posts_url( $user_id ),
];
}
/**
* Check if current context is a single-user edit screen (profile or user-edit).
*
* Intentionally excludes the users.php listing: no user_id exists there at
* load time — the seo-bar badge click handler sets is_user/user_id on the
* JS side when a specific user is selected.
*
* @param \WP_Screen|null $screen Current screen object.
* @since 1.9.0
* @return bool True if user context.
*/
private function is_user( $screen ): bool {
return $screen && in_array( $screen->base, [ 'profile', 'user-edit' ], true );
}
/**
* Process category link if needed.
*
* @param string $link Term link.
* @param int $tag_ID Term ID.
* @param \WP_Screen|null $screen Current screen object.
* @return string Processed link.
*/
private function process_category_link( string $link, int $tag_ID, $screen ): string {
if ( $screen && 'category' === $screen->taxonomy && apply_filters( 'surerank_remove_category_base', false ) ) {
$term = get_term( $tag_ID );
if ( $term && ! is_wp_error( $term ) ) {
return Crawl_Optimization::get_instance()->remove_category_base_from_links( $link, $term, $screen->taxonomy );
}
}
return $link;
}
/**
* Get post type for current context.
*
* @param string $editor_type Editor type.
* @param \WP_Screen|null $screen Current screen object.
* @return string Post type.
*/
private function get_post_type( string $editor_type, $screen ): string {
if ( $editor_type === 'bricks' ) {
$post_id = get_the_ID();
$post_type = $post_id ? get_post_type( $post_id ) : false;
return $post_type !== false ? $post_type : '';
}
if ( ! $screen ) {
return '';
}
return ! empty( $screen->taxonomy ) ? $screen->taxonomy : $screen->post_type;
}
/**
* Check if current context is taxonomy.
*
* @param string $editor_type Editor type.
* @param \WP_Screen|null $screen Current screen object.
* @return bool True if taxonomy context.
*/
private function is_taxonomy( string $editor_type, $screen ): bool {
if ( $editor_type === 'bricks' ) {
return false;
}
return $screen && ! empty( $screen->taxonomy );
}
/**
* Enqueue only the seo-popup stylesheet.
*
* Used on the frontend, where the popup markup (rendered by the
* front-end-meta-box entry inside a shadow root) still relies on the
* seo-popup styles, but the seo-popup script must not load because its
* wp-editor dependency breaks WooCommerce block hydration.
*
* @since x.x.x
* @return void
*/
private function enqueue_seo_popup_style(): void {
$asset_path = $this->build_path . 'seo-popup/index.asset.php';
$version = SURERANK_VERSION;
if ( file_exists( $asset_path ) ) {
$asset = include $asset_path;
$version = is_array( $asset ) && ! empty( $asset['version'] ) ? $asset['version'] : SURERANK_VERSION;
}
$this->style_operations( 'seo-popup', $this->build_url . 'seo-popup/style.css', [], $version );
wp_style_add_data( $this->enqueue_prefix . '-seo-popup', 'rtl', 'replace' );
}
/**
* Enqueue assets for SEO popup.
*
* @param string $editor_type Editor type.
* @param array{post_data: array<string, mixed>, term_data: array<string, mixed>, user_data?: array<string, mixed>, post_type: string, is_taxonomy: bool, is_user?: bool, is_frontend?: bool} $context_data Context data.
* @return void
*/
private function enqueue_assets( string $editor_type, array $context_data ): void {
$this->enqueue_vendor_and_common_assets();
$this->build_assets_operations(
'seo-popup',
[
'hook' => 'seo-popup',
'object_name' => 'seo_popup',
'data' => $this->get_localization_data( $editor_type, $context_data ),
]
);
}
/**
* Build the localization payload shared by the seo-popup and
* front-end-meta-box entries (exposed to JS as surerank_seo_popup).
*
* @param string $editor_type Editor type.
* @param array{post_data: array<string, mixed>, term_data: array<string, mixed>, user_data?: array<string, mixed>, post_type: string, is_taxonomy: bool, is_user?: bool, is_frontend?: bool} $context_data Context data.
* @since x.x.x
* @return array<string, mixed>
*/
private function get_localization_data( string $editor_type, array $context_data ): array {
return array_merge(
[
'admin_assets_url' => SURERANK_URL . 'inc/admin/assets',
'site_icon_url' => get_site_icon_url( 16 ),
'editor_type' => $editor_type,
'post_type' => $context_data['post_type'],
'is_taxonomy' => $context_data['is_taxonomy'],
'is_user' => $context_data['is_user'] ?? false,
'description_length' => Get::description_length(),
'title_length' => Get::title_length(),
'keyword_checks' => $this->keyword_checks(),
'page_checks' => $this->page_checks(),
'image_seo' => Image_Seo::get_instance()->status(),
'is_frontend' => $context_data['is_frontend'] ?? false,
'broken_link_ignored_urls' => Get::option( 'surerank_broken_link_ignored_urls', [] ),
'active_cache_plugins' => Migrations::is_cache_plugin_active(),
],
$context_data['post_data'],
$context_data['term_data'],
$context_data['user_data'] ?? [],
$this->get_indexing_status_localization( $context_data )
);
}
/**
* Get frontend context data.
*
* @return array{post_data: array<string, mixed>, term_data: array<string, mixed>, post_type: string, is_taxonomy: bool, is_frontend: bool}|false Context data or false if invalid.
*/
private function get_frontend_context_data() {
$post_data = [];
$term_data = [];
$post_type = '';
$is_taxonomy = false;
if ( is_singular() ) {
$post_id = get_the_ID();
if ( ! $post_id ) {
return false;
}
$post_type = get_post_type( $post_id );
if ( ! $post_type ) {
return false;
}
$post_data = [
'post_id' => $post_id,
'editor_type' => apply_filters( 'surerank_frontend_editor_type', 'classic' ),
'link' => get_the_permalink( $post_id ),
];
} elseif ( is_tax() || is_tag() || is_category() ) {
$object = get_queried_object();
if ( ! $object instanceof \WP_Term ) {
return false;
}
$term_link = get_term_link( $object );
if ( is_wp_error( $term_link ) ) {
return false;
}
$term_data = [
'term_id' => $object->term_id,
'link' => $term_link,
];
$post_type = $object->taxonomy;
$is_taxonomy = true;
} else {
return false;
}
return [
'post_data' => $post_data,
'term_data' => $term_data,
'post_type' => $post_type,
'is_taxonomy' => $is_taxonomy,
'is_frontend' => true,
];
}
/**
* Build the indexing-status slice of the localized SEO popup data.
*
* The pill mounts inside the popup header and needs four things at
* boot: whether GSC is connected, whether a site property is selected,
* whether that selected property matches the current WordPress site,
* and the last cached inspection result for the current post or term.
* Returning the cached value lets the pill paint on first frame
* without a REST round-trip. The cache is suppressed when the GSC
* property doesn't match this site so a previous property's result
* can't bleed through.
*
* @param array<string, mixed> $context_data Result of get_context_data().
* @return array<string, mixed>
* @since 1.7.5
*/
private function get_indexing_status_localization( array $context_data ): array {
$is_connected = (bool) GoogleSearchConsoleController::get_instance()->get_auth_status();
$selected_site = (string) GoogleSearchConsoleAuth::get_instance()->get_credentials( 'site_url' );
$has_site = '' !== $selected_site;
$is_matching = $is_connected && $has_site && Url_Inspection::selected_site_matches_current();
$indexing_cache = null;
if ( $is_matching ) {
$post_id = isset( $context_data['post_data']['post_id'] )
? absint( $context_data['post_data']['post_id'] )
: 0;
$term_id = isset( $context_data['term_data']['term_id'] )
? absint( $context_data['term_data']['term_id'] )
: 0;
if ( $term_id ) {
$cached = get_term_meta( $term_id, Url_Inspection::META_KEY, true );
} elseif ( $post_id ) {
$cached = get_post_meta( $post_id, Url_Inspection::META_KEY, true );
} else {
$cached = null;
}
$indexing_cache = is_array( $cached ) && ! empty( $cached ) ? $cached : null;
}
return [
'is_gsc_connected' => $is_connected,
'has_gsc_site_selected' => $has_site,
'is_gsc_site_matching' => $is_matching,
'indexing_status' => $indexing_cache,
'indexing_fresh_ttl' => Url_Inspection::FRESH_TTL,
];
}
}