first commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
/**
|
||||
* Abstract class for excluding certain post types from being indexed.
|
||||
*/
|
||||
abstract class Abstract_Exclude_Post_Type implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_indexable_excluded_post_types', [ $this, 'exclude_post_types' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the post type from the indexable table.
|
||||
*
|
||||
* @param array $excluded_post_types The excluded post types.
|
||||
*
|
||||
* @return array The excluded post types, including the specific post type.
|
||||
*/
|
||||
public function exclude_post_types( $excluded_post_types ) {
|
||||
return \array_merge( $excluded_post_types, $this->get_post_type() );
|
||||
}
|
||||
|
||||
/**
|
||||
* This integration is only active when the child class's conditionals are met.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the post types to be excluded.
|
||||
* To be used in the wpseo_indexable_excluded_post_types filter.
|
||||
*
|
||||
* @return array The names of the post types.
|
||||
*/
|
||||
abstract public function get_post_type();
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
|
||||
/**
|
||||
* Class Academy_Integration.
|
||||
*/
|
||||
class Academy_Integration implements Integration_Interface {
|
||||
|
||||
public const PAGE = 'wpseo_page_academy';
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Admin_Asset_Manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the Product_Helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
private $product_helper;
|
||||
|
||||
/**
|
||||
* Holds the Short_Link_Helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
private $shortlink_helper;
|
||||
|
||||
/**
|
||||
* Constructs Academy_Integration.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
* @param Product_Helper $product_helper The Product_Helper.
|
||||
* @param Short_Link_Helper $shortlink_helper The Short_Link_Helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
Product_Helper $product_helper,
|
||||
Short_Link_Helper $shortlink_helper
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->shortlink_helper = $shortlink_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class, User_Can_Manage_Wpseo_Options_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Add page.
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
|
||||
|
||||
// Are we on the settings page?
|
||||
if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the page.
|
||||
*
|
||||
* @param array $pages The pages.
|
||||
*
|
||||
* @return array The pages.
|
||||
*/
|
||||
public function add_page( $pages ) {
|
||||
\array_splice(
|
||||
$pages,
|
||||
3,
|
||||
0,
|
||||
[
|
||||
[
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Academy', 'wordpress-seo' ),
|
||||
'wpseo_manage_options',
|
||||
self::PAGE,
|
||||
[ $this, 'display_page' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_page() {
|
||||
echo '<div id="yoast-seo-academy"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// Remove the emoji script as it is incompatible with both React and any contenteditable fields.
|
||||
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
\wp_enqueue_media();
|
||||
$this->asset_manager->enqueue_script( 'academy' );
|
||||
$this->asset_manager->enqueue_style( 'academy' );
|
||||
$this->asset_manager->localize_script( 'academy', 'wpseoScriptData', $this->get_script_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all current WP notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notices() {
|
||||
\remove_all_actions( 'admin_notices' );
|
||||
\remove_all_actions( 'user_admin_notices' );
|
||||
\remove_all_actions( 'network_admin_notices' );
|
||||
\remove_all_actions( 'all_admin_notices' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the script data.
|
||||
*
|
||||
* @return array The script data.
|
||||
*/
|
||||
public function get_script_data() {
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
|
||||
$woocommerce_seo_active = $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG );
|
||||
$local_seo_active = $addon_manager->is_installed( WPSEO_Addon_Manager::LOCAL_SLUG );
|
||||
|
||||
return [
|
||||
'preferences' => [
|
||||
'isPremium' => $this->product_helper->is_premium(),
|
||||
'isWooActive' => $woocommerce_seo_active,
|
||||
'isLocalActive' => $local_seo_active,
|
||||
'isRtl' => \is_rtl(),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_FILE ),
|
||||
'upsellSettings' => [
|
||||
'actionId' => 'load-nfd-ctb',
|
||||
'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
|
||||
],
|
||||
],
|
||||
'linkParams' => $this->shortlink_helper->get_query_params(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* This integration registers a run of the cleanup routine whenever the plugin is activated.
|
||||
*/
|
||||
class Activation_Cleanup_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Activation_Cleanup_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Indexable_Helper $indexable_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the action to register a cleanup routine run after the plugin is activated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_activate', [ $this, 'register_cleanup_routine' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a run of the cleanup routine if this has not happened yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_cleanup_routine() {
|
||||
if ( ! $this->indexable_helper->should_index_indexables() ) {
|
||||
return;
|
||||
}
|
||||
$first_activated_on = $this->options_helper->get( 'first_activated_on', false );
|
||||
|
||||
if ( ! $first_activated_on || \time() > ( $first_activated_on + ( \MINUTE_IN_SECONDS * 5 ) ) ) {
|
||||
if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
|
||||
\wp_schedule_single_event( ( \time() + \DAY_IN_SECONDS ), Cleanup_Integration::START_HOOK );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Discussed in Tech Council, a better solution is being worked on.
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Addon_Installation;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Represents the Addon installation feature.
|
||||
*/
|
||||
class Dialog_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The addon manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
protected $addon_manager;
|
||||
|
||||
/**
|
||||
* The addons.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $owned_addons;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Licenses_Page_Conditional::class,
|
||||
Addon_Installation_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Addon_Installation constructor.
|
||||
*
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
*/
|
||||
public function __construct( WPSEO_Addon_Manager $addon_manager ) {
|
||||
$this->addon_manager = $addon_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'start_addon_installation' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the addon installation flow.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start_addon_installation() {
|
||||
// Only show the dialog when we explicitly want to see it.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: This is not a form.
|
||||
if ( ! isset( $_GET['install'] ) || $_GET['install'] !== 'true' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->bust_myyoast_addon_information_cache();
|
||||
$this->owned_addons = $this->get_owned_addons();
|
||||
|
||||
if ( \count( $this->owned_addons ) > 0 ) {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'show_modal' ] );
|
||||
}
|
||||
else {
|
||||
\add_action( 'admin_notices', [ $this, 'throw_no_owned_addons_warning' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a no owned addons warning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function throw_no_owned_addons_warning() {
|
||||
echo '<div class="notice notice-warning"><p>'
|
||||
. \sprintf(
|
||||
/* translators: %1$s expands to Yoast SEO */
|
||||
\esc_html__(
|
||||
'No %1$s plugins have been installed. You don\'t seem to own any active subscriptions.',
|
||||
'wordpress-seo'
|
||||
),
|
||||
'Yoast SEO'
|
||||
)
|
||||
. '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the modal.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show_modal() {
|
||||
\wp_localize_script(
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'addon-installation',
|
||||
'wpseoAddonInstallationL10n',
|
||||
[
|
||||
'addons' => $this->owned_addons,
|
||||
'nonce' => \wp_create_nonce( 'wpseo_addon_installation' ),
|
||||
]
|
||||
);
|
||||
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_script( 'addon-installation' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of owned addons for the site in MyYoast.
|
||||
*
|
||||
* @return array List of owned addons with slug as key and name as value.
|
||||
*/
|
||||
protected function get_owned_addons() {
|
||||
$owned_addons = [];
|
||||
|
||||
foreach ( $this->addon_manager->get_myyoast_site_information()->subscriptions as $addon ) {
|
||||
$owned_addons[] = $addon->product->name;
|
||||
}
|
||||
|
||||
return $owned_addons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bust the site information transients to have fresh data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bust_myyoast_addon_information_cache() {
|
||||
$this->addon_manager->remove_site_information_transients();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Discussed in Tech Council, a better solution is being worked on.
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Addon_Installation;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use Yoast\WP\SEO\Actions\Addon_Installation\Addon_Activate_Action;
|
||||
use Yoast\WP\SEO\Actions\Addon_Installation\Addon_Install_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Activation_Error_Exception;
|
||||
use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Already_Installed_Exception;
|
||||
use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Installation_Error_Exception;
|
||||
use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Activate_Plugins_Exception;
|
||||
use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Install_Plugins_Exception;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Represents the Addon installation feature.
|
||||
*/
|
||||
class Installation_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The installation action.
|
||||
*
|
||||
* @var Addon_Install_Action
|
||||
*/
|
||||
protected $addon_install_action;
|
||||
|
||||
/**
|
||||
* The activation action.
|
||||
*
|
||||
* @var Addon_Activate_Action
|
||||
*/
|
||||
protected $addon_activate_action;
|
||||
|
||||
/**
|
||||
* The addon manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
protected $addon_manager;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Licenses_Page_Conditional::class,
|
||||
Addon_Installation_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Addon_Installation constructor.
|
||||
*
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param Addon_Activate_Action $addon_activate_action The addon activate action.
|
||||
* @param Addon_Install_Action $addon_install_action The addon install action.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Addon_Activate_Action $addon_activate_action,
|
||||
Addon_Install_Action $addon_install_action
|
||||
) {
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->addon_activate_action = $addon_activate_action;
|
||||
$this->addon_install_action = $addon_install_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_install_and_activate_addons', [ $this, 'install_and_activate_addons' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs and activates missing addons.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install_and_activate_addons() {
|
||||
if ( ! isset( $_GET['action'] ) || ! \is_string( $_GET['action'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only strictly comparing action below.
|
||||
$action = \wp_unslash( $_GET['action'] );
|
||||
if ( $action !== 'install' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\check_admin_referer( 'wpseo_addon_installation', 'nonce' );
|
||||
|
||||
echo '<div class="wrap yoast wpseo_table_page">';
|
||||
|
||||
\printf(
|
||||
'<h1 id="wpseo-title" class="yoast-h1">%s</h1>',
|
||||
\esc_html__( 'Installing and activating addons', 'wordpress-seo' )
|
||||
);
|
||||
|
||||
$licensed_addons = $this->addon_manager->get_myyoast_site_information()->subscriptions;
|
||||
|
||||
foreach ( $licensed_addons as $addon ) {
|
||||
\printf( '<p><strong>%s</strong></p>', \esc_html( $addon->product->name ) );
|
||||
|
||||
list( $installed, $output ) = $this->install_addon( $addon->product->slug, $addon->product->download );
|
||||
|
||||
if ( $installed ) {
|
||||
$activation_output = $this->activate_addon( $addon->product->slug );
|
||||
|
||||
$output = \array_merge( $output, $activation_output );
|
||||
}
|
||||
|
||||
echo '<p>';
|
||||
echo \implode( '<br />', \array_map( 'esc_html', $output ) );
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
\printf(
|
||||
/* translators: %1$s expands to an anchor tag to the admin premium page, %2$s expands to Yoast SEO Premium, %3$s expands to a closing anchor tag */
|
||||
\esc_html__( '%1$s Continue to %2$s%3$s', 'wordpress-seo' ),
|
||||
'<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_licenses' ) ) . '">',
|
||||
'Yoast SEO Premium',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
echo '</div>';
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates an addon.
|
||||
*
|
||||
* @param string $addon_slug The addon to activate.
|
||||
*
|
||||
* @return array The output of the activation.
|
||||
*/
|
||||
public function activate_addon( $addon_slug ) {
|
||||
$output = [];
|
||||
|
||||
try {
|
||||
$this->addon_activate_action->activate_addon( $addon_slug );
|
||||
|
||||
/* Translators: %s expands to the name of the addon. */
|
||||
$output[] = \__( 'Addon activated.', 'wordpress-seo' );
|
||||
} catch ( User_Cannot_Activate_Plugins_Exception $exception ) {
|
||||
$output[] = \__( 'You are not allowed to activate plugins.', 'wordpress-seo' );
|
||||
} catch ( Addon_Activation_Error_Exception $exception ) {
|
||||
$output[] = \sprintf(
|
||||
/* Translators:%s expands to the error message. */
|
||||
\__( 'Addon activation failed because of an error: %s.', 'wordpress-seo' ),
|
||||
$exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an addon.
|
||||
*
|
||||
* @param string $addon_slug The slug of the addon to install.
|
||||
* @param string $addon_download The download URL of the addon.
|
||||
*
|
||||
* @return array The installation success state and the output of the installation.
|
||||
*/
|
||||
public function install_addon( $addon_slug, $addon_download ) {
|
||||
$installed = false;
|
||||
$output = [];
|
||||
|
||||
try {
|
||||
$installed = $this->addon_install_action->install_addon( $addon_slug, $addon_download );
|
||||
} catch ( Addon_Already_Installed_Exception $exception ) {
|
||||
/* Translators: %s expands to the name of the addon. */
|
||||
$output[] = \__( 'Addon installed.', 'wordpress-seo' );
|
||||
|
||||
$installed = true;
|
||||
} catch ( User_Cannot_Install_Plugins_Exception $exception ) {
|
||||
$output[] = \__( 'You are not allowed to install plugins.', 'wordpress-seo' );
|
||||
} catch ( Addon_Installation_Error_Exception $exception ) {
|
||||
$output[] = \sprintf(
|
||||
/* Translators: %s expands to the error message. */
|
||||
\__( 'Addon installation failed because of an error: %s.', 'wordpress-seo' ),
|
||||
$exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return [ $installed, $output ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Admin_Columns_Cache_Integration class.
|
||||
*/
|
||||
class Admin_Columns_Cache_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Cache of indexables.
|
||||
*
|
||||
* @var Indexable[]
|
||||
*/
|
||||
protected $indexable_cache = [];
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* In this case: only when on an admin page.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin_Columns_Cache_Integration constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $indexable_repository ) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the appropriate actions and filters to fill the cache with
|
||||
* indexables on admin pages.
|
||||
*
|
||||
* This cache is used in showing the Yoast SEO columns on the posts overview
|
||||
* page (e.g. keyword score, incoming link count, etc.)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Hook into tablenav to calculate links and linked.
|
||||
\add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_fill_cache' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure we calculate all values in one query by filling our cache beforehand.
|
||||
*
|
||||
* @param string $target Extra table navigation location which is triggered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_fill_cache( $target ) {
|
||||
if ( $target === 'top' ) {
|
||||
$this->fill_cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the cache of indexables for all known post IDs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fill_cache() {
|
||||
global $wp_query;
|
||||
|
||||
// No need to continue building a cache if the main query did not return anything to cache.
|
||||
if ( empty( $wp_query->posts ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$posts = $wp_query->posts;
|
||||
$post_ids = [];
|
||||
|
||||
// Post lists return a list of objects.
|
||||
if ( isset( $posts[0] ) && \is_a( $posts[0], 'WP_Post' ) ) {
|
||||
$post_ids = \wp_list_pluck( $posts, 'ID' );
|
||||
}
|
||||
elseif ( isset( $posts[0] ) && \is_object( $posts[0] ) ) {
|
||||
$post_ids = $this->get_current_page_page_ids( $posts );
|
||||
}
|
||||
elseif ( ! empty( $posts ) ) {
|
||||
// Page list returns an array of post IDs.
|
||||
$post_ids = \array_keys( $posts );
|
||||
}
|
||||
|
||||
if ( empty( $post_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $posts[0] ) && ! \is_a( $posts[0], WP_Post::class ) ) {
|
||||
// Prime the post caches as core would to avoid duplicate queries.
|
||||
// This needs to be done as this executes before core does.
|
||||
\_prime_post_caches( $post_ids );
|
||||
}
|
||||
|
||||
$indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_ids, 'post', false );
|
||||
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( $indexable instanceof Indexable ) {
|
||||
$this->indexable_cache[ $indexable->object_id ] = $indexable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexable for a given post ID.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return Indexable|false The indexable. False if none could be found.
|
||||
*/
|
||||
public function get_indexable( $post_id ) {
|
||||
if ( ! \array_key_exists( $post_id, $this->indexable_cache ) ) {
|
||||
$this->indexable_cache[ $post_id ] = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' );
|
||||
}
|
||||
return $this->indexable_cache[ $post_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the page IDs set to be shown on the current page.
|
||||
* This is copied over with some changes from WP_Posts_List_Table::_display_rows_hierarchical.
|
||||
*
|
||||
* @param array $pages The pages, each containing an ID and post_parent.
|
||||
*
|
||||
* @return array The IDs of all pages shown on the current page.
|
||||
*/
|
||||
private function get_current_page_page_ids( $pages ) {
|
||||
global $per_page;
|
||||
$pagenum = isset( $_REQUEST['paged'] ) ? \absint( $_REQUEST['paged'] ) : 0;
|
||||
$pagenum = \max( 1, $pagenum );
|
||||
|
||||
/*
|
||||
* Arrange pages into two parts: top level pages and children_pages
|
||||
* children_pages is two dimensional array, eg.
|
||||
* children_pages[10][] contains all sub-pages whose parent is 10.
|
||||
* It only takes O( N ) to arrange this and it takes O( 1 ) for subsequent lookup operations
|
||||
* If searching, ignore hierarchy and treat everything as top level
|
||||
*/
|
||||
if ( empty( $_REQUEST['s'] ) ) {
|
||||
$top_level_pages = [];
|
||||
$children_pages = [];
|
||||
$pages_map = [];
|
||||
|
||||
foreach ( $pages as $page ) {
|
||||
|
||||
// Catch and repair bad pages.
|
||||
if ( $page->post_parent === $page->ID ) {
|
||||
$page->post_parent = 0;
|
||||
}
|
||||
|
||||
if ( $page->post_parent === 0 ) {
|
||||
$top_level_pages[] = $page;
|
||||
}
|
||||
else {
|
||||
$children_pages[ $page->post_parent ][] = $page;
|
||||
}
|
||||
$pages_map[ $page->ID ] = $page;
|
||||
}
|
||||
|
||||
$pages = $top_level_pages;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$start = ( ( $pagenum - 1 ) * $per_page );
|
||||
$end = ( $start + $per_page );
|
||||
$to_display = [];
|
||||
|
||||
foreach ( $pages as $page ) {
|
||||
if ( $count >= $end ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $count >= $start ) {
|
||||
$to_display[] = $page->ID;
|
||||
}
|
||||
|
||||
++$count;
|
||||
|
||||
$this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map );
|
||||
}
|
||||
|
||||
// If it is the last pagenum and there are orphaned pages, display them with paging as well.
|
||||
if ( isset( $children_pages ) && $count < $end ) {
|
||||
foreach ( $children_pages as $orphans ) {
|
||||
foreach ( $orphans as $op ) {
|
||||
if ( $count >= $end ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $count >= $start ) {
|
||||
$to_display[] = $op->ID;
|
||||
}
|
||||
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $to_display;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all child pages due to be shown on the current page to the $to_display array.
|
||||
* Copied over with some changes from WP_Posts_List_Table::_page_rows.
|
||||
*
|
||||
* @param array $children_pages The full map of child pages.
|
||||
* @param int $count The number of pages already processed.
|
||||
* @param int $parent_id The id of the parent that's currently being processed.
|
||||
* @param int $start The number at which the current overview starts.
|
||||
* @param int $end The number at which the current overview ends.
|
||||
* @param int $to_display The page IDs to be shown.
|
||||
* @param int $pages_map A map of page ID to an object with ID and post_parent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function get_child_page_ids( &$children_pages, &$count, $parent_id, $start, $end, &$to_display, &$pages_map ) {
|
||||
if ( ! isset( $children_pages[ $parent_id ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $children_pages[ $parent_id ] as $page ) {
|
||||
if ( $count >= $end ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If the page starts in a subtree, print the parents.
|
||||
if ( $count === $start && $page->post_parent > 0 ) {
|
||||
$my_parents = [];
|
||||
$my_parent = $page->post_parent;
|
||||
while ( $my_parent ) {
|
||||
// Get the ID from the list or the attribute if my_parent is an object.
|
||||
$parent_id = $my_parent;
|
||||
if ( \is_object( $my_parent ) ) {
|
||||
$parent_id = $my_parent->ID;
|
||||
}
|
||||
|
||||
$my_parent = $pages_map[ $parent_id ];
|
||||
$my_parents[] = $my_parent;
|
||||
if ( ! $my_parent->post_parent ) {
|
||||
break;
|
||||
}
|
||||
$my_parent = $my_parent->post_parent;
|
||||
}
|
||||
while ( $my_parent = \array_pop( $my_parents ) ) {
|
||||
$to_display[] = $my_parent->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $count >= $start ) {
|
||||
$to_display[] = $page->ID;
|
||||
}
|
||||
|
||||
++$count;
|
||||
|
||||
$this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map );
|
||||
}
|
||||
|
||||
unset( $children_pages[ $parent_id ] ); // Required in order to keep track of orphans.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Conditionals\Get_Request_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Background_Indexing_Integration.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin
|
||||
*/
|
||||
class Background_Indexing_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the indexing completed action.
|
||||
*
|
||||
* @var Indexable_Indexing_Complete_Action
|
||||
*/
|
||||
protected $complete_indexation_action;
|
||||
|
||||
/**
|
||||
* Represents the indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* An object that checks if we are on the Yoast admin or on the dashboard page.
|
||||
*
|
||||
* @var Yoast_Admin_And_Dashboard_Conditional
|
||||
*/
|
||||
protected $yoast_admin_and_dashboard_conditional;
|
||||
|
||||
/**
|
||||
* All available indexing actions.
|
||||
*
|
||||
* @var Indexation_Action_Interface[]
|
||||
*/
|
||||
protected $indexing_actions;
|
||||
|
||||
/**
|
||||
* An object that checks if we are handling a GET request.
|
||||
*
|
||||
* @var Get_Request_Conditional
|
||||
*/
|
||||
private $get_request_conditional;
|
||||
|
||||
/**
|
||||
* An object that checks if WP_CRON is enabled.
|
||||
*
|
||||
* @var WP_CRON_Enabled_Conditional
|
||||
*/
|
||||
private $wp_cron_enabled_conditional;
|
||||
|
||||
/**
|
||||
* The indexable helper
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Shutdown_Indexing_Integration constructor.
|
||||
*
|
||||
* @param Indexable_Indexing_Complete_Action $complete_indexation_action The complete indexing action.
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional An object that checks if we are on the Yoast admin or on the dashboard page.
|
||||
* @param Get_Request_Conditional $get_request_conditional An object that checks if we are handling a GET request.
|
||||
* @param WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional An object that checks if WP_CRON is enabled.
|
||||
* @param Indexation_Action_Interface ...$indexing_actions A list of all available indexing actions.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Indexing_Complete_Action $complete_indexation_action,
|
||||
Indexing_Helper $indexing_helper,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional,
|
||||
Get_Request_Conditional $get_request_conditional,
|
||||
WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional,
|
||||
Indexation_Action_Interface ...$indexing_actions
|
||||
) {
|
||||
$this->indexing_actions = $indexing_actions;
|
||||
$this->complete_indexation_action = $complete_indexation_action;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->yoast_admin_and_dashboard_conditional = $yoast_admin_and_dashboard_conditional;
|
||||
$this->get_request_conditional = $get_request_conditional;
|
||||
$this->wp_cron_enabled_conditional = $wp_cron_enabled_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this integration should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Migrations_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'register_shutdown_indexing' ] );
|
||||
\add_action( 'wpseo_indexable_index_batch', [ $this, 'index' ] );
|
||||
// phpcs:ignore WordPress.WP.CronInterval -- The sniff doesn't understand values with parentheses. https://github.com/WordPress/WordPress-Coding-Standards/issues/2025
|
||||
\add_filter( 'cron_schedules', [ $this, 'add_cron_schedule' ] );
|
||||
\add_action( 'admin_init', [ $this, 'schedule_cron_indexing' ], 11 );
|
||||
|
||||
$this->add_limit_filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters that change the indexing limits.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_limit_filters() {
|
||||
\add_filter( 'wpseo_post_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
|
||||
\add_filter( 'wpseo_post_type_archive_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
|
||||
\add_filter( 'wpseo_term_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
|
||||
\add_filter( 'wpseo_prominent_words_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
|
||||
\add_filter( 'wpseo_link_indexing_limit', [ $this, 'throttle_cron_link_indexing' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_shutdown_indexing() {
|
||||
if ( $this->should_index_on_shutdown( $this->get_shutdown_limit() ) ) {
|
||||
$this->register_shutdown_function( 'index' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single indexing pass of each indexing action. Intended for use as a shutdown function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index() {
|
||||
if ( \wp_doing_cron() && ! $this->should_index_on_cron() ) {
|
||||
$this->unschedule_cron_indexing();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->indexing_actions as $indexation_action ) {
|
||||
$indexation_action->index();
|
||||
}
|
||||
|
||||
if ( $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) === 0 ) {
|
||||
// We set this as complete, even though prominent words might not be complete. But that's the way we always treated that.
|
||||
$this->complete_indexation_action->complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the 'Every fifteen minutes' cron schedule to WP-Cron.
|
||||
*
|
||||
* @param array $schedules The existing schedules.
|
||||
*
|
||||
* @return array The schedules containing the fifteen_minutes schedule.
|
||||
*/
|
||||
public function add_cron_schedule( $schedules ) {
|
||||
if ( ! \is_array( $schedules ) ) {
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
$schedules['fifteen_minutes'] = [
|
||||
'interval' => ( 15 * \MINUTE_IN_SECONDS ),
|
||||
'display' => \esc_html__( 'Every fifteen minutes', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule background indexing every 15 minutes if the index isn't already up to date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_cron_indexing() {
|
||||
/**
|
||||
* Filter: 'wpseo_unindexed_count_queries_ran' - Informs whether the expensive unindexed count queries have been ran already.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param bool $have_queries_ran
|
||||
*/
|
||||
$have_queries_ran = \apply_filters( 'wpseo_unindexed_count_queries_ran', false );
|
||||
|
||||
if ( ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) && ! $have_queries_ran ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \wp_next_scheduled( 'wpseo_indexable_index_batch' ) && $this->should_index_on_cron() ) {
|
||||
\wp_schedule_event( ( \time() + \HOUR_IN_SECONDS ), 'fifteen_minutes', 'wpseo_indexable_index_batch' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit cron indexing to 15 indexables per batch instead of 25.
|
||||
*
|
||||
* @param int $indexation_limit The current limit (filter input).
|
||||
*
|
||||
* @return int The new batch limit.
|
||||
*/
|
||||
public function throttle_cron_indexing( $indexation_limit ) {
|
||||
if ( \wp_doing_cron() ) {
|
||||
/**
|
||||
* Filter: 'wpseo_cron_indexing_limit_size' - Adds the possibility to limit the number of items that are indexed when in cron action.
|
||||
*
|
||||
* @param int $limit Maximum number of indexables to be indexed per indexing action.
|
||||
*/
|
||||
return \apply_filters( 'wpseo_cron_indexing_limit_size', 15 );
|
||||
}
|
||||
|
||||
return $indexation_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit cron indexing to 3 links per batch instead of 5.
|
||||
*
|
||||
* @param int $link_indexation_limit The current limit (filter input).
|
||||
*
|
||||
* @return int The new batch limit.
|
||||
*/
|
||||
public function throttle_cron_link_indexing( $link_indexation_limit ) {
|
||||
if ( \wp_doing_cron() ) {
|
||||
/**
|
||||
* Filter: 'wpseo_cron_link_indexing_limit_size' - Adds the possibility to limit the number of links that are indexed when in cron action.
|
||||
*
|
||||
* @param int $limit Maximum number of link indexables to be indexed per link indexing action.
|
||||
*/
|
||||
return \apply_filters( 'wpseo_cron_link_indexing_limit_size', 3 );
|
||||
}
|
||||
|
||||
return $link_indexation_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether cron indexation should be performed.
|
||||
*
|
||||
* @return bool Should cron indexation be performed.
|
||||
*/
|
||||
protected function should_index_on_cron() {
|
||||
if ( ! $this->indexable_helper->should_index_indexables() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The filter supersedes everything when preventing cron indexation.
|
||||
if ( \apply_filters( 'Yoast\WP\SEO\enable_cron_indexing', true ) !== true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether background indexation should be performed.
|
||||
*
|
||||
* @param int $shutdown_limit The shutdown limit used to determine whether indexation should be run.
|
||||
*
|
||||
* @return bool Should background indexation be performed.
|
||||
*/
|
||||
protected function should_index_on_shutdown( $shutdown_limit ) {
|
||||
if ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->indexable_helper->should_index_indexables() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->wp_cron_enabled_conditional->is_met() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$total_unindexed = $this->indexing_helper->get_limited_filtered_unindexed_count_background( $shutdown_limit );
|
||||
if ( $total_unindexed === 0 || $total_unindexed > $shutdown_limit ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the shutdown limit. This limit is the amount of indexables that is generated in the background.
|
||||
*
|
||||
* @return int The shutdown limit.
|
||||
*/
|
||||
protected function get_shutdown_limit() {
|
||||
/**
|
||||
* Filter 'wpseo_shutdown_indexation_limit' - Allow filtering the number of objects that can be indexed during shutdown.
|
||||
*
|
||||
* @param int $limit The maximum number of objects indexed.
|
||||
*/
|
||||
return \apply_filters( 'wpseo_shutdown_indexation_limit', 25 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cron indexing job from the scheduled event queue.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function unschedule_cron_indexing() {
|
||||
$scheduled = \wp_next_scheduled( 'wpseo_indexable_index_batch' );
|
||||
if ( $scheduled ) {
|
||||
\wp_unschedule_event( $scheduled, 'wpseo_indexable_index_batch' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a method to be executed on shutdown.
|
||||
* This wrapper mostly exists for making this class more unittestable.
|
||||
*
|
||||
* @param string $method_name The name of the method on the current instance to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function register_shutdown_function( $method_name ) {
|
||||
\register_shutdown_function( [ $this, $method_name ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Plugin_Upgrader;
|
||||
use WP_Error;
|
||||
use WP_Upgrader;
|
||||
use Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* The Check_Required_Version class.
|
||||
*
|
||||
* This class checks if the required version of Yoast SEO is installed.
|
||||
* It also adds the `Requires Yoast SEO` header to the list of headers and updates the comparison table for the plugin overwrite.
|
||||
*/
|
||||
class Check_Required_Version implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'upgrader_source_selection', [ $this, 'check_required_version' ], 10, 3 );
|
||||
\add_filter( 'install_plugin_overwrite_comparison', [ $this, 'update_comparison_table' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return string[] The conditionals based on which this loadable should be active.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Check_Required_Version_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the required version of Yoast SEO is installed.
|
||||
*
|
||||
* The code is partly inspired by Plugin_Upgrader::check_package() in wp-admin/includes/class-plugin-upgrader.php.
|
||||
*
|
||||
* @param string $source File source location.
|
||||
* @param string|null $remote_source Remote file source location.
|
||||
* @param WP_Upgrader|null $upgrader WP_Upgrader instance.
|
||||
*
|
||||
* @return string|WP_Error The source location or a WP_Error object if the required version is not installed.
|
||||
*/
|
||||
public function check_required_version( $source, $remote_source = null, $upgrader = null ) {
|
||||
global $wp_filesystem;
|
||||
|
||||
// Bail out early if we are not installing/upgrading a plugin.
|
||||
if ( ! $upgrader instanceof Plugin_Upgrader ) {
|
||||
return $source;
|
||||
}
|
||||
|
||||
$info = [];
|
||||
|
||||
if ( \is_wp_error( $source ) ) {
|
||||
return $source;
|
||||
}
|
||||
|
||||
$working_directory = \str_replace( $wp_filesystem->wp_content_dir(), \trailingslashit( \WP_CONTENT_DIR ), $source );
|
||||
if ( ! \is_dir( $working_directory ) ) { // Confidence check, if the above fails, let's not prevent installation.
|
||||
return $source;
|
||||
}
|
||||
|
||||
// Check that the folder contains at least 1 valid plugin.
|
||||
$files = \glob( $working_directory . '*.php' );
|
||||
if ( $files ) {
|
||||
foreach ( $files as $file ) {
|
||||
$info = \get_plugin_data( $file, false, false );
|
||||
if ( ! empty( $info['Name'] ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$requires_yoast_seo = ! empty( $info['Requires Yoast SEO'] ) ? $info['Requires Yoast SEO'] : false;
|
||||
|
||||
if ( ! $this->check_requirement( $requires_yoast_seo ) ) {
|
||||
$error = \sprintf(
|
||||
/* translators: 1: Current Yoast SEO version, 2: Version required by the uploaded plugin. */
|
||||
\__( 'The Yoast SEO version on your site is %1$s, however the uploaded plugin requires %2$s.', 'wordpress-seo' ),
|
||||
\WPSEO_VERSION,
|
||||
\esc_html( $requires_yoast_seo )
|
||||
);
|
||||
|
||||
return new WP_Error(
|
||||
'incompatible_yoast_seo_required_version',
|
||||
\__( 'The package could not be installed because it\'s not supported by the currently installed Yoast SEO version.', 'wordpress-seo' ),
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the comparison table for the plugin installation when overwriting an existing plugin.
|
||||
*
|
||||
* @param string $table The output table with Name, Version, Author, RequiresWP, and RequiresPHP info.
|
||||
* @param array<string> $current_plugin_data Array with current plugin data.
|
||||
* @param array<string> $new_plugin_data Array with uploaded plugin data.
|
||||
*
|
||||
* @return string The updated comparison table.
|
||||
*/
|
||||
public function update_comparison_table( $table, $current_plugin_data, $new_plugin_data ) {
|
||||
$requires_yoast_seo_current = ! empty( $current_plugin_data['Requires Yoast SEO'] ) ? $current_plugin_data['Requires Yoast SEO'] : false;
|
||||
$requires_yoast_seo_new = ! empty( $new_plugin_data['Requires Yoast SEO'] ) ? $new_plugin_data['Requires Yoast SEO'] : false;
|
||||
|
||||
if ( $requires_yoast_seo_current !== false || $requires_yoast_seo_new !== false ) {
|
||||
$new_row = \sprintf(
|
||||
'<tr><td class="name-label">%1$s</td><td>%2$s</td><td>%3$s</td></tr>',
|
||||
\__( 'Required Yoast SEO version', 'wordpress-seo' ),
|
||||
( $requires_yoast_seo_current !== false ) ? \esc_html( $requires_yoast_seo_current ) : '-',
|
||||
( $requires_yoast_seo_new !== false ) ? \esc_html( $requires_yoast_seo_new ) : '-'
|
||||
);
|
||||
|
||||
$table = \str_replace( '</tbody>', $new_row . '</tbody>', $table );
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the required Yoast SEO version is installed.
|
||||
*
|
||||
* @param string|bool $required_version The required version.
|
||||
*
|
||||
* @return bool Whether the required version is installed, or no version is required.
|
||||
*/
|
||||
private function check_requirement( $required_version ) {
|
||||
if ( $required_version === false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return \version_compare( \WPSEO_VERSION, $required_version . '-RC0', '>=' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Option;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
|
||||
use Yoast_Form;
|
||||
|
||||
/**
|
||||
* Crawl_Settings_Integration class
|
||||
*/
|
||||
class Crawl_Settings_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for the head clean up piece.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $basic_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for the feeds clean up.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $feed_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for permalink cleanup settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $permalink_cleanup_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for search cleanup settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $search_cleanup_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for unused resources settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unused_resources_settings;
|
||||
|
||||
/**
|
||||
* The shortlinker.
|
||||
*
|
||||
* @var WPSEO_Shortlinker
|
||||
*/
|
||||
private $shortlinker;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* In this case: when on an admin page.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawl_Settings_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param WPSEO_Shortlinker $shortlinker The shortlinker.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $admin_asset_manager, WPSEO_Shortlinker $shortlinker ) {
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->shortlinker = $shortlinker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an action to add a new tab to the General page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
$this->register_setting_labels();
|
||||
|
||||
\add_action( 'wpseo_settings_tab_crawl_cleanup_network', [ $this, 'add_crawl_settings_tab_content_network' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! \is_network_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Page is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_dashboard' ) {
|
||||
return;
|
||||
}
|
||||
$this->admin_asset_manager->enqueue_script( 'crawl-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the settings to their labels.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_setting_labels() {
|
||||
$this->feed_settings = [
|
||||
'remove_feed_global' => \__( 'Global feed', 'wordpress-seo' ),
|
||||
'remove_feed_global_comments' => \__( 'Global comment feeds', 'wordpress-seo' ),
|
||||
'remove_feed_post_comments' => \__( 'Post comments feeds', 'wordpress-seo' ),
|
||||
'remove_feed_authors' => \__( 'Post authors feeds', 'wordpress-seo' ),
|
||||
'remove_feed_post_types' => \__( 'Post type feeds', 'wordpress-seo' ),
|
||||
'remove_feed_categories' => \__( 'Category feeds', 'wordpress-seo' ),
|
||||
'remove_feed_tags' => \__( 'Tag feeds', 'wordpress-seo' ),
|
||||
'remove_feed_custom_taxonomies' => \__( 'Custom taxonomy feeds', 'wordpress-seo' ),
|
||||
'remove_feed_search' => \__( 'Search results feeds', 'wordpress-seo' ),
|
||||
'remove_atom_rdf_feeds' => \__( 'Atom/RDF feeds', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$this->basic_settings = [
|
||||
'remove_shortlinks' => \__( 'Shortlinks', 'wordpress-seo' ),
|
||||
'remove_rest_api_links' => \__( 'REST API links', 'wordpress-seo' ),
|
||||
'remove_rsd_wlw_links' => \__( 'RSD / WLW links', 'wordpress-seo' ),
|
||||
'remove_oembed_links' => \__( 'oEmbed links', 'wordpress-seo' ),
|
||||
'remove_generator' => \__( 'Generator tag', 'wordpress-seo' ),
|
||||
'remove_pingback_header' => \__( 'Pingback HTTP header', 'wordpress-seo' ),
|
||||
'remove_powered_by_header' => \__( 'Powered by HTTP header', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$this->permalink_cleanup_settings = [
|
||||
'clean_campaign_tracking_urls' => \__( 'Campaign tracking URL parameters', 'wordpress-seo' ),
|
||||
'clean_permalinks' => \__( 'Unregistered URL parameters', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$this->search_cleanup_settings = [
|
||||
'search_cleanup' => \__( 'Filter search terms', 'wordpress-seo' ),
|
||||
'search_cleanup_emoji' => \__( 'Filter searches with emojis and other special characters', 'wordpress-seo' ),
|
||||
'search_cleanup_patterns' => \__( 'Filter searches with common spam patterns', 'wordpress-seo' ),
|
||||
'deny_search_crawling' => \__( 'Prevent search engines from crawling site search URLs', 'wordpress-seo' ),
|
||||
'redirect_search_pretty_urls' => \__( 'Redirect pretty URLs for search pages to raw format', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$this->unused_resources_settings = [
|
||||
'remove_emoji_scripts' => \__( 'Emoji scripts', 'wordpress-seo' ),
|
||||
'deny_wp_json_crawling' => \__( 'Prevent search engines from crawling /wp-json/', 'wordpress-seo' ),
|
||||
'deny_adsbot_crawling' => \__( 'Prevent Google AdsBot from crawling', 'wordpress-seo' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content to the Crawl Cleanup network tab.
|
||||
*
|
||||
* @param Yoast_Form $yform The yoast form object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_crawl_settings_tab_content_network( $yform ) {
|
||||
$this->add_crawl_settings( $yform );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the settings sections.
|
||||
*
|
||||
* @param Yoast_Form $yform The Yoast form class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_crawl_settings( $yform ) {
|
||||
$this->print_toggles( $this->basic_settings, $yform, \__( 'Basic crawl settings', 'wordpress-seo' ) );
|
||||
|
||||
$this->print_toggles( $this->feed_settings, $yform, \__( 'Feed crawl settings', 'wordpress-seo' ) );
|
||||
$this->print_toggles( $this->unused_resources_settings, $yform, \__( 'Remove unused resources', 'wordpress-seo' ) );
|
||||
|
||||
$first_search_setting = \array_slice( $this->search_cleanup_settings, 0, 1 );
|
||||
$rest_search_settings = \array_slice( $this->search_cleanup_settings, 1 );
|
||||
$search_settings_toggles = [
|
||||
'off' => \__( 'Disabled', 'wordpress-seo' ),
|
||||
'on' => \__( 'Enabled', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$this->print_toggles( $first_search_setting, $yform, \__( 'Search cleanup settings', 'wordpress-seo' ), $search_settings_toggles );
|
||||
|
||||
$this->print_toggles( $rest_search_settings, $yform, '', $search_settings_toggles );
|
||||
|
||||
$permalink_warning = \sprintf(
|
||||
/* Translators: %1$s expands to an opening anchor tag for a link leading to the Yoast SEO page of the Permalink Cleanup features, %2$s expands to a closing anchor tag. */
|
||||
\esc_html__(
|
||||
'These are expert features, so make sure you know what you\'re doing before removing the parameters. %1$sRead more about how your site can be affected%2$s.',
|
||||
'wordpress-seo'
|
||||
),
|
||||
'<a href="' . \esc_url( $this->shortlinker->build_shortlink( 'https://yoa.st/permalink-cleanup' ) ) . '" target="_blank" rel="noopener noreferrer">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
$this->print_toggles( $this->permalink_cleanup_settings, $yform, \__( 'Permalink cleanup settings', 'wordpress-seo' ), [], $permalink_warning );
|
||||
|
||||
// Add the original option as hidden, so as not to lose any values if it's disabled and the form is saved.
|
||||
$yform->hidden( 'clean_permalinks_extra_variables', 'clean_permalinks_extra_variables' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a list of toggles for an array of settings with labels.
|
||||
*
|
||||
* @param array $settings The settings being displayed.
|
||||
* @param Yoast_Form $yform The Yoast form class.
|
||||
* @param string $title Optional title for the settings being displayed.
|
||||
* @param array $toggles Optional naming of the toggle buttons.
|
||||
* @param string $warning Optional warning to be displayed above the toggles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function print_toggles( array $settings, Yoast_Form $yform, $title = '', $toggles = [], $warning = '' ) {
|
||||
if ( ! empty( $title ) ) {
|
||||
echo '<h3 class="yoast-crawl-settings">', \esc_html( $title ), '</h3>';
|
||||
}
|
||||
|
||||
if ( ! empty( $warning ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in Alert_Presenter.
|
||||
echo new Alert_Presenter( $warning, 'warning' );
|
||||
}
|
||||
|
||||
if ( empty( $toggles ) ) {
|
||||
$toggles = [
|
||||
'off' => \__( 'Keep', 'wordpress-seo' ),
|
||||
'on' => \__( 'Remove', 'wordpress-seo' ),
|
||||
];
|
||||
}
|
||||
|
||||
$setting_prefix = WPSEO_Option::ALLOW_KEY_PREFIX;
|
||||
$toggles = [
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
'on' => \__( 'Allow Control', 'wordpress-seo' ),
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
'off' => \__( 'Disable', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
foreach ( $settings as $setting => $label ) {
|
||||
$attr = [];
|
||||
$variable = $setting_prefix . $setting;
|
||||
|
||||
if ( $this->should_feature_be_disabled_permalink( $setting ) ) {
|
||||
$attr = [
|
||||
'disabled' => true,
|
||||
];
|
||||
$variable = $setting_prefix . $setting . '_disabled';
|
||||
|
||||
// Also add the original option as hidden, so as not to lose any values if it's disabled and the form is saved.
|
||||
$yform->hidden( $setting_prefix . $setting, $setting_prefix . $setting );
|
||||
}
|
||||
elseif ( $this->should_feature_be_disabled_multisite( $setting ) ) {
|
||||
$attr = [
|
||||
'disabled' => true,
|
||||
'preserve_disabled_value' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$yform->toggle_switch(
|
||||
$variable,
|
||||
$toggles,
|
||||
$label,
|
||||
'',
|
||||
$attr
|
||||
);
|
||||
if ( $this->should_feature_be_disabled_permalink( $setting ) ) {
|
||||
echo '<p class="yoast-crawl-settings-help">';
|
||||
if ( \current_user_can( 'manage_options' ) ) {
|
||||
\printf(
|
||||
/* translators: 1: Link start tag to the Permalinks settings page, 2: Link closing tag. */
|
||||
\esc_html__( 'This feature is disabled when your site is not using %1$spretty permalinks%2$s.', 'wordpress-seo' ),
|
||||
'<a href="' . \esc_url( \admin_url( 'options-permalink.php' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
else {
|
||||
echo \esc_html__( 'This feature is disabled when your site is not using pretty permalinks.', 'wordpress-seo' );
|
||||
}
|
||||
echo '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the feature should be disabled due to non-pretty permalinks.
|
||||
*
|
||||
* @param string $setting The setting to be displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_feature_be_disabled_permalink( $setting ) {
|
||||
return (
|
||||
\in_array( $setting, [ 'clean_permalinks', 'clean_campaign_tracking_urls' ], true )
|
||||
&& empty( \get_option( 'permalink_structure' ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the feature should be disabled due to the site being a multisite.
|
||||
*
|
||||
* @param string $setting The setting to be displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_feature_be_disabled_multisite( $setting ) {
|
||||
return (
|
||||
\in_array( $setting, [ 'deny_search_crawling', 'deny_wp_json_crawling', 'deny_adsbot_crawling' ], true )
|
||||
&& \is_multisite()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Date_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Cron_Integration class.
|
||||
*/
|
||||
class Cron_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexing notification integration.
|
||||
*
|
||||
* @var Date_Helper
|
||||
*/
|
||||
protected $date_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron_Integration constructor
|
||||
*
|
||||
* @param Date_Helper $date_helper The date helper.
|
||||
*/
|
||||
public function __construct( Date_Helper $date_helper ) {
|
||||
$this->date_helper = $date_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! \wp_next_scheduled( Indexing_Notification_Integration::NOTIFICATION_ID ) ) {
|
||||
\wp_schedule_event(
|
||||
$this->date_helper->current_time(),
|
||||
'daily',
|
||||
Indexing_Notification_Integration::NOTIFICATION_ID
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter;
|
||||
|
||||
/**
|
||||
* Deactivated_Premium_Integration class
|
||||
*/
|
||||
class Deactivated_Premium_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options' helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class, Non_Multisite_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* First_Time_Configuration_Notice_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_notices', [ $this, 'premium_deactivated_notice' ] );
|
||||
\add_action( 'wp_ajax_dismiss_premium_deactivated_notice', [ $this, 'dismiss_premium_deactivated_notice' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notice if premium is installed but not activated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function premium_deactivated_notice() {
|
||||
global $pagenow;
|
||||
if ( $pagenow === 'update.php' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->options_helper->get( 'dismiss_premium_deactivated_notice', false ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$premium_file = 'wordpress-seo-premium/wp-seo-premium.php';
|
||||
|
||||
if ( ! \current_user_can( 'activate_plugin', $premium_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->premium_is_installed_not_activated( $premium_file ) ) {
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$content = \sprintf(
|
||||
/* translators: 1: Yoast SEO Premium 2: Link start tag to activate premium, 3: Link closing tag. */
|
||||
\__( 'You\'ve installed %1$s but it\'s not activated yet. %2$sActivate %1$s now!%3$s', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium',
|
||||
'<a href="' . \esc_url(
|
||||
\wp_nonce_url(
|
||||
\self_admin_url( 'plugins.php?action=activate&plugin=' . $premium_file ),
|
||||
'activate-plugin_' . $premium_file
|
||||
)
|
||||
) . '">',
|
||||
'</a>'
|
||||
);
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
|
||||
echo new Notice_Presenter(
|
||||
/* translators: 1: Yoast SEO Premium */
|
||||
\sprintf( \__( 'Activate %1$s!', 'wordpress-seo' ), 'Yoast SEO Premium' ),
|
||||
$content,
|
||||
'support-team.svg',
|
||||
null,
|
||||
true,
|
||||
'yoast-premium-deactivated-notice'
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// Enable permanently dismissing the notice.
|
||||
echo "<script>
|
||||
function dismiss_premium_deactivated_notice(){
|
||||
var data = {
|
||||
'action': 'dismiss_premium_deactivated_notice',
|
||||
};
|
||||
|
||||
jQuery( '#yoast-premium-deactivated-notice' ).hide();
|
||||
jQuery.post( ajaxurl, data, function( response ) {});
|
||||
}
|
||||
|
||||
jQuery( document ).ready( function() {
|
||||
jQuery( 'body' ).on( 'click', '#yoast-premium-deactivated-notice .notice-dismiss', function() {
|
||||
dismiss_premium_deactivated_notice();
|
||||
} );
|
||||
} );
|
||||
</script>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the premium deactivated notice.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dismiss_premium_deactivated_notice() {
|
||||
return $this->options_helper->set( 'dismiss_premium_deactivated_notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not premium is installed and not activated.
|
||||
*
|
||||
* @param string $premium_file The premium file.
|
||||
*
|
||||
* @return bool Whether or not premium is installed and not activated.
|
||||
*/
|
||||
protected function premium_is_installed_not_activated( $premium_file ) {
|
||||
return (
|
||||
! \defined( 'WPSEO_PREMIUM_FILE' )
|
||||
&& \file_exists( \WP_PLUGIN_DIR . '/' . $premium_file )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WP_User;
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Option_Tab;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\General\User_Interface\General_Page_Integration;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Social_Profiles_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Routes\Indexing_Route;
|
||||
|
||||
/**
|
||||
* First_Time_Configuration_Integration class
|
||||
*/
|
||||
class First_Time_Configuration_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The addon manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
private $addon_manager;
|
||||
|
||||
/**
|
||||
* The shortlinker.
|
||||
*
|
||||
* @var WPSEO_Shortlinker
|
||||
*/
|
||||
private $shortlinker;
|
||||
|
||||
/**
|
||||
* The options' helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The social profiles helper.
|
||||
*
|
||||
* @var Social_Profiles_Helper
|
||||
*/
|
||||
private $social_profiles_helper;
|
||||
|
||||
/**
|
||||
* The product helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
private $product_helper;
|
||||
|
||||
/**
|
||||
* The meta tags context helper.
|
||||
*
|
||||
* @var Meta_Tags_Context
|
||||
*/
|
||||
private $meta_tags_context;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* First_Time_Configuration_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param WPSEO_Shortlinker $shortlinker The shortlinker.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Social_Profiles_Helper $social_profiles_helper The social profile helper.
|
||||
* @param Product_Helper $product_helper The product helper.
|
||||
* @param Meta_Tags_Context $meta_tags_context The meta tags context helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
WPSEO_Shortlinker $shortlinker,
|
||||
Options_Helper $options_helper,
|
||||
Social_Profiles_Helper $social_profiles_helper,
|
||||
Product_Helper $product_helper,
|
||||
Meta_Tags_Context $meta_tags_context
|
||||
) {
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->shortlinker = $shortlinker;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->social_profiles_helper = $social_profiles_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->meta_tags_context = $meta_tags_context;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'wpseo_settings_tabs_dashboard', [ $this, 'add_first_time_configuration_tab' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dedicated tab in the General sub-page.
|
||||
*
|
||||
* @param WPSEO_Options_Tabs $dashboard_tabs Object representing the tabs of the General sub-page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_first_time_configuration_tab( $dashboard_tabs ) {
|
||||
$dashboard_tabs->add_tab(
|
||||
new WPSEO_Option_Tab(
|
||||
'first-time-configuration',
|
||||
\__( 'First-time configuration', 'wordpress-seo' ),
|
||||
[ 'save_button' => false ]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the data for the first-time configuration to the wpseoFirstTimeConfigurationData object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== General_Page_Integration::PAGE ) || \is_network_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->admin_asset_manager->enqueue_script( 'indexation' );
|
||||
$this->admin_asset_manager->enqueue_style( 'first-time-configuration' );
|
||||
$this->admin_asset_manager->enqueue_style( 'admin-css' );
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$data = [
|
||||
'disabled' => ! \YoastSEO()->helpers->indexable->should_index_indexables(),
|
||||
'amount' => \YoastSEO()->helpers->indexing->get_filtered_unindexed_count(),
|
||||
'firstTime' => ( \YoastSEO()->helpers->indexing->is_initial_indexing() === true ),
|
||||
'errorMessage' => '',
|
||||
'restApi' => [
|
||||
'root' => \esc_url_raw( \rest_url() ),
|
||||
'indexing_endpoints' => $this->get_endpoints(),
|
||||
'nonce' => \wp_create_nonce( 'wp_rest' ),
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_indexing_data' Filter to adapt the data used in the indexing process.
|
||||
*
|
||||
* @param array $data The indexing data to adapt.
|
||||
*/
|
||||
$data = \apply_filters( 'wpseo_indexing_data', $data );
|
||||
|
||||
$this->admin_asset_manager->localize_script( 'indexation', 'yoastIndexingData', $data );
|
||||
|
||||
$person_id = $this->get_person_id();
|
||||
$social_profiles = $this->get_social_profiles();
|
||||
|
||||
// This filter is documented in admin/views/tabs/metas/paper-content/general/knowledge-graph.php.
|
||||
$knowledge_graph_message = \apply_filters( 'wpseo_knowledge_graph_setting_msg', '' );
|
||||
|
||||
$finished_steps = $this->get_finished_steps();
|
||||
$options = $this->get_company_or_person_options();
|
||||
$selected_option_label = '';
|
||||
$filtered_options = \array_filter(
|
||||
$options,
|
||||
function ( $item ) {
|
||||
return $item['value'] === $this->is_company_or_person();
|
||||
}
|
||||
);
|
||||
$selected_option = \reset( $filtered_options );
|
||||
if ( \is_array( $selected_option ) ) {
|
||||
$selected_option_label = $selected_option['label'];
|
||||
}
|
||||
|
||||
$data_ftc = [
|
||||
'canEditUser' => $this->can_edit_profile( $person_id ),
|
||||
'companyOrPerson' => $this->is_company_or_person(),
|
||||
'companyOrPersonLabel' => $selected_option_label,
|
||||
'companyName' => $this->get_company_name(),
|
||||
'fallbackCompanyName' => $this->get_fallback_company_name( $this->get_company_name() ),
|
||||
'websiteName' => $this->get_website_name(),
|
||||
'fallbackWebsiteName' => $this->get_fallback_website_name( $this->get_website_name() ),
|
||||
'companyLogo' => $this->get_company_logo(),
|
||||
'companyLogoFallback' => $this->get_company_fallback_logo( $this->get_company_logo() ),
|
||||
'companyLogoId' => $this->get_person_logo_id(),
|
||||
'finishedSteps' => $finished_steps,
|
||||
'personId' => (int) $person_id,
|
||||
'personName' => $this->get_person_name(),
|
||||
'personLogo' => $this->get_person_logo(),
|
||||
'personLogoFallback' => $this->get_person_fallback_logo( $this->get_person_logo() ),
|
||||
'personLogoId' => $this->get_person_logo_id(),
|
||||
'siteTagline' => $this->get_site_tagline(),
|
||||
'socialProfiles' => [
|
||||
'facebookUrl' => $social_profiles['facebook_site'],
|
||||
'twitterUsername' => $social_profiles['twitter_site'],
|
||||
'otherSocialUrls' => $social_profiles['other_social_urls'],
|
||||
],
|
||||
'isPremium' => $this->product_helper->is_premium(),
|
||||
'tracking' => $this->has_tracking_enabled(),
|
||||
'isTrackingAllowedMultisite' => $this->is_tracking_enabled_multisite(),
|
||||
'isMainSite' => $this->is_main_site(),
|
||||
'companyOrPersonOptions' => $options,
|
||||
'shouldForceCompany' => $this->should_force_company(),
|
||||
'knowledgeGraphMessage' => $knowledge_graph_message,
|
||||
'shortlinks' => [
|
||||
'gdpr' => $this->shortlinker->build_shortlink( 'https://yoa.st/gdpr-config-workout' ),
|
||||
'configIndexables' => $this->shortlinker->build_shortlink( 'https://yoa.st/config-indexables' ),
|
||||
'configIndexablesBenefits' => $this->shortlinker->build_shortlink( 'https://yoa.st/config-indexables-benefits' ),
|
||||
],
|
||||
];
|
||||
|
||||
$this->admin_asset_manager->localize_script( 'general-page', 'wpseoFirstTimeConfigurationData', $data_ftc );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the endpoints to use.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
protected function get_endpoints() {
|
||||
$endpoints = [
|
||||
'prepare' => Indexing_Route::FULL_PREPARE_ROUTE,
|
||||
'terms' => Indexing_Route::FULL_TERMS_ROUTE,
|
||||
'posts' => Indexing_Route::FULL_POSTS_ROUTE,
|
||||
'archives' => Indexing_Route::FULL_POST_TYPE_ARCHIVES_ROUTE,
|
||||
'general' => Indexing_Route::FULL_GENERAL_ROUTE,
|
||||
'indexablesComplete' => Indexing_Route::FULL_INDEXABLES_COMPLETE_ROUTE,
|
||||
'post_link' => Indexing_Route::FULL_POST_LINKS_INDEXING_ROUTE,
|
||||
'term_link' => Indexing_Route::FULL_TERM_LINKS_INDEXING_ROUTE,
|
||||
];
|
||||
|
||||
$endpoints = \apply_filters( 'wpseo_indexing_endpoints', $endpoints );
|
||||
|
||||
$endpoints['complete'] = Indexing_Route::FULL_COMPLETE_ROUTE;
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
// ** Private functions ** //
|
||||
|
||||
/**
|
||||
* Returns the finished steps array.
|
||||
*
|
||||
* @return array An array with the finished steps.
|
||||
*/
|
||||
private function get_finished_steps() {
|
||||
return $this->options_helper->get( 'configuration_finished_steps', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity represented by the site.
|
||||
*
|
||||
* @return string The entity represented by the site.
|
||||
*/
|
||||
private function is_company_or_person() {
|
||||
return $this->options_helper->get( 'company_or_person', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company name from the option in the database.
|
||||
*
|
||||
* @return string The company name.
|
||||
*/
|
||||
private function get_company_name() {
|
||||
return $this->options_helper->get( 'company_name', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback company name from the option in the database if there is no company name.
|
||||
*
|
||||
* @param string $company_name The given company name by the user, default empty string.
|
||||
*
|
||||
* @return string|false The company name.
|
||||
*/
|
||||
private function get_fallback_company_name( $company_name ) {
|
||||
if ( $company_name ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \get_bloginfo( 'name' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the website name from the option in the database.
|
||||
*
|
||||
* @return string The website name.
|
||||
*/
|
||||
private function get_website_name() {
|
||||
return $this->options_helper->get( 'website_name', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback website name from the option in the database if there is no website name.
|
||||
*
|
||||
* @param string $website_name The given website name by the user, default empty string.
|
||||
*
|
||||
* @return string|false The website name.
|
||||
*/
|
||||
private function get_fallback_website_name( $website_name ) {
|
||||
if ( $website_name ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \get_bloginfo( 'name' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company logo from the option in the database.
|
||||
*
|
||||
* @return string The company logo.
|
||||
*/
|
||||
private function get_company_logo() {
|
||||
return $this->options_helper->get( 'company_logo', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company logo id from the option in the database.
|
||||
*
|
||||
* @return string The company logo id.
|
||||
*/
|
||||
private function get_company_logo_id() {
|
||||
return $this->options_helper->get( 'company_logo_id', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company logo url from the option in the database.
|
||||
*
|
||||
* @param string $company_logo The given company logo by the user, default empty.
|
||||
*
|
||||
* @return string|false The company logo URL.
|
||||
*/
|
||||
private function get_company_fallback_logo( $company_logo ) {
|
||||
if ( $company_logo ) {
|
||||
return false;
|
||||
}
|
||||
$logo_id = $this->meta_tags_context->fallback_to_site_logo();
|
||||
|
||||
return \esc_url( \wp_get_attachment_url( $logo_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the person id from the option in the database.
|
||||
*
|
||||
* @return int|null The person id, null if empty.
|
||||
*/
|
||||
private function get_person_id() {
|
||||
return $this->options_helper->get( 'company_or_person_user_id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the person id from the option in the database.
|
||||
*
|
||||
* @return int|null The person id, null if empty.
|
||||
*/
|
||||
private function get_person_name() {
|
||||
$user = \get_userdata( $this->get_person_id() );
|
||||
if ( $user instanceof WP_User ) {
|
||||
return $user->get( 'display_name' );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the person avatar from the option in the database.
|
||||
*
|
||||
* @return string The person logo.
|
||||
*/
|
||||
private function get_person_logo() {
|
||||
return $this->options_helper->get( 'person_logo', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the person logo url from the option in the database.
|
||||
*
|
||||
* @param string $person_logo The given person logo by the user, default empty.
|
||||
*
|
||||
* @return string|false The person logo URL.
|
||||
*/
|
||||
private function get_person_fallback_logo( $person_logo ) {
|
||||
if ( $person_logo ) {
|
||||
return false;
|
||||
}
|
||||
$logo_id = $this->meta_tags_context->fallback_to_site_logo();
|
||||
|
||||
return \esc_url( \wp_get_attachment_url( $logo_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the person logo id from the option in the database.
|
||||
*
|
||||
* @return string The person logo id.
|
||||
*/
|
||||
private function get_person_logo_id() {
|
||||
return $this->options_helper->get( 'person_logo_id', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the site tagline.
|
||||
*
|
||||
* @return string The site tagline.
|
||||
*/
|
||||
private function get_site_tagline() {
|
||||
return \get_bloginfo( 'description' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the social profiles stored in the database.
|
||||
*
|
||||
* @return string[] The social profiles.
|
||||
*/
|
||||
private function get_social_profiles() {
|
||||
return $this->social_profiles_helper->get_organization_social_profiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether tracking is enabled.
|
||||
*
|
||||
* @return bool True if tracking is enabled, false otherwise, null if in Free and conf. workout step not finished.
|
||||
*/
|
||||
private function has_tracking_enabled() {
|
||||
$default = false;
|
||||
|
||||
if ( $this->product_helper->is_premium() ) {
|
||||
$default = true;
|
||||
}
|
||||
|
||||
return $this->options_helper->get( 'tracking', $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether tracking option is allowed at network level.
|
||||
*
|
||||
* @return bool True if option change is allowed, false otherwise.
|
||||
*/
|
||||
private function is_tracking_enabled_multisite() {
|
||||
$default = true;
|
||||
|
||||
if ( ! \is_multisite() ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->options_helper->get( 'allow_tracking', $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we are in a main site.
|
||||
*
|
||||
* @return bool True if it's the main site or a single site, false if it's a subsite.
|
||||
*/
|
||||
private function is_main_site() {
|
||||
return \is_main_site();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the options for the Company or Person select.
|
||||
* Returns only the company option if it is forced (by Local SEO), otherwise returns company and person option.
|
||||
*
|
||||
* @return array The options for the company-or-person select.
|
||||
*/
|
||||
private function get_company_or_person_options() {
|
||||
$options = [
|
||||
[
|
||||
'label' => \__( 'Organization', 'wordpress-seo' ),
|
||||
'value' => 'company',
|
||||
'id' => 'company',
|
||||
],
|
||||
[
|
||||
'label' => \__( 'Person', 'wordpress-seo' ),
|
||||
'value' => 'person',
|
||||
'id' => 'person',
|
||||
],
|
||||
];
|
||||
if ( $this->should_force_company() ) {
|
||||
$options = [
|
||||
[
|
||||
'label' => \__( 'Organization', 'wordpress-seo' ),
|
||||
'value' => 'company',
|
||||
'id' => 'company',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we should force "Organization".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_force_company() {
|
||||
return $this->addon_manager->is_installed( WPSEO_Addon_Manager::LOCAL_SLUG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has the capability to edit a specific user.
|
||||
*
|
||||
* @param int $person_id The id of the person to edit.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function can_edit_profile( $person_id ) {
|
||||
return \current_user_can( 'edit_user', $person_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\First_Time_Configuration_Notice_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter;
|
||||
|
||||
/**
|
||||
* First_Time_Configuration_Notice_Integration class
|
||||
*/
|
||||
class First_Time_Configuration_Notice_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options' helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The first time configuration notice helper.
|
||||
*
|
||||
* @var First_Time_Configuration_Notice_Helper
|
||||
*/
|
||||
private $first_time_configuration_notice_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* First_Time_Configuration_Notice_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param First_Time_Configuration_Notice_Helper $first_time_configuration_notice_helper The first time configuration notice helper.
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
First_Time_Configuration_Notice_Helper $first_time_configuration_notice_helper,
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->first_time_configuration_notice_helper = $first_time_configuration_notice_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp_ajax_dismiss_first_time_configuration_notice', [ $this, 'dismiss_first_time_configuration_notice' ] );
|
||||
\add_action( 'admin_notices', [ $this, 'first_time_configuration_notice' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the First-time configuration notice.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dismiss_first_time_configuration_notice() {
|
||||
// Check for nonce.
|
||||
if ( ! \check_ajax_referer( 'wpseo-dismiss-first-time-configuration-notice', 'nonce', false ) ) {
|
||||
return false;
|
||||
}
|
||||
return $this->options_helper->set( 'dismiss_configuration_workout_notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether and where the "First-time SEO Configuration" admin notice should be displayed.
|
||||
*
|
||||
* @return bool Whether the "First-time SEO Configuration" admin notice should be displayed.
|
||||
*/
|
||||
public function should_display_first_time_configuration_notice() {
|
||||
return $this->first_time_configuration_notice_helper->should_display_first_time_configuration_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice when the first-time configuration has not been finished yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function first_time_configuration_notice() {
|
||||
if ( ! $this->should_display_first_time_configuration_notice() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$title = $this->first_time_configuration_notice_helper->get_first_time_configuration_title();
|
||||
$link_url = \esc_url( \self_admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) );
|
||||
|
||||
if ( ! $this->first_time_configuration_notice_helper->should_show_alternate_message() ) {
|
||||
$content = \sprintf(
|
||||
/* translators: 1: Link start tag to the first-time configuration, 2: Yoast SEO, 3: Link closing tag. */
|
||||
\__( 'Get started quickly with the %1$s%2$s First-time configuration%3$s and configure Yoast SEO with the optimal SEO settings for your site!', 'wordpress-seo' ),
|
||||
'<a href="' . $link_url . '">',
|
||||
'Yoast SEO',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
else {
|
||||
$content = \sprintf(
|
||||
/* translators: 1: Link start tag to the first-time configuration, 2: Link closing tag. */
|
||||
\__( 'We noticed that you haven\'t fully configured Yoast SEO yet. Optimize your SEO settings even further by using our improved %1$s First-time configuration%2$s.', 'wordpress-seo' ),
|
||||
'<a href="' . $link_url . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
$notice = new Notice_Presenter(
|
||||
$title,
|
||||
$content,
|
||||
'mirrored_fit_bubble_woman_1_optim.svg',
|
||||
null,
|
||||
true,
|
||||
'yoast-first-time-configuration-notice'
|
||||
);
|
||||
|
||||
//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe.
|
||||
echo $notice->present();
|
||||
|
||||
// Enable permanently dismissing the notice.
|
||||
echo '<script>
|
||||
jQuery( document ).ready( function() {
|
||||
jQuery( "body" ).on( "click", "#yoast-first-time-configuration-notice .notice-dismiss", function() {
|
||||
jQuery( "#yoast-first-time-configuration-notice" ).hide();
|
||||
const data = {
|
||||
"action": "dismiss_first_time_configuration_notice",
|
||||
"nonce": "' . \esc_js( \wp_create_nonce( 'wpseo-dismiss-first-time-configuration-notice' ) ) . '"
|
||||
};
|
||||
jQuery.post( ajaxurl, data, function( response ) {});
|
||||
} );
|
||||
} );
|
||||
</script>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WP_Screen;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\News_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Fix_News_Dependencies_Integration class.
|
||||
*/
|
||||
class Fix_News_Dependencies_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* In this case: when on an admin page.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class, News_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an action to disable script concatenation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
global $pagenow;
|
||||
|
||||
// Load the editor script when on an edit post or new post page.
|
||||
$is_post_edit_page = $pagenow === 'post.php' || $pagenow === 'post-new.php';
|
||||
if ( $is_post_edit_page ) {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'add_news_script_dependency' ], 11 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the news script dependency.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_news_script_dependency() {
|
||||
$scripts = \wp_scripts();
|
||||
|
||||
if ( ! isset( $scripts->registered['wpseo-news-editor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_block_editor = WP_Screen::get()->is_block_editor();
|
||||
$post_edit_handle = 'post-edit';
|
||||
if ( ! $is_block_editor ) {
|
||||
$post_edit_handle = 'post-edit-classic';
|
||||
}
|
||||
|
||||
$scripts->registered['wpseo-news-editor']->deps[] = WPSEO_Admin_Asset_Manager::PREFIX . $post_edit_handle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Services\Health_Check\Health_Check;
|
||||
|
||||
/**
|
||||
* Integrates health checks with WordPress' Site Health.
|
||||
*/
|
||||
class Health_Check_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Contains all the health check implementations.
|
||||
*
|
||||
* @var Health_Check[]
|
||||
*/
|
||||
private $health_checks = [];
|
||||
|
||||
/**
|
||||
* Uses the dependency injection container to obtain all available implementations of the Health_Check interface.
|
||||
*
|
||||
* @param Health_Check ...$health_checks The available health checks implementations.
|
||||
*/
|
||||
public function __construct( Health_Check ...$health_checks ) {
|
||||
$this->health_checks = $health_checks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks the health checks into WordPress' site status tests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'site_status_tests', [ $this, 'add_health_checks' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* In this case: only when on an admin page.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a WordPress site status tests array, and adds Yoast's health checks if it is.
|
||||
*
|
||||
* @param string[] $tests Array containing WordPress site status tests.
|
||||
* @return string[] Array containing WordPress site status tests with Yoast's health checks.
|
||||
*/
|
||||
public function add_health_checks( $tests ) {
|
||||
if ( ! $this->is_valid_site_status_tests_array( $tests ) ) {
|
||||
return $tests;
|
||||
}
|
||||
|
||||
return $this->add_health_checks_to_site_status_tests( $tests );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input array is a WordPress site status tests array.
|
||||
*
|
||||
* @param mixed $tests Array to check.
|
||||
* @return bool Returns true if the input array is a WordPress site status tests array.
|
||||
*/
|
||||
private function is_valid_site_status_tests_array( $tests ) {
|
||||
if ( ! \is_array( $tests ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! \array_key_exists( 'direct', $tests ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! \is_array( $tests['direct'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the health checks to WordPress' site status tests.
|
||||
*
|
||||
* @param string[] $tests Array containing WordPress site status tests.
|
||||
* @return string[] Array containing WordPress site status tests with Yoast's health checks.
|
||||
*/
|
||||
private function add_health_checks_to_site_status_tests( $tests ) {
|
||||
foreach ( $this->health_checks as $health_check ) {
|
||||
if ( $health_check->is_excluded() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tests['direct'][ $health_check->get_test_identifier() ] = [
|
||||
'test' => [ $health_check, 'run_and_get_result' ],
|
||||
];
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Tracking_Server_Data;
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Academy_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Integrations\Settings_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Support_Integration;
|
||||
|
||||
/**
|
||||
* Class WPSEO_HelpScout
|
||||
*/
|
||||
class HelpScout_Beacon implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The id for the beacon.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $beacon_id = '2496aba6-0292-489c-8f5d-1c0fba417c2f';
|
||||
|
||||
/**
|
||||
* The id for the beacon for users that have tracking on.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $beacon_id_tracking_users = '6b8e74c5-aa81-4295-b97b-c2a62a13ea7f';
|
||||
|
||||
/**
|
||||
* The products the beacon is loaded for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $products = [];
|
||||
|
||||
/**
|
||||
* Whether to ask the user's consent before loading in HelpScout.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ask_consent = true;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The array of pages we need to show the beacon on with their respective beacon IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pages_ids;
|
||||
|
||||
/**
|
||||
* The array of pages we need to show the beacon on.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $base_pages = [
|
||||
'wpseo_dashboard',
|
||||
Settings_Integration::PAGE,
|
||||
Academy_Integration::PAGE,
|
||||
Support_Integration::PAGE,
|
||||
'wpseo_search_console',
|
||||
'wpseo_tools',
|
||||
'wpseo_licenses',
|
||||
'wpseo_workouts',
|
||||
'wpseo_integrations',
|
||||
];
|
||||
|
||||
/**
|
||||
* The current admin page
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* The asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* The migration status object.
|
||||
*
|
||||
* @var Migration_Status
|
||||
*/
|
||||
protected $migration_status;
|
||||
|
||||
/**
|
||||
* Headless_Rest_Endpoints_Enabled_Conditional constructor.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
* @param Migration_Status $migration_status The migrations status.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, WPSEO_Admin_Asset_Manager $asset_manager, Migration_Status $migration_status ) {
|
||||
$this->options = $options;
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->ask_consent = ! $this->options->get( 'tracking' );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$this->page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) );
|
||||
}
|
||||
else {
|
||||
$this->page = null;
|
||||
}
|
||||
$this->migration_status = $migration_status;
|
||||
|
||||
foreach ( $this->base_pages as $page ) {
|
||||
if ( $this->ask_consent ) {
|
||||
// We want to be able to show surveys to people who have tracking on, so we give them a different beacon.
|
||||
$this->pages_ids[ $page ] = $this->beacon_id_tracking_users;
|
||||
}
|
||||
else {
|
||||
$this->pages_ids[ $page ] = $this->beacon_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_help_scout_script' ] );
|
||||
\add_action( 'admin_footer', [ $this, 'output_beacon_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the HelpScout script.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_help_scout_script() {
|
||||
// Make sure plugins can filter in their "stuff", before we check whether we're outputting a beacon.
|
||||
$this->filter_settings();
|
||||
if ( ! $this->is_beacon_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset_manager->enqueue_script( 'help-scout-beacon' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a small piece of javascript for the beacon.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function output_beacon_js() {
|
||||
if ( ! $this->is_beacon_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\printf(
|
||||
'<script type="text/javascript">window.%1$s(\'%2$s\', %3$s)</script>',
|
||||
( $this->ask_consent ) ? 'wpseoHelpScoutBeaconConsent' : 'wpseoHelpScoutBeacon',
|
||||
\esc_html( $this->pages_ids[ $this->page ] ),
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping done in format_json_encode.
|
||||
WPSEO_Utils::format_json_encode( (array) $this->get_session_data() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current page is a page containing the beacon.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_beacon_page() {
|
||||
$return = false;
|
||||
if ( ! empty( $this->page ) && $GLOBALS['pagenow'] === 'admin.php' && isset( $this->pages_ids[ $this->page ] ) ) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_helpscout_show_beacon' - Allows overriding whether we show the HelpScout beacon.
|
||||
*
|
||||
* @param bool $show_beacon Whether we show the beacon or not.
|
||||
*/
|
||||
return \apply_filters( 'wpseo_helpscout_show_beacon', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the identifying data.
|
||||
*
|
||||
* @return string The data to pass as identifying data.
|
||||
*/
|
||||
protected function get_session_data() {
|
||||
// Short-circuit if we can get the needed data from a transient.
|
||||
$transient_data = \get_transient( 'yoast_beacon_session_data' );
|
||||
|
||||
if ( \is_array( $transient_data ) ) {
|
||||
return WPSEO_Utils::format_json_encode( $transient_data );
|
||||
}
|
||||
|
||||
$current_user = \wp_get_current_user();
|
||||
|
||||
// Do not make these strings translatable! They are for our support agents, the user won't see them!
|
||||
$data = \array_merge(
|
||||
[
|
||||
'name' => \trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ),
|
||||
'email' => $current_user->user_email,
|
||||
'Languages' => $this->get_language_settings(),
|
||||
],
|
||||
$this->get_server_info(),
|
||||
[
|
||||
'WordPress Version' => $this->get_wordpress_version(),
|
||||
'Active theme' => $this->get_theme_info(),
|
||||
'Active plugins' => $this->get_active_plugins(),
|
||||
'Must-use and dropins' => $this->get_mustuse_and_dropins(),
|
||||
'Indexables status' => $this->get_indexables_status(),
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! empty( $this->products ) ) {
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
foreach ( $this->products as $product ) {
|
||||
$subscription = $addon_manager->get_subscription( $product );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[ $subscription->product->name ] = $this->get_product_info( $subscription );
|
||||
}
|
||||
}
|
||||
|
||||
// Store the data in a transient for 5 minutes to prevent overhead on every backend pageload.
|
||||
\set_transient( 'yoast_beacon_session_data', $data, ( 5 * \MINUTE_IN_SECONDS ) );
|
||||
|
||||
return WPSEO_Utils::format_json_encode( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic info about the server software.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_server_info() {
|
||||
$server_tracking_data = new WPSEO_Tracking_Server_Data();
|
||||
$server_data = $server_tracking_data->get();
|
||||
$server_data = $server_data['server'];
|
||||
|
||||
$fields_to_use = [
|
||||
'Server IP' => 'ip',
|
||||
'PHP Version' => 'PhpVersion',
|
||||
'cURL Version' => 'CurlVersion',
|
||||
];
|
||||
|
||||
$server_data['CurlVersion'] = $server_data['CurlVersion']['version'] . ' (SSL Support ' . $server_data['CurlVersion']['sslSupport'] . ')';
|
||||
|
||||
$server_info = [];
|
||||
|
||||
foreach ( $fields_to_use as $label => $field_to_use ) {
|
||||
if ( isset( $server_data[ $field_to_use ] ) ) {
|
||||
$server_info[ $label ] = \esc_html( $server_data[ $field_to_use ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Get the memory limits for the server and, if different, from WordPress as well.
|
||||
$memory_limit = \ini_get( 'memory_limit' );
|
||||
$server_info['Memory limits'] = 'Server memory limit: ' . $memory_limit;
|
||||
|
||||
if ( $memory_limit !== \WP_MEMORY_LIMIT ) {
|
||||
$server_info['Memory limits'] .= ', WP_MEMORY_LIMIT: ' . \WP_MEMORY_LIMIT;
|
||||
}
|
||||
|
||||
if ( $memory_limit !== \WP_MAX_MEMORY_LIMIT ) {
|
||||
$server_info['Memory limits'] .= ', WP_MAX_MEMORY_LIMIT: ' . \WP_MAX_MEMORY_LIMIT;
|
||||
}
|
||||
|
||||
return $server_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns info about the Yoast SEO plugin version and license.
|
||||
*
|
||||
* @param object $plugin The plugin.
|
||||
*
|
||||
* @return string The product info.
|
||||
*/
|
||||
private function get_product_info( $plugin ) {
|
||||
if ( empty( $plugin ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product_info = \sprintf(
|
||||
'Expiration date %1$s',
|
||||
$plugin->expiry_date
|
||||
);
|
||||
|
||||
return $product_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WordPress version + a suffix about the multisite status.
|
||||
*
|
||||
* @return string The WordPress version string.
|
||||
*/
|
||||
private function get_wordpress_version() {
|
||||
global $wp_version;
|
||||
|
||||
$wordpress_version = $wp_version;
|
||||
if ( \is_multisite() ) {
|
||||
$wordpress_version .= ' (multisite: yes)';
|
||||
}
|
||||
else {
|
||||
$wordpress_version .= ' (multisite: no)';
|
||||
}
|
||||
|
||||
return $wordpress_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the current theme.
|
||||
*
|
||||
* @return string The theme info as string.
|
||||
*/
|
||||
private function get_theme_info() {
|
||||
$theme = \wp_get_theme();
|
||||
|
||||
$theme_info = \sprintf(
|
||||
'%1$s (Version %2$s, %3$s)',
|
||||
\esc_html( $theme->display( 'Name' ) ),
|
||||
\esc_html( $theme->display( 'Version' ) ),
|
||||
\esc_attr( $theme->display( 'ThemeURI' ) )
|
||||
);
|
||||
|
||||
if ( \is_child_theme() ) {
|
||||
$theme_info .= \sprintf( ', this is a child theme of: %1$s', \esc_html( $theme->display( 'Template' ) ) );
|
||||
}
|
||||
|
||||
return $theme_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stringified list of all active plugins, separated by a pipe.
|
||||
*
|
||||
* @return string The active plugins.
|
||||
*/
|
||||
private function get_active_plugins() {
|
||||
$updates_available = \get_site_transient( 'update_plugins' );
|
||||
|
||||
$active_plugins = '';
|
||||
foreach ( \wp_get_active_and_valid_plugins() as $plugin ) {
|
||||
$plugin_data = \get_plugin_data( $plugin );
|
||||
$plugin_file = \str_replace( \trailingslashit( \WP_PLUGIN_DIR ), '', $plugin );
|
||||
$plugin_update_available = '';
|
||||
|
||||
if ( isset( $updates_available->response[ $plugin_file ] ) ) {
|
||||
$plugin_update_available = ' [update available]';
|
||||
}
|
||||
|
||||
$active_plugins .= \sprintf(
|
||||
'%1$s (Version %2$s%3$s, %4$s) | ',
|
||||
\esc_html( $plugin_data['Name'] ),
|
||||
\esc_html( $plugin_data['Version'] ),
|
||||
$plugin_update_available,
|
||||
\esc_attr( $plugin_data['PluginURI'] )
|
||||
);
|
||||
}
|
||||
|
||||
return $active_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV list of all must-use and drop-in plugins.
|
||||
*
|
||||
* @return string The active plugins.
|
||||
*/
|
||||
private function get_mustuse_and_dropins() {
|
||||
$dropins = \get_dropins();
|
||||
$mustuse_plugins = \get_mu_plugins();
|
||||
|
||||
if ( ! \is_array( $dropins ) ) {
|
||||
$dropins = [];
|
||||
}
|
||||
|
||||
if ( ! \is_array( $mustuse_plugins ) ) {
|
||||
$mustuse_plugins = [];
|
||||
}
|
||||
|
||||
return \sprintf( 'Must-Use plugins: %1$d, Drop-ins: %2$d', \count( $mustuse_plugins ), \count( $dropins ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the indexables status details.
|
||||
*
|
||||
* @return string The indexables status in a string.
|
||||
*/
|
||||
private function get_indexables_status() {
|
||||
$indexables_status = 'Indexing completed: ';
|
||||
$indexing_completed = $this->options->get( 'indexables_indexing_completed' );
|
||||
$indexing_reason = $this->options->get( 'indexing_reason' );
|
||||
|
||||
$indexables_status .= ( $indexing_completed ) ? 'yes' : 'no';
|
||||
$indexables_status .= ( $indexing_reason ) ? ', latest indexing reason: ' . \esc_html( $indexing_reason ) : '';
|
||||
|
||||
foreach ( [ 'free', 'premium' ] as $migration_name ) {
|
||||
$current_status = $this->migration_status->get_error( $migration_name );
|
||||
|
||||
if ( \is_array( $current_status ) && isset( $current_status['message'] ) ) {
|
||||
$indexables_status .= ', migration error: ' . \esc_html( $current_status['message'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $indexables_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns language settings for the website and the current user.
|
||||
*
|
||||
* @return string The locale settings of the site and user.
|
||||
*/
|
||||
private function get_language_settings() {
|
||||
$site_locale = \get_locale();
|
||||
$user_locale = \get_user_locale();
|
||||
|
||||
$language_settings = \sprintf(
|
||||
'Site locale: %1$s, user locale: %2$s',
|
||||
( \is_string( $site_locale ) ) ? \esc_html( $site_locale ) : 'unknown',
|
||||
( \is_string( $user_locale ) ) ? \esc_html( $user_locale ) : 'unknown'
|
||||
);
|
||||
|
||||
return $language_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this integration should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows filtering of the HelpScout settings. Hooked to admin_head to prevent timing issues, not too early, not too late.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function filter_settings() {
|
||||
$filterable_helpscout_setting = [
|
||||
'products' => $this->products,
|
||||
'pages_ids' => $this->pages_ids,
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_helpscout_beacon_settings' - Allows overriding the HelpScout beacon settings.
|
||||
*
|
||||
* @param string $beacon_settings The HelpScout beacon settings.
|
||||
*/
|
||||
$helpscout_settings = \apply_filters( 'wpseo_helpscout_beacon_settings', $filterable_helpscout_setting );
|
||||
|
||||
$this->products = $helpscout_settings['products'];
|
||||
$this->pages_ids = $helpscout_settings['pages_ids'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Import_Tool_Selected_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
|
||||
use Yoast\WP\SEO\Routes\Importing_Route;
|
||||
use Yoast\WP\SEO\Services\Importing\Importable_Detector_Service;
|
||||
|
||||
/**
|
||||
* Loads import script when on the Tool's page.
|
||||
*/
|
||||
class Import_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Contains the asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* The Importable Detector service.
|
||||
*
|
||||
* @var Importable_Detector_Service
|
||||
*/
|
||||
protected $importable_detector;
|
||||
|
||||
/**
|
||||
* The Importing Route class.
|
||||
*
|
||||
* @var Importing_Route
|
||||
*/
|
||||
protected $importing_route;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Import_Tool_Selected_Conditional::class,
|
||||
Yoast_Tools_Page_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
* @param Importable_Detector_Service $importable_detector The importable detector.
|
||||
* @param Importing_Route $importing_route The importing route.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
Importable_Detector_Service $importable_detector,
|
||||
Importing_Route $importing_route
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->importable_detector = $importable_detector;
|
||||
$this->importing_route = $importing_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_import_script' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the Import script.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_import_script() {
|
||||
\wp_enqueue_style( 'dashicons' );
|
||||
$this->asset_manager->enqueue_script( 'import' );
|
||||
|
||||
$data = [
|
||||
'restApi' => [
|
||||
'root' => \esc_url_raw( \rest_url() ),
|
||||
'cleanup_endpoints' => $this->get_cleanup_endpoints(),
|
||||
'importing_endpoints' => $this->get_importing_endpoints(),
|
||||
'nonce' => \wp_create_nonce( 'wp_rest' ),
|
||||
],
|
||||
'assets' => [
|
||||
'loading_msg_import' => \esc_html__( 'The import can take a long time depending on your site\'s size.', 'wordpress-seo' ),
|
||||
'loading_msg_cleanup' => \esc_html__( 'The cleanup can take a long time depending on your site\'s size.', 'wordpress-seo' ),
|
||||
'note' => \esc_html__( 'Note: ', 'wordpress-seo' ),
|
||||
'cleanup_after_import_msg' => \esc_html__( 'After you\'ve imported data from another SEO plugin, please make sure to clean up all the original data from that plugin. (step 5)', 'wordpress-seo' ),
|
||||
'select_placeholder' => \esc_html__( 'Select SEO plugin', 'wordpress-seo' ),
|
||||
'no_data_msg' => \esc_html__( 'No data found from other SEO plugins.', 'wordpress-seo' ),
|
||||
'validation_failure' => $this->get_validation_failure_alert(),
|
||||
'import_failure' => $this->get_import_failure_alert( true ),
|
||||
'cleanup_failure' => $this->get_import_failure_alert( false ),
|
||||
'spinner' => \admin_url( 'images/loading.gif' ),
|
||||
'replacing_texts' => [
|
||||
'cleanup_button' => \esc_html__( 'Clean up', 'wordpress-seo' ),
|
||||
'import_explanation' => \esc_html__( 'Please select an SEO plugin below to see what data can be imported.', 'wordpress-seo' ),
|
||||
'cleanup_explanation' => \esc_html__( 'Once you\'re certain that your site is working properly with the imported data from another SEO plugin, you can clean up all the original data from that plugin.', 'wordpress-seo' ),
|
||||
/* translators: %s: expands to the name of the plugin that is selected to be imported */
|
||||
'select_header' => \esc_html__( 'The import from %s includes:', 'wordpress-seo' ),
|
||||
'plugins' => [
|
||||
'aioseo' => [
|
||||
[
|
||||
'data_name' => \esc_html__( 'Post metadata (SEO titles, descriptions, etc.)', 'wordpress-seo' ),
|
||||
'data_note' => \esc_html__( 'Note: This metadata will only be imported if there is no existing Yoast SEO metadata yet.', 'wordpress-seo' ),
|
||||
],
|
||||
[
|
||||
'data_name' => \esc_html__( 'Default settings', 'wordpress-seo' ),
|
||||
'data_note' => \esc_html__( 'Note: These settings will overwrite the default settings of Yoast SEO.', 'wordpress-seo' ),
|
||||
],
|
||||
],
|
||||
'other' => [
|
||||
[
|
||||
'data_name' => \esc_html__( 'Post metadata (SEO titles, descriptions, etc.)', 'wordpress-seo' ),
|
||||
'data_note' => \esc_html__( 'Note: This metadata will only be imported if there is no existing Yoast SEO metadata yet.', 'wordpress-seo' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_importing_data' Filter to adapt the data used in the import process.
|
||||
*
|
||||
* @param array $data The import data to adapt.
|
||||
*/
|
||||
$data = \apply_filters( 'wpseo_importing_data', $data );
|
||||
|
||||
$this->asset_manager->localize_script( 'import', 'yoastImportData', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the importing endpoints to use.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
protected function get_importing_endpoints() {
|
||||
$available_actions = $this->importable_detector->detect_importers();
|
||||
$importing_endpoints = [];
|
||||
|
||||
$available_sorted_actions = $this->sort_actions( $available_actions );
|
||||
|
||||
foreach ( $available_sorted_actions as $plugin => $types ) {
|
||||
foreach ( $types as $type ) {
|
||||
$importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type );
|
||||
}
|
||||
}
|
||||
|
||||
return $importing_endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the array of importing actions, by moving any validating actions to the start for every plugin.
|
||||
*
|
||||
* @param array $available_actions The array of actions that we want to sort.
|
||||
*
|
||||
* @return array The sorted array of actions.
|
||||
*/
|
||||
protected function sort_actions( $available_actions ) {
|
||||
$first_action = 'validate_data';
|
||||
$available_sorted_actions = [];
|
||||
|
||||
foreach ( $available_actions as $plugin => $plugin_available_actions ) {
|
||||
|
||||
$validate_action_position = \array_search( $first_action, $plugin_available_actions, true );
|
||||
|
||||
if ( ! empty( $validate_action_position ) ) {
|
||||
unset( $plugin_available_actions[ $validate_action_position ] );
|
||||
\array_unshift( $plugin_available_actions, $first_action );
|
||||
}
|
||||
|
||||
$available_sorted_actions[ $plugin ] = $plugin_available_actions;
|
||||
}
|
||||
|
||||
return $available_sorted_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the importing endpoints to use.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
protected function get_cleanup_endpoints() {
|
||||
$available_actions = $this->importable_detector->detect_cleanups();
|
||||
$importing_endpoints = [];
|
||||
|
||||
foreach ( $available_actions as $plugin => $types ) {
|
||||
foreach ( $types as $type ) {
|
||||
$importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type );
|
||||
}
|
||||
}
|
||||
|
||||
return $importing_endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the validation failure alert using the Alert_Presenter.
|
||||
*
|
||||
* @return string The validation failure alert.
|
||||
*/
|
||||
protected function get_validation_failure_alert() {
|
||||
$content = \esc_html__( 'The AIOSEO import was cancelled because some AIOSEO data is missing. Please try and take the following steps to fix this:', 'wordpress-seo' );
|
||||
$content .= '<br/>';
|
||||
$content .= '<ol><li>';
|
||||
$content .= \esc_html__( 'If you have never saved any AIOSEO \'Search Appearance\' settings, please do that first and run the import again.', 'wordpress-seo' );
|
||||
$content .= '</li>';
|
||||
$content .= '<li>';
|
||||
$content .= \esc_html__( 'If you already have saved AIOSEO \'Search Appearance\' settings and the issue persists, please contact our support team so we can take a closer look.', 'wordpress-seo' );
|
||||
$content .= '</li></ol>';
|
||||
|
||||
$validation_failure_alert = new Alert_Presenter( $content, 'error' );
|
||||
|
||||
return $validation_failure_alert->present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the import failure alert using the Alert_Presenter.
|
||||
*
|
||||
* @param bool $is_import Wether it's an import or not.
|
||||
*
|
||||
* @return string The import failure alert.
|
||||
*/
|
||||
protected function get_import_failure_alert( $is_import ) {
|
||||
$content = \esc_html__( 'Cleanup failed with the following error:', 'wordpress-seo' );
|
||||
if ( $is_import ) {
|
||||
$content = \esc_html__( 'Import failed with the following error:', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
$content .= '<br/><br/>';
|
||||
$content .= \esc_html( '%s' );
|
||||
|
||||
$import_failure_alert = new Alert_Presenter( $content, 'error' );
|
||||
|
||||
return $import_failure_alert->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Indexables_Exclude_Taxonomy_Integration class
|
||||
*/
|
||||
class Indexables_Exclude_Taxonomy_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Indexables_Exclude_Taxonomy_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_indexable_excluded_taxonomies', [ $this, 'exclude_taxonomies_for_indexation' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the taxonomy from the indexable table.
|
||||
*
|
||||
* @param array $excluded_taxonomies The excluded taxonomies.
|
||||
*
|
||||
* @return array The excluded taxonomies, including specific taxonomies.
|
||||
*/
|
||||
public function exclude_taxonomies_for_indexation( $excluded_taxonomies ) {
|
||||
$taxonomies_to_exclude = \array_merge( $excluded_taxonomies, [ 'wp_pattern_category' ] );
|
||||
|
||||
if ( $this->options_helper->get( 'disable-post_format', false ) ) {
|
||||
return \array_merge( $taxonomies_to_exclude, [ 'post_format' ] );
|
||||
}
|
||||
|
||||
return $taxonomies_to_exclude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Environment_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Notification_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Indexing_Failed_Notification_Presenter;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Indexing_Notification_Presenter;
|
||||
use Yoast_Notification;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Class Indexing_Notification_Integration.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin
|
||||
*/
|
||||
class Indexing_Notification_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The notification ID.
|
||||
*/
|
||||
public const NOTIFICATION_ID = 'wpseo-reindex';
|
||||
|
||||
/**
|
||||
* The Yoast notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* The product helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
protected $product_helper;
|
||||
|
||||
/**
|
||||
* The current page helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $page_helper;
|
||||
|
||||
/**
|
||||
* The short link helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
protected $short_link_helper;
|
||||
|
||||
/**
|
||||
* The notification helper.
|
||||
*
|
||||
* @var Notification_Helper
|
||||
*/
|
||||
protected $notification_helper;
|
||||
|
||||
/**
|
||||
* The indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* The Addon Manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
protected $addon_manager;
|
||||
|
||||
/**
|
||||
* The Environment Helper.
|
||||
*
|
||||
* @var Environment_Helper
|
||||
*/
|
||||
protected $environment_helper;
|
||||
|
||||
/**
|
||||
* Indexing_Notification_Integration constructor.
|
||||
*
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Product_Helper $product_helper The product helper.
|
||||
* @param Current_Page_Helper $page_helper The current page helper.
|
||||
* @param Short_Link_Helper $short_link_helper The short link helper.
|
||||
* @param Notification_Helper $notification_helper The notification helper.
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param Environment_Helper $environment_helper The environment helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Product_Helper $product_helper,
|
||||
Current_Page_Helper $page_helper,
|
||||
Short_Link_Helper $short_link_helper,
|
||||
Notification_Helper $notification_helper,
|
||||
Indexing_Helper $indexing_helper,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Environment_Helper $environment_helper
|
||||
) {
|
||||
$this->notification_center = $notification_center;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->page_helper = $page_helper;
|
||||
$this->short_link_helper = $short_link_helper;
|
||||
$this->notification_helper = $notification_helper;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->environment_helper = $environment_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* Adds hooks and jobs to cleanup or add the notification when necessary.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->page_helper->get_current_yoast_seo_page() === 'wpseo_dashboard' ) {
|
||||
\add_action( 'admin_init', [ $this, 'maybe_cleanup_notification' ] );
|
||||
}
|
||||
|
||||
if ( $this->indexing_helper->has_reason() ) {
|
||||
\add_action( 'admin_init', [ $this, 'maybe_create_notification' ] );
|
||||
}
|
||||
|
||||
\add_action( self::NOTIFICATION_ID, [ $this, 'maybe_create_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Not_Admin_Ajax_Conditional::class,
|
||||
User_Can_Manage_Wpseo_Options_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the notification should be shown and adds
|
||||
* it to the notification center if this is the case.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_create_notification() {
|
||||
if ( ! $this->should_show_notification() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID ) ) {
|
||||
$notification = $this->notification();
|
||||
$this->notification_helper->restore_notification( $notification );
|
||||
$this->notification_center->add_notification( $notification );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the notification should not be shown anymore and removes
|
||||
* it from the notification center if this is the case.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_cleanup_notification() {
|
||||
$notification = $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID );
|
||||
|
||||
if ( $notification === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->should_show_notification() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the notification should be shown.
|
||||
*
|
||||
* @return bool If the notification should be shown.
|
||||
*/
|
||||
protected function should_show_notification() {
|
||||
if ( ! $this->environment_helper->is_production_mode() ) {
|
||||
return false;
|
||||
}
|
||||
// Don't show a notification if the indexing has already been started earlier.
|
||||
if ( $this->indexing_helper->get_started() > 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're about to perform expensive queries, let's inform.
|
||||
\add_filter( 'wpseo_unindexed_count_queries_ran', '__return_true' );
|
||||
|
||||
// Never show a notification when nothing should be indexed.
|
||||
return $this->indexing_helper->get_limited_filtered_unindexed_count( 1 ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the notification.
|
||||
*
|
||||
* @return Yoast_Notification The notification to show.
|
||||
*/
|
||||
protected function notification() {
|
||||
$reason = $this->indexing_helper->get_reason();
|
||||
|
||||
$presenter = $this->get_presenter( $reason );
|
||||
|
||||
return new Yoast_Notification(
|
||||
$presenter,
|
||||
[
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
'id' => self::NOTIFICATION_ID,
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 0.8,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the presenter to use to show the notification.
|
||||
*
|
||||
* @param string $reason The reason for the notification.
|
||||
*
|
||||
* @return Indexing_Failed_Notification_Presenter|Indexing_Notification_Presenter
|
||||
*/
|
||||
protected function get_presenter( $reason ) {
|
||||
if ( $reason === Indexing_Reasons::REASON_INDEXING_FAILED ) {
|
||||
$presenter = new Indexing_Failed_Notification_Presenter( $this->product_helper, $this->short_link_helper, $this->addon_manager );
|
||||
}
|
||||
else {
|
||||
$total_unindexed = $this->indexing_helper->get_filtered_unindexed_count();
|
||||
$presenter = new Indexing_Notification_Presenter( $this->short_link_helper, $total_unindexed, $reason );
|
||||
}
|
||||
|
||||
return $presenter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\No_Tool_Selected_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Indexing_Error_Presenter;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Indexing_List_Item_Presenter;
|
||||
use Yoast\WP\SEO\Routes\Importing_Route;
|
||||
use Yoast\WP\SEO\Routes\Indexing_Route;
|
||||
use Yoast\WP\SEO\Services\Importing\Importable_Detector_Service;
|
||||
|
||||
/**
|
||||
* Class Indexing_Tool_Integration. Bridge to the Javascript indexing tool on Yoast SEO Tools page.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin
|
||||
*/
|
||||
class Indexing_Tool_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Represents the indexables helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* The short link helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
protected $short_link_helper;
|
||||
|
||||
/**
|
||||
* Represents the indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* The addon manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
protected $addon_manager;
|
||||
|
||||
/**
|
||||
* The product helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
protected $product_helper;
|
||||
|
||||
/**
|
||||
* The Importable Detector service.
|
||||
*
|
||||
* @var Importable_Detector_Service
|
||||
*/
|
||||
protected $importable_detector;
|
||||
|
||||
/**
|
||||
* The Importing Route class.
|
||||
*
|
||||
* @var Importing_Route
|
||||
*/
|
||||
protected $importing_route;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this integration should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Migrations_Conditional::class,
|
||||
No_Tool_Selected_Conditional::class,
|
||||
Yoast_Tools_Page_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexing_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Short_Link_Helper $short_link_helper The short link helper.
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param Product_Helper $product_helper The product helper.
|
||||
* @param Importable_Detector_Service $importable_detector The importable detector.
|
||||
* @param Importing_Route $importing_route The importing route.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Short_Link_Helper $short_link_helper,
|
||||
Indexing_Helper $indexing_helper,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Product_Helper $product_helper,
|
||||
Importable_Detector_Service $importable_detector,
|
||||
Importing_Route $importing_route
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->short_link_helper = $short_link_helper;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->importable_detector = $importable_detector;
|
||||
$this->importing_route = $importing_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_tools_overview_list_items_internal', [ $this, 'render_indexing_list_item' ], 10 );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ], 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
$this->asset_manager->enqueue_script( 'indexation' );
|
||||
$this->asset_manager->enqueue_style( 'admin-css' );
|
||||
$this->asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$data = [
|
||||
'disabled' => ! $this->indexable_helper->should_index_indexables(),
|
||||
'amount' => $this->indexing_helper->get_filtered_unindexed_count(),
|
||||
'firstTime' => ( $this->indexing_helper->is_initial_indexing() === true ),
|
||||
'errorMessage' => $this->render_indexing_error(),
|
||||
'restApi' => [
|
||||
'root' => \esc_url_raw( \rest_url() ),
|
||||
'indexing_endpoints' => $this->get_indexing_endpoints(),
|
||||
'importing_endpoints' => $this->get_importing_endpoints(),
|
||||
'nonce' => \wp_create_nonce( 'wp_rest' ),
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_indexing_data' Filter to adapt the data used in the indexing process.
|
||||
*
|
||||
* @param array $data The indexing data to adapt.
|
||||
*/
|
||||
$data = \apply_filters( 'wpseo_indexing_data', $data );
|
||||
|
||||
$this->asset_manager->localize_script( 'indexation', 'yoastIndexingData', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* The error to show if optimization failed.
|
||||
*
|
||||
* @return string The error to show if optimization failed.
|
||||
*/
|
||||
protected function render_indexing_error() {
|
||||
$presenter = new Indexing_Error_Presenter(
|
||||
$this->short_link_helper,
|
||||
$this->product_helper,
|
||||
$this->addon_manager
|
||||
);
|
||||
|
||||
return $presenter->present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the site has a valid Premium subscription.
|
||||
*
|
||||
* @return bool If the site has a valid Premium subscription.
|
||||
*/
|
||||
protected function has_valid_premium_subscription() {
|
||||
return $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the indexing list item.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_indexing_list_item() {
|
||||
if ( \current_user_can( 'manage_options' ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The output is correctly escaped in the presenter.
|
||||
echo new Indexing_List_Item_Presenter( $this->short_link_helper );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the indexing endpoints to use.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
protected function get_indexing_endpoints() {
|
||||
$endpoints = [
|
||||
'prepare' => Indexing_Route::FULL_PREPARE_ROUTE,
|
||||
'terms' => Indexing_Route::FULL_TERMS_ROUTE,
|
||||
'posts' => Indexing_Route::FULL_POSTS_ROUTE,
|
||||
'archives' => Indexing_Route::FULL_POST_TYPE_ARCHIVES_ROUTE,
|
||||
'general' => Indexing_Route::FULL_GENERAL_ROUTE,
|
||||
'indexablesComplete' => Indexing_Route::FULL_INDEXABLES_COMPLETE_ROUTE,
|
||||
'post_link' => Indexing_Route::FULL_POST_LINKS_INDEXING_ROUTE,
|
||||
'term_link' => Indexing_Route::FULL_TERM_LINKS_INDEXING_ROUTE,
|
||||
];
|
||||
|
||||
$endpoints = \apply_filters( 'wpseo_indexing_endpoints', $endpoints );
|
||||
|
||||
$endpoints['complete'] = Indexing_Route::FULL_COMPLETE_ROUTE;
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the importing endpoints to use.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
protected function get_importing_endpoints() {
|
||||
$available_actions = $this->importable_detector->detect_importers();
|
||||
$importing_endpoints = [];
|
||||
|
||||
foreach ( $available_actions as $plugin => $types ) {
|
||||
foreach ( $types as $type ) {
|
||||
$importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type );
|
||||
}
|
||||
}
|
||||
|
||||
return $importing_endpoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Installation_Success_Integration class
|
||||
*/
|
||||
class Installation_Success_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The product helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
protected $product_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Installation_Success_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Product_Helper $product_helper The product helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Product_Helper $product_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'admin_menu', [ $this, 'add_submenu_page' ], 9 );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'admin_init', [ $this, 'maybe_redirect' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the installation success page if an installation has just occurred.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect() {
|
||||
if ( \defined( 'DOING_AJAX' ) && \DOING_AJAX ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->options_helper->get( 'should_redirect_after_install_free', false ) ) {
|
||||
return;
|
||||
}
|
||||
$this->options_helper->set( 'should_redirect_after_install_free', false );
|
||||
|
||||
if ( ! empty( $this->options_helper->get( 'activation_redirect_timestamp_free', 0 ) ) ) {
|
||||
return;
|
||||
}
|
||||
$this->options_helper->set( 'activation_redirect_timestamp_free', \time() );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification -- This is not a form.
|
||||
if ( isset( $_REQUEST['activate-multi'] ) && $_REQUEST['activate-multi'] === 'true' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->product_helper->is_premium() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \is_network_admin() || \is_plugin_active_for_network( \WPSEO_BASENAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_safe_redirect( \admin_url( 'admin.php?page=wpseo_installation_successful_free' ), 302, 'Yoast SEO' );
|
||||
$this->terminate_execution();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the installation success submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array the filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
\add_submenu_page(
|
||||
'',
|
||||
\__( 'Installation Successful', 'wordpress-seo' ),
|
||||
'',
|
||||
'manage_options',
|
||||
'wpseo_installation_successful_free',
|
||||
[ $this, 'render_page' ]
|
||||
);
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets on the Installation success page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_installation_successful_free' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_script( 'installation-success' );
|
||||
$asset_manager->enqueue_style( 'tailwind' );
|
||||
$asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$ftc_url = \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) );
|
||||
|
||||
$asset_manager->localize_script(
|
||||
'installation-success',
|
||||
'wpseoInstallationSuccess',
|
||||
[
|
||||
'pluginUrl' => \esc_url( \plugins_url( '', \WPSEO_FILE ) ),
|
||||
'firstTimeConfigurationUrl' => $ftc_url,
|
||||
'dashboardUrl' => \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the installation success page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page() {
|
||||
echo '<div id="wpseo-installation-successful-free" class="yoast"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the `exit` function to make unit testing easier.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function terminate_execution() {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Easy_Digital_Downloads;
|
||||
use SeriouslySimplePodcasting\Integrations\Yoast\Schema\PodcastEpisode;
|
||||
use TEC\Events\Integrations\Plugins\WordPress_SEO\Events_Schema;
|
||||
use WP_Recipe_Maker;
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Jetpack_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Integrations_Page class
|
||||
*/
|
||||
class Integrations_Page implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The Woocommerce helper.
|
||||
*
|
||||
* @var Woocommerce_Helper
|
||||
*/
|
||||
private $woocommerce_helper;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The elementor conditional.
|
||||
*
|
||||
* @var Elementor_Activated_Conditional
|
||||
*/
|
||||
private $elementor_conditional;
|
||||
|
||||
/**
|
||||
* The jetpack conditional.
|
||||
*
|
||||
* @var Jetpack_Conditional
|
||||
*/
|
||||
private $jetpack_conditional;
|
||||
|
||||
/**
|
||||
* The site kit integration configuration data.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_integration_data;
|
||||
|
||||
/**
|
||||
* The site kit consent management endpoint.
|
||||
*
|
||||
* @var Site_Kit_Consent_Management_Endpoint
|
||||
*/
|
||||
private $site_kit_consent_management_endpoint;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Workouts_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Woocommerce_Helper $woocommerce_helper The WooCommerce helper.
|
||||
* @param Elementor_Activated_Conditional $elementor_conditional The elementor conditional.
|
||||
* @param Jetpack_Conditional $jetpack_conditional The Jetpack conditional.
|
||||
* @param Site_Kit $site_kit_integration_data The site kit integration
|
||||
* configuration data.
|
||||
* @param Site_Kit_Consent_Management_Endpoint $site_kit_consent_management_endpoint The site kit consent
|
||||
* management endpoint.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
Options_Helper $options_helper,
|
||||
Woocommerce_Helper $woocommerce_helper,
|
||||
Elementor_Activated_Conditional $elementor_conditional,
|
||||
Jetpack_Conditional $jetpack_conditional,
|
||||
Site_Kit $site_kit_integration_data,
|
||||
Site_Kit_Consent_Management_Endpoint $site_kit_consent_management_endpoint
|
||||
) {
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->woocommerce_helper = $woocommerce_helper;
|
||||
$this->elementor_conditional = $elementor_conditional;
|
||||
$this->jetpack_conditional = $jetpack_conditional;
|
||||
$this->site_kit_integration_data = $site_kit_integration_data;
|
||||
$this->site_kit_consent_management_endpoint = $site_kit_consent_management_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 10 );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the integrations submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array The filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
$integrations_page = [
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Integrations', 'wordpress-seo' ),
|
||||
'wpseo_manage_options',
|
||||
'wpseo_integrations',
|
||||
[ $this, 'render_target' ],
|
||||
];
|
||||
|
||||
\array_splice( $submenu_pages, 1, 0, [ $integrations_page ] );
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the integrations app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_integrations' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->admin_asset_manager->enqueue_style( 'admin-css' );
|
||||
$this->admin_asset_manager->enqueue_style( 'tailwind' );
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$this->admin_asset_manager->enqueue_script( 'integrations-page' );
|
||||
|
||||
$woocommerce_seo_file = 'wpseo-woocommerce/wpseo-woocommerce.php';
|
||||
$acf_seo_file = 'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php';
|
||||
$acf_seo_file_github = 'yoast-acf-analysis/yoast-acf-analysis.php';
|
||||
$algolia_file = 'wp-search-with-algolia/algolia.php';
|
||||
$old_algolia_file = 'search-by-algolia-instant-relevant-results/algolia.php';
|
||||
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
$woocommerce_seo_installed = $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG );
|
||||
|
||||
$woocommerce_seo_active = \is_plugin_active( $woocommerce_seo_file );
|
||||
$woocommerce_active = $this->woocommerce_helper->is_active();
|
||||
$acf_seo_installed = \file_exists( \WP_PLUGIN_DIR . '/' . $acf_seo_file );
|
||||
$acf_seo_github_installed = \file_exists( \WP_PLUGIN_DIR . '/' . $acf_seo_file_github );
|
||||
$acf_seo_active = \is_plugin_active( $acf_seo_file );
|
||||
$acf_seo_github_active = \is_plugin_active( $acf_seo_file_github );
|
||||
$acf_active = \class_exists( 'acf' );
|
||||
$algolia_active = \is_plugin_active( $algolia_file );
|
||||
$edd_active = \class_exists( Easy_Digital_Downloads::class );
|
||||
$old_algolia_active = \is_plugin_active( $old_algolia_file );
|
||||
$tec_active = \class_exists( Events_Schema::class );
|
||||
$ssp_active = \class_exists( PodcastEpisode::class );
|
||||
$wp_recipe_maker_active = \class_exists( WP_Recipe_Maker::class );
|
||||
$mastodon_active = $this->is_mastodon_active();
|
||||
|
||||
$woocommerce_seo_activate_url = \wp_nonce_url(
|
||||
\self_admin_url( 'plugins.php?action=activate&plugin=' . $woocommerce_seo_file ),
|
||||
'activate-plugin_' . $woocommerce_seo_file
|
||||
);
|
||||
|
||||
if ( $acf_seo_installed ) {
|
||||
$acf_seo_activate_url = \wp_nonce_url(
|
||||
\self_admin_url( 'plugins.php?action=activate&plugin=' . $acf_seo_file ),
|
||||
'activate-plugin_' . $acf_seo_file
|
||||
);
|
||||
}
|
||||
else {
|
||||
$acf_seo_activate_url = \wp_nonce_url(
|
||||
\self_admin_url( 'plugins.php?action=activate&plugin=' . $acf_seo_file_github ),
|
||||
'activate-plugin_' . $acf_seo_file_github
|
||||
);
|
||||
}
|
||||
|
||||
$acf_seo_install_url = \wp_nonce_url(
|
||||
\self_admin_url( 'update.php?action=install-plugin&plugin=acf-content-analysis-for-yoast-seo' ),
|
||||
'install-plugin_acf-content-analysis-for-yoast-seo'
|
||||
);
|
||||
|
||||
$this->admin_asset_manager->localize_script(
|
||||
'integrations-page',
|
||||
'wpseoIntegrationsData',
|
||||
[
|
||||
'semrush_integration_active' => $this->options_helper->get( 'semrush_integration_active', true ),
|
||||
'allow_semrush_integration' => $this->options_helper->get( 'allow_semrush_integration_active', true ),
|
||||
'algolia_integration_active' => $this->options_helper->get( 'algolia_integration_active', false ),
|
||||
'allow_algolia_integration' => $this->options_helper->get( 'allow_algolia_integration_active', true ),
|
||||
'wincher_integration_active' => $this->options_helper->get( 'wincher_integration_active', true ),
|
||||
'allow_wincher_integration' => null,
|
||||
'elementor_integration_active' => $this->elementor_conditional->is_met(),
|
||||
'jetpack_integration_active' => $this->jetpack_conditional->is_met(),
|
||||
'woocommerce_seo_installed' => $woocommerce_seo_installed,
|
||||
'woocommerce_seo_active' => $woocommerce_seo_active,
|
||||
'woocommerce_active' => $woocommerce_active,
|
||||
'woocommerce_seo_activate_url' => $woocommerce_seo_activate_url,
|
||||
'acf_seo_installed' => $acf_seo_installed || $acf_seo_github_installed,
|
||||
'acf_seo_active' => $acf_seo_active || $acf_seo_github_active,
|
||||
'acf_active' => $acf_active,
|
||||
'acf_seo_activate_url' => $acf_seo_activate_url,
|
||||
'acf_seo_install_url' => $acf_seo_install_url,
|
||||
'algolia_active' => $algolia_active || $old_algolia_active,
|
||||
'edd_integration_active' => $edd_active,
|
||||
'ssp_integration_active' => $ssp_active,
|
||||
'tec_integration_active' => $tec_active,
|
||||
'wp-recipe-maker_integration_active' => $wp_recipe_maker_active,
|
||||
'mastodon_active' => $mastodon_active,
|
||||
'is_multisite' => \is_multisite(),
|
||||
'plugin_url' => \plugins_url( '', \WPSEO_FILE ),
|
||||
'site_kit_configuration' => $this->site_kit_integration_data->to_array(),
|
||||
'site_kit_consent_management_url' => $this->site_kit_consent_management_endpoint->get_url(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the target for the React to mount to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_target() {
|
||||
?>
|
||||
<div class="wrap yoast wpseo-admin-page page-wpseo">
|
||||
<div class="wp-header-end" style="height: 0; width: 0;"></div>
|
||||
<div id="wpseo-integrations"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Mastodon profile field has been filled in.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_mastodon_active() {
|
||||
return \apply_filters( 'wpseo_mastodon_active', false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WP_Query;
|
||||
use wpdb;
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Link_Count_Columns_Integration class.
|
||||
*/
|
||||
class Link_Count_Columns_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Partial column name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const COLUMN_LINKED = 'linked';
|
||||
|
||||
/**
|
||||
* Partial column name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const COLUMN_LINKS = 'links';
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* The database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* The post link builder.
|
||||
*
|
||||
* @var Post_Link_Indexing_Action
|
||||
*/
|
||||
protected $post_link_indexing_action;
|
||||
|
||||
/**
|
||||
* The admin columns cache.
|
||||
*
|
||||
* @var Admin_Columns_Cache_Integration
|
||||
*/
|
||||
protected $admin_columns_cache;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Posts_Overview_Or_Ajax_Conditional::class,
|
||||
Should_Index_Links_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Link_Count_Columns_Integration constructor
|
||||
*
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
* @param wpdb $wpdb The wpdb object.
|
||||
* @param Post_Link_Indexing_Action $post_link_indexing_action The post link indexing action.
|
||||
* @param Admin_Columns_Cache_Integration $admin_columns_cache The admin columns cache.
|
||||
*/
|
||||
public function __construct(
|
||||
Post_Type_Helper $post_type_helper,
|
||||
wpdb $wpdb,
|
||||
Post_Link_Indexing_Action $post_link_indexing_action,
|
||||
Admin_Columns_Cache_Integration $admin_columns_cache
|
||||
) {
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->post_link_indexing_action = $post_link_indexing_action;
|
||||
$this->admin_columns_cache = $admin_columns_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'posts_clauses', [ $this, 'order_by_links' ], 1, 2 );
|
||||
\add_filter( 'posts_clauses', [ $this, 'order_by_linked' ], 1, 2 );
|
||||
|
||||
\add_action( 'admin_init', [ $this, 'register_init_hooks' ] );
|
||||
|
||||
// Adds a filter to exclude the attachments from the link count.
|
||||
\add_filter( 'wpseo_link_count_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks that need to be registered after `init` due to all post types not yet being registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_init_hooks() {
|
||||
$public_post_types = \apply_filters( 'wpseo_link_count_post_types', $this->post_type_helper->get_accessible_post_types() );
|
||||
|
||||
if ( ! \is_array( $public_post_types ) || empty( $public_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $public_post_types as $post_type ) {
|
||||
\add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'add_post_columns' ] );
|
||||
\add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
|
||||
\add_filter( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the columns for the post overview.
|
||||
*
|
||||
* @param array $columns Array with columns.
|
||||
*
|
||||
* @return array The extended array with columns.
|
||||
*/
|
||||
public function add_post_columns( $columns ) {
|
||||
if ( ! \is_array( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns[ 'wpseo-' . self::COLUMN_LINKS ] = \sprintf(
|
||||
'<span class="yoast-linked-to yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
|
||||
\esc_attr__( 'Number of outgoing internal links in this post.', 'wordpress-seo' ),
|
||||
/* translators: Hidden accessibility text. */
|
||||
\esc_html__( 'Outgoing internal links', 'wordpress-seo' )
|
||||
);
|
||||
|
||||
if ( $this->post_link_indexing_action->get_total_unindexed() === 0 ) {
|
||||
$columns[ 'wpseo-' . self::COLUMN_LINKED ] = \sprintf(
|
||||
'<span class="yoast-linked-from yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
|
||||
\esc_attr__( 'Number of internal links linking to this post.', 'wordpress-seo' ),
|
||||
/* translators: Hidden accessibility text. */
|
||||
\esc_html__( 'Received internal links', 'wordpress-seo' )
|
||||
);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query pieces to allow ordering column by links to post.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_linked( $pieces, $query ) {
|
||||
if ( $query->get( 'orderby' ) !== 'wpseo-' . self::COLUMN_LINKED ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
return $this->build_sort_query_pieces( $pieces, $query, 'incoming_link_count' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query pieces to allow ordering column by links to post.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_links( $pieces, $query ) {
|
||||
if ( $query->get( 'orderby' ) !== 'wpseo-' . self::COLUMN_LINKS ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
return $this->build_sort_query_pieces( $pieces, $query, 'link_count' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the pieces for a sorting query.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
* @param string $field The field in the table to JOIN on.
|
||||
*
|
||||
* @return array Modified Query pieces.
|
||||
*/
|
||||
protected function build_sort_query_pieces( $pieces, $query, $field ) {
|
||||
// We only want our code to run in the main WP query.
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
// Get the order query variable - ASC or DESC.
|
||||
$order = \strtoupper( $query->get( 'order' ) );
|
||||
|
||||
// Make sure the order setting qualifies. If not, set default as ASC.
|
||||
if ( ! \in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
|
||||
$order = 'ASC';
|
||||
}
|
||||
|
||||
$table = Model::get_table_name( 'Indexable' );
|
||||
|
||||
$pieces['join'] .= " LEFT JOIN $table AS yoast_indexable ON yoast_indexable.object_id = {$this->wpdb->posts}.ID AND yoast_indexable.object_type = 'post' ";
|
||||
$pieces['orderby'] = "yoast_indexable.$field $order, FIELD( {$this->wpdb->posts}.post_status, 'publish' ) $order, {$pieces['orderby']}";
|
||||
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $column_name Column to display the content for.
|
||||
* @param int $post_id Post to display the column content for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function column_content( $column_name, $post_id ) {
|
||||
$indexable = $this->admin_columns_cache->get_indexable( $post_id );
|
||||
// Nothing to output if we don't have the value.
|
||||
if ( $indexable === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $column_name ) {
|
||||
case 'wpseo-' . self::COLUMN_LINKS:
|
||||
echo (int) $indexable->link_count;
|
||||
return;
|
||||
case 'wpseo-' . self::COLUMN_LINKED:
|
||||
if ( \get_post_status( $post_id ) === 'publish' ) {
|
||||
echo (int) $indexable->incoming_link_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sortable columns.
|
||||
*
|
||||
* @param array $columns Array with sortable columns.
|
||||
*
|
||||
* @return array The extended array with sortable columns.
|
||||
*/
|
||||
public function column_sort( array $columns ) {
|
||||
$columns[ 'wpseo-' . self::COLUMN_LINKS ] = 'wpseo-' . self::COLUMN_LINKS;
|
||||
$columns[ 'wpseo-' . self::COLUMN_LINKED ] = 'wpseo-' . self::COLUMN_LINKED;
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Menu_Badge_Integration class.
|
||||
*/
|
||||
class Menu_Badge_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'add_inline_styles' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the migration error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_inline_styles() {
|
||||
$custom_css = 'ul.wp-submenu span.yoast-premium-badge::after, #wpadminbar span.yoast-premium-badge::after { content:"'
|
||||
. \__( 'Premium', 'wordpress-seo' ) . '"}';
|
||||
\wp_add_inline_style( WPSEO_Admin_Asset_Manager::PREFIX . 'admin-global', $custom_css );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Migration_Error_Presenter;
|
||||
|
||||
/**
|
||||
* Migration_Error_Integration class.
|
||||
*/
|
||||
class Migration_Error_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The migration status object.
|
||||
*
|
||||
* @var Migration_Status
|
||||
*/
|
||||
protected $migration_status;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration_Error_Integration constructor.
|
||||
*
|
||||
* @param Migration_Status $migration_status The migration status object.
|
||||
*/
|
||||
public function __construct( Migration_Status $migration_status ) {
|
||||
$this->migration_status = $migration_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->migration_status->get_error( 'free' ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_action( 'admin_notices', [ $this, 'render_migration_error' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the migration error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_migration_error() {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- The Migration_Error_Presenter already escapes it's output.
|
||||
echo new Migration_Error_Presenter( $this->migration_status->get_error( 'free' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Old_Configuration_Integration class
|
||||
*/
|
||||
class Old_Configuration_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'admin_menu', [ $this, 'add_submenu_page' ], 11 );
|
||||
\add_action( 'admin_init', [ $this, 'redirect_to_new_configuration' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the old configuration submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array the filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
\add_submenu_page(
|
||||
'',
|
||||
\__( 'Old Configuration Wizard', 'wordpress-seo' ),
|
||||
'',
|
||||
'manage_options',
|
||||
'wpseo_configurator',
|
||||
[ $this, 'render_page' ]
|
||||
);
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the old configuration page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page() {
|
||||
// This page is never to be displayed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects from the old configuration page to the new configuration page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirect_to_new_configuration() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_configurator' ) {
|
||||
return;
|
||||
}
|
||||
$redirect_url = 'admin.php?page=wpseo_dashboard#/first-time-configuration';
|
||||
\wp_safe_redirect( \admin_url( $redirect_url ), 302, 'Yoast SEO' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Redirect_Integration.
|
||||
*/
|
||||
class Redirect_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper
|
||||
*/
|
||||
private $redirect;
|
||||
|
||||
/**
|
||||
* Sets the helpers.
|
||||
*
|
||||
* @param Redirect_Helper $redirect The redirect helper.
|
||||
*/
|
||||
public function __construct( Redirect_Helper $redirect ) {
|
||||
$this->redirect = $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp_loaded', [ $this, 'old_settings_redirect' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to new settings URLs. We're adding this, so that not-updated add-ons don't point to non-existent pages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function old_settings_redirect() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( ! isset( $_GET['page'] ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$current_page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) );
|
||||
|
||||
switch ( $current_page ) {
|
||||
case 'wpseo_titles':
|
||||
$this->redirect->do_safe_redirect( \admin_url( 'admin.php?page=wpseo_page_settings#/site-representation' ), 301 );
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Premium_Inactive_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Redirects_Page_Integration class.
|
||||
*/
|
||||
class Redirects_Page_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Sets up the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* In this case: only when on an admin page and Premium is not active.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Premium_Inactive_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the redirects submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array The filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
$submenu_pages[] = [
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Redirects', 'wordpress-seo' ) . ' <span class="yoast-badge yoast-premium-badge"></span>',
|
||||
'edit_others_posts',
|
||||
'wpseo_redirects',
|
||||
[ $this, 'display' ],
|
||||
];
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the redirects page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display() {
|
||||
require \WPSEO_PATH . 'admin/pages/redirects.php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter;
|
||||
|
||||
/**
|
||||
* WorkoutsIntegration class
|
||||
*/
|
||||
class Workouts_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The addon manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
private $addon_manager;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The product helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
private $product_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Workouts_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Product_Helper $product_helper The product helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
Options_Helper $options_helper,
|
||||
Product_Helper $product_helper
|
||||
) {
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 8 );
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'remove_old_submenu_page' ], 10 );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the workouts submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array The filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
$submenu_pages[] = [
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Workouts', 'wordpress-seo' ) . ' <span class="yoast-badge yoast-premium-badge"></span>',
|
||||
'edit_others_posts',
|
||||
'wpseo_workouts',
|
||||
[ $this, 'render_target' ],
|
||||
];
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the workouts submenu page from older Premium versions
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array The filtered submenu pages.
|
||||
*/
|
||||
public function remove_old_submenu_page( $submenu_pages ) {
|
||||
if ( ! $this->should_update_premium() ) {
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
// Copy only the Workouts page item that comes first in the array.
|
||||
$result_submenu_pages = [];
|
||||
$workouts_page_encountered = false;
|
||||
foreach ( $submenu_pages as $item ) {
|
||||
if ( $item[4] !== 'wpseo_workouts' || ! $workouts_page_encountered ) {
|
||||
$result_submenu_pages[] = $item;
|
||||
}
|
||||
if ( $item[4] === 'wpseo_workouts' ) {
|
||||
$workouts_page_encountered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $result_submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_workouts' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->should_update_premium() ) {
|
||||
\wp_dequeue_script( 'yoast-seo-premium-workouts' );
|
||||
}
|
||||
|
||||
$this->admin_asset_manager->enqueue_style( 'workouts' );
|
||||
|
||||
$workouts_option = $this->get_workouts_option();
|
||||
$ftc_url = \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) );
|
||||
|
||||
$this->admin_asset_manager->enqueue_script( 'workouts' );
|
||||
$this->admin_asset_manager->localize_script(
|
||||
'workouts',
|
||||
'wpseoWorkoutsData',
|
||||
[
|
||||
'workouts' => $workouts_option,
|
||||
'homeUrl' => \home_url(),
|
||||
'pluginUrl' => \esc_url( \plugins_url( '', \WPSEO_FILE ) ),
|
||||
'toolsPageUrl' => \esc_url( \admin_url( 'admin.php?page=wpseo_tools' ) ),
|
||||
'usersPageUrl' => \esc_url( \admin_url( 'users.php' ) ),
|
||||
'firstTimeConfigurationUrl' => $ftc_url,
|
||||
'isPremium' => $this->product_helper->is_premium(),
|
||||
'upsellText' => $this->get_upsell_text(),
|
||||
'upsellLink' => $this->get_upsell_link(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the target for the React to mount to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_target() {
|
||||
if ( $this->should_update_premium() ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in get_update_premium_notice.
|
||||
echo $this->get_update_premium_notice();
|
||||
}
|
||||
|
||||
echo '<div id="wpseo-workouts-container-free" class="yoast"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the workouts option.
|
||||
*
|
||||
* @return mixed|null Returns workouts option if found, null if not.
|
||||
*/
|
||||
private function get_workouts_option() {
|
||||
$workouts_option = $this->options_helper->get( 'workouts_data' );
|
||||
|
||||
// This filter is documented in src/routes/workouts-route.php.
|
||||
return \apply_filters( 'Yoast\WP\SEO\workouts_options', $workouts_option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notification to show when Premium needs to be updated.
|
||||
*
|
||||
* @return string The notification to update Premium.
|
||||
*/
|
||||
private function get_update_premium_notice() {
|
||||
$url = $this->get_upsell_link();
|
||||
|
||||
if ( $this->has_premium_subscription_expired() ) {
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
$title = \sprintf( \__( 'Renew your subscription of %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
|
||||
$copy = \sprintf(
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
\esc_html__(
|
||||
'Accessing the latest workouts requires an updated version of %s (at least 17.7), but it looks like your subscription has expired. Please renew your subscription to update and gain access to all the latest features.',
|
||||
'wordpress-seo'
|
||||
),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
$button = '<a class="yoast-button yoast-button-upsell yoast-button--small" href="' . \esc_url( $url ) . '" target="_blank">'
|
||||
. \esc_html__( 'Renew your subscription', 'wordpress-seo' )
|
||||
/* translators: Hidden accessibility text. */
|
||||
. '<span class="screen-reader-text">' . \__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>'
|
||||
. '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>'
|
||||
. '</a>';
|
||||
}
|
||||
elseif ( $this->has_premium_subscription_activated() ) {
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
$title = \sprintf( \__( 'Update to the latest version of %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
|
||||
$copy = \sprintf(
|
||||
/* translators: 1: expands to 'Yoast SEO Premium', 2: Link start tag to the page to update Premium, 3: Link closing tag. */
|
||||
\esc_html__( 'It looks like you\'re running an outdated version of %1$s, please %2$supdate to the latest version (at least 17.7)%3$s to gain access to our updated workouts section.', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium',
|
||||
'<a href="' . \esc_url( $url ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
$button = null;
|
||||
}
|
||||
else {
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
$title = \sprintf( \__( 'Activate your subscription of %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
|
||||
$url_button = 'https://yoa.st/workouts-activate-notice-help';
|
||||
$copy = \sprintf(
|
||||
/* translators: 1: expands to 'Yoast SEO Premium', 2: Link start tag to the page to update Premium, 3: Link closing tag. */
|
||||
\esc_html__( 'It looks like you’re running an outdated and unactivated version of %1$s, please activate your subscription in %2$sMyYoast%3$s and update to the latest version (at least 17.7) to gain access to our updated workouts section.', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium',
|
||||
'<a href="' . \esc_url( $url ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
$button = '<a class="yoast-button yoast-button--primary yoast-button--small" href="' . \esc_url( $url_button ) . '" target="_blank">'
|
||||
. \esc_html__( 'Get help activating your subscription', 'wordpress-seo' )
|
||||
/* translators: Hidden accessibility text. */
|
||||
. '<span class="screen-reader-text">' . \__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>'
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
$notice = new Notice_Presenter(
|
||||
$title,
|
||||
$copy,
|
||||
null,
|
||||
$button
|
||||
);
|
||||
|
||||
return $notice->present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether Premium should be updated.
|
||||
*
|
||||
* @return bool Returns true when Premium is enabled and the version is below 17.7.
|
||||
*/
|
||||
private function should_update_premium() {
|
||||
$premium_version = $this->product_helper->get_premium_version();
|
||||
return $premium_version !== null && \version_compare( $premium_version, '17.7-RC1', '<' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the Premium subscription has expired.
|
||||
*
|
||||
* @return bool Returns true when Premium subscription has expired.
|
||||
*/
|
||||
private function has_premium_subscription_expired() {
|
||||
$subscription = $this->addon_manager->get_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG );
|
||||
|
||||
return ( isset( $subscription->expiry_date ) && ( \strtotime( $subscription->expiry_date ) - \time() ) < 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the Premium subscription is activated.
|
||||
*
|
||||
* @return bool Returns true when Premium subscription is activated.
|
||||
*/
|
||||
private function has_premium_subscription_activated() {
|
||||
return $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upsell/update copy to show in the card buttons.
|
||||
*
|
||||
* @return string Returns a string with the upsell/update copy for the card buttons.
|
||||
*/
|
||||
private function get_upsell_text() {
|
||||
if ( ! $this->product_helper->is_premium() || ! $this->should_update_premium() ) {
|
||||
// Use the default defined in the component.
|
||||
return '';
|
||||
}
|
||||
if ( $this->has_premium_subscription_expired() ) {
|
||||
return \sprintf(
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
\__( 'Renew %s', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
}
|
||||
if ( $this->has_premium_subscription_activated() ) {
|
||||
return \sprintf(
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
\__( 'Update %s', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
}
|
||||
return \sprintf(
|
||||
/* translators: %s: expands to 'Yoast SEO Premium'. */
|
||||
\__( 'Activate %s', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upsell/update link to show in the card buttons.
|
||||
*
|
||||
* @return string Returns a string with the upsell/update link for the card buttons.
|
||||
*/
|
||||
private function get_upsell_link() {
|
||||
if ( ! $this->product_helper->is_premium() || ! $this->should_update_premium() ) {
|
||||
// Use the default defined in the component.
|
||||
return '';
|
||||
}
|
||||
if ( $this->has_premium_subscription_expired() ) {
|
||||
return 'https://yoa.st/workout-renew-notice';
|
||||
}
|
||||
if ( $this->has_premium_subscription_activated() ) {
|
||||
return \wp_nonce_url( \self_admin_url( 'update.php?action=upgrade-plugin&plugin=wordpress-seo-premium/wp-seo-premium.php' ), 'upgrade-plugin_wordpress-seo-premium/wp-seo-premium.php' );
|
||||
}
|
||||
return 'https://yoa.st/workouts-activate-notice-myyoast';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Dismissable_Alert class.
|
||||
*/
|
||||
abstract class Abstract_Dismissable_Alert implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_allowed_dismissable_alerts', [ $this, 'register_dismissable_alert' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the dismissable alert.
|
||||
*
|
||||
* @param string[] $allowed_dismissable_alerts The allowed dismissable alerts.
|
||||
*
|
||||
* @return string[] The allowed dismissable alerts.
|
||||
*/
|
||||
public function register_dismissable_alert( $allowed_dismissable_alerts ) {
|
||||
$allowed_dismissable_alerts[] = $this->alert_identifier;
|
||||
|
||||
return $allowed_dismissable_alerts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
/**
|
||||
* Black_Friday_Product_Editor_Checklist_Notification class.
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Black_Friday_Product_Editor_Checklist_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'black-friday-2023-product-editor-checklist';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
/**
|
||||
* Black_Friday_Promotion_Notification class.
|
||||
*/
|
||||
class Black_Friday_Promotion_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'black-friday-2024-promotion';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
/**
|
||||
* Black_Friday_Promo_Notification class.
|
||||
*/
|
||||
class Black_Friday_Sidebar_Checklist_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'black-friday-2023-sidebar-checklist';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
/**
|
||||
* Trustpilot_Review_Notification class.
|
||||
*/
|
||||
class Trustpilot_Review_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'trustpilot-review-notification';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Alerts;
|
||||
|
||||
/**
|
||||
* Webinar_Promo_Notification class.
|
||||
*/
|
||||
class Webinar_Promo_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'webinar-promo-notification';
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Dynamic_Block class.
|
||||
*/
|
||||
abstract class Dynamic_Block_V3 implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name;
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script;
|
||||
|
||||
/**
|
||||
* The base path for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_path;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* Integrations hooking on `init` need to have a priority of 11 or higher to
|
||||
* ensure that they run, as priority 10 is used by the loader to load the integrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'init', [ $this, 'register_block' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block() {
|
||||
\register_block_type(
|
||||
$this->base_path . $this->block_name . '/block.json',
|
||||
[
|
||||
'editor_script' => $this->script,
|
||||
'render_callback' => [ $this, 'present' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output. This is abstract because in the loop we need to be able to build the data for the
|
||||
* presenter in the last moment.
|
||||
*
|
||||
* @param array<string, bool|string|int|array> $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
abstract public function present( $attributes );
|
||||
|
||||
/**
|
||||
* Checks whether the links in the block should have target="blank".
|
||||
*
|
||||
* This is needed because when the editor is loaded in an Iframe the link needs to open in a different browser window.
|
||||
* We don't want this behaviour in the front-end and the way to check this is to check if the block is rendered in a REST request with the `context` set as 'edit'. Thus being in the editor.
|
||||
*
|
||||
* @return bool returns if the block should be opened in another window.
|
||||
*/
|
||||
protected function should_link_target_blank(): bool {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['context'] ) && \is_string( $_GET['context'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing.
|
||||
if ( \wp_unslash( $_GET['context'] ) === 'edit' ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Dynamic_Block class.
|
||||
*/
|
||||
abstract class Dynamic_Block implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name;
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'init', [ $this, 'register_block' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block() {
|
||||
\register_block_type(
|
||||
'yoast-seo/' . $this->block_name,
|
||||
[
|
||||
'editor_script' => $this->script,
|
||||
'render_callback' => [ $this, 'present' ],
|
||||
'attributes' => [
|
||||
'className' => [
|
||||
'default' => '',
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output. This is abstract because in the loop we need to be able to build the data for the
|
||||
* presenter in the last moment.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
abstract public function present( $attributes );
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Internal_Linking_Category block class.
|
||||
*/
|
||||
class Internal_Linking_Category implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'block_categories_all', [ $this, 'add_block_categories' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Yoast block categories.
|
||||
*
|
||||
* @param array $categories The categories.
|
||||
* @return array The filtered categories.
|
||||
*/
|
||||
public function add_block_categories( $categories ) {
|
||||
$categories[] = [
|
||||
'slug' => 'yoast-structured-data-blocks',
|
||||
'title' => \sprintf(
|
||||
/* translators: %1$s expands to Yoast. */
|
||||
\__( '%1$s Structured Data Blocks', 'wordpress-seo' ),
|
||||
'Yoast'
|
||||
),
|
||||
];
|
||||
$categories[] = [
|
||||
'slug' => 'yoast-internal-linking-blocks',
|
||||
'title' => \sprintf(
|
||||
/* translators: %1$s expands to Yoast. */
|
||||
\__( '%1$s Internal Linking Blocks', 'wordpress-seo' ),
|
||||
'Yoast'
|
||||
),
|
||||
];
|
||||
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Block_Editor_Integration class to enqueue the block editor assets also for the iframe.
|
||||
*/
|
||||
class Block_Editor_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array<Post_Conditional>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Post_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager ) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'enqueue_block_assets', [ $this, 'enqueue' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets for the block editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
$this->asset_manager->enqueue_style( 'block-editor' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use WPSEO_Replace_Vars;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Presenters\Breadcrumbs_Presenter;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
|
||||
|
||||
/**
|
||||
* Siblings block class
|
||||
*/
|
||||
class Breadcrumbs_Block extends Dynamic_Block_V3 {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'breadcrumbs';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'yoast-seo-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* The Meta_Tags_Context_Memoizer.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
private $context_memoizer;
|
||||
|
||||
/**
|
||||
* The Replace vars helper.
|
||||
*
|
||||
* @var WPSEO_Replace_Vars
|
||||
*/
|
||||
private $replace_vars;
|
||||
|
||||
/**
|
||||
* The helpers surface.
|
||||
*
|
||||
* @var Helpers_Surface
|
||||
*/
|
||||
private $helpers;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Siblings_Block constructor.
|
||||
*
|
||||
* @param Meta_Tags_Context_Memoizer $context_memoizer The context.
|
||||
* @param WPSEO_Replace_Vars $replace_vars The replace variable helper.
|
||||
* @param Helpers_Surface $helpers The Helpers surface.
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Meta_Tags_Context_Memoizer $context_memoizer,
|
||||
WPSEO_Replace_Vars $replace_vars,
|
||||
Helpers_Surface $helpers,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
$this->context_memoizer = $context_memoizer;
|
||||
$this->replace_vars = $replace_vars;
|
||||
$this->helpers = $helpers;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
|
||||
$this->base_path = \WPSEO_PATH . 'blocks/dynamic-blocks/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the breadcrumbs output for the current page or the available post_id.
|
||||
*
|
||||
* @param array<string, bool|string|int|array> $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes ) {
|
||||
$presenter = new Breadcrumbs_Presenter();
|
||||
// $this->context_memoizer->for_current_page only works on the frontend. To render the right breadcrumb in the
|
||||
// editor, we need the repository.
|
||||
if ( \wp_is_serving_rest_request() || \is_admin() ) {
|
||||
$post_id = \get_the_ID();
|
||||
if ( $post_id ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' );
|
||||
|
||||
if ( ! $indexable ) {
|
||||
$post = \get_post( $post_id );
|
||||
$indexable = $this->indexable_repository->query()->create(
|
||||
[
|
||||
'object_id' => $post_id,
|
||||
'object_type' => 'post',
|
||||
'object_sub_type' => $post->post_type,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$context = $this->context_memoizer->get( $indexable, 'Post_Type' );
|
||||
}
|
||||
}
|
||||
if ( ! isset( $context ) ) {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
}
|
||||
|
||||
/** This filter is documented in src/integrations/front-end-integration.php */
|
||||
$presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
|
||||
$presenter->presentation = $presentation;
|
||||
$presenter->replace_vars = $this->replace_vars;
|
||||
$presenter->helpers = $this->helpers;
|
||||
$class_name = 'yoast-breadcrumbs';
|
||||
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class_name .= ' ' . \esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
return '<div class="' . $class_name . '">' . $presenter->present() . '</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Image_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class to load assets required for structured data blocks.
|
||||
*/
|
||||
class Structured_Data_Blocks implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* An instance of the WPSEO_Admin_Asset_Manager class.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* An instance of the image helper class.
|
||||
*
|
||||
* @var Image_Helper
|
||||
*/
|
||||
protected $image_helper;
|
||||
|
||||
/**
|
||||
* The image caches per post.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $caches = [];
|
||||
|
||||
/**
|
||||
* The used cache keys per post.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $used_caches = [];
|
||||
|
||||
/**
|
||||
* Whether or not we've registered our shutdown function.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $registered_shutdown_function = false;
|
||||
|
||||
/**
|
||||
* Structured_Data_Blocks constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
* @param Image_Helper $image_helper The image helper.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Image_Helper $image_helper ) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->image_helper = $image_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks for Structured Data Blocks with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
$this->register_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the blocks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_blocks() {
|
||||
/**
|
||||
* Filter: 'wpseo_enable_structured_data_blocks' - Allows disabling Yoast's schema blocks entirely.
|
||||
*
|
||||
* @param bool $enable If false, our structured data blocks won't show.
|
||||
*/
|
||||
if ( ! \apply_filters( 'wpseo_enable_structured_data_blocks', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\register_block_type(
|
||||
\WPSEO_PATH . 'blocks/structured-data-blocks/faq/block.json',
|
||||
[
|
||||
'render_callback' => [ $this, 'optimize_faq_images' ],
|
||||
]
|
||||
);
|
||||
\register_block_type(
|
||||
\WPSEO_PATH . 'blocks/structured-data-blocks/how-to/block.json',
|
||||
[
|
||||
'render_callback' => [ $this, 'optimize_how_to_images' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes images in the FAQ blocks.
|
||||
*
|
||||
* @param array $attributes The attributes.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The content with images optimized.
|
||||
*/
|
||||
public function optimize_faq_images( $attributes, $content ) {
|
||||
if ( ! isset( $attributes['questions'] ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $this->optimize_images( $attributes['questions'], 'answer', $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the durations into a translated string containing the count, and either singular or plural unit.
|
||||
* For example (in en-US): If 'days' is 1, it returns "1 day". If 'days' is 2, it returns "2 days".
|
||||
* If a number value is 0, we don't output the string.
|
||||
*
|
||||
* @param number $days Number of days.
|
||||
* @param number $hours Number of hours.
|
||||
* @param number $minutes Number of minutes.
|
||||
* @return array Array of pluralized durations.
|
||||
*/
|
||||
private function transform_duration_to_string( $days, $hours, $minutes ) {
|
||||
$strings = [];
|
||||
if ( $days ) {
|
||||
$strings[] = \sprintf(
|
||||
/* translators: %d expands to the number of day/days. */
|
||||
\_n( '%d day', '%d days', $days, 'wordpress-seo' ),
|
||||
$days
|
||||
);
|
||||
}
|
||||
if ( $hours ) {
|
||||
$strings[] = \sprintf(
|
||||
/* translators: %d expands to the number of hour/hours. */
|
||||
\_n( '%d hour', '%d hours', $hours, 'wordpress-seo' ),
|
||||
$hours
|
||||
);
|
||||
}
|
||||
if ( $minutes ) {
|
||||
$strings[] = \sprintf(
|
||||
/* translators: %d expands to the number of minute/minutes. */
|
||||
\_n( '%d minute', '%d minutes', $minutes, 'wordpress-seo' ),
|
||||
$minutes
|
||||
);
|
||||
}
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the durations into a translated string.
|
||||
*
|
||||
* @param array $attributes The attributes.
|
||||
* @return string The formatted duration.
|
||||
*/
|
||||
private function build_duration_string( $attributes ) {
|
||||
$days = ( $attributes['days'] ?? 0 );
|
||||
$hours = ( $attributes['hours'] ?? 0 );
|
||||
$minutes = ( $attributes['minutes'] ?? 0 );
|
||||
$elements = $this->transform_duration_to_string( $days, $hours, $minutes );
|
||||
$elements_length = \count( $elements );
|
||||
|
||||
switch ( $elements_length ) {
|
||||
case 1:
|
||||
return $elements[0];
|
||||
case 2:
|
||||
return \sprintf(
|
||||
/* translators: %s expands to a unit of time (e.g. 1 day). */
|
||||
\__( '%1$s and %2$s', 'wordpress-seo' ),
|
||||
...$elements
|
||||
);
|
||||
case 3:
|
||||
return \sprintf(
|
||||
/* translators: %s expands to a unit of time (e.g. 1 day). */
|
||||
\__( '%1$s, %2$s and %3$s', 'wordpress-seo' ),
|
||||
...$elements
|
||||
);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the duration text of the How-To block in the site language.
|
||||
*
|
||||
* @param array $attributes The attributes.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The content with the duration text in the site language.
|
||||
*/
|
||||
public function present_duration_text( $attributes, $content ) {
|
||||
$duration = $this->build_duration_string( $attributes );
|
||||
// 'Time needed:' is the default duration text that will be shown if a user doesn't add one.
|
||||
$duration_text = \__( 'Time needed:', 'wordpress-seo' );
|
||||
|
||||
if ( isset( $attributes['durationText'] ) && $attributes['durationText'] !== '' ) {
|
||||
$duration_text = $attributes['durationText'];
|
||||
}
|
||||
|
||||
return \preg_replace(
|
||||
'/(<p class="schema-how-to-total-time">)(<span class="schema-how-to-duration-time-text">.*<\/span>)(.[^\/p>]*)(<\/p>)/',
|
||||
'<p class="schema-how-to-total-time"><span class="schema-how-to-duration-time-text">' . $duration_text . ' </span>' . $duration . '</p>',
|
||||
$content,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes images in the How-To blocks.
|
||||
*
|
||||
* @param array $attributes The attributes.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The content with images optimized.
|
||||
*/
|
||||
public function optimize_how_to_images( $attributes, $content ) {
|
||||
if ( ! isset( $attributes['steps'] ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$content = $this->present_duration_text( $attributes, $content );
|
||||
|
||||
return $this->optimize_images( $attributes['steps'], 'text', $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes images in structured data blocks.
|
||||
*
|
||||
* @param array $elements The list of elements from the block attributes.
|
||||
* @param string $key The key in the data to iterate over.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The content with images optimized.
|
||||
*/
|
||||
private function optimize_images( $elements, $key, $content ) {
|
||||
global $post;
|
||||
if ( ! $post ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$this->add_images_from_attributes_to_used_cache( $post->ID, $elements, $key );
|
||||
|
||||
// Then replace all images with optimized versions in the content.
|
||||
$content = \preg_replace_callback(
|
||||
'/<img[^>]+>/',
|
||||
function ( $matches ) {
|
||||
\preg_match( '/src="([^"]+)"/', $matches[0], $src_matches );
|
||||
if ( ! $src_matches || ! isset( $src_matches[1] ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
$attachment_id = $this->attachment_src_to_id( $src_matches[1] );
|
||||
if ( $attachment_id === 0 ) {
|
||||
return $matches[0];
|
||||
}
|
||||
$image_size = 'full';
|
||||
$image_style = [ 'style' => 'max-width: 100%; height: auto;' ];
|
||||
\preg_match( '/style="[^"]*width:\s*(\d+)px[^"]*"/', $matches[0], $style_matches );
|
||||
if ( $style_matches && isset( $style_matches[1] ) ) {
|
||||
$width = (int) $style_matches[1];
|
||||
$meta_data = \wp_get_attachment_metadata( $attachment_id );
|
||||
if ( isset( $meta_data['height'] ) && isset( $meta_data['width'] ) && $meta_data['height'] > 0 && $meta_data['width'] > 0 ) {
|
||||
$aspect_ratio = ( $meta_data['height'] / $meta_data['width'] );
|
||||
$height = ( $width * $aspect_ratio );
|
||||
$image_size = [ $width, $height ];
|
||||
}
|
||||
$image_style = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_structured_data_blocks_image_size' - Allows adjusting the image size in structured data blocks.
|
||||
*
|
||||
* @since 18.2
|
||||
*
|
||||
* @param string|int[] $image_size The image size. Accepts any registered image size name, or an array of width and height values in pixels (in that order).
|
||||
* @param int $attachment_id The id of the attachment.
|
||||
* @param string $attachment_src The attachment src.
|
||||
*/
|
||||
$image_size = \apply_filters(
|
||||
'wpseo_structured_data_blocks_image_size',
|
||||
$image_size,
|
||||
$attachment_id,
|
||||
$src_matches[1]
|
||||
);
|
||||
$image_html = \wp_get_attachment_image(
|
||||
$attachment_id,
|
||||
$image_size,
|
||||
false,
|
||||
$image_style
|
||||
);
|
||||
|
||||
if ( empty( $image_html ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return $image_html;
|
||||
},
|
||||
$content
|
||||
);
|
||||
|
||||
if ( ! $this->registered_shutdown_function ) {
|
||||
\register_shutdown_function( [ $this, 'maybe_save_used_caches' ] );
|
||||
$this->registered_shutdown_function = true;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the caches of structured data block images have been changed, saves them.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_save_used_caches() {
|
||||
foreach ( $this->used_caches as $post_id => $used_cache ) {
|
||||
if ( isset( $this->caches[ $post_id ] ) && $used_cache === $this->caches[ $post_id ] ) {
|
||||
continue;
|
||||
}
|
||||
\update_post_meta( $post_id, 'yoast-structured-data-blocks-images-cache', $used_cache );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an attachment src to an attachment ID.
|
||||
*
|
||||
* @param string $src The attachment src.
|
||||
*
|
||||
* @return int The attachment ID. 0 if none was found.
|
||||
*/
|
||||
private function attachment_src_to_id( $src ) {
|
||||
global $post;
|
||||
|
||||
if ( isset( $this->used_caches[ $post->ID ][ $src ] ) ) {
|
||||
return $this->used_caches[ $post->ID ][ $src ];
|
||||
}
|
||||
|
||||
$cache = $this->get_cache_for_post( $post->ID );
|
||||
if ( isset( $cache[ $src ] ) ) {
|
||||
$this->used_caches[ $post->ID ][ $src ] = $cache[ $src ];
|
||||
return $cache[ $src ];
|
||||
}
|
||||
|
||||
$this->used_caches[ $post->ID ][ $src ] = $this->image_helper->get_attachment_by_url( $src );
|
||||
return $this->used_caches[ $post->ID ][ $src ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache from postmeta for a given post.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return array The images cache.
|
||||
*/
|
||||
private function get_cache_for_post( $post_id ) {
|
||||
if ( isset( $this->caches[ $post_id ] ) ) {
|
||||
return $this->caches[ $post_id ];
|
||||
}
|
||||
|
||||
$cache = \get_post_meta( $post_id, 'yoast-structured-data-blocks-images-cache', true );
|
||||
if ( ! $cache ) {
|
||||
$cache = [];
|
||||
}
|
||||
|
||||
$this->caches[ $post_id ] = $cache;
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any images that have their ID in the block attributes to the cache.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param array $elements The elements.
|
||||
* @param string $key The key in the elements we should loop over.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_images_from_attributes_to_used_cache( $post_id, $elements, $key ) {
|
||||
// First grab all image IDs from the attributes.
|
||||
$images = [];
|
||||
foreach ( $elements as $element ) {
|
||||
if ( ! isset( $element[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( isset( $element[ $key ] ) && \is_array( $element[ $key ] ) ) {
|
||||
foreach ( $element[ $key ] as $part ) {
|
||||
if ( ! \is_array( $part ) || ! isset( $part['type'] ) || $part['type'] !== 'img' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( $part['key'] ) || ! isset( $part['props']['src'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$images[ $part['props']['src'] ] = (int) $part['key'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $this->used_caches[ $post_id ] ) ) {
|
||||
$this->used_caches[ $post_id ] = \array_merge( $this->used_caches[ $post_id ], $images );
|
||||
}
|
||||
else {
|
||||
$this->used_caches[ $post_id ] = $images;
|
||||
}
|
||||
}
|
||||
|
||||
/* DEPRECATED METHODS */
|
||||
|
||||
/**
|
||||
* Enqueue Gutenberg block assets for backend editor.
|
||||
*
|
||||
* @deprecated 22.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_block_editor_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 22.7' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WPSEO_Replace_Vars;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Presenters\Breadcrumbs_Presenter;
|
||||
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
|
||||
|
||||
/**
|
||||
* Adds customizations to the front end for breadcrumbs.
|
||||
*/
|
||||
class Breadcrumbs_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The breadcrumbs presenter.
|
||||
*
|
||||
* @var Breadcrumbs_Presenter
|
||||
*/
|
||||
private $presenter;
|
||||
|
||||
/**
|
||||
* The meta tags context memoizer.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
private $context_memoizer;
|
||||
|
||||
/**
|
||||
* Breadcrumbs integration constructor.
|
||||
*
|
||||
* @param Helpers_Surface $helpers The helpers.
|
||||
* @param WPSEO_Replace_Vars $replace_vars The replace vars.
|
||||
* @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
|
||||
*/
|
||||
public function __construct(
|
||||
Helpers_Surface $helpers,
|
||||
WPSEO_Replace_Vars $replace_vars,
|
||||
Meta_Tags_Context_Memoizer $context_memoizer
|
||||
) {
|
||||
$this->context_memoizer = $context_memoizer;
|
||||
$this->presenter = new Breadcrumbs_Presenter();
|
||||
$this->presenter->helpers = $helpers;
|
||||
$this->presenter->replace_vars = $replace_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the `wpseo_breadcrumb` shortcode.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_shortcode( 'wpseo_breadcrumb', [ $this, 'render' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the breadcrumbs.
|
||||
*
|
||||
* @return string The rendered breadcrumbs.
|
||||
*/
|
||||
public function render() {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
|
||||
/** This filter is documented in src/integrations/front-end-integration.php */
|
||||
$presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
|
||||
|
||||
$this->presenter->presentation = $presentation;
|
||||
|
||||
return $this->presenter->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Closure;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository;
|
||||
|
||||
/**
|
||||
* Adds cleanup hooks.
|
||||
*/
|
||||
class Cleanup_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Identifier used to determine the current task.
|
||||
*/
|
||||
public const CURRENT_TASK_OPTION = 'wpseo-cleanup-current-task';
|
||||
|
||||
/**
|
||||
* Identifier for the cron job.
|
||||
*/
|
||||
public const CRON_HOOK = 'wpseo_cleanup_cron';
|
||||
|
||||
/**
|
||||
* Identifier for starting the cleanup.
|
||||
*/
|
||||
public const START_HOOK = 'wpseo_start_cleanup_indexables';
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The cleanup repository.
|
||||
*
|
||||
* @var Indexable_Cleanup_Repository
|
||||
*/
|
||||
private $cleanup_repository;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Indexable_Cleanup_Repository $cleanup_repository The cleanup repository.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Cleanup_Repository $cleanup_repository,
|
||||
Indexable_Helper $indexable_helper
|
||||
) {
|
||||
$this->cleanup_repository = $cleanup_repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( self::START_HOOK, [ $this, 'run_cleanup' ] );
|
||||
\add_action( self::CRON_HOOK, [ $this, 'run_cleanup_cron' ] );
|
||||
\add_action( 'wpseo_deactivate', [ $this, 'reset_cleanup' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the indexables cleanup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run_cleanup() {
|
||||
$this->reset_cleanup();
|
||||
|
||||
if ( ! $this->indexable_helper->should_index_indexables() ) {
|
||||
\wp_unschedule_hook( self::START_HOOK );
|
||||
return;
|
||||
}
|
||||
|
||||
$cleanups = $this->get_cleanup_tasks();
|
||||
$limit = $this->get_limit();
|
||||
|
||||
foreach ( $cleanups as $name => $action ) {
|
||||
$items_cleaned = $action( $limit );
|
||||
|
||||
if ( $items_cleaned === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $items_cleaned < $limit ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// There are more items to delete for the current cleanup job, start a cronjob at the specified job.
|
||||
$this->start_cron_job( $name );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of cleanup tasks.
|
||||
*
|
||||
* @return Closure[] The cleanup tasks.
|
||||
*/
|
||||
public function get_cleanup_tasks() {
|
||||
return \array_merge(
|
||||
[
|
||||
'clean_indexables_with_object_type_and_object_sub_type_shop_order' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order', $limit );
|
||||
},
|
||||
'clean_indexables_by_post_status_auto-draft' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_with_post_status( 'auto-draft', $limit );
|
||||
},
|
||||
'clean_indexables_for_non_publicly_viewable_post' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post( $limit );
|
||||
},
|
||||
'clean_indexables_for_non_publicly_viewable_taxonomies' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_taxonomies( $limit );
|
||||
},
|
||||
'clean_indexables_for_non_publicly_viewable_post_type_archive_pages' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit );
|
||||
},
|
||||
'clean_indexables_for_authors_archive_disabled' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_authors_archive_disabled( $limit );
|
||||
},
|
||||
'clean_indexables_for_authors_without_archive' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_authors_without_archive( $limit );
|
||||
},
|
||||
'update_indexables_author_to_reassigned' => function ( $limit ) {
|
||||
return $this->cleanup_repository->update_indexables_author_to_reassigned( $limit );
|
||||
},
|
||||
'clean_orphaned_user_indexables_without_wp_user' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_orphaned_users( $limit );
|
||||
},
|
||||
'clean_orphaned_user_indexables_without_wp_post' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post', $limit );
|
||||
},
|
||||
'clean_orphaned_user_indexables_without_wp_term' => function ( $limit ) {
|
||||
return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term', $limit );
|
||||
},
|
||||
],
|
||||
$this->get_additional_indexable_cleanups(),
|
||||
[
|
||||
/* These should always be the last ones to be called. */
|
||||
'clean_orphaned_content_indexable_hierarchy' => function ( $limit ) {
|
||||
return $this->cleanup_repository->cleanup_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id', $limit );
|
||||
},
|
||||
'clean_orphaned_content_seo_links_indexable_id' => function ( $limit ) {
|
||||
return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'indexable_id', $limit );
|
||||
},
|
||||
'clean_orphaned_content_seo_links_target_indexable_id' => function ( $limit ) {
|
||||
return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'target_indexable_id', $limit );
|
||||
},
|
||||
|
||||
],
|
||||
$this->get_additional_misc_cleanups()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets additional tasks from the 'wpseo_cleanup_tasks' filter.
|
||||
*
|
||||
* @return Closure[] Associative array of indexable cleanup functions.
|
||||
*/
|
||||
private function get_additional_indexable_cleanups() {
|
||||
|
||||
/**
|
||||
* Filter: Adds the possibility to add additional indexable cleanup functions.
|
||||
*
|
||||
* @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit.
|
||||
*/
|
||||
$additional_tasks = \apply_filters( 'wpseo_cleanup_tasks', [] );
|
||||
|
||||
return $this->validate_additional_tasks( $additional_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets additional tasks from the 'wpseo_misc_cleanup_tasks' filter.
|
||||
*
|
||||
* @return Closure[] Associative array of indexable cleanup functions.
|
||||
*/
|
||||
private function get_additional_misc_cleanups() {
|
||||
|
||||
/**
|
||||
* Filter: Adds the possibility to add additional non-indexable cleanup functions.
|
||||
*
|
||||
* @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit.
|
||||
*/
|
||||
$additional_tasks = \apply_filters( 'wpseo_misc_cleanup_tasks', [] );
|
||||
|
||||
return $this->validate_additional_tasks( $additional_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the additional tasks.
|
||||
*
|
||||
* @param Closure[] $additional_tasks The additional tasks to validate.
|
||||
*
|
||||
* @return Closure[] The validated additional tasks.
|
||||
*/
|
||||
private function validate_additional_tasks( $additional_tasks ) {
|
||||
if ( ! \is_array( $additional_tasks ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ( $additional_tasks as $key => $value ) {
|
||||
if ( \is_int( $key ) ) {
|
||||
return [];
|
||||
}
|
||||
if ( ( ! \is_object( $value ) ) || ! ( $value instanceof Closure ) ) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $additional_tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deletion limit for cleanups.
|
||||
*
|
||||
* @return int The limit for the amount of entities to be cleaned.
|
||||
*/
|
||||
private function get_limit() {
|
||||
/**
|
||||
* Filter: Adds the possibility to limit the number of items that are deleted from the database on cleanup.
|
||||
*
|
||||
* @param int $limit Maximum number of indexables to be cleaned up per query.
|
||||
*/
|
||||
$limit = \apply_filters( 'wpseo_cron_query_limit_size', 1000 );
|
||||
|
||||
if ( ! \is_int( $limit ) ) {
|
||||
$limit = 1000;
|
||||
}
|
||||
|
||||
return \abs( $limit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets and stops the cleanup integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_cleanup() {
|
||||
\delete_option( self::CURRENT_TASK_OPTION );
|
||||
\wp_unschedule_hook( self::CRON_HOOK );
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the cleanup cron job.
|
||||
*
|
||||
* @param string $task_name The task name of the next cleanup task to run.
|
||||
* @param int $schedule_time The time in seconds to wait before running the first cron job. Default is 1 hour.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start_cron_job( $task_name, $schedule_time = 3600 ) {
|
||||
\update_option( self::CURRENT_TASK_OPTION, $task_name );
|
||||
\wp_schedule_event(
|
||||
( \time() + $schedule_time ),
|
||||
'hourly',
|
||||
self::CRON_HOOK
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback that is called for the cleanup cron job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run_cleanup_cron() {
|
||||
if ( ! $this->indexable_helper->should_index_indexables() ) {
|
||||
$this->reset_cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$current_task_name = \get_option( self::CURRENT_TASK_OPTION );
|
||||
|
||||
if ( $current_task_name === false ) {
|
||||
$this->reset_cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = $this->get_limit();
|
||||
$tasks = $this->get_cleanup_tasks();
|
||||
|
||||
// The task may have been added by a filter that has been removed, in that case just start over.
|
||||
if ( ! isset( $tasks[ $current_task_name ] ) ) {
|
||||
$current_task_name = \key( $tasks );
|
||||
}
|
||||
|
||||
$current_task = \current( $tasks );
|
||||
while ( $current_task !== false ) {
|
||||
// Skip the tasks that have already been done.
|
||||
if ( \key( $tasks ) !== $current_task_name ) {
|
||||
$current_task = \next( $tasks );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the cleanup callback function that accompanies the current task.
|
||||
$items_cleaned = $current_task( $limit );
|
||||
|
||||
if ( $items_cleaned === false ) {
|
||||
$this->reset_cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $items_cleaned === 0 ) {
|
||||
// Check if we are finished with all tasks.
|
||||
if ( \next( $tasks ) === false ) {
|
||||
$this->reset_cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with the next task next time the cron job is run.
|
||||
\update_option( self::CURRENT_TASK_OPTION, \key( $tasks ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// There were items deleted for the current task, continue with the same task next cron call.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional;
|
||||
|
||||
/**
|
||||
* Estimated reading time class.
|
||||
*/
|
||||
class Estimated_Reading_Time implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Estimated_Reading_Time_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_estimated_reading_time_hidden_fields' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an estimated-reading-time hidden field.
|
||||
*
|
||||
* @param array $field_defs The $fields_defs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_estimated_reading_time_hidden_fields( $field_defs ) {
|
||||
if ( \is_array( $field_defs ) ) {
|
||||
$field_defs['estimated-reading-time-minutes'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'estimated-reading-time-minutes',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Attachment_Redirections_Enabled_Conditional;
|
||||
|
||||
/**
|
||||
* Excludes Attachment post types from the indexable table.
|
||||
*
|
||||
* Posts with these post types will not be saved to the indexable table.
|
||||
*/
|
||||
class Exclude_Attachment_Post_Type extends Abstract_Exclude_Post_Type {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Attachment_Redirections_Enabled_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the post types to be excluded.
|
||||
* To be used in the wpseo_indexable_excluded_post_types filter.
|
||||
*
|
||||
* @return array The names of the post types.
|
||||
*/
|
||||
public function get_post_type() {
|
||||
return [ 'attachment' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
|
||||
/**
|
||||
* Excludes certain oEmbed Cache-specific post types from the indexable table.
|
||||
*
|
||||
* Posts with these post types will not be saved to the indexable table.
|
||||
*/
|
||||
class Exclude_Oembed_Cache_Post_Type extends Abstract_Exclude_Post_Type {
|
||||
|
||||
/**
|
||||
* This integration is only active when the database migrations have been run.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the post types to be excluded.
|
||||
* To be used in the wpseo_indexable_excluded_post_types filter.
|
||||
*
|
||||
* @return array The names of the post types.
|
||||
*/
|
||||
public function get_post_type() {
|
||||
return [ 'oembed_cache' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Feature_Flag_Conditional;
|
||||
|
||||
/**
|
||||
* Gathers all feature flags and surfaces them to the JavaScript side of the plugin.
|
||||
*/
|
||||
class Feature_Flag_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* All of the feature flag conditionals.
|
||||
*
|
||||
* @var Feature_Flag_Conditional[]
|
||||
*/
|
||||
protected $feature_flags;
|
||||
|
||||
/**
|
||||
* Feature_Flag_Integration constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
|
||||
* @param Feature_Flag_Conditional ...$feature_flags All of the known feature flag conditionals.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Feature_Flag_Conditional ...$feature_flags ) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->feature_flags = $feature_flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return string[] The conditionals based on which this loadable should be active.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'add_feature_flags' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers all the feature flags and injects them into the JavaScript.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_feature_flags() {
|
||||
$enabled_features = $this->get_enabled_features();
|
||||
// Localize under both names for BC.
|
||||
$this->asset_manager->localize_script( 'feature-flag-package', 'wpseoFeatureFlags', $enabled_features );
|
||||
$this->asset_manager->localize_script( 'feature-flag-package', 'wpseoFeaturesL10n', $enabled_features );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all enabled feature flags.
|
||||
*
|
||||
* @return string[] The array of enabled features.
|
||||
*/
|
||||
public function get_enabled_features() {
|
||||
$enabled_features = [];
|
||||
foreach ( $this->feature_flags as $feature_flag ) {
|
||||
if ( $feature_flag->is_met() ) {
|
||||
$enabled_features[] = $feature_flag->get_feature_name();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filter_enabled_features( $enabled_features );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the list of enabled feature flags through a filter.
|
||||
*
|
||||
* @param string[] $enabled_features The list of currently enabled feature flags.
|
||||
*
|
||||
* @return string[] The (possibly adapted) list of enabled features.
|
||||
*/
|
||||
protected function filter_enabled_features( $enabled_features ) {
|
||||
/**
|
||||
* Filters the list of currently enabled feature flags.
|
||||
*
|
||||
* @param string[] $enabled_features The current list of enabled feature flags.
|
||||
*/
|
||||
$filtered_enabled_features = \apply_filters( 'wpseo_enable_feature', $enabled_features );
|
||||
|
||||
if ( ! \is_array( $filtered_enabled_features ) ) {
|
||||
$filtered_enabled_features = $enabled_features;
|
||||
}
|
||||
|
||||
return $filtered_enabled_features;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WP_HTML_Tag_Processor;
|
||||
use WPSEO_Replace_Vars;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter;
|
||||
use Yoast\WP\SEO\Presenters\Debug\Marker_Close_Presenter;
|
||||
use Yoast\WP\SEO\Presenters\Debug\Marker_Open_Presenter;
|
||||
use Yoast\WP\SEO\Presenters\Title_Presenter;
|
||||
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
|
||||
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class Front_End_Integration.
|
||||
*/
|
||||
class Front_End_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The memoizer for the meta tags context.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
private $context_memoizer;
|
||||
|
||||
/**
|
||||
* The container.
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Represents the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The helpers surface.
|
||||
*
|
||||
* @var Helpers_Surface
|
||||
*/
|
||||
protected $helpers;
|
||||
|
||||
/**
|
||||
* The replace vars helper.
|
||||
*
|
||||
* @var WPSEO_Replace_Vars
|
||||
*/
|
||||
protected $replace_vars;
|
||||
|
||||
/**
|
||||
* The presenters we loop through on each page load.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $base_presenters = [
|
||||
'Title',
|
||||
'Meta_Description',
|
||||
'Robots',
|
||||
];
|
||||
|
||||
/**
|
||||
* The presenters we loop through on each page load.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $indexing_directive_presenters = [
|
||||
'Canonical',
|
||||
'Rel_Prev',
|
||||
'Rel_Next',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Open Graph specific presenters.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $open_graph_presenters = [
|
||||
'Open_Graph\Locale',
|
||||
'Open_Graph\Type',
|
||||
'Open_Graph\Title',
|
||||
'Open_Graph\Description',
|
||||
'Open_Graph\Url',
|
||||
'Open_Graph\Site_Name',
|
||||
'Open_Graph\Article_Publisher',
|
||||
'Open_Graph\Article_Author',
|
||||
'Open_Graph\Article_Published_Time',
|
||||
'Open_Graph\Article_Modified_Time',
|
||||
'Open_Graph\Image',
|
||||
'Meta_Author',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Open Graph specific presenters that should be output on error pages.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $open_graph_error_presenters = [
|
||||
'Open_Graph\Locale',
|
||||
'Open_Graph\Title',
|
||||
'Open_Graph\Site_Name',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Twitter card specific presenters.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $twitter_card_presenters = [
|
||||
'Twitter\Card',
|
||||
'Twitter\Title',
|
||||
'Twitter\Description',
|
||||
'Twitter\Image',
|
||||
'Twitter\Creator',
|
||||
'Twitter\Site',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Slack specific presenters.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $slack_presenters = [
|
||||
'Slack\Enhanced_Data',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Webmaster verification specific presenters.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $webmaster_verification_presenters = [
|
||||
'Webmaster\Baidu',
|
||||
'Webmaster\Bing',
|
||||
'Webmaster\Google',
|
||||
'Webmaster\Pinterest',
|
||||
'Webmaster\Yandex',
|
||||
];
|
||||
|
||||
/**
|
||||
* Presenters that are only needed on singular pages.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $singular_presenters = [
|
||||
'Meta_Author',
|
||||
'Open_Graph\Article_Author',
|
||||
'Open_Graph\Article_Publisher',
|
||||
'Open_Graph\Article_Published_Time',
|
||||
'Open_Graph\Article_Modified_Time',
|
||||
'Twitter\Creator',
|
||||
'Slack\Enhanced_Data',
|
||||
];
|
||||
|
||||
/**
|
||||
* The presenters we want to be last in our output.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $closing_presenters = [
|
||||
'Schema',
|
||||
];
|
||||
|
||||
/**
|
||||
* The next output.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $next;
|
||||
|
||||
/**
|
||||
* The prev output.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prev;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Front_End_Integration constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It sets dependencies.
|
||||
*
|
||||
* @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
|
||||
* @param ContainerInterface $service_container The DI container.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Helpers_Surface $helpers The helpers surface.
|
||||
* @param WPSEO_Replace_Vars $replace_vars The replace vars helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Meta_Tags_Context_Memoizer $context_memoizer,
|
||||
ContainerInterface $service_container,
|
||||
Options_Helper $options,
|
||||
Helpers_Surface $helpers,
|
||||
WPSEO_Replace_Vars $replace_vars
|
||||
) {
|
||||
$this->container = $service_container;
|
||||
$this->context_memoizer = $context_memoizer;
|
||||
$this->options = $options;
|
||||
$this->helpers = $helpers;
|
||||
$this->replace_vars = $replace_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the appropriate hooks to show the SEO metadata on the frontend.
|
||||
*
|
||||
* Removes some actions to remove metadata that WordPress shows on the frontend,
|
||||
* to avoid duplicate and/or mismatched metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'render_block', [ $this, 'query_loop_next_prev' ], 1, 2 );
|
||||
|
||||
\add_action( 'wp_head', [ $this, 'call_wpseo_head' ], 1 );
|
||||
// Filter the title for compatibility with other plugins and themes.
|
||||
\add_filter( 'wp_title', [ $this, 'filter_title' ], 15 );
|
||||
// Filter the title for compatibility with block-based themes.
|
||||
\add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
|
||||
|
||||
// Removes our robots presenter from the list when wp_robots is handling this.
|
||||
\add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'filter_robots_presenter' ] );
|
||||
|
||||
\add_action( 'wpseo_head', [ $this, 'present_head' ], -9999 );
|
||||
|
||||
\remove_action( 'wp_head', 'rel_canonical' );
|
||||
\remove_action( 'wp_head', 'index_rel_link' );
|
||||
\remove_action( 'wp_head', 'start_post_rel_link' );
|
||||
\remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
|
||||
\remove_action( 'wp_head', 'noindex', 1 );
|
||||
\remove_action( 'wp_head', '_wp_render_title_tag', 1 );
|
||||
\remove_action( 'wp_head', '_block_template_render_title_tag', 1 );
|
||||
\remove_action( 'wp_head', 'gutenberg_render_title_tag', 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the title, mainly used for compatibility reasons.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_title() {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
|
||||
$title_presenter = new Title_Presenter();
|
||||
|
||||
/** This filter is documented in src/integrations/front-end-integration.php */
|
||||
$title_presenter->presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
|
||||
$title_presenter->replace_vars = $this->replace_vars;
|
||||
$title_presenter->helpers = $this->helpers;
|
||||
|
||||
\remove_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
|
||||
$title = \esc_html( $title_presenter->get() );
|
||||
\add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the next and prev links in the query loop block.
|
||||
*
|
||||
* @param string $html The HTML output.
|
||||
* @param array<string|array|null> $block The block.
|
||||
* @return string The filtered HTML output.
|
||||
*/
|
||||
public function query_loop_next_prev( $html, $block ) {
|
||||
if ( $block['blockName'] === 'core/query' ) {
|
||||
// Check that the query does not inherit the main query.
|
||||
if ( isset( $block['attrs']['query']['inherit'] ) && ! $block['attrs']['query']['inherit'] ) {
|
||||
\add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 1, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $block['blockName'] === 'core/query-pagination-next' ) {
|
||||
$this->next = $html;
|
||||
}
|
||||
|
||||
if ( $block['blockName'] === 'core/query-pagination-previous' ) {
|
||||
$this->prev = $html;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns correct adjacent pages when Query loop block does not inherit query from template.
|
||||
* Prioritizes existing prev and next links.
|
||||
* Includes a safety check for full urls though it is not expected in the query pagination block.
|
||||
*
|
||||
* @param string $link The current link.
|
||||
* @param string $rel Link relationship, prev or next.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string The correct link.
|
||||
*/
|
||||
public function adjacent_rel_url( $link, $rel, $presentation = null ) {
|
||||
// Prioritize existing prev and next links.
|
||||
if ( $link ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
// Safety check for rel value.
|
||||
if ( $rel !== 'next' && $rel !== 'prev' ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
// Check $this->next or $this->prev for existing links.
|
||||
if ( $this->$rel === null ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$processor = new WP_HTML_Tag_Processor( $this->$rel );
|
||||
|
||||
if ( ! $processor->next_tag( [ 'tag_name' => 'a' ] ) ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$href = $processor->get_attribute( 'href' );
|
||||
|
||||
if ( ! $href ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
// Safety check for full url, not expected.
|
||||
if ( \strpos( $href, 'http' ) === 0 ) {
|
||||
return $href;
|
||||
}
|
||||
|
||||
// Check if $href is relative and append last part of the url to permalink.
|
||||
if ( \strpos( $href, '/' ) === 0 ) {
|
||||
$href_parts = \explode( '/', $href );
|
||||
return $presentation->permalink . \end( $href_parts );
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters our robots presenter, but only when wp_robots is attached to the wp_head action.
|
||||
*
|
||||
* @param array<string> $presenters The presenters for current page.
|
||||
*
|
||||
* @return array<string> The filtered presenters.
|
||||
*/
|
||||
public function filter_robots_presenter( $presenters ) {
|
||||
if ( ! \function_exists( 'wp_robots' ) ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
if ( ! \has_action( 'wp_head', 'wp_robots' ) ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
if ( \wp_is_serving_rest_request() ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
return \array_diff( $presenters, [ 'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the head in the front-end. Resets wp_query if it's not the main query.
|
||||
*
|
||||
* @codeCoverageIgnore It just calls a WordPress function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function call_wpseo_head() {
|
||||
global $wp_query;
|
||||
|
||||
$old_wp_query = $wp_query;
|
||||
// phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query.
|
||||
\wp_reset_query();
|
||||
|
||||
\do_action( 'wpseo_head' );
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query.
|
||||
$GLOBALS['wp_query'] = $old_wp_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes all applicable presenters for a page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function present_head() {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
$presenters = $this->get_presenters( $context->page_type, $context );
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_frontend_presentation' - Allow filtering the presentation used to output our meta values.
|
||||
*
|
||||
* @param Indexable_Presention $presentation The indexable presentation.
|
||||
* @param Meta_Tags_Context $context The meta tags context for the current page.
|
||||
*/
|
||||
$presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
|
||||
|
||||
echo \PHP_EOL;
|
||||
foreach ( $presenters as $presenter ) {
|
||||
$presenter->presentation = $presentation;
|
||||
$presenter->helpers = $this->helpers;
|
||||
$presenter->replace_vars = $this->replace_vars;
|
||||
|
||||
$output = $presenter->present();
|
||||
if ( ! empty( $output ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Presenters are responsible for correctly escaping their output.
|
||||
echo "\t" . $output . \PHP_EOL;
|
||||
}
|
||||
}
|
||||
echo \PHP_EOL . \PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all presenters for this page.
|
||||
*
|
||||
* @param string $page_type The page type.
|
||||
* @param Meta_Tags_Context|null $context The meta tags context for the current page.
|
||||
*
|
||||
* @return Abstract_Indexable_Presenter[] The presenters.
|
||||
*/
|
||||
public function get_presenters( $page_type, $context = null ) {
|
||||
if ( $context === null ) {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
}
|
||||
|
||||
$needed_presenters = $this->get_needed_presenters( $page_type );
|
||||
|
||||
$callback = static function ( $presenter ) {
|
||||
if ( ! \class_exists( $presenter ) ) {
|
||||
return null;
|
||||
}
|
||||
return new $presenter();
|
||||
};
|
||||
$presenters = \array_filter( \array_map( $callback, $needed_presenters ) );
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request.
|
||||
*
|
||||
* @param Abstract_Indexable_Presenter[] $presenters List of presenter instances.
|
||||
* @param Meta_Tags_Context $context The meta tags context for the current page.
|
||||
*/
|
||||
$presenter_instances = \apply_filters( 'wpseo_frontend_presenters', $presenters, $context );
|
||||
|
||||
if ( ! \is_array( $presenter_instances ) ) {
|
||||
$presenter_instances = $presenters;
|
||||
}
|
||||
|
||||
$is_presenter_callback = static function ( $presenter_instance ) {
|
||||
return $presenter_instance instanceof Abstract_Indexable_Presenter;
|
||||
};
|
||||
$presenter_instances = \array_filter( $presenter_instances, $is_presenter_callback );
|
||||
|
||||
return \array_merge(
|
||||
[ new Marker_Open_Presenter() ],
|
||||
$presenter_instances,
|
||||
[ new Marker_Close_Presenter() ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the array of presenters we need for the current request.
|
||||
*
|
||||
* @param string $page_type The page type we're retrieving presenters for.
|
||||
*
|
||||
* @return string[] The presenters.
|
||||
*/
|
||||
private function get_needed_presenters( $page_type ) {
|
||||
$presenters = $this->get_presenters_for_page_type( $page_type );
|
||||
|
||||
$presenters = $this->maybe_remove_title_presenter( $presenters );
|
||||
|
||||
$callback = static function ( $presenter ) {
|
||||
return "Yoast\WP\SEO\Presenters\\{$presenter}_Presenter";
|
||||
};
|
||||
$presenters = \array_map( $callback, $presenters );
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_frontend_presenter_classes' - Allow filtering presenters in or out of the request.
|
||||
*
|
||||
* @param array $presenters List of presenters.
|
||||
* @param string $page_type The current page type.
|
||||
*/
|
||||
$presenters = \apply_filters( 'wpseo_frontend_presenter_classes', $presenters, $page_type );
|
||||
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the presenters based on the page type.
|
||||
*
|
||||
* @param string $page_type The page type.
|
||||
*
|
||||
* @return string[] The presenters.
|
||||
*/
|
||||
private function get_presenters_for_page_type( $page_type ) {
|
||||
if ( $page_type === 'Error_Page' ) {
|
||||
$presenters = $this->base_presenters;
|
||||
if ( $this->options->get( 'opengraph' ) === true ) {
|
||||
$presenters = \array_merge( $presenters, $this->open_graph_error_presenters );
|
||||
}
|
||||
return \array_merge( $presenters, $this->closing_presenters );
|
||||
}
|
||||
|
||||
$presenters = $this->get_all_presenters();
|
||||
if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) {
|
||||
$presenters = \array_merge( $presenters, $this->webmaster_verification_presenters );
|
||||
}
|
||||
|
||||
// Filter out the presenters only needed for singular pages on non-singular pages.
|
||||
if ( ! \in_array( $page_type, [ 'Post_Type', 'Static_Home_Page' ], true ) ) {
|
||||
$presenters = \array_diff( $presenters, $this->singular_presenters );
|
||||
}
|
||||
|
||||
// Filter out `twitter:data` presenters for static home pages.
|
||||
if ( $page_type === 'Static_Home_Page' ) {
|
||||
$presenters = \array_diff( $presenters, $this->slack_presenters );
|
||||
}
|
||||
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all available presenters based on settings.
|
||||
*
|
||||
* @return string[] The presenters.
|
||||
*/
|
||||
private function get_all_presenters() {
|
||||
$presenters = \array_merge( $this->base_presenters, $this->indexing_directive_presenters );
|
||||
if ( $this->options->get( 'opengraph' ) === true ) {
|
||||
$presenters = \array_merge( $presenters, $this->open_graph_presenters );
|
||||
}
|
||||
if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) {
|
||||
$presenters = \array_merge( $presenters, $this->twitter_card_presenters );
|
||||
}
|
||||
if ( $this->options->get( 'enable_enhanced_slack_sharing' ) === true && \apply_filters( 'wpseo_output_enhanced_slack_data', true ) !== false ) {
|
||||
$presenters = \array_merge( $presenters, $this->slack_presenters );
|
||||
}
|
||||
|
||||
return \array_merge( $presenters, $this->closing_presenters );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the title presenter should be removed.
|
||||
*
|
||||
* @return bool True when the title presenter should be removed, false otherwise.
|
||||
*/
|
||||
public function should_title_presenter_be_removed() {
|
||||
return ! \get_theme_support( 'title-tag' ) && ! $this->options->get( 'forcerewritetitle', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Title presenter needs to be removed.
|
||||
*
|
||||
* @param string[] $presenters The presenters.
|
||||
*
|
||||
* @return string[] The presenters.
|
||||
*/
|
||||
private function maybe_remove_title_presenter( $presenters ) {
|
||||
// Do not remove the title if we're on a REST request.
|
||||
if ( \wp_is_serving_rest_request() ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
// Remove the title presenter if the theme is hardcoded to output a title tag so we don't have two title tags.
|
||||
if ( $this->should_title_presenter_be_removed() ) {
|
||||
$presenters = \array_diff( $presenters, [ 'Title' ] );
|
||||
}
|
||||
|
||||
return $presenters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds actions that were previously called and are now deprecated.
|
||||
*/
|
||||
class Backwards_Compatibility implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Backwards_Compatibility constructor
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->options->get( 'opengraph' ) === true ) {
|
||||
\add_action( 'wpseo_head', [ $this, 'call_wpseo_opengraph' ], 30 );
|
||||
}
|
||||
if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) {
|
||||
\add_action( 'wpseo_head', [ $this, 'call_wpseo_twitter' ], 40 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the old wpseo_opengraph action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function call_wpseo_opengraph() {
|
||||
\do_action_deprecated( 'wpseo_opengraph', [], '14.0', 'wpseo_frontend_presenters' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the old wpseo_twitter action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function call_wpseo_twitter() {
|
||||
\do_action_deprecated( 'wpseo_twitter', [], '14.0', 'wpseo_frontend_presenters' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds support for shortcodes to category and term descriptions.
|
||||
*/
|
||||
class Category_Term_Description implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'category_description', [ $this, 'add_shortcode_support' ] );
|
||||
\add_filter( 'term_description', [ $this, 'add_shortcode_support' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds shortcode support to category and term descriptions.
|
||||
*
|
||||
* This methods wrap in output buffering to prevent shortcodes that echo stuff
|
||||
* instead of return from breaking things.
|
||||
*
|
||||
* @param string $description String to add shortcodes in.
|
||||
*
|
||||
* @return string Content with shortcodes filtered out.
|
||||
*/
|
||||
public function add_shortcode_support( $description ) {
|
||||
\ob_start();
|
||||
$description = \do_shortcode( $description );
|
||||
\ob_end_clean();
|
||||
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Robots_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Comment_Link_Fixer.
|
||||
*/
|
||||
class Comment_Link_Fixer implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The redirects helper.
|
||||
*
|
||||
* @var Redirect_Helper
|
||||
*/
|
||||
protected $redirect;
|
||||
|
||||
/**
|
||||
* The robots helper.
|
||||
*
|
||||
* @var Robots_Helper
|
||||
*/
|
||||
protected $robots;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Comment_Link_Fixer constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets depedencies.
|
||||
*
|
||||
* @param Redirect_Helper $redirect The redirect helper.
|
||||
* @param Robots_Helper $robots The robots helper.
|
||||
*/
|
||||
public function __construct( Redirect_Helper $redirect, Robots_Helper $robots ) {
|
||||
$this->redirect = $redirect;
|
||||
$this->robots = $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->clean_reply_to_com() ) {
|
||||
\add_filter( 'comment_reply_link', [ $this, 'remove_reply_to_com' ] );
|
||||
\add_action( 'template_redirect', [ $this, 'replytocom_redirect' ], 1 );
|
||||
}
|
||||
|
||||
// When users view a reply to a comment, this URL parameter is set. These should never be indexed separately.
|
||||
if ( $this->get_replytocom_parameter() !== null ) {
|
||||
\add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the url contains the ?replytocom query parameter.
|
||||
*
|
||||
* @codeCoverageIgnore Wraps the filter input.
|
||||
*
|
||||
* @return string|null The value of replytocom or null if it does not exist.
|
||||
*/
|
||||
protected function get_replytocom_parameter() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['replytocom'] ) && \is_string( $_GET['replytocom'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return \sanitize_text_field( \wp_unslash( $_GET['replytocom'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the ?replytocom variable from the link, replacing it with a #comment-<number> anchor.
|
||||
*
|
||||
* @todo Should this function also allow for relative urls ?
|
||||
*
|
||||
* @param string $link The comment link as a string.
|
||||
*
|
||||
* @return string The modified link.
|
||||
*/
|
||||
public function remove_reply_to_com( $link ) {
|
||||
return \preg_replace( '`href=(["\'])(?:.*(?:\?|&|&)replytocom=(\d+)#respond)`', 'href=$1#comment-$2', $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects out the ?replytocom variables.
|
||||
*
|
||||
* @return bool True when redirect has been done.
|
||||
*/
|
||||
public function replytocom_redirect() {
|
||||
if ( isset( $_GET['replytocom'] ) && \is_singular() ) {
|
||||
$url = \get_permalink( $GLOBALS['post']->ID );
|
||||
$hash = \sanitize_text_field( \wp_unslash( $_GET['replytocom'] ) );
|
||||
$query_string = '';
|
||||
if ( isset( $_SERVER['QUERY_STRING'] ) ) {
|
||||
$query_string = \remove_query_arg( 'replytocom', \sanitize_text_field( \wp_unslash( $_SERVER['QUERY_STRING'] ) ) );
|
||||
}
|
||||
if ( ! empty( $query_string ) ) {
|
||||
$url .= '?' . $query_string;
|
||||
}
|
||||
$url .= '#comment-' . $hash;
|
||||
|
||||
$this->redirect->do_safe_redirect( $url, 301 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we can allow the feature that removes ?replytocom query parameters.
|
||||
*
|
||||
* @codeCoverageIgnore It just wraps a call to a filter.
|
||||
*
|
||||
* @return bool True to remove, false not to remove.
|
||||
*/
|
||||
private function clean_reply_to_com() {
|
||||
/**
|
||||
* Filter: 'wpseo_remove_reply_to_com' - Allow disabling the feature that removes ?replytocom query parameters.
|
||||
*
|
||||
* @param bool $return True to remove, false not to remove.
|
||||
*/
|
||||
return (bool) \apply_filters( 'wpseo_remove_reply_to_com', true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Crawl_Cleanup_Basic.
|
||||
*/
|
||||
class Crawl_Cleanup_Basic implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Crawl Cleanup Basic integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Remove HTTP headers we don't want.
|
||||
\add_action( 'wp', [ $this, 'clean_headers' ], 0 );
|
||||
|
||||
if ( $this->is_true( 'remove_shortlinks' ) ) {
|
||||
// Remove shortlinks.
|
||||
\remove_action( 'wp_head', 'wp_shortlink_wp_head' );
|
||||
\remove_action( 'template_redirect', 'wp_shortlink_header', 11 );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_rest_api_links' ) ) {
|
||||
// Remove REST API links.
|
||||
\remove_action( 'wp_head', 'rest_output_link_wp_head' );
|
||||
\remove_action( 'template_redirect', 'rest_output_link_header', 11 );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_rsd_wlw_links' ) ) {
|
||||
// Remove RSD and WLW Manifest links.
|
||||
\remove_action( 'wp_head', 'rsd_link' );
|
||||
\remove_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
|
||||
\remove_action( 'wp_head', 'wlwmanifest_link' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_oembed_links' ) ) {
|
||||
// Remove JSON+XML oEmbed links.
|
||||
\remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_generator' ) ) {
|
||||
\remove_action( 'wp_head', 'wp_generator' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_emoji_scripts' ) ) {
|
||||
// Remove emoji scripts and additional stuff they cause.
|
||||
\remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
||||
\remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
\remove_action( 'admin_print_styles', 'print_emoji_styles' );
|
||||
\add_filter( 'wp_resource_hints', [ $this, 'resource_hints_plain_cleanup' ], 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes X-Pingback and X-Powered-By headers as they're unneeded.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clean_headers() {
|
||||
if ( \headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_powered_by_header' ) ) {
|
||||
\header_remove( 'X-Powered-By' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_pingback_header' ) ) {
|
||||
\header_remove( 'X-Pingback' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the core s.w.org hint as it's only used for emoji stuff we don't use.
|
||||
*
|
||||
* @param array $hints The hints we're adding to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function resource_hints_plain_cleanup( $hints ) {
|
||||
foreach ( $hints as $key => $hint ) {
|
||||
if ( \is_array( $hint ) && isset( $hint['href'] ) ) {
|
||||
if ( \strpos( $hint['href'], '//s.w.org' ) !== false ) {
|
||||
unset( $hints[ $key ] );
|
||||
}
|
||||
}
|
||||
elseif ( \strpos( $hint, '//s.w.org' ) !== false ) {
|
||||
unset( $hints[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of an option is set to true.
|
||||
*
|
||||
* @param string $option_name The option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_true( $option_name ) {
|
||||
return $this->options_helper->get( $option_name ) === true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds actions that cleanup unwanted rss feed links.
|
||||
*/
|
||||
class Crawl_Cleanup_Rss implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Crawl Cleanup RSS integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our RSS related hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->is_true( 'remove_feed_global' ) ) {
|
||||
\add_filter( 'feed_links_show_posts_feed', '__return_false' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_feed_global_comments' ) ) {
|
||||
\add_filter( 'feed_links_show_comments_feed', '__return_false' );
|
||||
}
|
||||
|
||||
\add_action( 'wp', [ $this, 'maybe_disable_feeds' ] );
|
||||
\add_action( 'wp', [ $this, 'maybe_redirect_feeds' ], -10000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable feeds on selected cases.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_disable_feeds() {
|
||||
if ( $this->is_true( 'remove_feed_post_comments' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_post_comments_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_authors' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_author_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_categories' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_category_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_tags' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_tag_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_custom_taxonomies' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_tax_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_post_types' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_post_type_archive_feed', '__return_false' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_feed_search' ) ) {
|
||||
\add_filter( 'feed_links_extra_show_search_feed', '__return_false' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect feeds we don't want away.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect_feeds() {
|
||||
global $wp_query;
|
||||
|
||||
if ( ! \is_feed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \in_array( \get_query_var( 'feed' ), [ 'atom', 'rdf' ], true ) && $this->is_true( 'remove_atom_rdf_feeds' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable Atom/RDF feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
// Only if we're on the global feed, the query is _just_ `'feed' => 'feed'`, hence this check.
|
||||
if ( ( $wp_query->query === [ 'feed' => 'feed' ]
|
||||
|| $wp_query->query === [ 'feed' => 'atom' ]
|
||||
|| $wp_query->query === [ 'feed' => 'rdf' ] )
|
||||
&& $this->is_true( 'remove_feed_global' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable the RSS feed for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_comment_feed() && ! ( \is_singular() || \is_attachment() ) && $this->is_true( 'remove_feed_global_comments' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable comment feeds for performance reasons.' );
|
||||
}
|
||||
elseif ( \is_comment_feed()
|
||||
&& \is_singular()
|
||||
&& ( $this->is_true( 'remove_feed_post_comments' ) || $this->is_true( 'remove_feed_global_comments' ) ) ) {
|
||||
$url = \get_permalink( \get_queried_object() );
|
||||
$this->redirect_feed( $url, 'We disable post comment feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_author() && $this->is_true( 'remove_feed_authors' ) ) {
|
||||
$author_id = (int) \get_query_var( 'author' );
|
||||
$url = \get_author_posts_url( $author_id );
|
||||
$this->redirect_feed( $url, 'We disable author feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( ( \is_category() && $this->is_true( 'remove_feed_categories' ) )
|
||||
|| ( \is_tag() && $this->is_true( 'remove_feed_tags' ) )
|
||||
|| ( \is_tax() && $this->is_true( 'remove_feed_custom_taxonomies' ) ) ) {
|
||||
$term = \get_queried_object();
|
||||
$url = \get_term_link( $term, $term->taxonomy );
|
||||
if ( \is_wp_error( $url ) ) {
|
||||
$url = \home_url();
|
||||
}
|
||||
$this->redirect_feed( $url, 'We disable taxonomy feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( ( \is_post_type_archive() ) && $this->is_true( 'remove_feed_post_types' ) ) {
|
||||
$url = \get_post_type_archive_link( $this->get_queried_post_type() );
|
||||
$this->redirect_feed( $url, 'We disable post type feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_search() && $this->is_true( 'remove_feed_search' ) ) {
|
||||
$url = \trailingslashit( \home_url() ) . '?s=' . \get_search_query();
|
||||
$this->redirect_feed( $url, 'We disable search RSS feeds for performance reasons.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a cache control header.
|
||||
*
|
||||
* @param int $expiration The expiration time.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cache_control_header( $expiration ) {
|
||||
\header_remove( 'Expires' );
|
||||
|
||||
// The cacheability of the current request. 'public' allows caching, 'private' would not allow caching by proxies like CloudFlare.
|
||||
$cacheability = 'public';
|
||||
$format = '%1$s, max-age=%2$d, s-maxage=%2$d, stale-while-revalidate=120, stale-if-error=14400';
|
||||
|
||||
if ( \is_user_logged_in() ) {
|
||||
$expiration = 0;
|
||||
$cacheability = 'private';
|
||||
$format = '%1$s, max-age=%2$d';
|
||||
}
|
||||
|
||||
\header( \sprintf( 'Cache-Control: ' . $format, $cacheability, $expiration ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect a feed result to somewhere else.
|
||||
*
|
||||
* @param string $url The location we're redirecting to.
|
||||
* @param string $reason The reason we're redirecting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_feed( $url, $reason ) {
|
||||
\header_remove( 'Content-Type' );
|
||||
\header_remove( 'Last-Modified' );
|
||||
|
||||
$this->cache_control_header( 7 * \DAY_IN_SECONDS );
|
||||
|
||||
\wp_safe_redirect( $url, 301, 'Yoast SEO: ' . $reason );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the queried post type.
|
||||
*
|
||||
* @return string The queried post type.
|
||||
*/
|
||||
private function get_queried_post_type() {
|
||||
$post_type = \get_query_var( 'post_type' );
|
||||
if ( \is_array( $post_type ) ) {
|
||||
$post_type = \reset( $post_type );
|
||||
}
|
||||
return $post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of an option is set to true.
|
||||
*
|
||||
* @param string $option_name The option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_true( $option_name ) {
|
||||
return $this->options_helper->get( $option_name ) === true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use WP_Query;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Crawl_Cleanup_Searches.
|
||||
*/
|
||||
class Crawl_Cleanup_Searches implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Patterns to match against to find spam.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $patterns = [
|
||||
'/[:()【】[]]+/u',
|
||||
'/(TALK|QQ)\:/iu',
|
||||
];
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper
|
||||
*/
|
||||
private $redirect_helper;
|
||||
|
||||
/**
|
||||
* Crawl_Cleanup_Searches integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
* @param Redirect_Helper $redirect_helper The redirect helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Redirect_Helper $redirect_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->redirect_helper = $redirect_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->options_helper->get( 'search_cleanup' ) ) {
|
||||
\add_filter( 'pre_get_posts', [ $this, 'validate_search' ] );
|
||||
}
|
||||
if ( $this->options_helper->get( 'redirect_search_pretty_urls' ) && ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
\add_action( 'template_redirect', [ $this, 'maybe_redirect_searches' ], 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we want to allow this search to happen.
|
||||
*
|
||||
* @param WP_Query $query The main query.
|
||||
*
|
||||
* @return WP_Query
|
||||
*/
|
||||
public function validate_search( WP_Query $query ) {
|
||||
if ( ! $query->is_search() ) {
|
||||
return $query;
|
||||
}
|
||||
// First check against emoji and patterns we might not want.
|
||||
$this->check_unwanted_patterns( $query );
|
||||
|
||||
// Then limit characters if still needed.
|
||||
$this->limit_characters();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect pretty search URLs to the "raw" equivalent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect_searches() {
|
||||
if ( ! \is_search() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && \stripos( $_SERVER['REQUEST_URI'], '/search/' ) === 0 ) {
|
||||
$args = [];
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
$parsed = \wp_parse_url( $_SERVER['REQUEST_URI'] );
|
||||
|
||||
if ( ! empty( $parsed['query'] ) ) {
|
||||
\wp_parse_str( $parsed['query'], $args );
|
||||
}
|
||||
|
||||
$args['s'] = \get_search_query();
|
||||
|
||||
$proper_url = \home_url( '/' );
|
||||
|
||||
if ( \intval( \get_query_var( 'paged' ) ) > 1 ) {
|
||||
$proper_url .= \sprintf( 'page/%s/', \get_query_var( 'paged' ) );
|
||||
unset( $args['paged'] );
|
||||
}
|
||||
|
||||
$proper_url = \add_query_arg( \array_map( 'rawurlencode_deep', $args ), $proper_url );
|
||||
|
||||
if ( ! empty( $parsed['fragment'] ) ) {
|
||||
$proper_url .= '#' . \rawurlencode( $parsed['fragment'] );
|
||||
}
|
||||
|
||||
$this->redirect_away( 'We redirect pretty URLs to the raw format.', $proper_url );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check query against unwanted search patterns.
|
||||
*
|
||||
* @param WP_Query $query The main WordPress query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function check_unwanted_patterns( WP_Query $query ) {
|
||||
$s = \rawurldecode( $query->query_vars['s'] );
|
||||
if ( $this->options_helper->get( 'search_cleanup_emoji' ) && $this->has_emoji( $s ) ) {
|
||||
$this->redirect_away( 'We don\'t allow searches with emojis and other special characters.' );
|
||||
}
|
||||
|
||||
if ( ! $this->options_helper->get( 'search_cleanup_patterns' ) ) {
|
||||
return;
|
||||
}
|
||||
foreach ( $this->patterns as $pattern ) {
|
||||
$outcome = \preg_match( $pattern, $s, $matches );
|
||||
if ( $outcome && $matches !== [] ) {
|
||||
$this->redirect_away( 'Your search matched a common spam pattern.' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the homepage for invalid searches.
|
||||
*
|
||||
* @param string $reason The reason for redirecting away.
|
||||
* @param string $to_url The URL to redirect to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_away( $reason, $to_url = '' ) {
|
||||
if ( empty( $to_url ) ) {
|
||||
$to_url = \get_home_url();
|
||||
}
|
||||
|
||||
$this->redirect_helper->do_safe_redirect( $to_url, 301, 'Yoast Search Filtering: ' . $reason );
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the number of characters in the search query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function limit_characters() {
|
||||
// We retrieve the search term unescaped because we want to count the characters properly. We make sure to escape it afterwards, if we do something with it.
|
||||
$unescaped_s = \get_search_query( false );
|
||||
|
||||
// We then unslash the search term, again because we want to count the characters properly. We make sure to slash it afterwards, if we do something with it.
|
||||
$raw_s = \wp_unslash( $unescaped_s );
|
||||
if ( \mb_strlen( $raw_s, 'UTF-8' ) > $this->options_helper->get( 'search_character_limit' ) ) {
|
||||
$new_s = \mb_substr( $raw_s, 0, $this->options_helper->get( 'search_character_limit' ), 'UTF-8' );
|
||||
\set_query_var( 's', \wp_slash( \esc_attr( $new_s ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a text string contains an emoji or not.
|
||||
*
|
||||
* @param string $text The text string to detect emoji in.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function has_emoji( $text ) {
|
||||
$emojis_regex = '/([^-\p{L}\x00-\x7F]+)/u';
|
||||
\preg_match( $emojis_regex, $text, $matches );
|
||||
|
||||
return ! empty( $matches );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* Class Feed_Improvements
|
||||
*/
|
||||
class Feed_Improvements implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Holds the meta helper surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* Canonical_Header constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets depedencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Meta_Surface $meta The meta surface.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, Meta_Surface $meta ) {
|
||||
$this->options = $options;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'get_bloginfo_rss', [ $this, 'filter_bloginfo_rss' ], 10, 2 );
|
||||
\add_filter( 'document_title_separator', [ $this, 'filter_document_title_separator' ] );
|
||||
|
||||
\add_action( 'do_feed_rss', [ $this, 'handle_rss_feed' ], 9 );
|
||||
\add_action( 'do_feed_rss2', [ $this, 'send_canonical_header' ], 9 );
|
||||
\add_action( 'do_feed_rss2', [ $this, 'add_robots_headers' ], 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter `bloginfo_rss` output to give the URL for what's being shown instead of just always the homepage.
|
||||
*
|
||||
* @param string $show The output so far.
|
||||
* @param string $what What is being shown.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_bloginfo_rss( $show, $what ) {
|
||||
if ( $what === 'url' ) {
|
||||
return $this->get_url_for_queried_object( $show );
|
||||
}
|
||||
|
||||
return $show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure send canonical header always runs, because this RSS hook does not support the for_comments parameter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_rss_feed() {
|
||||
$this->send_canonical_header( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a canonical link header to the main canonical URL for the requested feed object. If it is not a comment
|
||||
* feed.
|
||||
*
|
||||
* @param bool $for_comments If the RRS feed is meant for a comment feed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function send_canonical_header( $for_comments ) {
|
||||
|
||||
if ( $for_comments || \headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queried_object = \get_queried_object();
|
||||
// Don't call get_class with null. This gives a warning.
|
||||
$class = ( $queried_object !== null ) ? \get_class( $queried_object ) : null;
|
||||
|
||||
$url = $this->get_url_for_queried_object( $this->meta->for_home_page()->canonical );
|
||||
if ( ( ! empty( $url ) && $url !== $this->meta->for_home_page()->canonical ) || $class === null ) {
|
||||
\header( \sprintf( 'Link: <%s>; rel="canonical"', $url ), false );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds noindex, follow tag for comment feeds.
|
||||
*
|
||||
* @param bool $for_comments If the RSS feed is meant for a comment feed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_robots_headers( $for_comments ) {
|
||||
if ( $for_comments && ! \headers_sent() ) {
|
||||
\header( 'X-Robots-Tag: noindex, follow', true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the title separator set in Yoast SEO is used for all feeds.
|
||||
*
|
||||
* @param string $separator The separator from WordPress.
|
||||
*
|
||||
* @return string The separator from Yoast SEO's settings.
|
||||
*/
|
||||
public function filter_document_title_separator( $separator ) {
|
||||
return \html_entity_decode( $this->options->get_title_separator() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the main URL for the queried object.
|
||||
*
|
||||
* @param string $url The URL determined so far.
|
||||
*
|
||||
* @return string The canonical URL for the queried object.
|
||||
*/
|
||||
protected function get_url_for_queried_object( $url = '' ) {
|
||||
$queried_object = \get_queried_object();
|
||||
// Don't call get_class with null. This gives a warning.
|
||||
$class = ( $queried_object !== null ) ? \get_class( $queried_object ) : null;
|
||||
$meta = false;
|
||||
|
||||
switch ( $class ) {
|
||||
// Post type archive feeds.
|
||||
case 'WP_Post_Type':
|
||||
$meta = $this->meta->for_post_type_archive( $queried_object->name );
|
||||
break;
|
||||
// Post comment feeds.
|
||||
case 'WP_Post':
|
||||
$meta = $this->meta->for_post( $queried_object->ID );
|
||||
break;
|
||||
// Term feeds.
|
||||
case 'WP_Term':
|
||||
$meta = $this->meta->for_term( $queried_object->term_id );
|
||||
break;
|
||||
// Author feeds.
|
||||
case 'WP_User':
|
||||
$meta = $this->meta->for_author( $queried_object->ID );
|
||||
break;
|
||||
// This would be NULL on the home page and on date archive feeds.
|
||||
case null:
|
||||
$meta = $this->meta->for_home_page();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $meta ) {
|
||||
return $meta->canonical;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;
|
||||
|
||||
/**
|
||||
* Class Force_Rewrite_Title.
|
||||
*/
|
||||
class Force_Rewrite_Title implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Toggle indicating whether output buffering has been started.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $ob_started = false;
|
||||
|
||||
/**
|
||||
* The WP Query wrapper.
|
||||
*
|
||||
* @var WP_Query_Wrapper
|
||||
*/
|
||||
private $wp_query;
|
||||
|
||||
/**
|
||||
* Sets the helpers.
|
||||
*
|
||||
* @codeCoverageIgnore It just handles dependencies.
|
||||
*
|
||||
* @param Options_Helper $options Options helper.
|
||||
* @param WP_Query_Wrapper $wp_query WP query wrapper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, WP_Query_Wrapper $wp_query ) {
|
||||
$this->options = $options;
|
||||
$this->wp_query = $wp_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// When the option is disabled.
|
||||
if ( ! $this->options->get( 'forcerewritetitle', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For WordPress versions below 4.4.
|
||||
if ( \current_theme_supports( 'title-tag' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_action( 'template_redirect', [ $this, 'force_rewrite_output_buffer' ], 99999 );
|
||||
\add_action( 'wp_footer', [ $this, 'flush_cache' ], -1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO
|
||||
* title and then flushes the output.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush_cache() {
|
||||
if ( $this->ob_started !== true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = $this->get_buffered_output();
|
||||
|
||||
$old_wp_query = $this->wp_query->get_query();
|
||||
|
||||
\wp_reset_query();
|
||||
|
||||
// When the file has the debug mark.
|
||||
if ( \preg_match( '/(?\'before\'.*)<!-- This site is optimized with the Yoast SEO.*<!-- \/ Yoast SEO( Premium)? plugin. -->(?\'after\'.*)/is', $content, $matches ) ) {
|
||||
$content = $this->replace_titles_from_content( $content, $matches );
|
||||
|
||||
unset( $matches );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride -- The query gets reset here with the original query.
|
||||
$GLOBALS['wp_query'] = $old_wp_query;
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- The output should already have been escaped, we are only filtering it.
|
||||
echo $content;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the output buffer so it can later be fixed by flush_cache().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function force_rewrite_output_buffer() {
|
||||
$this->ob_started = true;
|
||||
$this->start_output_buffering();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the titles from the parts that contains a title.
|
||||
*
|
||||
* @param string $content The content to remove the titles from.
|
||||
* @param array $parts_with_title The parts containing a title.
|
||||
*
|
||||
* @return string The modified content.
|
||||
*/
|
||||
protected function replace_titles_from_content( $content, $parts_with_title ) {
|
||||
if ( isset( $parts_with_title['before'] ) && \is_string( $parts_with_title['before'] ) ) {
|
||||
$content = $this->replace_title( $parts_with_title['before'], $content );
|
||||
}
|
||||
|
||||
if ( isset( $parts_with_title['after'] ) ) {
|
||||
$content = $this->replace_title( $parts_with_title['after'], $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the title from the part that contains the title and put this modified part back
|
||||
* into the content.
|
||||
*
|
||||
* @param string $part_with_title The part with the title that needs to be replaced.
|
||||
* @param string $content The entire content.
|
||||
*
|
||||
* @return string The altered content.
|
||||
*/
|
||||
protected function replace_title( $part_with_title, $content ) {
|
||||
$part_without_title = \preg_replace( '/<title.*?\/title>/i', '', $part_with_title );
|
||||
|
||||
return \str_replace( $part_with_title, $part_without_title, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the output buffering.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function start_output_buffering() {
|
||||
\ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the buffered output.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string|false The buffered output.
|
||||
*/
|
||||
protected function get_buffered_output() {
|
||||
return \ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;
|
||||
|
||||
/**
|
||||
* Handles intercepting requests.
|
||||
*/
|
||||
class Handle_404 implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The WP Query wrapper.
|
||||
*
|
||||
* @var WP_Query_Wrapper
|
||||
*/
|
||||
private $query_wrapper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'pre_handle_404', [ $this, 'handle_404' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle_404 constructor.
|
||||
*
|
||||
* @codeCoverageIgnore Handles dependencies.
|
||||
*
|
||||
* @param WP_Query_Wrapper $query_wrapper The query wrapper.
|
||||
*/
|
||||
public function __construct( WP_Query_Wrapper $query_wrapper ) {
|
||||
$this->query_wrapper = $query_wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 404 status code.
|
||||
*
|
||||
* @param bool $handled Whether we've handled the request.
|
||||
*
|
||||
* @return bool True if it's 404.
|
||||
*/
|
||||
public function handle_404( $handled ) {
|
||||
if ( ! $this->is_feed_404() ) {
|
||||
return $handled;
|
||||
}
|
||||
|
||||
$this->set_404();
|
||||
$this->set_headers();
|
||||
|
||||
\add_filter( 'old_slug_redirect_url', '__return_false' );
|
||||
\add_filter( 'redirect_canonical', '__return_false' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are no posts in a feed, make it 404 instead of sending an empty RSS feed.
|
||||
*
|
||||
* @return bool True if it's 404.
|
||||
*/
|
||||
protected function is_feed_404() {
|
||||
if ( ! \is_feed() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$wp_query = $this->query_wrapper->get_query();
|
||||
|
||||
// Don't 404 if the query contains post(s) or an object.
|
||||
if ( $wp_query->posts || $wp_query->get_queried_object() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't 404 if it isn't archive or singular.
|
||||
if ( ! $wp_query->is_archive() && ! $wp_query->is_singular() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 404 status code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_404() {
|
||||
$wp_query = $this->query_wrapper->get_query();
|
||||
$wp_query->is_feed = false;
|
||||
$wp_query->set_404();
|
||||
$this->query_wrapper->set_query( $wp_query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers for http.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_headers() {
|
||||
// Overwrite Content-Type header.
|
||||
if ( ! \headers_sent() ) {
|
||||
\header( 'Content-Type: ' . \get_option( 'html_type' ) . '; charset=' . \get_option( 'blog_charset' ) );
|
||||
}
|
||||
|
||||
\status_header( 404 );
|
||||
\nocache_headers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Robots_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Indexing_Controls.
|
||||
*/
|
||||
class Indexing_Controls implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The robots helper.
|
||||
*
|
||||
* @var Robots_Helper
|
||||
*/
|
||||
protected $robots;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @codeCoverageIgnore Sets the dependencies.
|
||||
*
|
||||
* @param Robots_Helper $robots The robots helper.
|
||||
*/
|
||||
public function __construct( Robots_Helper $robots ) {
|
||||
$this->robots = $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// The option `blog_public` is set in Settings > Reading > Search Engine Visibility.
|
||||
if ( (string) \get_option( 'blog_public' ) === '0' ) {
|
||||
\add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] );
|
||||
}
|
||||
|
||||
\add_action( 'template_redirect', [ $this, 'noindex_robots' ] );
|
||||
\add_filter( 'loginout', [ $this, 'nofollow_link' ] );
|
||||
\add_filter( 'register', [ $this, 'nofollow_link' ] );
|
||||
|
||||
// Remove actions that we will handle through our wpseo_head call, and probably change the output of.
|
||||
\remove_action( 'wp_head', 'rel_canonical' );
|
||||
\remove_action( 'wp_head', 'index_rel_link' );
|
||||
\remove_action( 'wp_head', 'start_post_rel_link' );
|
||||
\remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
|
||||
\remove_action( 'wp_head', 'noindex', 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines
|
||||
* to follow the links in the object at the URL.
|
||||
*
|
||||
* @return bool Boolean indicating whether the noindex header was sent.
|
||||
*/
|
||||
public function noindex_robots() {
|
||||
if ( ! \is_robots() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->set_robots_header();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds rel="nofollow" to a link, only used for login / registration links.
|
||||
*
|
||||
* @param string $input The link element as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function nofollow_link( $input ) {
|
||||
return \str_replace( '<a ', '<a rel="nofollow" ', $input );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the x-robots-tag to noindex follow.
|
||||
*
|
||||
* @codeCoverageIgnore Too difficult to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_robots_header() {
|
||||
if ( \headers_sent() === false ) {
|
||||
\header( 'X-Robots-Tag: noindex, follow', true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* Class Open_Graph_OEmbed.
|
||||
*/
|
||||
class Open_Graph_OEmbed implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The meta surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* The oEmbed data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* The post ID for the current post.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $post_id;
|
||||
|
||||
/**
|
||||
* The post meta.
|
||||
*
|
||||
* @var Meta|false
|
||||
*/
|
||||
private $post_meta;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class, Open_Graph_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'oembed_response_data', [ $this, 'set_oembed_data' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open_Graph_OEmbed constructor.
|
||||
*
|
||||
* @param Meta_Surface $meta The meta surface.
|
||||
*/
|
||||
public function __construct( Meta_Surface $meta ) {
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to pass to the oEmbed's response data that will enable
|
||||
* support for using the image and title set by the WordPress SEO plugin's fields. This
|
||||
* address the concern where some social channels/subscribed use oEmebed data over Open Graph data
|
||||
* if both are present.
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/hooks/oembed_response_data/ for hook info.
|
||||
*
|
||||
* @param array $data The oEmbed data.
|
||||
* @param WP_Post $post The current Post object.
|
||||
*
|
||||
* @return array An array of oEmbed data with modified values where appropriate.
|
||||
*/
|
||||
public function set_oembed_data( $data, $post ) {
|
||||
// Data to be returned.
|
||||
$this->data = $data;
|
||||
$this->post_id = $post->ID;
|
||||
$this->post_meta = $this->meta->for_post( $this->post_id );
|
||||
|
||||
if ( ! empty( $this->post_meta ) ) {
|
||||
$this->set_title();
|
||||
$this->set_description();
|
||||
$this->set_image();
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OpenGraph title if configured.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_title() {
|
||||
$opengraph_title = $this->post_meta->open_graph_title;
|
||||
|
||||
if ( ! empty( $opengraph_title ) ) {
|
||||
$this->data['title'] = $opengraph_title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OpenGraph description if configured.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_description() {
|
||||
$opengraph_description = $this->post_meta->open_graph_description;
|
||||
|
||||
if ( ! empty( $opengraph_description ) ) {
|
||||
$this->data['description'] = $opengraph_description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image if it has been configured.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_image() {
|
||||
$images = $this->post_meta->open_graph_images;
|
||||
|
||||
if ( ! \is_array( $images ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image = \reset( $images );
|
||||
|
||||
if ( empty( $image ) || ! isset( $image['url'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data['thumbnail_url'] = $image['url'];
|
||||
|
||||
if ( isset( $image['width'] ) ) {
|
||||
$this->data['thumbnail_width'] = $image['width'];
|
||||
}
|
||||
|
||||
if ( isset( $image['height'] ) ) {
|
||||
$this->data['thumbnail_height'] = $image['height'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Meta_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Url_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Redirects.
|
||||
*/
|
||||
class Redirects implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The meta helper.
|
||||
*
|
||||
* @var Meta_Helper
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* The current page helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $current_page;
|
||||
|
||||
/**
|
||||
* The redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper
|
||||
*/
|
||||
private $redirect;
|
||||
|
||||
/**
|
||||
* The URL helper.
|
||||
*
|
||||
* @var Url_Helper
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* Holds the WP_Query variables we should get rid of.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $date_query_variables = [
|
||||
'year',
|
||||
'm',
|
||||
'monthnum',
|
||||
'day',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the helpers.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options Options helper.
|
||||
* @param Meta_Helper $meta Meta helper.
|
||||
* @param Current_Page_Helper $current_page The current page helper.
|
||||
* @param Redirect_Helper $redirect The redirect helper.
|
||||
* @param Url_Helper $url The URL helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, Meta_Helper $meta, Current_Page_Helper $current_page, Redirect_Helper $redirect, Url_Helper $url ) {
|
||||
$this->options = $options;
|
||||
$this->meta = $meta;
|
||||
$this->current_page = $current_page;
|
||||
$this->redirect = $redirect;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp', [ $this, 'archive_redirect' ] );
|
||||
\add_action( 'wp', [ $this, 'page_redirect' ], 99 );
|
||||
\add_action( 'wp', [ $this, 'category_redirect' ] );
|
||||
\add_action( 'template_redirect', [ $this, 'attachment_redirect' ], 1 );
|
||||
\add_action( 'template_redirect', [ $this, 'disable_date_queries' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable date queries, if they're disabled in Yoast SEO settings, to prevent indexing the wrong things.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disable_date_queries() {
|
||||
if ( $this->options->get( 'disable-date', false ) ) {
|
||||
$exploded_url = \explode( '?', $this->url->recreate_current_url(), 2 );
|
||||
list( $base_url, $query_string ) = \array_pad( $exploded_url, 2, '' );
|
||||
\parse_str( $query_string, $query_vars );
|
||||
foreach ( $this->date_query_variables as $variable ) {
|
||||
if ( \in_array( $variable, \array_keys( $query_vars ), true ) ) {
|
||||
$this->do_date_redirect( $query_vars, $base_url );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When certain archives are disabled, this redirects those to the homepage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function archive_redirect() {
|
||||
if ( $this->need_archive_redirect() ) {
|
||||
$this->redirect->do_safe_redirect( \get_bloginfo( 'url' ), 301 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the redirect meta value, this function determines whether it should redirect the current post / page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function page_redirect() {
|
||||
if ( ! $this->current_page->is_simple_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = \get_post();
|
||||
if ( ! \is_object( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirect = $this->meta->get_value( 'redirect', $post->ID );
|
||||
if ( $redirect === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect->do_safe_redirect( $redirect, 301 );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the option to disable attachment URLs is checked, this performs the redirect to the attachment.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function attachment_redirect() {
|
||||
if ( ! $this->current_page->is_attachment() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'disable-attachment', false ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->get_attachment_url();
|
||||
if ( empty( $url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect->do_unsafe_redirect( $url, 301 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if certain archive pages are disabled to determine if a archive redirect is needed.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether or not to redirect an archive page.
|
||||
*/
|
||||
protected function need_archive_redirect() {
|
||||
if ( $this->options->get( 'disable-date', false ) && $this->current_page->is_date_archive() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'disable-author', false ) && $this->current_page->is_author_archive() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'disable-post_format', false ) && $this->current_page->is_post_format_archive() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the attachment url for the current page.
|
||||
*
|
||||
* @codeCoverageIgnore It wraps WordPress functions.
|
||||
*
|
||||
* @return string The attachment url.
|
||||
*/
|
||||
protected function get_attachment_url() {
|
||||
/**
|
||||
* Allows the developer to change the target redirection URL for attachments.
|
||||
*
|
||||
* @since 7.5.3
|
||||
*
|
||||
* @param string $attachment_url The attachment URL for the queried object.
|
||||
* @param object $queried_object The queried object.
|
||||
*/
|
||||
return \apply_filters(
|
||||
'wpseo_attachment_redirect_url',
|
||||
\wp_get_attachment_url( \get_queried_object_id() ),
|
||||
\get_queried_object()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects away query variables that shouldn't work.
|
||||
*
|
||||
* @param array $query_vars The query variables in the current URL.
|
||||
* @param string $base_url The base URL without query string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function do_date_redirect( $query_vars, $base_url ) {
|
||||
foreach ( $this->date_query_variables as $variable ) {
|
||||
unset( $query_vars[ $variable ] );
|
||||
}
|
||||
$url = $base_url;
|
||||
if ( \count( $query_vars ) > 0 ) {
|
||||
$url .= '?' . \http_build_query( $query_vars );
|
||||
}
|
||||
|
||||
$this->redirect->do_safe_redirect( $url, 301 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips `cat=-1` from the URL and redirects to the resulting URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function category_redirect() {
|
||||
/**
|
||||
* Allows the developer to keep cat=-1 GET parameters
|
||||
*
|
||||
* @since 19.9
|
||||
*
|
||||
* @param bool $remove_cat_parameter Whether to remove the `cat=-1` GET parameter. Default true.
|
||||
*/
|
||||
$should_remove_parameter = \apply_filters( 'wpseo_remove_cat_parameter', true );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved.
|
||||
if ( $should_remove_parameter && isset( $_GET['cat'] ) && $_GET['cat'] === '-1' ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved.
|
||||
unset( $_GET['cat'] );
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is just a replace and the data is never saved.
|
||||
$_SERVER['REQUEST_URI'] = \remove_query_arg( 'cat' );
|
||||
}
|
||||
$this->redirect->do_safe_redirect( $this->url->recreate_current_url(), 301, 'Stripping cat=-1 from the URL' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use WPSEO_Sitemaps_Router;
|
||||
use Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Robots_Txt_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Robots_Txt_Presenter;
|
||||
|
||||
/**
|
||||
* Handles adding the sitemap to the `robots.txt`.
|
||||
*/
|
||||
class Robots_Txt_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Holds the robots txt helper.
|
||||
*
|
||||
* @var Robots_Txt_Helper
|
||||
*/
|
||||
protected $robots_txt_helper;
|
||||
|
||||
/**
|
||||
* Holds the robots txt presenter.
|
||||
*
|
||||
* @var Robots_Txt_Presenter
|
||||
*/
|
||||
protected $robots_txt_presenter;
|
||||
|
||||
/**
|
||||
* Sets the helpers.
|
||||
*
|
||||
* @param Options_Helper $options_helper Options helper.
|
||||
* @param Robots_Txt_Helper $robots_txt_helper Robots txt helper.
|
||||
* @param Robots_Txt_Presenter $robots_txt_presenter Robots txt presenter.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Robots_Txt_Helper $robots_txt_helper, Robots_Txt_Presenter $robots_txt_presenter ) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->robots_txt_helper = $robots_txt_helper;
|
||||
$this->robots_txt_presenter = $robots_txt_presenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Robots_Txt_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'robots_txt', [ $this, 'filter_robots' ], 99999 );
|
||||
|
||||
if ( $this->options_helper->get( 'deny_search_crawling' ) && ! \is_multisite() ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_search_to_robots' ], 10, 1 );
|
||||
}
|
||||
if ( $this->options_helper->get( 'deny_wp_json_crawling' ) && ! \is_multisite() ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_wp_json_to_robots' ], 10, 1 );
|
||||
}
|
||||
if ( $this->options_helper->get( 'deny_adsbot_crawling' ) && ! \is_multisite() ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_adsbot' ], 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the robots.txt output.
|
||||
*
|
||||
* @param string $robots_txt The robots.txt output from WordPress.
|
||||
*
|
||||
* @return string Filtered robots.txt output.
|
||||
*/
|
||||
public function filter_robots( $robots_txt ) {
|
||||
$robots_txt = $this->remove_default_robots( $robots_txt );
|
||||
$this->maybe_add_xml_sitemap();
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_should_add_subdirectory_multisite_xml_sitemaps' - Disabling this filter removes subdirectory sites from xml sitemaps.
|
||||
*
|
||||
* @since 19.8
|
||||
*
|
||||
* @param bool $show Whether to display multisites in the xml sitemaps.
|
||||
*/
|
||||
if ( \apply_filters( 'wpseo_should_add_subdirectory_multisite_xml_sitemaps', true ) ) {
|
||||
$this->add_subdirectory_multisite_xml_sitemaps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow registering custom robots rules to be outputted within the Yoast content block in robots.txt.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper object.
|
||||
*/
|
||||
\do_action( 'Yoast\WP\SEO\register_robots_rules', $this->robots_txt_helper );
|
||||
|
||||
return \trim( $robots_txt . \PHP_EOL . $this->robots_txt_presenter->present() . \PHP_EOL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for search to robots.txt.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The robots txt helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_search_to_robots( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( '*', '/?s=' );
|
||||
$robots_txt_helper->add_disallow( '*', '/page/*/?s=' );
|
||||
$robots_txt_helper->add_disallow( '*', '/search/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for /wp-json/ to robots.txt.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The robots txt helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_wp_json_to_robots( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( '*', '/wp-json/' );
|
||||
$robots_txt_helper->add_disallow( '*', '/?rest_route=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for AdsBot agents to robots.txt.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The robots txt helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_adsbot( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( 'AdsBot', '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the default WordPress robots.txt output.
|
||||
*
|
||||
* @param string $robots_txt Input robots.txt.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function remove_default_robots( $robots_txt ) {
|
||||
return \preg_replace(
|
||||
'`User-agent: \*[\r\n]+Disallow: /wp-admin/[\r\n]+Allow: /wp-admin/admin-ajax\.php[\r\n]+`',
|
||||
'',
|
||||
$robots_txt
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds XML sitemap reference to robots.txt.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_add_xml_sitemap() {
|
||||
// If the XML sitemap is disabled, bail.
|
||||
if ( ! $this->options_helper->get( 'enable_xml_sitemap', false ) ) {
|
||||
return;
|
||||
}
|
||||
$this->robots_txt_helper->add_sitemap( \esc_url( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subdomain multisite' XML sitemap references to robots.txt.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_subdirectory_multisite_xml_sitemaps() {
|
||||
// If not on a multisite subdirectory, bail.
|
||||
if ( ! \is_multisite() || \is_subdomain_install() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sitemaps_enabled = $this->get_xml_sitemaps_enabled();
|
||||
|
||||
foreach ( $sitemaps_enabled as $blog_id => $is_sitemap_enabled ) {
|
||||
if ( ! $is_sitemap_enabled ) {
|
||||
continue;
|
||||
}
|
||||
$this->robots_txt_helper->add_sitemap( \esc_url( \get_home_url( $blog_id, 'sitemap_index.xml' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the XML sitemaps are enabled, keyed by blog ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_xml_sitemaps_enabled() {
|
||||
$is_allowed = $this->is_sitemap_allowed();
|
||||
$blog_ids = $this->get_blog_ids();
|
||||
$is_enabled = [];
|
||||
foreach ( $blog_ids as $blog_id ) {
|
||||
$is_enabled[ $blog_id ] = $is_allowed && $this->is_sitemap_enabled_for( $blog_id );
|
||||
}
|
||||
|
||||
return $is_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the sitemap is allowed on a sub site.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_sitemap_allowed() {
|
||||
$options = \get_network_option( null, 'wpseo_ms' );
|
||||
if ( ! $options || ! isset( $options['allow_enable_xml_sitemap'] ) ) {
|
||||
// Default is enabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $options['allow_enable_xml_sitemap'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the sitemap is enabled on a site.
|
||||
*
|
||||
* @param int $blog_id The blog ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_sitemap_enabled_for( $blog_id ) {
|
||||
if ( ! $this->is_yoast_active_on( $blog_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = \get_blog_option( $blog_id, 'wpseo' );
|
||||
if ( ! $options || ! isset( $options['enable_xml_sitemap'] ) ) {
|
||||
// Default is enabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $options['enable_xml_sitemap'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether Yoast SEO is active.
|
||||
*
|
||||
* @param int $blog_id The blog ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_yoast_active_on( $blog_id ) {
|
||||
return \in_array( 'wordpress-seo/wp-seo.php', (array) \get_blog_option( $blog_id, 'active_plugins', [] ), true ) || $this->is_yoast_active_for_network();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether Yoast SEO is active for the entire network.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_yoast_active_for_network() {
|
||||
$plugins = \get_network_option( null, 'active_sitewide_plugins' );
|
||||
if ( isset( $plugins['wordpress-seo/wp-seo.php'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the blog IDs of public, "active" sites on the network.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_blog_ids() {
|
||||
$criteria = [
|
||||
'archived' => 0,
|
||||
'deleted' => 0,
|
||||
'public' => 1,
|
||||
'spam' => 0,
|
||||
'fields' => 'ids',
|
||||
'network_id' => \get_current_network_id(),
|
||||
];
|
||||
|
||||
return \get_sites( $criteria );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class RSS_Footer_Embed.
|
||||
*/
|
||||
class RSS_Footer_Embed implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the required helpers.
|
||||
*
|
||||
* @codeCoverageIgnore It only handles dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'the_content_feed', [ $this, 'embed_rssfooter' ] );
|
||||
\add_filter( 'the_excerpt_rss', [ $this, 'embed_rssfooter_excerpt' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the RSS footer (or header) to the full RSS feed item.
|
||||
*
|
||||
* @param string $content Feed item content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function embed_rssfooter( $content ) {
|
||||
if ( ! $this->include_rss_footer( 'full' ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $this->embed_rss( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the RSS footer (or header) to the excerpt RSS feed item.
|
||||
*
|
||||
* @param string $content Feed item excerpt.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function embed_rssfooter_excerpt( $content ) {
|
||||
if ( ! $this->include_rss_footer( 'excerpt' ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $this->embed_rss( \wpautop( $content ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the RSS footer should included.
|
||||
*
|
||||
* @param string $context The context of the RSS content.
|
||||
*
|
||||
* @return bool Whether or not the RSS footer should included.
|
||||
*/
|
||||
protected function include_rss_footer( $context ) {
|
||||
if ( ! \is_feed() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_include_rss_footer' - Allow the RSS footer to be dynamically shown/hidden.
|
||||
*
|
||||
* @param bool $show_embed Indicates if the RSS footer should be shown or not.
|
||||
* @param string $context The context of the RSS content - 'full' or 'excerpt'.
|
||||
*/
|
||||
if ( ! \apply_filters( 'wpseo_include_rss_footer', true, $context ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->is_configured();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the RSS feed fields are configured.
|
||||
*
|
||||
* @return bool True when one of the fields has a value.
|
||||
*/
|
||||
protected function is_configured() {
|
||||
return ( $this->options->get( 'rssbefore', '' ) !== '' || $this->options->get( 'rssafter', '' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the RSS footer and/or header to an RSS feed item.
|
||||
*
|
||||
* @param string $content Feed item content.
|
||||
*
|
||||
* @return string The content to add.
|
||||
*/
|
||||
protected function embed_rss( $content ) {
|
||||
$before = $this->rss_replace_vars( $this->options->get( 'rssbefore', '' ) );
|
||||
$after = $this->rss_replace_vars( $this->options->get( 'rssafter', '' ) );
|
||||
$content = $before . $content . $after;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the possible RSS variables with their actual values.
|
||||
*
|
||||
* @param string $content The RSS content that should have the variables replaced.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function rss_replace_vars( $content ) {
|
||||
if ( $content === '' ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$replace_vars = $this->get_replace_vars( $this->get_link_template(), \get_post() );
|
||||
|
||||
$content = \stripslashes( \trim( $content ) );
|
||||
$content = \str_ireplace( \array_keys( $replace_vars ), \array_values( $replace_vars ), $content );
|
||||
|
||||
return \wpautop( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the replacement variables.
|
||||
*
|
||||
* @codeCoverageIgnore It just contains too much WordPress functions.
|
||||
*
|
||||
* @param string $link_template The link template.
|
||||
* @param mixed $post The post to use.
|
||||
*
|
||||
* @return array The replacement variables.
|
||||
*/
|
||||
protected function get_replace_vars( $link_template, $post ) {
|
||||
$author_link = '';
|
||||
if ( \is_object( $post ) ) {
|
||||
$author_link = \sprintf( $link_template, \esc_url( \get_author_posts_url( $post->post_author ) ), \esc_html( \get_the_author() ) );
|
||||
}
|
||||
|
||||
return [
|
||||
'%%AUTHORLINK%%' => $author_link,
|
||||
'%%POSTLINK%%' => \sprintf( $link_template, \esc_url( \get_permalink() ), \esc_html( \get_the_title() ) ),
|
||||
'%%BLOGLINK%%' => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) ),
|
||||
'%%BLOGDESCLINK%%' => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) . ' - ' . \esc_html( \get_bloginfo( 'description' ) ) ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the link template.
|
||||
*
|
||||
* @return string The link template.
|
||||
*/
|
||||
protected function get_link_template() {
|
||||
/**
|
||||
* Filter: 'nofollow_rss_links' - Allow the developer to determine whether or not to follow the links in
|
||||
* the bits Yoast SEO adds to the RSS feed, defaults to false.
|
||||
*
|
||||
* @since 1.4.20
|
||||
*
|
||||
* @param bool $unsigned Whether or not to follow the links in RSS feed, defaults to true.
|
||||
*/
|
||||
if ( \apply_filters( 'nofollow_rss_links', false ) ) {
|
||||
return '<a rel="nofollow" href="%1$s">%2$s</a>';
|
||||
}
|
||||
|
||||
return '<a href="%1$s">%2$s</a>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece;
|
||||
use Yoast\WP\SEO\Generators\Schema\Article;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds the table of contents accessibility feature to the article piece with a fallback to the webpage piece.
|
||||
*/
|
||||
class Schema_Accessibility_Feature implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_schema_webpage', [ $this, 'maybe_add_accessibility_feature' ], 10, 4 );
|
||||
\add_filter( 'wpseo_schema_article', [ $this, 'add_accessibility_feature' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the accessibility feature to the webpage if there is no article.
|
||||
*
|
||||
* @param array $piece The graph piece.
|
||||
* @param Meta_Tags_Context $context The context.
|
||||
* @param Abstract_Schema_Piece $the_generator The current schema generator.
|
||||
* @param Abstract_Schema_Piece[] $generators The schema generators.
|
||||
*
|
||||
* @return array The graph piece.
|
||||
*/
|
||||
public function maybe_add_accessibility_feature( $piece, $context, $the_generator, $generators ) {
|
||||
foreach ( $generators as $generator ) {
|
||||
if ( \is_a( $generator, Article::class ) && $generator->is_needed() ) {
|
||||
return $piece;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->add_accessibility_feature( $piece, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the accessibility feature to a schema graph piece.
|
||||
*
|
||||
* @param array $piece The schema piece.
|
||||
* @param Meta_Tags_Context $context The context.
|
||||
*
|
||||
* @return array The graph piece.
|
||||
*/
|
||||
public function add_accessibility_feature( $piece, $context ) {
|
||||
if ( empty( $context->blocks['yoast-seo/table-of-contents'] ) ) {
|
||||
return $piece;
|
||||
}
|
||||
|
||||
if ( isset( $piece['accessibilityFeature'] ) ) {
|
||||
$piece['accessibilityFeature'][] = 'tableOfContents';
|
||||
}
|
||||
else {
|
||||
$piece['accessibilityFeature'] = [
|
||||
'tableOfContents',
|
||||
];
|
||||
}
|
||||
return $piece;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WP_Robots_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Presenters\Robots_Presenter;
|
||||
|
||||
/**
|
||||
* Class WP_Robots_Integration
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Front_End
|
||||
*/
|
||||
class WP_Robots_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The meta tags context memoizer.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
protected $context_memoizer;
|
||||
|
||||
/**
|
||||
* Sets the dependencies for this integration.
|
||||
*
|
||||
* @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
|
||||
*/
|
||||
public function __construct( Meta_Tags_Context_Memoizer $context_memoizer ) {
|
||||
$this->context_memoizer = $context_memoizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
/**
|
||||
* Allow control of the `wp_robots` filter by prioritizing our hook 10 less than max.
|
||||
* Use the `wpseo_robots` filter to filter the Yoast robots output, instead of WordPress core.
|
||||
*/
|
||||
\add_filter( 'wp_robots', [ $this, 'add_robots' ], ( \PHP_INT_MAX - 10 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Front_End_Conditional::class,
|
||||
WP_Robots_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds our robots tag value to the WordPress robots tag output.
|
||||
*
|
||||
* @param array $robots The current robots data.
|
||||
*
|
||||
* @return array The robots data.
|
||||
*/
|
||||
public function add_robots( $robots ) {
|
||||
if ( ! \is_array( $robots ) ) {
|
||||
return $this->get_robots_value();
|
||||
}
|
||||
|
||||
$merged_robots = \array_merge( $robots, $this->get_robots_value() );
|
||||
$filtered_robots = $this->enforce_robots_congruence( $merged_robots );
|
||||
$sorted_robots = $this->sort_robots( $filtered_robots );
|
||||
|
||||
// Filter all falsy-null robot values.
|
||||
return \array_filter( $sorted_robots );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the robots key-value pairs.
|
||||
*
|
||||
* @return array The robots key-value pairs.
|
||||
*/
|
||||
protected function get_robots_value() {
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
|
||||
$robots_presenter = new Robots_Presenter();
|
||||
$robots_presenter->presentation = $context->presentation;
|
||||
return $this->format_robots( $robots_presenter->get() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats our robots fields, to match the pattern WordPress is using.
|
||||
*
|
||||
* Our format: `[ 'index' => 'noindex', 'max-image-preview' => 'max-image-preview:large', ... ]`
|
||||
* WordPress format: `[ 'noindex' => true, 'max-image-preview' => 'large', ... ]`
|
||||
*
|
||||
* @param array $robots Our robots value.
|
||||
*
|
||||
* @return array The formatted robots.
|
||||
*/
|
||||
protected function format_robots( $robots ) {
|
||||
foreach ( $robots as $key => $value ) {
|
||||
// When the entry represents for example: max-image-preview:large.
|
||||
$colon_position = \strpos( $value, ':' );
|
||||
if ( $colon_position !== false ) {
|
||||
$robots[ $key ] = \substr( $value, ( $colon_position + 1 ) );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// When index => noindex, we want a separate noindex as entry in array.
|
||||
if ( \strpos( $value, 'no' ) === 0 ) {
|
||||
$robots[ $key ] = false;
|
||||
$robots[ $value ] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// When the key is equal to the value, just make its value a boolean.
|
||||
if ( $key === $value ) {
|
||||
$robots[ $key ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures all other possible robots values are congruent with nofollow and or noindex.
|
||||
*
|
||||
* WordPress might add some robot values again.
|
||||
* When the page is set to noindex we want to filter out these values.
|
||||
*
|
||||
* @param array $robots The robots.
|
||||
*
|
||||
* @return array The filtered robots.
|
||||
*/
|
||||
protected function enforce_robots_congruence( $robots ) {
|
||||
if ( ! empty( $robots['nofollow'] ) ) {
|
||||
$robots['follow'] = null;
|
||||
}
|
||||
if ( ! empty( $robots['noarchive'] ) ) {
|
||||
$robots['archive'] = null;
|
||||
}
|
||||
if ( ! empty( $robots['noimageindex'] ) ) {
|
||||
$robots['imageindex'] = null;
|
||||
|
||||
// `max-image-preview` should set be to `none` when `noimageindex` is present.
|
||||
// Using `isset` rather than `! empty` here so that in the rare case of `max-image-preview`
|
||||
// being equal to an empty string due to filtering, its value would still be set to `none`.
|
||||
if ( isset( $robots['max-image-preview'] ) ) {
|
||||
$robots['max-image-preview'] = 'none';
|
||||
}
|
||||
}
|
||||
if ( ! empty( $robots['nosnippet'] ) ) {
|
||||
$robots['snippet'] = null;
|
||||
}
|
||||
if ( ! empty( $robots['noindex'] ) ) {
|
||||
$robots['index'] = null;
|
||||
$robots['imageindex'] = null;
|
||||
$robots['noimageindex'] = null;
|
||||
$robots['archive'] = null;
|
||||
$robots['noarchive'] = null;
|
||||
$robots['snippet'] = null;
|
||||
$robots['nosnippet'] = null;
|
||||
$robots['max-snippet'] = null;
|
||||
$robots['max-image-preview'] = null;
|
||||
$robots['max-video-preview'] = null;
|
||||
}
|
||||
|
||||
return $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the robots array.
|
||||
*
|
||||
* @param array $robots The robots array.
|
||||
*
|
||||
* @return array The sorted robots array.
|
||||
*/
|
||||
protected function sort_robots( $robots ) {
|
||||
\uksort(
|
||||
$robots,
|
||||
static function ( $a, $b ) {
|
||||
$order = [
|
||||
'index' => 0,
|
||||
'noindex' => 1,
|
||||
'follow' => 2,
|
||||
'nofollow' => 3,
|
||||
];
|
||||
$ai = ( $order[ $a ] ?? 4 );
|
||||
$bi = ( $order[ $b ] ?? 4 );
|
||||
|
||||
return ( $ai - $bi );
|
||||
}
|
||||
);
|
||||
|
||||
return $robots;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Loadable_Interface;
|
||||
|
||||
/**
|
||||
* An interface for registering integrations with WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore It represents an interface.
|
||||
*/
|
||||
interface Integration_Interface extends Loadable_Interface {
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use stdClass;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
use WPSEO_Primary_Term;
|
||||
use Yoast\WP\SEO\Conditionals\Primary_Category_Conditional;
|
||||
|
||||
/**
|
||||
* Adds customizations to the front end for the primary category.
|
||||
*/
|
||||
class Primary_Category implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* In this case only when on the frontend, the post overview, post edit or new post admin page.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Primary_Category_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a filter to change a post's primary category.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'post_link_category', [ $this, 'post_link_category' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters post_link_category to change the category to the chosen category by the user.
|
||||
*
|
||||
* @param stdClass $category The category that is now used for the post link.
|
||||
* @param array|null $categories This parameter is not used.
|
||||
* @param WP_Post|null $post The post in question.
|
||||
*
|
||||
* @return array|object|WP_Error|null The category we want to use for the post link.
|
||||
*/
|
||||
public function post_link_category( $category, $categories = null, $post = null ) {
|
||||
$post = \get_post( $post );
|
||||
if ( $post === null ) {
|
||||
return $category;
|
||||
}
|
||||
|
||||
$primary_category = $this->get_primary_category( $post );
|
||||
if ( $primary_category !== false && $primary_category !== $category->cat_ID ) {
|
||||
$category = \get_category( $primary_category );
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the primary category.
|
||||
*
|
||||
* @codeCoverageIgnore It justs wraps a dependency.
|
||||
*
|
||||
* @param WP_Post $post The post in question.
|
||||
*
|
||||
* @return int Primary category id.
|
||||
*/
|
||||
protected function get_primary_category( $post ) {
|
||||
$primary_term = new WPSEO_Primary_Term( 'category', $post->ID );
|
||||
|
||||
return $primary_term->get_primary_term();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,997 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WP_Post;
|
||||
use WP_Post_Type;
|
||||
use WP_Taxonomy;
|
||||
use WP_User;
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Admin_Editor_Specific_Replace_Vars;
|
||||
use WPSEO_Admin_Recommended_Replace_Vars;
|
||||
use WPSEO_Option_Titles;
|
||||
use WPSEO_Options;
|
||||
use WPSEO_Replace_Vars;
|
||||
use WPSEO_Shortlinker;
|
||||
use WPSEO_Sitemaps_Router;
|
||||
use Yoast\WP\SEO\Conditionals\Settings_Conditional;
|
||||
use Yoast\WP\SEO\Config\Schema_Types;
|
||||
use Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Language_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Schema\Article_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
|
||||
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
|
||||
|
||||
/**
|
||||
* Class Settings_Integration.
|
||||
*/
|
||||
class Settings_Integration implements Integration_Interface {
|
||||
|
||||
public const PAGE = 'wpseo_page_settings';
|
||||
|
||||
/**
|
||||
* Holds the included WordPress options.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public const WP_OPTIONS = [ 'blogdescription' ];
|
||||
|
||||
/**
|
||||
* Holds the allowed option groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const ALLOWED_OPTION_GROUPS = [ 'wpseo', 'wpseo_titles', 'wpseo_social' ];
|
||||
|
||||
/**
|
||||
* Holds the disallowed settings, per option group.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const DISALLOWED_SETTINGS = [
|
||||
'wpseo' => [
|
||||
'myyoast-oauth',
|
||||
'semrush_tokens',
|
||||
'custom_taxonomy_slugs',
|
||||
'import_cursors',
|
||||
'workouts_data',
|
||||
'configuration_finished_steps',
|
||||
'importing_completed',
|
||||
'wincher_tokens',
|
||||
'least_readability_ignore_list',
|
||||
'least_seo_score_ignore_list',
|
||||
'most_linked_ignore_list',
|
||||
'least_linked_ignore_list',
|
||||
'indexables_page_reading_list',
|
||||
'show_new_content_type_notification',
|
||||
'new_post_types',
|
||||
'new_taxonomies',
|
||||
],
|
||||
'wpseo_titles' => [
|
||||
'company_logo_meta',
|
||||
'person_logo_meta',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the disabled on multisite settings, per option group.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const DISABLED_ON_MULTISITE_SETTINGS = [
|
||||
'wpseo' => [
|
||||
'deny_search_crawling',
|
||||
'deny_wp_json_crawling',
|
||||
'deny_adsbot_crawling',
|
||||
'deny_ccbot_crawling',
|
||||
'deny_google_extended_crawling',
|
||||
'deny_gptbot_crawling',
|
||||
'enable_llms_txt',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Admin_Asset_Manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Replace_Vars.
|
||||
*
|
||||
* @var WPSEO_Replace_Vars
|
||||
*/
|
||||
protected $replace_vars;
|
||||
|
||||
/**
|
||||
* Holds the Schema_Types.
|
||||
*
|
||||
* @var Schema_Types
|
||||
*/
|
||||
protected $schema_types;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the Post_Type_Helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* Holds the Language_Helper.
|
||||
*
|
||||
* @var Language_Helper
|
||||
*/
|
||||
protected $language_helper;
|
||||
|
||||
/**
|
||||
* Holds the Taxonomy_Helper.
|
||||
*
|
||||
* @var Taxonomy_Helper
|
||||
*/
|
||||
protected $taxonomy_helper;
|
||||
|
||||
/**
|
||||
* Holds the Product_Helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
protected $product_helper;
|
||||
|
||||
/**
|
||||
* Holds the Woocommerce_Helper.
|
||||
*
|
||||
* @var Woocommerce_Helper
|
||||
*/
|
||||
protected $woocommerce_helper;
|
||||
|
||||
/**
|
||||
* Holds the Article_Helper.
|
||||
*
|
||||
* @var Article_Helper
|
||||
*/
|
||||
protected $article_helper;
|
||||
|
||||
/**
|
||||
* Holds the User_Helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
protected $user_helper;
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Holds the Content_Type_Visibility_Dismiss_Notifications instance.
|
||||
*
|
||||
* @var Content_Type_Visibility_Dismiss_Notifications
|
||||
*/
|
||||
protected $content_type_visibility;
|
||||
|
||||
/**
|
||||
* Constructs Settings_Integration.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
|
||||
* @param WPSEO_Replace_Vars $replace_vars The WPSEO_Replace_Vars.
|
||||
* @param Schema_Types $schema_types The Schema_Types.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
* @param Post_Type_Helper $post_type_helper The Post_Type_Helper.
|
||||
* @param Language_Helper $language_helper The Language_Helper.
|
||||
* @param Taxonomy_Helper $taxonomy_helper The Taxonomy_Helper.
|
||||
* @param Product_Helper $product_helper The Product_Helper.
|
||||
* @param Woocommerce_Helper $woocommerce_helper The Woocommerce_Helper.
|
||||
* @param Article_Helper $article_helper The Article_Helper.
|
||||
* @param User_Helper $user_helper The User_Helper.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Content_Type_Visibility_Dismiss_Notifications $content_type_visibility The Content_Type_Visibility_Dismiss_Notifications instance.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
WPSEO_Replace_Vars $replace_vars,
|
||||
Schema_Types $schema_types,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Language_Helper $language_helper,
|
||||
Taxonomy_Helper $taxonomy_helper,
|
||||
Product_Helper $product_helper,
|
||||
Woocommerce_Helper $woocommerce_helper,
|
||||
Article_Helper $article_helper,
|
||||
User_Helper $user_helper,
|
||||
Options_Helper $options,
|
||||
Content_Type_Visibility_Dismiss_Notifications $content_type_visibility
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->replace_vars = $replace_vars;
|
||||
$this->schema_types = $schema_types;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->taxonomy_helper = $taxonomy_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->language_helper = $language_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->woocommerce_helper = $woocommerce_helper;
|
||||
$this->article_helper = $article_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->options = $options;
|
||||
$this->content_type_visibility = $content_type_visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Settings_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Add page.
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
|
||||
\add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
|
||||
|
||||
// Are we saving the settings?
|
||||
if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
|
||||
$post_action = '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
|
||||
if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
|
||||
$post_action = \wp_unslash( $_POST['action'] );
|
||||
}
|
||||
$option_page = '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
|
||||
if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
|
||||
$option_page = \wp_unslash( $_POST['option_page'] );
|
||||
}
|
||||
|
||||
if ( $post_action === 'update' && $option_page === self::PAGE ) {
|
||||
\add_action( 'admin_init', [ $this, 'register_setting' ] );
|
||||
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Are we on the settings page?
|
||||
if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
|
||||
\add_action( 'admin_init', [ $this, 'register_setting' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the different options under the setting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_setting() {
|
||||
foreach ( WPSEO_Options::$options as $name => $instance ) {
|
||||
if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
|
||||
\register_setting( self::PAGE, $name );
|
||||
}
|
||||
}
|
||||
// Only register WP options when the user is allowed to manage them.
|
||||
if ( \current_user_can( 'manage_options' ) ) {
|
||||
foreach ( self::WP_OPTIONS as $name ) {
|
||||
\register_setting( self::PAGE, $name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the page.
|
||||
*
|
||||
* @param array $pages The pages.
|
||||
*
|
||||
* @return array The pages.
|
||||
*/
|
||||
public function add_page( $pages ) {
|
||||
\array_splice(
|
||||
$pages,
|
||||
1,
|
||||
0,
|
||||
[
|
||||
[
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Settings', 'wordpress-seo' ),
|
||||
'wpseo_manage_options',
|
||||
self::PAGE,
|
||||
[ $this, 'display_page' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dummy page.
|
||||
*
|
||||
* Because the options route NEEDS to redirect to something.
|
||||
*
|
||||
* @param array $pages The pages.
|
||||
*
|
||||
* @return array The pages.
|
||||
*/
|
||||
public function add_settings_saved_page( $pages ) {
|
||||
\add_submenu_page(
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'wpseo_manage_options',
|
||||
self::PAGE . '_saved',
|
||||
static function () {
|
||||
// Add success indication to HTML response.
|
||||
$success = empty( \get_settings_errors() ) ? 'true' : 'false';
|
||||
echo \esc_html( "{{ yoast-success: $success }}" );
|
||||
}
|
||||
);
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_page() {
|
||||
echo '<div id="yoast-seo-settings"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// Remove the emoji script as it is incompatible with both React and any contenteditable fields.
|
||||
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
\wp_enqueue_media();
|
||||
$this->asset_manager->enqueue_script( 'new-settings' );
|
||||
$this->asset_manager->enqueue_style( 'new-settings' );
|
||||
if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) {
|
||||
$this->asset_manager->enqueue_style( 'black-friday-banner' );
|
||||
}
|
||||
$this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all current WP notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notices() {
|
||||
\remove_all_actions( 'admin_notices' );
|
||||
\remove_all_actions( 'user_admin_notices' );
|
||||
\remove_all_actions( 'network_admin_notices' );
|
||||
\remove_all_actions( 'all_admin_notices' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the script data.
|
||||
*
|
||||
* @return array The script data.
|
||||
*/
|
||||
protected function get_script_data() {
|
||||
$default_setting_values = $this->get_default_setting_values();
|
||||
$settings = $this->get_settings( $default_setting_values );
|
||||
$post_types = $this->post_type_helper->get_indexable_post_type_objects();
|
||||
$taxonomies = $this->taxonomy_helper->get_indexable_taxonomy_objects();
|
||||
|
||||
// Check if attachments are included in indexation.
|
||||
if ( ! \array_key_exists( 'attachment', $post_types ) ) {
|
||||
// Always include attachments in the settings, to let the user enable them again.
|
||||
$attachment_object = \get_post_type_object( 'attachment' );
|
||||
if ( ! empty( $attachment_object ) ) {
|
||||
$post_types['attachment'] = $attachment_object;
|
||||
}
|
||||
}
|
||||
// Check if post formats are included in indexation.
|
||||
if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
|
||||
// Always include post_format in the settings, to let the user enable them again.
|
||||
$post_format_object = \get_taxonomy( 'post_format' );
|
||||
if ( ! empty( $post_format_object ) ) {
|
||||
$taxonomies['post_format'] = $post_format_object;
|
||||
}
|
||||
}
|
||||
|
||||
$transformed_post_types = $this->transform_post_types( $post_types );
|
||||
$transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
|
||||
|
||||
// Check if there is a new content type to show notification only once in the settings.
|
||||
$show_new_content_type_notification = $this->content_type_visibility->maybe_add_settings_notification();
|
||||
|
||||
return [
|
||||
'settings' => $this->transform_settings( $settings ),
|
||||
'defaultSettingValues' => $default_setting_values,
|
||||
'disabledSettings' => $this->get_disabled_settings( $settings ),
|
||||
'endpoint' => \admin_url( 'options.php' ),
|
||||
'nonce' => \wp_create_nonce( self::PAGE . '-options' ),
|
||||
'separators' => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
|
||||
'replacementVariables' => $this->get_replacement_variables(),
|
||||
'schema' => $this->get_schema( $transformed_post_types ),
|
||||
'preferences' => $this->get_preferences( $settings ),
|
||||
'linkParams' => WPSEO_Shortlinker::get_query_params(),
|
||||
'postTypes' => $transformed_post_types,
|
||||
'taxonomies' => $transformed_taxonomies,
|
||||
'fallbacks' => $this->get_fallbacks(),
|
||||
'showNewContentTypeNotification' => $show_new_content_type_notification,
|
||||
'currentPromotions' => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the preferences.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
*
|
||||
* @return array The preferences.
|
||||
*/
|
||||
protected function get_preferences( $settings ) {
|
||||
$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
|
||||
$homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
|
||||
$page_on_front = \get_option( 'page_on_front' );
|
||||
$page_for_posts = \get_option( 'page_for_posts' );
|
||||
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
$woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) );
|
||||
|
||||
if ( empty( $page_on_front ) ) {
|
||||
$page_on_front = $page_for_posts;
|
||||
}
|
||||
|
||||
$business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
|
||||
if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
|
||||
$local_options = \get_option( 'wpseo_local' );
|
||||
$multiple_locations = $local_options['use_multiple_locations'];
|
||||
$same_organization = $local_options['multiple_locations_same_organization'];
|
||||
if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
|
||||
$business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'isPremium' => $this->product_helper->is_premium(),
|
||||
'isRtl' => \is_rtl(),
|
||||
'isNetworkAdmin' => \is_network_admin(),
|
||||
'isMainSite' => \is_main_site(),
|
||||
'isMultisite' => \is_multisite(),
|
||||
'isWooCommerceActive' => $this->woocommerce_helper->is_active(),
|
||||
'isLocalSeoActive' => \defined( 'WPSEO_LOCAL_FILE' ),
|
||||
'isNewsSeoActive' => \defined( 'WPSEO_NEWS_FILE' ),
|
||||
'isWooCommerceSEOActive' => $woocommerce_seo_active,
|
||||
'siteUrl' => \get_bloginfo( 'url' ),
|
||||
'siteTitle' => \get_bloginfo( 'name' ),
|
||||
'sitemapUrl' => WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ),
|
||||
'hasWooCommerceShopPage' => $shop_page_id !== -1,
|
||||
'editWooCommerceShopPageUrl' => \get_edit_post_link( $shop_page_id, 'js' ),
|
||||
'wooCommerceShopPageSettingUrl' => \get_admin_url( null, 'admin.php?page=wc-settings&tab=products' ),
|
||||
'localSeoPageSettingUrl' => $business_settings_url,
|
||||
'homepageIsLatestPosts' => $homepage_is_latest_posts,
|
||||
'homepagePageEditUrl' => \get_edit_post_link( $page_on_front, 'js' ),
|
||||
'homepagePostsEditUrl' => \get_edit_post_link( $page_for_posts, 'js' ),
|
||||
'createUserUrl' => \admin_url( 'user-new.php' ),
|
||||
'createPageUrl' => \admin_url( 'post-new.php?post_type=page' ),
|
||||
'editUserUrl' => \admin_url( 'user-edit.php' ),
|
||||
'editTaxonomyUrl' => \admin_url( 'edit-tags.php' ),
|
||||
'generalSettingsUrl' => \admin_url( 'options-general.php' ),
|
||||
'companyOrPersonMessage' => \apply_filters( 'wpseo_knowledge_graph_setting_msg', '' ),
|
||||
'currentUserId' => \get_current_user_id(),
|
||||
'canCreateUsers' => \current_user_can( 'create_users' ),
|
||||
'canCreatePages' => \current_user_can( 'edit_pages' ),
|
||||
'canEditUsers' => \current_user_can( 'edit_users' ),
|
||||
'canManageOptions' => \current_user_can( 'manage_options' ),
|
||||
'userLocale' => \str_replace( '_', '-', \get_user_locale() ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_FILE ),
|
||||
'showForceRewriteTitlesSetting' => ! \current_theme_supports( 'title-tag' ) && ! ( \function_exists( 'wp_is_block_theme' ) && \wp_is_block_theme() ),
|
||||
'upsellSettings' => $this->get_upsell_settings(),
|
||||
'siteRepresentsPerson' => $this->get_site_represents_person( $settings ),
|
||||
'siteBasicsPolicies' => $this->get_site_basics_policies( $settings ),
|
||||
'llmsTxtUrl' => \home_url( 'llms.txt' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the currently represented person.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
*
|
||||
* @return array The currently represented person's ID and name.
|
||||
*/
|
||||
protected function get_site_represents_person( $settings ) {
|
||||
$person = [
|
||||
'id' => false,
|
||||
'name' => '',
|
||||
];
|
||||
|
||||
if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) {
|
||||
$person['id'] = $settings['wpseo_titles']['company_or_person_user_id'];
|
||||
$user = \get_userdata( $person['id'] );
|
||||
if ( $user instanceof WP_User ) {
|
||||
$person['name'] = $user->get( 'display_name' );
|
||||
}
|
||||
}
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site policy data.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
*
|
||||
* @return array The policy data.
|
||||
*/
|
||||
private function get_site_basics_policies( $settings ) {
|
||||
$policies = [];
|
||||
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' );
|
||||
$policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' );
|
||||
|
||||
return $policies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds policy data if it is present.
|
||||
*
|
||||
* @param array $policies The existing policy data.
|
||||
* @param int $policy The policy id to check.
|
||||
* @param string $key The option key name.
|
||||
*
|
||||
* @return array<int, string> The policy data.
|
||||
*/
|
||||
private function maybe_add_policy( $policies, $policy, $key ) {
|
||||
$policy_array = [
|
||||
'id' => 0,
|
||||
'name' => \__( 'None', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
if ( isset( $policy ) && \is_int( $policy ) ) {
|
||||
$policy_array['id'] = $policy;
|
||||
$post = \get_post( $policy );
|
||||
if ( $post instanceof WP_Post ) {
|
||||
if ( $post->post_status !== 'publish' || $post->post_password !== '' ) {
|
||||
return $policies;
|
||||
}
|
||||
$policy_array['name'] = $post->post_title;
|
||||
}
|
||||
}
|
||||
|
||||
$policies[ $key ] = $policy_array;
|
||||
|
||||
return $policies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns settings for the Call to Buy (CTB) buttons.
|
||||
*
|
||||
* @return array<string> The array of CTB settings.
|
||||
*/
|
||||
public function get_upsell_settings() {
|
||||
return [
|
||||
'actionId' => 'load-nfd-ctb',
|
||||
'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default setting values.
|
||||
*
|
||||
* These default values are currently being used in the UI for dummy fields.
|
||||
* Dummy fields should not expose or reflect the actual data.
|
||||
*
|
||||
* @return array The default setting values.
|
||||
*/
|
||||
protected function get_default_setting_values() {
|
||||
$defaults = [];
|
||||
|
||||
// Add Yoast settings.
|
||||
foreach ( WPSEO_Options::$options as $option_name => $instance ) {
|
||||
if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
|
||||
$option_instance = WPSEO_Options::get_option_instance( $option_name );
|
||||
$defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : [];
|
||||
}
|
||||
}
|
||||
// Add WP settings.
|
||||
foreach ( self::WP_OPTIONS as $option_name ) {
|
||||
$defaults[ $option_name ] = '';
|
||||
}
|
||||
|
||||
// Remove disallowed settings.
|
||||
foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
|
||||
foreach ( $disallowed_settings as $disallowed_setting ) {
|
||||
unset( $defaults[ $option_name ][ $disallowed_setting ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
|
||||
$defaults = $this->get_defaults_from_local_seo( $defaults );
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the organization schema values from Local SEO for defaults in Site representation fields.
|
||||
* Specifically for the org-vat-id, org-tax-id, org-email and org-phone options.
|
||||
*
|
||||
* @param array<string|int|bool> $defaults The settings defaults.
|
||||
*
|
||||
* @return array<string|int|bool> The settings defaults with local seo overides.
|
||||
*/
|
||||
protected function get_defaults_from_local_seo( $defaults ) {
|
||||
$local_options = \get_option( 'wpseo_local' );
|
||||
$multiple_locations = $local_options['use_multiple_locations'];
|
||||
$same_organization = $local_options['multiple_locations_same_organization'];
|
||||
$shared_info = $local_options['multiple_locations_shared_business_info'];
|
||||
if ( $multiple_locations !== 'on' || ( $multiple_locations === 'on' && $same_organization === 'on' && $shared_info === 'on' ) ) {
|
||||
$defaults['wpseo_titles']['org-vat-id'] = $local_options['location_vat_id'];
|
||||
$defaults['wpseo_titles']['org-tax-id'] = $local_options['location_tax_id'];
|
||||
$defaults['wpseo_titles']['org-email'] = $local_options['location_email'];
|
||||
$defaults['wpseo_titles']['org-phone'] = $local_options['location_phone'];
|
||||
}
|
||||
|
||||
if ( \wpseo_has_primary_location() ) {
|
||||
$primary_location = $local_options['multiple_locations_primary_location'];
|
||||
|
||||
$location_keys = [
|
||||
'org-phone' => [
|
||||
'is_overridden' => '_wpseo_is_overridden_business_phone',
|
||||
'value' => '_wpseo_business_phone',
|
||||
],
|
||||
'org-email' => [
|
||||
'is_overridden' => '_wpseo_is_overridden_business_email',
|
||||
'value' => '_wpseo_business_email',
|
||||
],
|
||||
'org-tax-id' => [
|
||||
'is_overridden' => '_wpseo_is_overridden_business_tax_id',
|
||||
'value' => '_wpseo_business_tax_id',
|
||||
],
|
||||
'org-vat-id' => [
|
||||
'is_overridden' => '_wpseo_is_overridden_business_vat_id',
|
||||
'value' => '_wpseo_business_vat_id',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $location_keys as $key => $meta_keys ) {
|
||||
$is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false;
|
||||
if ( $is_overridden === 'on' || $shared_info !== 'on' ) {
|
||||
$post_meta_value = \get_post_meta( $primary_location, $meta_keys['value'], true );
|
||||
$defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the settings and their values.
|
||||
*
|
||||
* @param array $default_setting_values The default setting values.
|
||||
*
|
||||
* @return array The settings.
|
||||
*/
|
||||
protected function get_settings( $default_setting_values ) {
|
||||
$settings = [];
|
||||
|
||||
// Add Yoast settings.
|
||||
foreach ( WPSEO_Options::$options as $option_name => $instance ) {
|
||||
if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
|
||||
$settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) );
|
||||
}
|
||||
}
|
||||
// Add WP settings.
|
||||
foreach ( self::WP_OPTIONS as $option_name ) {
|
||||
$settings[ $option_name ] = \get_option( $option_name );
|
||||
}
|
||||
|
||||
// Remove disallowed settings.
|
||||
foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
|
||||
foreach ( $disallowed_settings as $disallowed_setting ) {
|
||||
unset( $settings[ $option_name ][ $disallowed_setting ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms setting values.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
*
|
||||
* @return array The settings.
|
||||
*/
|
||||
protected function transform_settings( $settings ) {
|
||||
if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) {
|
||||
/**
|
||||
* The breadcrumbs separator default value is the HTML entity `»`.
|
||||
* Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception.
|
||||
*/
|
||||
$settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode(
|
||||
$settings['wpseo_titles']['breadcrumbs-sep'],
|
||||
( \ENT_NOQUOTES | \ENT_HTML5 ),
|
||||
'UTF-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode some WP options.
|
||||
*/
|
||||
$settings['blogdescription'] = \html_entity_decode(
|
||||
$settings['blogdescription'],
|
||||
( \ENT_NOQUOTES | \ENT_HTML5 ),
|
||||
'UTF-8'
|
||||
);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the disabled settings.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
*
|
||||
* @return array The settings.
|
||||
*/
|
||||
protected function get_disabled_settings( $settings ) {
|
||||
$disabled_settings = [];
|
||||
$site_language = $this->language_helper->get_language();
|
||||
|
||||
foreach ( WPSEO_Options::$options as $option_name => $instance ) {
|
||||
if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$disabled_settings[ $option_name ] = [];
|
||||
$option_instance = WPSEO_Options::get_option_instance( $option_name );
|
||||
if ( $option_instance === false ) {
|
||||
continue;
|
||||
}
|
||||
foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) {
|
||||
if ( $option_instance->is_disabled( $setting_name ) ) {
|
||||
$disabled_settings[ $option_name ][ $setting_name ] = 'network';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove disabled on multisite settings.
|
||||
if ( \is_multisite() ) {
|
||||
foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) {
|
||||
if ( \array_key_exists( $option_name, $disabled_settings ) ) {
|
||||
foreach ( $disabled_ms_settings as $disabled_ms_setting ) {
|
||||
$disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) {
|
||||
$disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language';
|
||||
}
|
||||
|
||||
return $disabled_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the replacement variables.
|
||||
*
|
||||
* @return array The replacement variables.
|
||||
*/
|
||||
protected function get_replacement_variables() {
|
||||
$recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
|
||||
$specific_replace_vars = new WPSEO_Admin_Editor_Specific_Replace_Vars();
|
||||
$replacement_variables = $this->replace_vars->get_replacement_variables_with_labels();
|
||||
|
||||
return [
|
||||
'variables' => $replacement_variables,
|
||||
'recommended' => $recommended_replace_vars->get_recommended_replacevars(),
|
||||
'specific' => $specific_replace_vars->get(),
|
||||
'shared' => $specific_replace_vars->get_generic( $replacement_variables ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the schema.
|
||||
*
|
||||
* @param array $post_types The post types.
|
||||
*
|
||||
* @return array The schema.
|
||||
*/
|
||||
protected function get_schema( array $post_types ) {
|
||||
$schema = [];
|
||||
|
||||
foreach ( $this->schema_types->get_article_type_options() as $article_type ) {
|
||||
$schema['articleTypes'][ $article_type['value'] ] = [
|
||||
'label' => $article_type['name'],
|
||||
'value' => $article_type['value'],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ( $this->schema_types->get_page_type_options() as $page_type ) {
|
||||
$schema['pageTypes'][ $page_type['value'] ] = [
|
||||
'label' => $page_type['name'],
|
||||
'value' => $page_type['value'],
|
||||
];
|
||||
}
|
||||
|
||||
$schema['articleTypeDefaults'] = [];
|
||||
$schema['pageTypeDefaults'] = [];
|
||||
foreach ( $post_types as $name => $post_type ) {
|
||||
$schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" );
|
||||
$schema['pageTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the post types, to represent them.
|
||||
*
|
||||
* @param WP_Post_Type[] $post_types The WP_Post_Type array to transform.
|
||||
*
|
||||
* @return array The post types.
|
||||
*/
|
||||
protected function transform_post_types( $post_types ) {
|
||||
$transformed = [];
|
||||
$new_post_types = $this->options->get( 'new_post_types', [] );
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$transformed[ $post_type->name ] = [
|
||||
'name' => $post_type->name,
|
||||
'route' => $this->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ),
|
||||
'label' => $post_type->label,
|
||||
'singularLabel' => $post_type->labels->singular_name,
|
||||
'hasArchive' => $this->post_type_helper->has_archive( $post_type ),
|
||||
'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ),
|
||||
'menuPosition' => $post_type->menu_position,
|
||||
'isNew' => \in_array( $post_type->name, $new_post_types, true ),
|
||||
];
|
||||
}
|
||||
|
||||
\uasort( $transformed, [ $this, 'compare_post_types' ] );
|
||||
|
||||
return $transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two post types.
|
||||
*
|
||||
* @param array $a The first post type.
|
||||
* @param array $b The second post type.
|
||||
*
|
||||
* @return int The order.
|
||||
*/
|
||||
protected function compare_post_types( $a, $b ) {
|
||||
if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) {
|
||||
return 1;
|
||||
}
|
||||
if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) {
|
||||
// No position specified, order alphabetically by label.
|
||||
return \strnatcmp( $a['label'], $b['label'] );
|
||||
}
|
||||
|
||||
return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the taxonomies, to represent them.
|
||||
*
|
||||
* @param WP_Taxonomy[] $taxonomies The WP_Taxonomy array to transform.
|
||||
* @param string[] $post_type_names The post type names.
|
||||
*
|
||||
* @return array The taxonomies.
|
||||
*/
|
||||
protected function transform_taxonomies( $taxonomies, $post_type_names ) {
|
||||
$transformed = [];
|
||||
$new_taxonomies = $this->options->get( 'new_taxonomies', [] );
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$transformed[ $taxonomy->name ] = [
|
||||
'name' => $taxonomy->name,
|
||||
'route' => $this->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ),
|
||||
'label' => $taxonomy->label,
|
||||
'showUi' => $taxonomy->show_ui,
|
||||
'singularLabel' => $taxonomy->labels->singular_name,
|
||||
'postTypes' => \array_filter(
|
||||
$taxonomy->object_type,
|
||||
static function ( $object_type ) use ( $post_type_names ) {
|
||||
return \in_array( $object_type, $post_type_names, true );
|
||||
}
|
||||
),
|
||||
'isNew' => \in_array( $taxonomy->name, $new_taxonomies, true ),
|
||||
];
|
||||
}
|
||||
|
||||
\uasort(
|
||||
$transformed,
|
||||
static function ( $a, $b ) {
|
||||
return \strnatcmp( $a['label'], $b['label'] );
|
||||
}
|
||||
);
|
||||
|
||||
return $transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route from a name, rewrite and rest_base.
|
||||
*
|
||||
* @param string $name The name.
|
||||
* @param array $rewrite The rewrite data.
|
||||
* @param string $rest_base The rest base.
|
||||
*
|
||||
* @return string The route.
|
||||
*/
|
||||
protected function get_route( $name, $rewrite, $rest_base ) {
|
||||
$route = $name;
|
||||
if ( isset( $rewrite['slug'] ) ) {
|
||||
$route = $rewrite['slug'];
|
||||
}
|
||||
if ( ! empty( $rest_base ) ) {
|
||||
$route = $rest_base;
|
||||
}
|
||||
// Always strip leading slashes.
|
||||
while ( \substr( $route, 0, 1 ) === '/' ) {
|
||||
$route = \substr( $route, 1 );
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fallbacks.
|
||||
*
|
||||
* @return array The fallbacks.
|
||||
*/
|
||||
protected function get_fallbacks() {
|
||||
$site_logo_id = \get_option( 'site_logo' );
|
||||
if ( ! $site_logo_id ) {
|
||||
$site_logo_id = \get_theme_mod( 'custom_logo' );
|
||||
}
|
||||
if ( ! $site_logo_id ) {
|
||||
$site_logo_id = '0';
|
||||
}
|
||||
|
||||
return [
|
||||
'siteLogoId' => $site_logo_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Product_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
|
||||
|
||||
/**
|
||||
* Class Support_Integration.
|
||||
*/
|
||||
class Support_Integration implements Integration_Interface {
|
||||
|
||||
public const PAGE = 'wpseo_page_support';
|
||||
|
||||
public const CAPABILITY = 'wpseo_manage_options';
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Admin_Asset_Manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the Product_Helper.
|
||||
*
|
||||
* @var Product_Helper
|
||||
*/
|
||||
private $product_helper;
|
||||
|
||||
/**
|
||||
* Holds the Short_Link_Helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
private $shortlink_helper;
|
||||
|
||||
/**
|
||||
* Constructs Support_Integration.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
* @param Product_Helper $product_helper The Product_Helper.
|
||||
* @param Short_Link_Helper $shortlink_helper The Short_Link_Helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
Product_Helper $product_helper,
|
||||
Short_Link_Helper $shortlink_helper
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->product_helper = $product_helper;
|
||||
$this->shortlink_helper = $shortlink_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class, User_Can_Manage_Wpseo_Options_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Add page.
|
||||
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ], \PHP_INT_MAX );
|
||||
|
||||
// Are we on the settings page?
|
||||
if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the page.
|
||||
*
|
||||
* @param array $pages The pages.
|
||||
*
|
||||
* @return array The pages.
|
||||
*/
|
||||
public function add_page( $pages ) {
|
||||
$pages[] = [
|
||||
'wpseo_dashboard',
|
||||
'',
|
||||
\__( 'Support', 'wordpress-seo' ),
|
||||
self::CAPABILITY,
|
||||
self::PAGE,
|
||||
[ $this, 'display_page' ],
|
||||
];
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_page() {
|
||||
echo '<div id="yoast-seo-support"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// Remove the emoji script as it is incompatible with both React and any contenteditable fields.
|
||||
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
$this->asset_manager->enqueue_script( 'support' );
|
||||
$this->asset_manager->enqueue_style( 'support' );
|
||||
if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) {
|
||||
$this->asset_manager->enqueue_style( 'black-friday-banner' );
|
||||
}
|
||||
$this->asset_manager->localize_script( 'support', 'wpseoScriptData', $this->get_script_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all current WP notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notices() {
|
||||
\remove_all_actions( 'admin_notices' );
|
||||
\remove_all_actions( 'user_admin_notices' );
|
||||
\remove_all_actions( 'network_admin_notices' );
|
||||
\remove_all_actions( 'all_admin_notices' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the script data.
|
||||
*
|
||||
* @return array The script data.
|
||||
*/
|
||||
public function get_script_data() {
|
||||
return [
|
||||
'preferences' => [
|
||||
'isPremium' => $this->product_helper->is_premium(),
|
||||
'isRtl' => \is_rtl(),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_FILE ),
|
||||
'upsellSettings' => [
|
||||
'actionId' => 'load-nfd-ctb',
|
||||
'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
|
||||
],
|
||||
],
|
||||
'linkParams' => $this->shortlink_helper->get_query_params(),
|
||||
'currentPromotions' => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
|
||||
];
|
||||
}
|
||||
}
|
||||
61
wp-content/plugins/wordpress-seo/src/integrations/third-party/amp.php
vendored
Normal file
61
wp-content/plugins/wordpress-seo/src/integrations/third-party/amp.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Front_End_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* AMP integration.
|
||||
*/
|
||||
class AMP implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The front end integration.
|
||||
*
|
||||
* @var Front_End_Integration
|
||||
*/
|
||||
protected $front_end;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the AMP integration
|
||||
*
|
||||
* @param Front_End_Integration $front_end The front end integration.
|
||||
*/
|
||||
public function __construct( Front_End_Integration $front_end ) {
|
||||
$this->front_end = $front_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'amp_post_template_head', [ $this, 'remove_amp_meta_output' ], 0 );
|
||||
\add_action( 'amp_post_template_head', [ $this->front_end, 'call_wpseo_head' ], 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes amp meta output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_amp_meta_output() {
|
||||
\remove_action( 'amp_post_template_head', 'amp_post_template_add_title' );
|
||||
\remove_action( 'amp_post_template_head', 'amp_post_template_add_canonical' );
|
||||
\remove_action( 'amp_post_template_head', 'amp_print_schemaorg_metadata' );
|
||||
}
|
||||
}
|
||||
62
wp-content/plugins/wordpress-seo/src/integrations/third-party/bbpress.php
vendored
Normal file
62
wp-content/plugins/wordpress-seo/src/integrations/third-party/bbpress.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* BbPress integration.
|
||||
*/
|
||||
class BbPress implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* BbPress constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->options->get( 'breadcrumbs-enable' ) !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If breadcrumbs are active (which they supposedly are if the users has enabled this settings,
|
||||
* there's no reason to have bbPress breadcrumbs as well.
|
||||
*
|
||||
* {@internal The class itself is only loaded when the template tag is encountered
|
||||
* via the template tag function in the wpseo-functions.php file.}}
|
||||
*/
|
||||
\add_filter( 'bbp_get_breadcrumb', '__return_false' );
|
||||
}
|
||||
}
|
||||
786
wp-content/plugins/wordpress-seo/src/integrations/third-party/elementor.php
vendored
Normal file
786
wp-content/plugins/wordpress-seo/src/integrations/third-party/elementor.php
vendored
Normal file
@@ -0,0 +1,786 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use WP_Screen;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Admin_Recommended_Replace_Vars;
|
||||
use WPSEO_Meta;
|
||||
use WPSEO_Metabox_Analysis_Inclusive_Language;
|
||||
use WPSEO_Metabox_Analysis_Readability;
|
||||
use WPSEO_Metabox_Analysis_SEO;
|
||||
use WPSEO_Metabox_Formatter;
|
||||
use WPSEO_Post_Metabox_Formatter;
|
||||
use WPSEO_Replace_Vars;
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional;
|
||||
use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository;
|
||||
use Yoast\WP\SEO\Elementor\Infrastructure\Request_Post;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter;
|
||||
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
|
||||
|
||||
/**
|
||||
* Integrates the Yoast SEO metabox in the Elementor editor.
|
||||
*/
|
||||
class Elementor implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the post.
|
||||
*
|
||||
* @var WP_Post|null
|
||||
*/
|
||||
protected $post;
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Represents the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Represents the capability helper.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
protected $capability;
|
||||
|
||||
/**
|
||||
* Holds the Request_Post.
|
||||
*
|
||||
* @var Request_Post
|
||||
*/
|
||||
private $request_post;
|
||||
|
||||
/**
|
||||
* Holds whether the socials are enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $social_is_enabled;
|
||||
|
||||
/**
|
||||
* Holds whether the advanced settings are enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_advanced_metadata_enabled;
|
||||
|
||||
/**
|
||||
* Helper to determine whether or not the SEO analysis is enabled.
|
||||
*
|
||||
* @var WPSEO_Metabox_Analysis_SEO
|
||||
*/
|
||||
protected $seo_analysis;
|
||||
|
||||
/**
|
||||
* Helper to determine whether or not the readability analysis is enabled.
|
||||
*
|
||||
* @var WPSEO_Metabox_Analysis_Readability
|
||||
*/
|
||||
protected $readability_analysis;
|
||||
|
||||
/**
|
||||
* Helper to determine whether or not the inclusive language analysis is enabled.
|
||||
*
|
||||
* @var WPSEO_Metabox_Analysis_Inclusive_Language
|
||||
*/
|
||||
protected $inclusive_language_analysis;
|
||||
|
||||
/**
|
||||
* Holds the promotion manager.
|
||||
*
|
||||
* @var Promotion_Manager
|
||||
*/
|
||||
protected $promotion_manager;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Elementor_Edit_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Capability_Helper $capability The capability helper.
|
||||
* @param Request_Post $request_post The Request_Post.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
Options_Helper $options,
|
||||
Capability_Helper $capability,
|
||||
Request_Post $request_post
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->options = $options;
|
||||
$this->capability = $capability;
|
||||
$this->request_post = $request_post;
|
||||
|
||||
$this->seo_analysis = new WPSEO_Metabox_Analysis_SEO();
|
||||
$this->readability_analysis = new WPSEO_Metabox_Analysis_Readability();
|
||||
$this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language();
|
||||
$this->social_is_enabled = $this->options->get( 'opengraph', false ) || $this->options->get( 'twitter', false );
|
||||
$this->is_advanced_metadata_enabled = $this->capability->current_user_can( 'wpseo_edit_advanced_metadata' ) || $this->options->get( 'disableadvanced_meta' ) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp_ajax_wpseo_elementor_save', [ $this, 'save_postdata' ] );
|
||||
|
||||
// We need to delay the post type lookup to give other plugins a chance to register custom post types.
|
||||
\add_action( 'init', [ $this, 'register_elementor_hooks' ], \PHP_INT_MAX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers our Elementor hooks.
|
||||
* This is done for pages with metabox on page load and not on ajax request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_elementor_hooks() {
|
||||
if ( $this->get_metabox_post() === null || ! $this->display_metabox( $this->get_metabox_post()->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'init' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
$this->asset_manager->register_assets();
|
||||
$this->enqueue();
|
||||
$this->render_hidden_fields();
|
||||
}
|
||||
|
||||
// Below is mostly copied from `class-metabox.php`. That constructor has side-effects we do not need.
|
||||
|
||||
/**
|
||||
* Determines whether the metabox should be shown for the passed identifier.
|
||||
*
|
||||
* By default, the check is done for post types, but can also be used for taxonomies.
|
||||
*
|
||||
* @param string|null $identifier The identifier to check.
|
||||
* @param string $type The type of object to check. Defaults to post_type.
|
||||
*
|
||||
* @return bool Whether the metabox should be displayed.
|
||||
*/
|
||||
public function display_metabox( $identifier = null, $type = 'post_type' ) {
|
||||
return WPSEO_Utils::is_metabox_active( $identifier, $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the WP SEO metadata for posts.
|
||||
*
|
||||
* Outputs JSON via wp_send_json then stops code execution.
|
||||
*
|
||||
* {@internal $_POST parameters are validated via sanitize_post_meta().}}
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_postdata() {
|
||||
global $post;
|
||||
|
||||
if ( ! isset( $_POST['post_id'] ) || ! \is_string( $_POST['post_id'] ) ) {
|
||||
\wp_send_json_error( 'Bad Request', 400 );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: No sanitization needed because we cast to an integer.
|
||||
$post_id = (int) \wp_unslash( $_POST['post_id'] );
|
||||
|
||||
if ( $post_id <= 0 ) {
|
||||
\wp_send_json_error( 'Bad Request', 400 );
|
||||
}
|
||||
|
||||
if ( ! \current_user_can( 'edit_post', $post_id ) ) {
|
||||
\wp_send_json_error( 'Forbidden', 403 );
|
||||
}
|
||||
|
||||
\check_ajax_referer( 'wpseo_elementor_save', '_wpseo_elementor_nonce' );
|
||||
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( \is_multisite() && \ms_is_switched() ) {
|
||||
\wp_send_json_error( 'Switched multisite', 409 );
|
||||
}
|
||||
|
||||
\clean_post_cache( $post_id );
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly.
|
||||
$post = \get_post( $post_id );
|
||||
|
||||
if ( ! \is_object( $post ) ) {
|
||||
// Non-existent post.
|
||||
\wp_send_json_error( 'Post not found', 400 );
|
||||
}
|
||||
|
||||
\do_action( 'wpseo_save_compare_data', $post );
|
||||
|
||||
// Initialize meta, amongst other things it registers sanitization.
|
||||
WPSEO_Meta::init();
|
||||
|
||||
$social_fields = [];
|
||||
if ( $this->social_is_enabled ) {
|
||||
$social_fields = WPSEO_Meta::get_meta_field_defs( 'social', $post->post_type );
|
||||
}
|
||||
|
||||
// The below methods use the global post so make sure it is setup.
|
||||
\setup_postdata( $post );
|
||||
$meta_boxes = \apply_filters( 'wpseo_save_metaboxes', [] );
|
||||
$meta_boxes = \array_merge(
|
||||
$meta_boxes,
|
||||
WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ),
|
||||
WPSEO_Meta::get_meta_field_defs( 'advanced', $post->post_type ),
|
||||
$social_fields,
|
||||
WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type )
|
||||
);
|
||||
|
||||
foreach ( $meta_boxes as $key => $meta_box ) {
|
||||
// If analysis is disabled remove that analysis score value from the DB.
|
||||
if ( $this->is_meta_value_disabled( $key ) ) {
|
||||
WPSEO_Meta::delete( $key, $post_id );
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = null;
|
||||
$field_name = WPSEO_Meta::$form_prefix . $key;
|
||||
|
||||
if ( $meta_box['type'] === 'checkbox' ) {
|
||||
$data = isset( $_POST[ $field_name ] ) ? 'on' : 'off';
|
||||
}
|
||||
else {
|
||||
if ( isset( $_POST[ $field_name ] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Sanitized through sanitize_post_meta.
|
||||
$data = \wp_unslash( $_POST[ $field_name ] );
|
||||
|
||||
// For multi-select.
|
||||
if ( \is_array( $data ) ) {
|
||||
$data = \array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $data );
|
||||
}
|
||||
|
||||
if ( \is_string( $data ) ) {
|
||||
$data = ( $key !== 'canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
|
||||
}
|
||||
}
|
||||
|
||||
// Reset options when no entry is present with multiselect - only applies to `meta-robots-adv` currently.
|
||||
if ( ! isset( $_POST[ $field_name ] ) && ( $meta_box['type'] === 'multiselect' ) ) {
|
||||
$data = [];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $data !== null ) {
|
||||
WPSEO_Meta::set_value( $key, $data, $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) && \is_string( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) ) {
|
||||
$slug = \sanitize_title( \wp_unslash( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) );
|
||||
if ( $post->post_name !== $slug ) {
|
||||
$post_array = $post->to_array();
|
||||
$post_array['post_name'] = $slug;
|
||||
|
||||
$save_successful = \wp_insert_post( $post_array );
|
||||
if ( \is_wp_error( $save_successful ) ) {
|
||||
\wp_send_json_error( 'Slug not saved', 400 );
|
||||
}
|
||||
|
||||
// Update the post object to ensure we have the actual slug.
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Updating the post is needed to get the current slug.
|
||||
$post = \get_post( $post_id );
|
||||
if ( ! \is_object( $post ) ) {
|
||||
\wp_send_json_error( 'Updated slug not found', 400 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\do_action( 'wpseo_saved_postdata' );
|
||||
|
||||
// Output the slug, because it is processed by WP and we need the actual slug again.
|
||||
\wp_send_json_success( [ 'slug' => $post->post_name ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given meta value key is disabled.
|
||||
*
|
||||
* @param string $key The key of the meta value.
|
||||
*
|
||||
* @return bool Whether the given meta value key is disabled.
|
||||
*/
|
||||
public function is_meta_value_disabled( $key ) {
|
||||
if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $key === 'inclusive_language_score' && ! $this->inclusive_language_analysis->is_enabled() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the needed JS and CSS.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
$post_id = \get_queried_object_id();
|
||||
if ( empty( $post_id ) ) {
|
||||
$post_id = 0;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['post'] ) && \is_string( $_GET['post'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- Reason: No sanitization needed because we cast to an integer,We are not processing form information.
|
||||
$post_id = (int) \wp_unslash( $_GET['post'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $post_id !== 0 ) {
|
||||
// Enqueue files needed for upload functionality.
|
||||
\wp_enqueue_media( [ 'post' => $post_id ] );
|
||||
}
|
||||
|
||||
$this->asset_manager->enqueue_style( 'admin-global' );
|
||||
$this->asset_manager->enqueue_style( 'metabox-css' );
|
||||
$this->asset_manager->enqueue_style( 'scoring' );
|
||||
$this->asset_manager->enqueue_style( 'monorepo' );
|
||||
$this->asset_manager->enqueue_style( 'admin-css' );
|
||||
$this->asset_manager->enqueue_style( 'ai-generator' );
|
||||
$this->asset_manager->enqueue_style( 'elementor' );
|
||||
|
||||
$this->asset_manager->enqueue_script( 'admin-global' );
|
||||
$this->asset_manager->enqueue_script( 'elementor' );
|
||||
|
||||
$this->asset_manager->localize_script( 'elementor', 'wpseoAdminGlobalL10n', \YoastSEO()->helpers->wincher->get_admin_global_links() );
|
||||
$this->asset_manager->localize_script( 'elementor', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
|
||||
$this->asset_manager->localize_script( 'elementor', 'wpseoFeaturesL10n', WPSEO_Utils::retrieve_enabled_features() );
|
||||
|
||||
$plugins_script_data = [
|
||||
'replaceVars' => [
|
||||
'replace_vars' => $this->get_replace_vars(),
|
||||
'recommended_replace_vars' => $this->get_recommended_replace_vars(),
|
||||
'hidden_replace_vars' => $this->get_hidden_replace_vars(),
|
||||
'scope' => $this->determine_scope(),
|
||||
'has_taxonomies' => $this->current_post_type_has_taxonomies(),
|
||||
],
|
||||
'shortcodes' => [
|
||||
'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
|
||||
'wpseo_filter_shortcodes_nonce' => \wp_create_nonce( 'wpseo-filter-shortcodes' ),
|
||||
],
|
||||
];
|
||||
|
||||
$worker_script_data = [
|
||||
'url' => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
|
||||
'dependencies' => \YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
|
||||
'keywords_assessment_url' => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
|
||||
'log_level' => WPSEO_Utils::get_analysis_worker_log_level(),
|
||||
// We need to make the feature flags separately available inside of the analysis web worker.
|
||||
'enabled_features' => WPSEO_Utils::retrieve_enabled_features(),
|
||||
];
|
||||
|
||||
$permalink = $this->get_permalink();
|
||||
$page_on_front = (int) \get_option( 'page_on_front' );
|
||||
$homepage_is_page = \get_option( 'show_on_front' ) === 'page';
|
||||
$is_front_page = $homepage_is_page && $page_on_front === $post_id;
|
||||
|
||||
$script_data = [
|
||||
'metabox' => $this->get_metabox_script_data( $permalink ),
|
||||
'isPost' => true,
|
||||
'isBlockEditor' => WP_Screen::get()->is_block_editor(),
|
||||
'isElementorEditor' => true,
|
||||
'isAlwaysIntroductionV2' => $this->is_elementor_version_compatible_with_introduction_v2(),
|
||||
'postStatus' => \get_post_status( $post_id ),
|
||||
'postType' => \get_post_type( $post_id ),
|
||||
'analysis' => [
|
||||
'plugins' => $plugins_script_data,
|
||||
'worker' => $worker_script_data,
|
||||
],
|
||||
'usedKeywordsNonce' => \wp_create_nonce( 'wpseo-keyword-usage-and-post-types' ),
|
||||
'isFrontPage' => $is_front_page,
|
||||
];
|
||||
|
||||
/**
|
||||
* The website information repository.
|
||||
*
|
||||
* @var Website_Information_Repository $repo
|
||||
*/
|
||||
$repo = \YoastSEO()->classes->get( Website_Information_Repository::class );
|
||||
$site_information = $repo->get_post_site_information();
|
||||
$site_information->set_permalink( $permalink );
|
||||
$script_data = \array_merge_recursive( $site_information->get_legacy_site_information(), $script_data );
|
||||
|
||||
$this->asset_manager->localize_script( 'elementor', 'wpseoScriptData', $script_data );
|
||||
$this->asset_manager->enqueue_user_language_script();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current Elementor version is compatible with our introduction v2.
|
||||
*
|
||||
* In version 3.30.0, Elementor removed the experimental flag for the editor v2.
|
||||
* Resulting in the editor v2 being the default.
|
||||
*
|
||||
* @return bool Whether the Elementor version is compatible with introduction v2.
|
||||
*/
|
||||
private function is_elementor_version_compatible_with_introduction_v2(): bool {
|
||||
if ( ! \defined( 'ELEMENTOR_VERSION' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Take the semver version from their version string.
|
||||
$matches = [];
|
||||
$version = ( \preg_match( '/^([0-9]+.[0-9]+.[0-9]+)/', \ELEMENTOR_VERSION, $matches ) > 0 ) ? $matches[1] : \ELEMENTOR_VERSION;
|
||||
|
||||
// Check if the version is 3.30.0 or higher. This is where the editor v2 was taken out of the experimental into the default state.
|
||||
return \version_compare( $version, '3.30.0', '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the metabox hidden fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function render_hidden_fields() {
|
||||
// Wrap in a form with an action and post_id for the submit.
|
||||
\printf(
|
||||
'<form id="yoast-form" method="post" action="%1$s"><input type="hidden" name="action" value="wpseo_elementor_save" /><input type="hidden" id="post_ID" name="post_id" value="%2$s" />',
|
||||
\esc_url( \admin_url( 'admin-ajax.php' ) ),
|
||||
\esc_attr( $this->get_metabox_post()->ID )
|
||||
);
|
||||
|
||||
\wp_nonce_field( 'wpseo_elementor_save', '_wpseo_elementor_nonce' );
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
|
||||
echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' );
|
||||
|
||||
if ( $this->is_advanced_metadata_enabled ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
|
||||
echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
|
||||
echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type );
|
||||
|
||||
if ( $this->social_is_enabled ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
|
||||
echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' );
|
||||
}
|
||||
|
||||
\printf(
|
||||
'<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
|
||||
\esc_attr( WPSEO_Meta::$form_prefix . 'slug' ),
|
||||
/**
|
||||
* It is important that this slug value is the same as in the database.
|
||||
* If the DB value is empty we can auto-generate a slug.
|
||||
* But if not empty, we should not touch it anymore.
|
||||
*/
|
||||
\esc_attr( $this->get_metabox_post()->post_name )
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter.
|
||||
echo \apply_filters( 'wpseo_elementor_hidden_fields', '' );
|
||||
|
||||
echo '</form>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns post in metabox context.
|
||||
*
|
||||
* @return WP_Post|null
|
||||
*/
|
||||
protected function get_metabox_post() {
|
||||
if ( $this->post !== null ) {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
$this->post = $this->request_post->get_post();
|
||||
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes variables to js for use with the post-scraper.
|
||||
*
|
||||
* @param string $permalink The permalink.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_metabox_script_data( $permalink ) {
|
||||
$post_formatter = new WPSEO_Metabox_Formatter(
|
||||
new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink )
|
||||
);
|
||||
|
||||
$values = $post_formatter->get_values();
|
||||
|
||||
/** This filter is documented in admin/filters/class-cornerstone-filter.php. */
|
||||
$post_types = \apply_filters( 'wpseo_cornerstone_post_types', \YoastSEO()->helpers->post_type->get_accessible_post_types() );
|
||||
if ( $values['cornerstoneActive'] && ! \in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) {
|
||||
$values['cornerstoneActive'] = false;
|
||||
}
|
||||
|
||||
$values['elementorMarkerStatus'] = $this->is_highlighting_available() ? 'enabled' : 'hidden';
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permalink.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_permalink(): string {
|
||||
$permalink = '';
|
||||
|
||||
if ( \is_object( $this->get_metabox_post() ) ) {
|
||||
$permalink = \get_sample_permalink( $this->get_metabox_post()->ID );
|
||||
$permalink = $permalink[0];
|
||||
}
|
||||
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the highlighting functionality is available for Elementor:
|
||||
* - in Free it's always available (as an upsell).
|
||||
* - in Premium it's available as long as the version is 21.8-RC0 or above.
|
||||
*
|
||||
* @return bool Whether the highlighting functionality is available.
|
||||
*/
|
||||
private function is_highlighting_available() {
|
||||
$is_premium = \YoastSEO()->helpers->product->is_premium();
|
||||
$premium_version = \YoastSEO()->helpers->product->get_premium_version();
|
||||
|
||||
return ! $is_premium || \version_compare( $premium_version, '21.8-RC0', '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the replace vars for localization.
|
||||
*
|
||||
* @return array Replace vars.
|
||||
*/
|
||||
protected function get_replace_vars() {
|
||||
$cached_replacement_vars = [];
|
||||
|
||||
$vars_to_cache = [
|
||||
'date',
|
||||
'id',
|
||||
'sitename',
|
||||
'sitedesc',
|
||||
'sep',
|
||||
'page',
|
||||
'currentyear',
|
||||
'currentdate',
|
||||
'currentmonth',
|
||||
'currentday',
|
||||
'tag',
|
||||
'category',
|
||||
'category_title',
|
||||
'primary_category',
|
||||
'pt_single',
|
||||
'pt_plural',
|
||||
'modified',
|
||||
'name',
|
||||
'user_description',
|
||||
'pagetotal',
|
||||
'pagenumber',
|
||||
'post_year',
|
||||
'post_month',
|
||||
'post_day',
|
||||
'author_first_name',
|
||||
'author_last_name',
|
||||
'permalink',
|
||||
'post_content',
|
||||
];
|
||||
|
||||
foreach ( $vars_to_cache as $var ) {
|
||||
$cached_replacement_vars[ $var ] = \wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() );
|
||||
}
|
||||
|
||||
// Merge custom replace variables with the WordPress ones.
|
||||
return \array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the recommended replace vars for localization.
|
||||
*
|
||||
* @return array Recommended replacement variables.
|
||||
*/
|
||||
protected function get_recommended_replace_vars() {
|
||||
$recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
|
||||
|
||||
// What is recommended depends on the current context.
|
||||
$post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() );
|
||||
|
||||
return $recommended_replace_vars->get_recommended_replacevars_for( $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of replace vars that should be hidden inside the editor.
|
||||
*
|
||||
* @return string[] The hidden replace vars.
|
||||
*/
|
||||
protected function get_hidden_replace_vars() {
|
||||
return ( new WPSEO_Replace_Vars() )->get_hidden_replace_vars();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom replace variables for custom taxonomies and fields.
|
||||
*
|
||||
* @param WP_Post $post The post to check for custom taxonomies and fields.
|
||||
*
|
||||
* @return array Array containing all the replacement variables.
|
||||
*/
|
||||
protected function get_custom_replace_vars( $post ) {
|
||||
return [
|
||||
'custom_fields' => $this->get_custom_fields_replace_vars( $post ),
|
||||
'custom_taxonomies' => $this->get_custom_taxonomies_replace_vars( $post ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom replace variables for custom taxonomies.
|
||||
*
|
||||
* @param WP_Post $post The post to check for custom taxonomies.
|
||||
*
|
||||
* @return array Array containing all the replacement variables.
|
||||
*/
|
||||
protected function get_custom_taxonomies_replace_vars( $post ) {
|
||||
$taxonomies = \get_object_taxonomies( $post, 'objects' );
|
||||
$custom_replace_vars = [];
|
||||
|
||||
foreach ( $taxonomies as $taxonomy_name => $taxonomy ) {
|
||||
|
||||
if ( \is_string( $taxonomy ) ) { // If attachment, see https://core.trac.wordpress.org/ticket/37368 .
|
||||
$taxonomy_name = $taxonomy;
|
||||
$taxonomy = \get_taxonomy( $taxonomy_name );
|
||||
}
|
||||
|
||||
if ( $taxonomy->_builtin && $taxonomy->public ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$custom_replace_vars[ $taxonomy_name ] = [
|
||||
'name' => $taxonomy->name,
|
||||
'description' => $taxonomy->description,
|
||||
];
|
||||
}
|
||||
|
||||
return $custom_replace_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom replace variables for custom fields.
|
||||
*
|
||||
* @param WP_Post $post The post to check for custom fields.
|
||||
*
|
||||
* @return array Array containing all the replacement variables.
|
||||
*/
|
||||
protected function get_custom_fields_replace_vars( $post ) {
|
||||
$custom_replace_vars = [];
|
||||
|
||||
// If no post object is passed, return the empty custom_replace_vars array.
|
||||
if ( ! \is_object( $post ) ) {
|
||||
return $custom_replace_vars;
|
||||
}
|
||||
|
||||
$custom_fields = \get_post_custom( $post->ID );
|
||||
|
||||
// Simply concatenate all fields containing replace vars so we can handle them all with a single regex find.
|
||||
$replace_vars_fields = \implode(
|
||||
' ',
|
||||
[
|
||||
\YoastSEO()->meta->for_post( $post->ID )->presentation->title,
|
||||
\YoastSEO()->meta->for_post( $post->ID )->presentation->meta_description,
|
||||
]
|
||||
);
|
||||
|
||||
\preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches );
|
||||
$fields_to_include = $matches[1];
|
||||
foreach ( $custom_fields as $custom_field_name => $custom_field ) {
|
||||
// Skip private custom fields.
|
||||
if ( \substr( $custom_field_name, 0, 1 ) === '_' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip custom fields that are not used, new ones will be fetched dynamically.
|
||||
if ( ! \in_array( $custom_field_name, $fields_to_include, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip custom field values that are serialized.
|
||||
if ( \is_serialized( $custom_field[0] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$custom_replace_vars[ $custom_field_name ] = $custom_field[0];
|
||||
}
|
||||
|
||||
return $custom_replace_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the scope based on the post type.
|
||||
* This can be used by the replacevar plugin to determine if a replacement needs to be executed.
|
||||
*
|
||||
* @return string String describing the current scope.
|
||||
*/
|
||||
protected function determine_scope() {
|
||||
if ( $this->get_metabox_post()->post_type === 'page' ) {
|
||||
return 'page';
|
||||
}
|
||||
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the current post type has registered taxonomies.
|
||||
*
|
||||
* @return bool Whether the current post type has taxonomies.
|
||||
*/
|
||||
protected function current_post_type_has_taxonomies() {
|
||||
$post_taxonomies = \get_object_taxonomies( $this->get_metabox_post()->post_type );
|
||||
|
||||
return ! empty( $post_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with shortcode tags for all registered shortcodes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_valid_shortcode_tags() {
|
||||
$shortcode_tags = [];
|
||||
|
||||
foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
|
||||
$shortcode_tags[] = $tag;
|
||||
}
|
||||
|
||||
return $shortcode_tags;
|
||||
}
|
||||
}
|
||||
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/exclude-elementor-post-types.php
vendored
Normal file
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/exclude-elementor-post-types.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type;
|
||||
|
||||
/**
|
||||
* Excludes certain Elementor-specific post types from the indexable table.
|
||||
*
|
||||
* Posts with these post types will not be saved to the indexable table.
|
||||
*/
|
||||
class Exclude_Elementor_Post_Types extends Abstract_Exclude_Post_Type {
|
||||
|
||||
/**
|
||||
* This integration is only active when the Elementor plugin
|
||||
* is installed and activated.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Elementor_Activated_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the post types to be excluded.
|
||||
* To be used in the wpseo_indexable_excluded_post_types filter.
|
||||
*
|
||||
* @return array The names of the post types.
|
||||
*/
|
||||
public function get_post_type() {
|
||||
return [ 'elementor_library' ];
|
||||
}
|
||||
}
|
||||
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/exclude-woocommerce-post-types.php
vendored
Normal file
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/exclude-woocommerce-post-types.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type;
|
||||
|
||||
/**
|
||||
* Excludes certain WooCommerce-specific post types from the indexable table.
|
||||
*
|
||||
* Posts with these post types will not be saved to the indexable table.
|
||||
*/
|
||||
class Exclude_WooCommerce_Post_Types extends Abstract_Exclude_Post_Type {
|
||||
|
||||
/**
|
||||
* This integration is only active when the WooCommerce plugin
|
||||
* is installed and activated.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WooCommerce_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the post types to be excluded.
|
||||
* To be used in the wpseo_indexable_excluded_post_types filter.
|
||||
*
|
||||
* @return array The names of the post types.
|
||||
*/
|
||||
public function get_post_type() {
|
||||
return [ 'shop_order' ];
|
||||
}
|
||||
}
|
||||
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/jetpack.php
vendored
Normal file
34
wp-content/plugins/wordpress-seo/src/integrations/third-party/jetpack.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Jetpack_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Jetpack integration.
|
||||
*/
|
||||
class Jetpack implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class, Jetpack_Conditional::class, Open_Graph_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'jetpack_enable_open_graph', '__return_false' );
|
||||
}
|
||||
}
|
||||
33
wp-content/plugins/wordpress-seo/src/integrations/third-party/w3-total-cache.php
vendored
Normal file
33
wp-content/plugins/wordpress-seo/src/integrations/third-party/w3-total-cache.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* W3 Total Cache integration.
|
||||
*/
|
||||
class W3_Total_Cache implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ W3_Total_Cache_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* On successful update/add of the taxonomy meta option, flush the W3TC cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'add_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' );
|
||||
\add_action( 'update_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' );
|
||||
}
|
||||
}
|
||||
47
wp-content/plugins/wordpress-seo/src/integrations/third-party/web-stories-post-edit.php
vendored
Normal file
47
wp-content/plugins/wordpress-seo/src/integrations/third-party/web-stories-post-edit.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Web_Stories_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Web Stories integration.
|
||||
*/
|
||||
class Web_Stories_Post_Edit implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Web_Stories_Conditional::class, Post_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_admin_l10n', [ $this, 'add_admin_l10n' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a isWebStoriesIntegrationActive variable to the Adminl10n array.
|
||||
*
|
||||
* @param array $input The array to add the isWebStoriesIntegrationActive to.
|
||||
*
|
||||
* @return array The passed array with the additional isWebStoriesIntegrationActive variable set to 1 if we are editing a web story.
|
||||
*/
|
||||
public function add_admin_l10n( $input ) {
|
||||
if ( \get_post_type() === 'web-story' ) {
|
||||
$input['isWebStoriesIntegrationActive'] = 1;
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
136
wp-content/plugins/wordpress-seo/src/integrations/third-party/web-stories.php
vendored
Normal file
136
wp-content/plugins/wordpress-seo/src/integrations/third-party/web-stories.php
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Web_Stories_Conditional;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Integrations\Front_End_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presentations\Indexable_Presentation;
|
||||
use Yoast\WP\SEO\Presenters\Title_Presenter;
|
||||
|
||||
/**
|
||||
* Web Stories integration.
|
||||
*/
|
||||
class Web_Stories implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The front end integration.
|
||||
*
|
||||
* @var Front_End_Integration
|
||||
*/
|
||||
protected $front_end;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Web_Stories_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Web Stories integration
|
||||
*
|
||||
* @param Front_End_Integration $front_end The front end integration.
|
||||
*/
|
||||
public function __construct( Front_End_Integration $front_end ) {
|
||||
$this->front_end = $front_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Disable default title and meta description output in the Web Stories plugin,
|
||||
// and force-add title & meta description presenter, regardless of theme support.
|
||||
\add_filter( 'web_stories_enable_document_title', '__return_false' );
|
||||
\add_filter( 'web_stories_enable_metadata', '__return_false' );
|
||||
\add_filter( 'wpseo_frontend_presenters', [ $this, 'filter_frontend_presenters' ], 10, 2 );
|
||||
|
||||
\add_action( 'web_stories_enable_schemaorg_metadata', '__return_false' );
|
||||
\add_action( 'web_stories_enable_open_graph_metadata', '__return_false' );
|
||||
\add_action( 'web_stories_enable_twitter_metadata', '__return_false' );
|
||||
|
||||
\add_action( 'web_stories_story_head', [ $this, 'web_stories_story_head' ], 1 );
|
||||
\add_filter( 'wpseo_schema_article_type', [ $this, 'filter_schema_article_type' ], 10, 2 );
|
||||
\add_filter( 'wpseo_metadesc', [ $this, 'filter_meta_description' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request.
|
||||
*
|
||||
* @param array $presenters The presenters.
|
||||
* @param Meta_Tags_Context $context The meta tags context for the current page.
|
||||
* @return array Filtered presenters.
|
||||
*/
|
||||
public function filter_frontend_presenters( $presenters, $context ) {
|
||||
if ( $context->indexable->object_sub_type !== 'web-story' ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
$has_title_presenter = false;
|
||||
|
||||
foreach ( $presenters as $presenter ) {
|
||||
if ( $presenter instanceof Title_Presenter ) {
|
||||
$has_title_presenter = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $has_title_presenter ) {
|
||||
$presenters[] = new Title_Presenter();
|
||||
}
|
||||
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into web story <head> generation to modify output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function web_stories_story_head() {
|
||||
\remove_action( 'web_stories_story_head', 'rel_canonical' );
|
||||
\add_action( 'web_stories_story_head', [ $this->front_end, 'call_wpseo_head' ], 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the meta description for stories.
|
||||
*
|
||||
* @param string $description The description sentence.
|
||||
* @param Indexable_Presentation $presentation The presentation of an indexable.
|
||||
* @return string The description sentence.
|
||||
*/
|
||||
public function filter_meta_description( $description, $presentation ) {
|
||||
if ( $description || $presentation->model->object_sub_type !== 'web-story' ) {
|
||||
return $description;
|
||||
}
|
||||
|
||||
return \get_the_excerpt( $presentation->model->object_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters Article type for Web Stories.
|
||||
*
|
||||
* @param string|string[] $type The Article type.
|
||||
* @param Indexable $indexable The indexable.
|
||||
* @return string|string[] Article type.
|
||||
*/
|
||||
public function filter_schema_article_type( $type, $indexable ) {
|
||||
if ( $indexable->object_sub_type !== 'web-story' ) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if ( \is_string( $type ) && $type === 'None' ) {
|
||||
return 'Article';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
166
wp-content/plugins/wordpress-seo/src/integrations/third-party/wincher-publish.php
vendored
Normal file
166
wp-content/plugins/wordpress-seo/src/integrations/third-party/wincher-publish.php
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action;
|
||||
use Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Wincher_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Handles automatically tracking published posts with Wincher.
|
||||
*/
|
||||
class Wincher_Publish implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The Wincher enabled conditional.
|
||||
*
|
||||
* @var Wincher_Enabled_Conditional
|
||||
*/
|
||||
protected $wincher_enabled;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The Wincher keyphrases action handler.
|
||||
*
|
||||
* @var Wincher_Keyphrases_Action
|
||||
*/
|
||||
protected $keyphrases_action;
|
||||
|
||||
/**
|
||||
* The Wincher account action handler.
|
||||
*
|
||||
* @var Wincher_Account_Action
|
||||
*/
|
||||
protected $account_action;
|
||||
|
||||
/**
|
||||
* Wincher publish constructor.
|
||||
*
|
||||
* @param Wincher_Enabled_Conditional $wincher_enabled The WPML WPSEO conditional.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Wincher_Keyphrases_Action $keyphrases_action The keyphrases action class.
|
||||
* @param Wincher_Account_Action $account_action The account action class.
|
||||
*/
|
||||
public function __construct(
|
||||
Wincher_Enabled_Conditional $wincher_enabled,
|
||||
Options_Helper $options_helper,
|
||||
Wincher_Keyphrases_Action $keyphrases_action,
|
||||
Wincher_Account_Action $account_action
|
||||
) {
|
||||
$this->wincher_enabled = $wincher_enabled;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->keyphrases_action = $keyphrases_action;
|
||||
$this->account_action = $account_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
/**
|
||||
* Called in the REST API when submitting the post copy in the Block Editor.
|
||||
* Runs the republishing of the copy onto the original.
|
||||
*/
|
||||
\add_action( 'rest_after_insert_post', [ $this, 'track_after_rest_api_request' ] );
|
||||
|
||||
/**
|
||||
* Called by `wp_insert_post()` when submitting the post copy, which runs in two cases:
|
||||
* - In the Classic Editor, where there's only one request that updates everything.
|
||||
* - In the Block Editor, only when there are custom meta boxes.
|
||||
*/
|
||||
\add_action( 'wp_insert_post', [ $this, 'track_after_post_request' ], \PHP_INT_MAX, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* This integration should only be active when the feature is enabled, a token is available and automatically tracking is enabled.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Wincher_Conditional::class,
|
||||
Wincher_Enabled_Conditional::class,
|
||||
Wincher_Automatically_Track_Conditional::class,
|
||||
Wincher_Token_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current request is a REST request.
|
||||
*
|
||||
* @deprecated 23.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether the request is a REST request.
|
||||
*/
|
||||
public function is_rest_request() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 23.6', 'wp_is_serving_rest_request' );
|
||||
return \defined( 'REST_REQUEST' ) && \REST_REQUEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the keyphrases associated with the post to Wincher for automatic tracking.
|
||||
*
|
||||
* @param WP_Post $post The post to extract the keyphrases from.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function track_request( $post ) {
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out empty entries.
|
||||
$keyphrases = \array_filter( $this->keyphrases_action->collect_keyphrases_from_post( $post ) );
|
||||
|
||||
if ( ! empty( $keyphrases ) ) {
|
||||
$this->keyphrases_action->track_keyphrases( $keyphrases, $this->account_action->check_limit() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Republishes the original post with the passed post, when using the Block Editor.
|
||||
*
|
||||
* @param WP_Post $post The copy's post object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function track_after_rest_api_request( $post ) {
|
||||
$this->track_request( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Republishes the original post with the passed post, when using the Classic Editor.
|
||||
*
|
||||
* Runs also in the Block Editor to save the custom meta data only when there
|
||||
* are custom meta boxes.
|
||||
*
|
||||
* @param int $post_id The copy's post ID.
|
||||
* @param WP_Post $post The copy's post object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function track_after_post_request( $post_id, $post ) {
|
||||
if ( \wp_is_serving_rest_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->track_request( $post );
|
||||
}
|
||||
}
|
||||
112
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce-permalinks.php
vendored
Normal file
112
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce-permalinks.php
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* The permalink watcher.
|
||||
*/
|
||||
class Woocommerce_Permalinks implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WooCommerce_Conditional::class, Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Indexable_Helper $indexable_helper Indexable Helper.
|
||||
*/
|
||||
public function __construct( Indexable_Helper $indexable_helper ) {
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_post_types_reset_permalinks', [ $this, 'filter_product_from_post_types' ] );
|
||||
\add_action( 'update_option_woocommerce_permalinks', [ $this, 'reset_woocommerce_permalinks' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the product post type from the post type.
|
||||
*
|
||||
* @param array $post_types The post types to filter.
|
||||
*
|
||||
* @return array The filtered post types.
|
||||
*/
|
||||
public function filter_product_from_post_types( $post_types ) {
|
||||
unset( $post_types['product'] );
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the indexables for WooCommerce based on the changed permalink fields.
|
||||
*
|
||||
* @param array $old_value The old value.
|
||||
* @param array $new_value The new value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_woocommerce_permalinks( $old_value, $new_value ) {
|
||||
$changed_options = \array_diff( $old_value, $new_value );
|
||||
|
||||
if ( \array_key_exists( 'product_base', $changed_options ) ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'post', 'product' );
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'attribute_base', $changed_options ) ) {
|
||||
$attribute_taxonomies = $this->get_attribute_taxonomies();
|
||||
|
||||
foreach ( $attribute_taxonomies as $attribute_name ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', $attribute_name );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'category_base', $changed_options ) ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', 'product_cat' );
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'tag_base', $changed_options ) ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', 'product_tag' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the taxonomies based on the attributes.
|
||||
*
|
||||
* @return array The taxonomies.
|
||||
*/
|
||||
protected function get_attribute_taxonomies() {
|
||||
$taxonomies = [];
|
||||
foreach ( \wc_get_attribute_taxonomies() as $attribute_taxonomy ) {
|
||||
$taxonomies[] = \wc_attribute_taxonomy_name( $attribute_taxonomy->attribute_name );
|
||||
}
|
||||
|
||||
$taxonomies = \array_filter( $taxonomies );
|
||||
|
||||
return $taxonomies;
|
||||
}
|
||||
}
|
||||
49
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce-post-edit.php
vendored
Normal file
49
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce-post-edit.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* A WooCommerce integration that runs in the post editor.
|
||||
*/
|
||||
class WooCommerce_Post_Edit implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Register the hooks for this integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_post_edit_values', [ $this, 'remove_meta_description_date' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Only run this integration when WooCommerce is active and the user is in the post editor.
|
||||
*
|
||||
* @return string[] The conditionals that should be met before this integration is loaded.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WooCommerce_Conditional::class, Post_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't show the date in the Google preview for WooCommerce products,
|
||||
* since Google does not show dates for product pages in the search results.
|
||||
*
|
||||
* @param array $values Key-value map of variables we enqueue in the JavaScript of the post editor.
|
||||
* @param WP_Post $post The post currently opened in the editor.
|
||||
*
|
||||
* @return array The values, where the `metaDescriptionDate` is set to the empty string.
|
||||
*/
|
||||
public function remove_meta_description_date( $values, $post ) {
|
||||
if ( $post->post_type === 'product' ) {
|
||||
$values['metaDescriptionDate'] = '';
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
350
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce.php
vendored
Normal file
350
wp-content/plugins/wordpress-seo/src/integrations/third-party/woocommerce.php
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use WPSEO_Replace_Vars;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Pagination_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presentations\Indexable_Presentation;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* WooCommerce integration.
|
||||
*/
|
||||
class WooCommerce implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* The WPSEO Replace Vars object.
|
||||
*
|
||||
* @var WPSEO_Replace_Vars
|
||||
*/
|
||||
private $replace_vars;
|
||||
|
||||
/**
|
||||
* The memoizer for the meta tags context.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
protected $context_memoizer;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* The pagination helper.
|
||||
*
|
||||
* @var Pagination_Helper
|
||||
*/
|
||||
protected $pagination_helper;
|
||||
|
||||
/**
|
||||
* The WooCommerce helper.
|
||||
*
|
||||
* @var Woocommerce_Helper
|
||||
*/
|
||||
private $woocommerce_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WooCommerce_Conditional::class, Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce constructor.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param WPSEO_Replace_Vars $replace_vars The replace vars helper.
|
||||
* @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
|
||||
* @param Indexable_Repository $repository The indexable repository.
|
||||
* @param Pagination_Helper $pagination_helper The paginataion helper.
|
||||
* @param Woocommerce_Helper $woocommerce_helper The WooCommerce helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options,
|
||||
WPSEO_Replace_Vars $replace_vars,
|
||||
Meta_Tags_Context_Memoizer $context_memoizer,
|
||||
Indexable_Repository $repository,
|
||||
Pagination_Helper $pagination_helper,
|
||||
Woocommerce_Helper $woocommerce_helper
|
||||
) {
|
||||
$this->options = $options;
|
||||
$this->replace_vars = $replace_vars;
|
||||
$this->context_memoizer = $context_memoizer;
|
||||
$this->repository = $repository;
|
||||
$this->pagination_helper = $pagination_helper;
|
||||
$this->woocommerce_helper = $woocommerce_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_frontend_page_type_simple_page_id', [ $this, 'get_page_id' ] );
|
||||
\add_filter( 'wpseo_breadcrumb_indexables', [ $this, 'add_shop_to_breadcrumbs' ] );
|
||||
|
||||
\add_filter( 'wpseo_title', [ $this, 'title' ], 10, 2 );
|
||||
\add_filter( 'wpseo_metadesc', [ $this, 'description' ], 10, 2 );
|
||||
\add_filter( 'wpseo_canonical', [ $this, 'canonical' ], 10, 2 );
|
||||
\add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct canonical when WooCommerce is enabled.
|
||||
*
|
||||
* @param string $canonical The current canonical.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string The correct canonical.
|
||||
*/
|
||||
public function canonical( $canonical, $presentation = null ) {
|
||||
if ( ! $this->woocommerce_helper->is_shop_page() ) {
|
||||
return $canonical;
|
||||
}
|
||||
|
||||
$url = $this->get_shop_paginated_link( 'curr', $presentation );
|
||||
|
||||
if ( $url ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return $canonical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns correct adjacent pages when WooCommerce is enabled.
|
||||
*
|
||||
* @param string $link The current link.
|
||||
* @param string $rel Link relationship, prev or next.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string The correct link.
|
||||
*/
|
||||
public function adjacent_rel_url( $link, $rel, $presentation = null ) {
|
||||
if ( ! $this->woocommerce_helper->is_shop_page() ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
if ( $rel !== 'next' && $rel !== 'prev' ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$url = $this->get_shop_paginated_link( $rel, $presentation );
|
||||
|
||||
if ( $url ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a breadcrumb for the shop page.
|
||||
*
|
||||
* @param Indexable[] $indexables The array with indexables.
|
||||
*
|
||||
* @return Indexable[] The indexables to be shown in the breadcrumbs, with the shop page added.
|
||||
*/
|
||||
public function add_shop_to_breadcrumbs( $indexables ) {
|
||||
$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
|
||||
|
||||
if ( ! \is_int( $shop_page_id ) || $shop_page_id < 1 ) {
|
||||
return $indexables;
|
||||
}
|
||||
|
||||
foreach ( $indexables as $index => $indexable ) {
|
||||
if ( $indexable->object_type === 'post-type-archive' && $indexable->object_sub_type === 'product' ) {
|
||||
$shop_page_indexable = $this->repository->find_by_id_and_type( $shop_page_id, 'post' );
|
||||
if ( \is_a( $shop_page_indexable, Indexable::class ) ) {
|
||||
$indexables[ $index ] = $shop_page_indexable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $indexables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the WooCommerce shop page when the currently opened page is the shop page.
|
||||
*
|
||||
* @param int $page_id The page id.
|
||||
*
|
||||
* @return int The Page ID of the shop.
|
||||
*/
|
||||
public function get_page_id( $page_id ) {
|
||||
if ( ! $this->woocommerce_helper->is_shop_page() ) {
|
||||
return $page_id;
|
||||
}
|
||||
|
||||
return $this->woocommerce_helper->get_shop_page_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the title.
|
||||
*
|
||||
* @param string $title The title.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string The title to use.
|
||||
*/
|
||||
public function title( $title, $presentation = null ) {
|
||||
$presentation = $this->ensure_presentation( $presentation );
|
||||
|
||||
if ( $presentation->model->title ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
if ( ! $this->woocommerce_helper->is_shop_page() ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
if ( ! \is_archive() ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
|
||||
if ( $shop_page_id < 1 ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
$product_template_title = $this->get_product_template( 'title-product', $shop_page_id );
|
||||
if ( $product_template_title ) {
|
||||
return $product_template_title;
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the meta description.
|
||||
*
|
||||
* @param string $description The title.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string The description to use.
|
||||
*/
|
||||
public function description( $description, $presentation = null ) {
|
||||
$presentation = $this->ensure_presentation( $presentation );
|
||||
|
||||
if ( $presentation->model->description ) {
|
||||
return $description;
|
||||
}
|
||||
|
||||
if ( ! $this->woocommerce_helper->is_shop_page() ) {
|
||||
return $description;
|
||||
}
|
||||
|
||||
if ( ! \is_archive() ) {
|
||||
return $description;
|
||||
}
|
||||
|
||||
$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
|
||||
if ( $shop_page_id < 1 ) {
|
||||
return $description;
|
||||
}
|
||||
|
||||
$product_template_description = $this->get_product_template( 'metadesc-product', $shop_page_id );
|
||||
if ( $product_template_description ) {
|
||||
return $product_template_description;
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses template for the given option name and replace the replacement variables on it.
|
||||
*
|
||||
* @param string $option_name The option name to get the template for.
|
||||
* @param string $shop_page_id The page id to retrieve template for.
|
||||
*
|
||||
* @return string The rendered value.
|
||||
*/
|
||||
protected function get_product_template( $option_name, $shop_page_id ) {
|
||||
$template = $this->options->get( $option_name );
|
||||
$page = \get_post( $shop_page_id );
|
||||
|
||||
return $this->replace_vars->replace( $template, $page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated link for shop page.
|
||||
*
|
||||
* @param string $rel Link relationship, prev or next or curr.
|
||||
* @param Indexable_Presentation|null $presentation The indexable presentation.
|
||||
*
|
||||
* @return string|null The link.
|
||||
*/
|
||||
protected function get_shop_paginated_link( $rel, $presentation = null ) {
|
||||
$presentation = $this->ensure_presentation( $presentation );
|
||||
|
||||
$permalink = $presentation->permalink;
|
||||
if ( ! $permalink ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$current_page = \max( 1, $this->pagination_helper->get_current_archive_page_number() );
|
||||
|
||||
if ( $rel === 'curr' && $current_page === 1 ) {
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
if ( $rel === 'curr' && $current_page > 1 ) {
|
||||
return $this->pagination_helper->get_paginated_url( $permalink, $current_page );
|
||||
}
|
||||
|
||||
if ( $rel === 'prev' && $current_page === 2 ) {
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
if ( $rel === 'prev' && $current_page > 2 ) {
|
||||
return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page - 1 ) );
|
||||
}
|
||||
|
||||
if ( $rel === 'next' && $current_page < $this->pagination_helper->get_number_of_archive_pages() ) {
|
||||
return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page + 1 ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a presentation is available.
|
||||
*
|
||||
* @param Indexable_Presentation $presentation The indexable presentation.
|
||||
*
|
||||
* @return Indexable_Presentation The presentation, taken from the current page if the input was invalid.
|
||||
*/
|
||||
protected function ensure_presentation( $presentation ) {
|
||||
if ( \is_a( $presentation, Indexable_Presentation::class ) ) {
|
||||
return $presentation;
|
||||
}
|
||||
|
||||
$context = $this->context_memoizer->for_current_page();
|
||||
|
||||
return $context->presentation;
|
||||
}
|
||||
}
|
||||
120
wp-content/plugins/wordpress-seo/src/integrations/third-party/wpml-wpseo-notification.php
vendored
Normal file
120
wp-content/plugins/wordpress-seo/src/integrations/third-party/wpml-wpseo-notification.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast_Notification;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Adds a notification to the dashboard if the WPML plugin is installed,
|
||||
* but the Yoast SEO Multilingual plugin (a glue plugin to make Yoast SEO and WPML work nicely together)
|
||||
* is not.
|
||||
*/
|
||||
class WPML_WPSEO_Notification implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The notification ID.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const NOTIFICATION_ID = 'wpml-wpseo-not-installed';
|
||||
|
||||
/**
|
||||
* The short link helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
protected $short_link_helper;
|
||||
|
||||
/**
|
||||
* The notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* The WPML WPSEO conditional.
|
||||
*
|
||||
* @var WPML_WPSEO_Conditional
|
||||
*/
|
||||
protected $wpml_wpseo_conditional;
|
||||
|
||||
/**
|
||||
* WPML WPSEO notification constructor.
|
||||
*
|
||||
* @param Short_Link_Helper $short_link_helper The short link helper.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param WPML_WPSEO_Conditional $wpml_wpseo_conditional The WPML WPSEO conditional.
|
||||
*/
|
||||
public function __construct(
|
||||
Short_Link_Helper $short_link_helper,
|
||||
Yoast_Notification_Center $notification_center,
|
||||
WPML_WPSEO_Conditional $wpml_wpseo_conditional
|
||||
) {
|
||||
$this->short_link_helper = $short_link_helper;
|
||||
$this->notification_center = $notification_center;
|
||||
$this->wpml_wpseo_conditional = $wpml_wpseo_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_notices', [ $this, 'notify_not_installed' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* This integration should only be active when WPML is installed and activated.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WPML_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the user that the Yoast SEO Multilingual plugin is not installed
|
||||
* (when the WPML plugin is installed).
|
||||
*
|
||||
* Remove the notification again when it is installed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notify_not_installed() {
|
||||
if ( ! $this->wpml_wpseo_conditional->is_met() ) {
|
||||
$this->notification_center->add_notification( $this->get_notification() );
|
||||
return;
|
||||
}
|
||||
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the notification to show to the user when WPML is installed,
|
||||
* but the Yoast SEO Multilingual plugin is not.
|
||||
*
|
||||
* @return Yoast_Notification The notification.
|
||||
*/
|
||||
protected function get_notification() {
|
||||
return new Yoast_Notification(
|
||||
\sprintf(
|
||||
/* translators: %1$s expands to an opening anchor tag, %2$s expands to an closing anchor tag. */
|
||||
\__( 'We notice that you have installed WPML. To make sure your canonical URLs are set correctly, %1$sinstall and activate the WPML SEO add-on%2$s as well!', 'wordpress-seo' ),
|
||||
'<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/wpml-yoast-seo' ) ) . '" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
[
|
||||
'id' => self::NOTIFICATION_ID,
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
70
wp-content/plugins/wordpress-seo/src/integrations/third-party/wpml.php
vendored
Normal file
70
wp-content/plugins/wordpress-seo/src/integrations/third-party/wpml.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* WPML integration.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded -- Known false positive with acronyms. Fix expected in YoastCS 3.x.
|
||||
*/
|
||||
class WPML implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_home_url', [ $this, 'filter_home_url_before' ] );
|
||||
\add_filter( 'home_url', [ $this, 'filter_home_url_after' ], 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ WPML_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter to WPML's wpml_get_home_url filter to ensure we get the unmanipulated home URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function filter_home_url_before() {
|
||||
\add_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the wpml_get_home_url filter to return the WPML, language-enriched home URL.
|
||||
*
|
||||
* @param string $home_url The filtered home URL.
|
||||
*
|
||||
* @return string The unfiltered home URL.
|
||||
*/
|
||||
public function filter_home_url_after( $home_url ) {
|
||||
\remove_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10 );
|
||||
|
||||
return $home_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original URL instead of the language-enriched URL.
|
||||
* This method gets automatically triggered by the wpml_get_home_url filter.
|
||||
*
|
||||
* @param string $home_url The url altered by WPML. Unused.
|
||||
* @param string $url The url that isn't altered by WPML.
|
||||
*
|
||||
* @return string The original url.
|
||||
*/
|
||||
public function wpml_get_home_url( $home_url, $url ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
|
||||
/**
|
||||
* Class to manage the integration with the WP uninstall flow.
|
||||
*/
|
||||
class Uninstall_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'uninstall_' . \WPSEO_BASENAME, [ $this, 'wpseo_uninstall' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all necessary actions that should happen upon plugin uninstall.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wpseo_uninstall() {
|
||||
$this->clear_import_statuses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the persistent import statuses.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_import_statuses() {
|
||||
$yoast_options = \get_site_option( 'wpseo' );
|
||||
|
||||
if ( isset( $yoast_options['importing_completed'] ) ) {
|
||||
$yoast_options['importing_completed'] = [];
|
||||
|
||||
\update_site_option( 'wpseo', $yoast_options );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Enables Yoast add-on auto updates when Yoast SEO is enabled and the other way around.
|
||||
*
|
||||
* Also removes the auto-update toggles from the Yoast SEO add-ons.
|
||||
*/
|
||||
class Addon_Update_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* ID string used by WordPress to identify the free plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const WPSEO_FREE_PLUGIN_ID = 'wordpress-seo/wp-seo.php';
|
||||
|
||||
/**
|
||||
* A list of Yoast add-on identifiers.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public const ADD_ON_PLUGIN_FILES = [
|
||||
'wordpress-seo-premium/wp-seo-premium.php',
|
||||
'wpseo-video/video-seo.php',
|
||||
'wpseo-local/local-seo.php', // When installing Local through a released zip, the path is different from the path on a dev environment.
|
||||
'wpseo-woocommerce/wpseo-woocommerce.php',
|
||||
'wpseo-news/wpseo-news.php',
|
||||
'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php', // When installing ACF for Yoast through a released zip, the path is different from the path on a dev environment.
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'add_site_option_auto_update_plugins', [ $this, 'call_toggle_auto_updates_with_empty_array' ], 10, 2 );
|
||||
\add_action( 'update_site_option_auto_update_plugins', [ $this, 'toggle_auto_updates_for_add_ons' ], 10, 3 );
|
||||
\add_filter( 'plugin_auto_update_setting_html', [ $this, 'replace_auto_update_toggles_of_addons' ], 10, 2 );
|
||||
\add_action( 'activated_plugin', [ $this, 'maybe_toggle_auto_updates_for_new_install' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the auto-update toggle links for the Yoast add-ons
|
||||
* with a text explaining that toggling the Yoast SEO auto-update setting
|
||||
* automatically toggles the one for the setting for the add-ons as well.
|
||||
*
|
||||
* @param string $old_html The old HTML.
|
||||
* @param string $plugin The plugin.
|
||||
*
|
||||
* @return string The new HTML, with the auto-update toggle link replaced.
|
||||
*/
|
||||
public function replace_auto_update_toggles_of_addons( $old_html, $plugin ) {
|
||||
if ( ! \is_string( $old_html ) ) {
|
||||
return $old_html;
|
||||
}
|
||||
|
||||
$not_a_yoast_addon = ! \in_array( $plugin, self::ADD_ON_PLUGIN_FILES, true );
|
||||
|
||||
if ( $not_a_yoast_addon ) {
|
||||
return $old_html;
|
||||
}
|
||||
|
||||
$auto_updated_plugins = \get_site_option( 'auto_update_plugins' );
|
||||
|
||||
if ( $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $auto_updated_plugins ) ) {
|
||||
return \sprintf(
|
||||
'<em>%s</em>',
|
||||
\sprintf(
|
||||
/* Translators: %1$s resolves to Yoast SEO. */
|
||||
\esc_html__( 'Auto-updates are enabled based on this setting for %1$s.', 'wordpress-seo' ),
|
||||
'Yoast SEO'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return \sprintf(
|
||||
'<em>%s</em>',
|
||||
\sprintf(
|
||||
/* Translators: %1$s resolves to Yoast SEO. */
|
||||
\esc_html__( 'Auto-updates are disabled based on this setting for %1$s.', 'wordpress-seo' ),
|
||||
'Yoast SEO'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the situation where the auto_update_plugins option did not previously exist.
|
||||
*
|
||||
* @param string $option The name of the option that is being created.
|
||||
* @param array|mixed $value The new (and first) value of the option that is being created.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function call_toggle_auto_updates_with_empty_array( $option, $value ) {
|
||||
if ( $option !== 'auto_update_plugins' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->toggle_auto_updates_for_add_ons( $option, $value, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables premium auto updates when free are enabled and the other way around.
|
||||
*
|
||||
* @param string $option The name of the option that has been updated.
|
||||
* @param array $new_value The new value of the `auto_update_plugins` option.
|
||||
* @param array $old_value The old value of the `auto_update_plugins` option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function toggle_auto_updates_for_add_ons( $option, $new_value, $old_value ) {
|
||||
if ( $option !== 'auto_update_plugins' ) {
|
||||
// If future versions of WordPress change this filter's behavior, our behavior should stay consistent.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$auto_updates_are_enabled = $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $new_value );
|
||||
$auto_updates_were_enabled = $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $old_value );
|
||||
|
||||
if ( $auto_updates_are_enabled === $auto_updates_were_enabled ) {
|
||||
// Auto-updates for Yoast SEO have stayed the same, so have neither been enabled or disabled.
|
||||
return;
|
||||
}
|
||||
|
||||
$auto_updates_have_been_enabled = $auto_updates_are_enabled && ! $auto_updates_were_enabled;
|
||||
|
||||
if ( $auto_updates_have_been_enabled ) {
|
||||
$this->enable_auto_updates_for_addons( $new_value );
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$this->disable_auto_updates_for_addons( $new_value );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $auto_updates_are_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$auto_updates_have_been_removed = false;
|
||||
foreach ( self::ADD_ON_PLUGIN_FILES as $addon ) {
|
||||
if ( ! $this->are_auto_updates_enabled( $addon, $new_value ) ) {
|
||||
$auto_updates_have_been_removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $auto_updates_have_been_removed ) {
|
||||
$this->enable_auto_updates_for_addons( $new_value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a change in the auto update detection whenever a new Yoast addon is activated.
|
||||
*
|
||||
* @param string $plugin The plugin that is activated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_toggle_auto_updates_for_new_install( $plugin ) {
|
||||
$not_a_yoast_addon = ! \in_array( $plugin, self::ADD_ON_PLUGIN_FILES, true );
|
||||
|
||||
if ( $not_a_yoast_addon ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled_auto_updates = \get_site_option( 'auto_update_plugins' );
|
||||
$this->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables auto-updates for all addons.
|
||||
*
|
||||
* @param string[] $auto_updated_plugins The current list of auto-updated plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_auto_updates_for_addons( $auto_updated_plugins ) {
|
||||
$plugins = \array_unique( \array_merge( $auto_updated_plugins, self::ADD_ON_PLUGIN_FILES ) );
|
||||
\update_site_option( 'auto_update_plugins', $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables auto-updates for all addons.
|
||||
*
|
||||
* @param string[] $auto_updated_plugins The current list of auto-updated plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function disable_auto_updates_for_addons( $auto_updated_plugins ) {
|
||||
$plugins = \array_values( \array_diff( $auto_updated_plugins, self::ADD_ON_PLUGIN_FILES ) );
|
||||
\update_site_option( 'auto_update_plugins', $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether auto updates for a plugin are enabled.
|
||||
*
|
||||
* @param string $plugin_id The plugin ID.
|
||||
* @param array $auto_updated_plugins The array of auto updated plugins.
|
||||
*
|
||||
* @return bool Whether auto updates for a plugin are enabled.
|
||||
*/
|
||||
protected function are_auto_updates_enabled( $plugin_id, $auto_updated_plugins ) {
|
||||
if ( $auto_updated_plugins === false || ! \is_array( $auto_updated_plugins ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \in_array( $plugin_id, $auto_updated_plugins, true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Shows a notification for users who have WordPress auto updates enabled but not Yoast SEO auto updates.
|
||||
*/
|
||||
class Auto_Update_Watcher implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The notification ID.
|
||||
*/
|
||||
public const NOTIFICATION_ID = 'wpseo-auto-update';
|
||||
|
||||
/**
|
||||
* The Yoast notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* Auto_Update constructor.
|
||||
*
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
*/
|
||||
public function __construct( Yoast_Notification_Center $notification_center ) {
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* On admin_init, it is checked whether the notification to auto-update Yoast SEO needs to be shown or removed.
|
||||
* This is also done when major WP core updates are being enabled or disabled,
|
||||
* and when automatic updates for Yoast SEO are being enabled or disabled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'remove_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the notification from the notification center, if it exists.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notification() {
|
||||
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Permalink_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Ancestor watcher to update the ancestor's children.
|
||||
*
|
||||
* Updates its children's permalink when the ancestor itself is updated.
|
||||
*/
|
||||
class Indexable_Ancestor_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Represents the indexable hierarchy builder.
|
||||
*
|
||||
* @var Indexable_Hierarchy_Builder
|
||||
*/
|
||||
protected $indexable_hierarchy_builder;
|
||||
|
||||
/**
|
||||
* Represents the indexable hierarchy repository.
|
||||
*
|
||||
* @var Indexable_Hierarchy_Repository
|
||||
*/
|
||||
protected $indexable_hierarchy_repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Represents the permalink helper.
|
||||
*
|
||||
* @var Permalink_Helper
|
||||
*/
|
||||
protected $permalink_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* Sets the needed dependencies.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
* @param Indexable_Hierarchy_Builder $indexable_hierarchy_builder The indexable hierarchy builder.
|
||||
* @param Indexable_Hierarchy_Repository $indexable_hierarchy_repository The indexable hierarchy repository.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Permalink_Helper $permalink_helper The permalink helper.
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $indexable_repository,
|
||||
Indexable_Hierarchy_Builder $indexable_hierarchy_builder,
|
||||
Indexable_Hierarchy_Repository $indexable_hierarchy_repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Permalink_Helper $permalink_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->indexable_hierarchy_builder = $indexable_hierarchy_builder;
|
||||
$this->indexable_hierarchy_repository = $indexable_hierarchy_repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->permalink_helper = $permalink_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the appropriate hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_save_indexable', [ $this, 'reset_children' ], \PHP_INT_MAX, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<Migrations_Conditional>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* If an indexable's permalink has changed, updates its children in the hierarchy table and resets the children's permalink.
|
||||
*
|
||||
* @param Indexable $indexable The indexable.
|
||||
* @param Indexable $indexable_before The old indexable.
|
||||
*
|
||||
* @return bool True if the children were reset.
|
||||
*/
|
||||
public function reset_children( $indexable, $indexable_before ) {
|
||||
if ( ! \in_array( $indexable->object_type, [ 'post', 'term' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the permalink was null it means it was reset instead of changed.
|
||||
if ( $indexable->permalink === $indexable_before->permalink || $indexable_before->permalink === null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$child_indexable_ids = $this->indexable_hierarchy_repository->find_children( $indexable );
|
||||
$child_indexables = $this->indexable_repository->find_by_ids( $child_indexable_ids );
|
||||
|
||||
\array_walk( $child_indexables, [ $this, 'update_hierarchy_and_permalink' ] );
|
||||
if ( $indexable->object_type === 'term' ) {
|
||||
$child_indexables_for_term = $this->get_children_for_term( $indexable->object_id, $child_indexables );
|
||||
|
||||
\array_walk( $child_indexables_for_term, [ $this, 'update_hierarchy_and_permalink' ] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all child indexables for the given term.
|
||||
*
|
||||
* @param int $term_id Term to fetch the indexable for.
|
||||
* @param array<Indexable> $child_indexables The already known child indexables.
|
||||
*
|
||||
* @return array<Indexable> The list of additional child indexables for a given term.
|
||||
*/
|
||||
public function get_children_for_term( $term_id, array $child_indexables ) {
|
||||
// Finds object_ids (posts) for the term.
|
||||
$post_object_ids = $this->get_object_ids_for_term( $term_id, $child_indexables );
|
||||
|
||||
// Removes the objects that are already present in the children.
|
||||
$existing_post_indexables = \array_filter(
|
||||
$child_indexables,
|
||||
static function ( $indexable ) {
|
||||
return $indexable->object_type === 'post';
|
||||
}
|
||||
);
|
||||
|
||||
$existing_post_object_ids = \wp_list_pluck( $existing_post_indexables, 'object_id' );
|
||||
$post_object_ids = \array_diff( $post_object_ids, $existing_post_object_ids );
|
||||
|
||||
// Finds the indexables for the fetched post_object_ids.
|
||||
$post_indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_object_ids, 'post', false );
|
||||
|
||||
// Finds the indexables for the posts that are attached to the term.
|
||||
$post_indexable_ids = \wp_list_pluck( $post_indexables, 'id' );
|
||||
$additional_indexable_ids = $this->indexable_hierarchy_repository->find_children_by_ancestor_ids( $post_indexable_ids );
|
||||
|
||||
// Makes sure we only have indexable id's that we haven't fetched before.
|
||||
$additional_indexable_ids = \array_diff( $additional_indexable_ids, $post_indexable_ids );
|
||||
|
||||
// Finds the additional indexables.
|
||||
$additional_indexables = $this->indexable_repository->find_by_ids( $additional_indexable_ids );
|
||||
|
||||
// Merges all fetched indexables.
|
||||
return \array_merge( $post_indexables, $additional_indexables );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the indexable hierarchy and indexable permalink.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to update the hierarchy and permalink for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function update_hierarchy_and_permalink( $indexable ) {
|
||||
if ( \is_a( $indexable, Indexable::class ) ) {
|
||||
$this->indexable_hierarchy_builder->build( $indexable );
|
||||
|
||||
$indexable->permalink = $this->permalink_helper->get_permalink_for_indexable( $indexable );
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object id's for a term based on the term-post relationship.
|
||||
*
|
||||
* @param int $term_id The term to get the object id's for.
|
||||
* @param array<Indexable> $child_indexables The child indexables.
|
||||
*
|
||||
* @return array<int> List with object ids for the term.
|
||||
*/
|
||||
protected function get_object_ids_for_term( $term_id, $child_indexables ) {
|
||||
global $wpdb;
|
||||
|
||||
$filter_terms = static function ( $child ) {
|
||||
return $child->object_type === 'term';
|
||||
};
|
||||
|
||||
$child_terms = \array_filter( $child_indexables, $filter_terms );
|
||||
$child_object_ids = \array_merge( [ $term_id ], \wp_list_pluck( $child_terms, 'object_id' ) );
|
||||
|
||||
// Get the term-taxonomy id's for the term and its children.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$term_taxonomy_ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
'SELECT term_taxonomy_id
|
||||
FROM %i
|
||||
WHERE term_id IN( ' . \implode( ', ', \array_fill( 0, ( \count( $child_object_ids ) ), '%s' ) ) . ' )',
|
||||
$wpdb->term_taxonomy,
|
||||
...$child_object_ids
|
||||
)
|
||||
);
|
||||
|
||||
// In the case of faulty data having been saved the above query can return 0 results.
|
||||
if ( empty( $term_taxonomy_ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the (post) object id's that are attached to the term.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
return $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
'SELECT DISTINCT object_id
|
||||
FROM %i
|
||||
WHERE term_taxonomy_id IN( ' . \implode( ', ', \array_fill( 0, \count( $term_taxonomy_ids ), '%s' ) ) . ' )',
|
||||
$wpdb->term_relationships,
|
||||
...$term_taxonomy_ids
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Watches the disable-attachment key in wpseo_titles, in order to clear the permalink of the category indexables.
|
||||
*/
|
||||
class Indexable_Attachment_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* The attachment cleanup helper.
|
||||
*
|
||||
* @var Attachment_Cleanup_Helper
|
||||
*/
|
||||
protected $attachment_cleanup;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* The notifications center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
private $notification_center;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Attachment_Watcher constructor.
|
||||
*
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param Attachment_Cleanup_Helper $attachment_cleanup The attachment cleanup helper.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexing_Helper $indexing_helper,
|
||||
Attachment_Cleanup_Helper $attachment_cleanup,
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Indexable_Helper $indexable_helper
|
||||
) {
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->attachment_cleanup = $attachment_cleanup;
|
||||
$this->notification_center = $notification_center;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 20, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the disable-attachment key in wpseo_titles has a change in value, and if so,
|
||||
* either it cleans up attachment indexables when it has been toggled to true,
|
||||
* or it starts displaying a notification for the user to start a new SEO optimization.
|
||||
*
|
||||
* @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification
|
||||
*
|
||||
* @param array $old_value The old value of the wpseo_titles option.
|
||||
* @param array $new_value The new value of the wpseo_titles option.
|
||||
*
|
||||
* @phpcs:enable
|
||||
* @return void
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
// If this is the first time saving the option, in which case its value would be false.
|
||||
if ( $old_value === false ) {
|
||||
$old_value = [];
|
||||
}
|
||||
|
||||
// If either value is not an array, return.
|
||||
if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If both values aren't set, they haven't changed.
|
||||
if ( ! isset( $old_value['disable-attachment'] ) && ! isset( $new_value['disable-attachment'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a new value has been set for 'disable-attachment', there's two things we might need to do, depending on what's the new value.
|
||||
if ( $old_value['disable-attachment'] !== $new_value['disable-attachment'] ) {
|
||||
// Delete cache because we now might have new stuff to index or old unindexed stuff don't need indexing anymore.
|
||||
\delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
|
||||
\delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT );
|
||||
|
||||
// Set this core option (introduced in WP 6.4) to ensure consistency.
|
||||
if ( \get_option( 'wp_attachment_pages_enabled' ) !== false ) {
|
||||
\update_option( 'wp_attachment_pages_enabled', (int) ! $new_value['disable-attachment'] );
|
||||
}
|
||||
|
||||
switch ( $new_value['disable-attachment'] ) {
|
||||
case false:
|
||||
$this->indexing_helper->set_reason( Indexing_Reasons::REASON_ATTACHMENTS_MADE_ENABLED );
|
||||
return;
|
||||
case true:
|
||||
$this->attachment_cleanup->remove_attachment_indexables( false );
|
||||
$this->attachment_cleanup->clean_attachment_links_from_target_indexable_ids( false );
|
||||
|
||||
if ( $this->indexable_helper->should_index_indexables() && ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
|
||||
// This just schedules the cleanup routine cron again.
|
||||
\wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Watches the `wpseo_titles` option for changes to the author archive settings.
|
||||
*/
|
||||
class Indexable_Author_Archive_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* Indexable_Author_Archive_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*/
|
||||
public function __construct( Indexable_Helper $indexable_helper ) {
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the author archives are disabled whenever the `wpseo_titles` option
|
||||
* changes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action(
|
||||
'update_option_wpseo_titles',
|
||||
[ $this, 'reschedule_indexable_cleanup_when_author_archives_are_disabled' ],
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This watcher should only be run when the migrations have been run.
|
||||
* (Otherwise there may not be an indexable table to clean).
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule the indexable cleanup routine if the author archives are disabled.
|
||||
* to make sure that all authors are removed from the indexables table.
|
||||
*
|
||||
* When author archives are disabled, they can never be indexed.
|
||||
*
|
||||
* @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification
|
||||
*
|
||||
* @param array $old_value The old `wpseo_titles` option value.
|
||||
* @param array $new_value The new `wpseo_titles` option value.
|
||||
*
|
||||
* @phpcs:enable
|
||||
* @return void
|
||||
*/
|
||||
public function reschedule_indexable_cleanup_when_author_archives_are_disabled( $old_value, $new_value ) {
|
||||
if ( $old_value['disable-author'] !== true && $new_value['disable-author'] === true && $this->indexable_helper->should_index_indexables() ) {
|
||||
$cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK );
|
||||
if ( $cleanup_not_yet_scheduled ) {
|
||||
\wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Watches an Author to save the meta information to an Indexable when updated.
|
||||
*/
|
||||
class Indexable_Author_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Author_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Indexable_Builder $builder The builder to use.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Indexable_Builder $builder
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'user_register', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'profile_update', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'deleted_user', [ $this, 'handle_user_delete' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes user meta.
|
||||
*
|
||||
* @param int $user_id User ID to delete the metadata of.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_indexable( $user_id ) {
|
||||
$indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false );
|
||||
|
||||
if ( ! $indexable ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable->delete();
|
||||
\do_action( 'wpseo_indexable_deleted', $indexable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves user meta.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable( $user_id ) {
|
||||
$indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false );
|
||||
$indexable = $this->builder->build_for_id_and_type( $user_id, 'user', $indexable );
|
||||
|
||||
if ( $indexable ) {
|
||||
$indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) );
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the case in which an author is deleted.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int|null $new_user_id The ID of the user the old author's posts are reassigned to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_user_delete( $user_id, $new_user_id = null ) {
|
||||
if ( $new_user_id !== null ) {
|
||||
$this->maybe_reassign_user_indexables( $user_id, $new_user_id );
|
||||
}
|
||||
|
||||
$this->delete_indexable( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reassigns the indexables of a user to another user.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @param int $new_user_id The user ID to reassign the indexables to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_reassign_user_indexables( $user_id, $new_user_id ) {
|
||||
$this->repository->query()
|
||||
->set( 'author_id', $new_user_id )
|
||||
->where( 'author_id', $user_id )
|
||||
->update_many();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
|
||||
/**
|
||||
* Watches the stripcategorybase key in wpseo_titles, in order to clear the permalink of the category indexables.
|
||||
*/
|
||||
class Indexable_Category_Permalink_Watcher extends Indexable_Permalink_Watcher {
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the stripcategorybase key in wpseo_titles has a change in value, and if so,
|
||||
* clears the permalink for category indexables.
|
||||
*
|
||||
* @param array $old_value The old value of the wpseo_titles option.
|
||||
* @param array $new_value The new value of the wpseo_titles option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
// If this is the first time saving the option, in which case its value would be false.
|
||||
if ( $old_value === false ) {
|
||||
$old_value = [];
|
||||
}
|
||||
|
||||
// If either value is not an array, return.
|
||||
if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If both values aren't set, they haven't changed.
|
||||
if ( ! isset( $old_value['stripcategorybase'] ) && ! isset( $new_value['stripcategorybase'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a new value has been set for 'stripcategorybase', clear the category permalinks.
|
||||
if ( $old_value['stripcategorybase'] !== $new_value['stripcategorybase'] ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', 'category', Indexing_Reasons::REASON_CATEGORY_BASE_PREFIX );
|
||||
// Clear the rewrites, so the new permalink structure is used.
|
||||
WPSEO_Utils::clear_rewrites();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Date archive watcher to save the meta data to an indexable.
|
||||
*
|
||||
* Watches the date archive options to save the meta information when updated.
|
||||
*/
|
||||
class Indexable_Date_Archive_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Date_Archive_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Builder $builder The date archive builder to use.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $repository, Indexable_Builder $builder ) {
|
||||
$this->repository = $repository;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the date archive indexable needs to be rebuild based on option values.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
$relevant_keys = [ 'title-archive-wpseo', 'breadcrumbs-archiveprefix', 'metadesc-archive-wpseo', 'noindex-archive-wpseo' ];
|
||||
|
||||
foreach ( $relevant_keys as $key ) {
|
||||
// If both values aren't set they haven't changed.
|
||||
if ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value was set but now isn't, is set but wasn't or is not the same it has changed.
|
||||
if ( ! isset( $old_value[ $key ] ) || ! isset( $new_value[ $key ] ) || $old_value[ $key ] !== $new_value[ $key ] ) {
|
||||
$this->build_indexable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the date archive.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable() {
|
||||
$indexable = $this->repository->find_for_date_archive( false );
|
||||
$this->builder->build_for_date_archive( $indexable );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Home page watcher to save the meta data to an Indexable.
|
||||
*
|
||||
* Watches the home page options to save the meta information when updated.
|
||||
*/
|
||||
class Indexable_Home_Page_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Home_Page_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Indexable_Builder $builder The post builder to use.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Indexable_Builder $builder
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 15, 3 );
|
||||
\add_action( 'update_option_wpseo_social', [ $this, 'check_option' ], 15, 3 );
|
||||
\add_action( 'update_option_blog_public', [ $this, 'build_indexable' ] );
|
||||
\add_action( 'update_option_blogdescription', [ $this, 'build_indexable' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the home page indexable needs to be rebuild based on option values.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
* @param string $option The name of the option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_option( $old_value, $new_value, $option ) {
|
||||
$relevant_keys = [
|
||||
'wpseo_titles' => [
|
||||
'title-home-wpseo',
|
||||
'breadcrumbs-home',
|
||||
'metadesc-home-wpseo',
|
||||
'open_graph_frontpage_title',
|
||||
'open_graph_frontpage_desc',
|
||||
'open_graph_frontpage_image',
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! isset( $relevant_keys[ $option ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $relevant_keys[ $option ] as $key ) {
|
||||
// If both values aren't set they haven't changed.
|
||||
if ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value was set but now isn't, is set but wasn't or is not the same it has changed.
|
||||
if ( ! isset( $old_value[ $key ] ) || ! isset( $new_value[ $key ] ) || $old_value[ $key ] !== $new_value[ $key ] ) {
|
||||
$this->build_indexable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the home page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable() {
|
||||
$indexable = $this->repository->find_for_home_page( false );
|
||||
|
||||
if ( $indexable === false && ! $this->indexable_helper->should_index_indexables() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable = $this->builder->build_for_home_page( $indexable );
|
||||
|
||||
if ( $indexable ) {
|
||||
$indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use WP_CLI;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Home url option watcher.
|
||||
*
|
||||
* Handles updates to the home URL option for the Indexables table.
|
||||
*/
|
||||
class Indexable_HomeUrl_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_HomeUrl_Watcher constructor.
|
||||
*
|
||||
* @param Post_Type_Helper $post_type The post type helper.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Indexable_Helper $indexable The indexable helper.
|
||||
*/
|
||||
public function __construct( Post_Type_Helper $post_type, Options_Helper $options, Indexable_Helper $indexable ) {
|
||||
$this->post_type = $post_type;
|
||||
$this->options_helper = $options;
|
||||
$this->indexable_helper = $indexable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_home', [ $this, 'reset_permalinks' ] );
|
||||
\add_action( 'wpseo_permalink_structure_check', [ $this, 'force_reset_permalinks' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the permalinks for everything that is related to the permalink structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_permalinks() {
|
||||
$this->indexable_helper->reset_permalink_indexables( null, null, Indexing_Reasons::REASON_HOME_URL_OPTION );
|
||||
|
||||
// Reset the home_url option.
|
||||
$this->options_helper->set( 'home_url', \get_home_url() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the permalink indexables automatically, if necessary.
|
||||
*
|
||||
* @return bool Whether the request ran.
|
||||
*/
|
||||
public function force_reset_permalinks() {
|
||||
if ( $this->should_reset_permalinks() ) {
|
||||
$this->reset_permalinks();
|
||||
|
||||
if ( \defined( 'WP_CLI' ) && \WP_CLI ) {
|
||||
WP_CLI::success( \__( 'All permalinks were successfully reset', 'wordpress-seo' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether permalinks should be reset.
|
||||
*
|
||||
* @return bool Whether the permalinks should be reset.
|
||||
*/
|
||||
public function should_reset_permalinks() {
|
||||
return \get_home_url() !== $this->options_helper->get( 'home_url' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* WordPress Permalink structure watcher.
|
||||
*
|
||||
* Handles updates to the permalink_structure for the Indexables table.
|
||||
*/
|
||||
class Indexable_Permalink_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The taxonomy helper.
|
||||
*
|
||||
* @var Taxonomy_Helper
|
||||
*/
|
||||
protected $taxonomy_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
protected $indexable_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Permalink_Watcher constructor.
|
||||
*
|
||||
* @param Post_Type_Helper $post_type The post type helper.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Indexable_Helper $indexable The indexable helper.
|
||||
* @param Taxonomy_Helper $taxonomy_helper The taxonomy helper.
|
||||
*/
|
||||
public function __construct( Post_Type_Helper $post_type, Options_Helper $options, Indexable_Helper $indexable, Taxonomy_Helper $taxonomy_helper ) {
|
||||
$this->post_type = $post_type;
|
||||
$this->options_helper = $options;
|
||||
$this->indexable_helper = $indexable;
|
||||
$this->taxonomy_helper = $taxonomy_helper;
|
||||
|
||||
$this->schedule_cron();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_permalink_structure', [ $this, 'reset_permalinks' ] );
|
||||
\add_action( 'update_option_category_base', [ $this, 'reset_permalinks_term' ], 10, 3 );
|
||||
\add_action( 'update_option_tag_base', [ $this, 'reset_permalinks_term' ], 10, 3 );
|
||||
\add_action( 'wpseo_permalink_structure_check', [ $this, 'force_reset_permalinks' ] );
|
||||
\add_action( 'wpseo_deactivate', [ $this, 'unschedule_cron' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the permalinks for everything that is related to the permalink structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_permalinks() {
|
||||
|
||||
$post_types = $this->get_post_types();
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$this->reset_permalinks_post_type( $post_type );
|
||||
}
|
||||
|
||||
$taxonomies = $this->get_taxonomies_for_post_types( $post_types );
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', $taxonomy );
|
||||
}
|
||||
|
||||
$this->indexable_helper->reset_permalink_indexables( 'user' );
|
||||
$this->indexable_helper->reset_permalink_indexables( 'date-archive' );
|
||||
$this->indexable_helper->reset_permalink_indexables( 'system-page' );
|
||||
|
||||
// Always update `permalink_structure` in the wpseo option.
|
||||
$this->options_helper->set( 'permalink_structure', \get_option( 'permalink_structure' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the permalink for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type to reset.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_permalinks_post_type( $post_type ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'post', $post_type );
|
||||
$this->indexable_helper->reset_permalink_indexables( 'post-type-archive', $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the term indexables when the base has been changed.
|
||||
*
|
||||
* @param string $old_value Unused. The old option value.
|
||||
* @param string $new_value Unused. The new option value.
|
||||
* @param string $type The option name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_permalinks_term( $old_value, $new_value, $type ) {
|
||||
$subtype = $type;
|
||||
|
||||
$reason = Indexing_Reasons::REASON_PERMALINK_SETTINGS;
|
||||
|
||||
// When the subtype contains _base, just strip it.
|
||||
if ( \strstr( $subtype, '_base' ) ) {
|
||||
$subtype = \substr( $type, 0, -5 );
|
||||
}
|
||||
|
||||
if ( $subtype === 'tag' ) {
|
||||
$subtype = 'post_tag';
|
||||
$reason = Indexing_Reasons::REASON_TAG_BASE_PREFIX;
|
||||
}
|
||||
|
||||
if ( $subtype === 'category' ) {
|
||||
$reason = Indexing_Reasons::REASON_CATEGORY_BASE_PREFIX;
|
||||
}
|
||||
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', $subtype, $reason );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the permalink indexables automatically, if necessary.
|
||||
*
|
||||
* @return bool Whether the reset request ran.
|
||||
*/
|
||||
public function force_reset_permalinks() {
|
||||
if ( \get_option( 'tag_base' ) !== $this->options_helper->get( 'tag_base_url' ) ) {
|
||||
$this->reset_permalinks_term( null, null, 'tag_base' );
|
||||
$this->options_helper->set( 'tag_base_url', \get_option( 'tag_base' ) );
|
||||
}
|
||||
if ( \get_option( 'category_base' ) !== $this->options_helper->get( 'category_base_url' ) ) {
|
||||
$this->reset_permalinks_term( null, null, 'category_base' );
|
||||
$this->options_helper->set( 'category_base_url', \get_option( 'category_base' ) );
|
||||
}
|
||||
|
||||
if ( $this->should_reset_permalinks() ) {
|
||||
$this->reset_permalinks();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->reset_altered_custom_taxonomies();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the permalinks should be reset after `permalink_structure` has changed.
|
||||
*
|
||||
* @return bool Whether the permalinks should be reset.
|
||||
*/
|
||||
public function should_reset_permalinks() {
|
||||
return \get_option( 'permalink_structure' ) !== $this->options_helper->get( 'permalink_structure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets custom taxonomies if their slugs have changed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_altered_custom_taxonomies() {
|
||||
$taxonomies = $this->taxonomy_helper->get_custom_taxonomies();
|
||||
$custom_taxonomy_bases = $this->options_helper->get( 'custom_taxonomy_slugs', [] );
|
||||
$new_taxonomy_bases = [];
|
||||
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$taxonomy_slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
|
||||
|
||||
$new_taxonomy_bases[ $taxonomy ] = $taxonomy_slug;
|
||||
|
||||
if ( ! \array_key_exists( $taxonomy, $custom_taxonomy_bases ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $taxonomy_slug !== $custom_taxonomy_bases[ $taxonomy ] ) {
|
||||
$this->indexable_helper->reset_permalink_indexables( 'term', $taxonomy );
|
||||
}
|
||||
}
|
||||
|
||||
$this->options_helper->set( 'custom_taxonomy_slugs', $new_taxonomy_bases );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list with the public post types.
|
||||
*
|
||||
* @return array The post types.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
/**
|
||||
* Filter: Gives the possibility to filter out post types.
|
||||
*
|
||||
* @param array $post_types The post type names.
|
||||
*
|
||||
* @return array The post types.
|
||||
*/
|
||||
$post_types = \apply_filters( 'wpseo_post_types_reset_permalinks', $this->post_type->get_public_post_types() );
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the taxonomies that belongs to the public post types.
|
||||
*
|
||||
* @param array $post_types The post types to get taxonomies for.
|
||||
*
|
||||
* @return array The retrieved taxonomies.
|
||||
*/
|
||||
protected function get_taxonomies_for_post_types( $post_types ) {
|
||||
$taxonomies = [];
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$taxonomies[] = \get_object_taxonomies( $post_type, 'names' );
|
||||
}
|
||||
|
||||
$taxonomies = \array_merge( [], ...$taxonomies );
|
||||
$taxonomies = \array_unique( $taxonomies );
|
||||
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the WP-Cron job to check the permalink_structure status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule_cron() {
|
||||
if ( \wp_next_scheduled( 'wpseo_permalink_structure_check' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_schedule_event( \time(), 'daily', 'wpseo_permalink_structure_check' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedules the WP-Cron job to check the permalink_structure status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unschedule_cron() {
|
||||
if ( ! \wp_next_scheduled( 'wpseo_permalink_structure_check' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_clear_scheduled_hook( 'wpseo_permalink_structure_check' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use WPSEO_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* WordPress post meta watcher.
|
||||
*/
|
||||
class Indexable_Post_Meta_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The post watcher.
|
||||
*
|
||||
* @var Indexable_Post_Watcher
|
||||
*/
|
||||
protected $post_watcher;
|
||||
|
||||
/**
|
||||
* An array of post IDs that need to be updated.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
protected $post_ids_to_update = [];
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Postmeta_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Post_Watcher $post_watcher The post watcher.
|
||||
*/
|
||||
public function __construct( Indexable_Post_Watcher $post_watcher ) {
|
||||
$this->post_watcher = $post_watcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Register all posts whose meta have changed.
|
||||
\add_action( 'added_post_meta', [ $this, 'add_post_id' ], 10, 3 );
|
||||
\add_action( 'updated_post_meta', [ $this, 'add_post_id' ], 10, 3 );
|
||||
\add_action( 'deleted_post_meta', [ $this, 'add_post_id' ], 10, 3 );
|
||||
|
||||
// Remove posts that get saved as they are handled by the Indexable_Post_Watcher.
|
||||
\add_action( 'wp_insert_post', [ $this, 'remove_post_id' ] );
|
||||
\add_action( 'delete_post', [ $this, 'remove_post_id' ] );
|
||||
\add_action( 'edit_attachment', [ $this, 'remove_post_id' ] );
|
||||
\add_action( 'add_attachment', [ $this, 'remove_post_id' ] );
|
||||
\add_action( 'delete_attachment', [ $this, 'remove_post_id' ] );
|
||||
|
||||
// Update indexables of all registered posts.
|
||||
\register_shutdown_function( [ $this, 'update_indexables' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a post id to the array of posts to update.
|
||||
*
|
||||
* @param int|string $meta_id The meta ID.
|
||||
* @param int|string $post_id The post ID.
|
||||
* @param string $meta_key The meta key.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_post_id( $meta_id, $post_id, $meta_key ) {
|
||||
// Only register changes to our own meta.
|
||||
if ( \is_string( $meta_key ) && \strpos( $meta_key, WPSEO_Meta::$meta_prefix ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \in_array( $post_id, $this->post_ids_to_update, true ) ) {
|
||||
$this->post_ids_to_update[] = (int) $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a post id from the array of posts to update.
|
||||
*
|
||||
* @param int|string $post_id The post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_post_id( $post_id ) {
|
||||
$this->post_ids_to_update = \array_diff( $this->post_ids_to_update, [ (int) $post_id ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all indexables changed during the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_indexables() {
|
||||
foreach ( $this->post_ids_to_update as $post_id ) {
|
||||
$this->post_watcher->build_indexable( $post_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Post type archive watcher to save the meta data to an Indexable.
|
||||
*
|
||||
* Watches the home page options to save the meta information when updated.
|
||||
*/
|
||||
class Indexable_Post_Type_Archive_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Post_Type_Archive_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Indexable_Builder $builder The post builder to use.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Indexable_Builder $builder
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the home page indexable needs to be rebuild based on option values.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether or not the option has been saved.
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
$relevant_keys = [ 'title-ptarchive-', 'metadesc-ptarchive-', 'bctitle-ptarchive-', 'noindex-ptarchive-' ];
|
||||
|
||||
// If this is the first time saving the option, thus when value is false.
|
||||
if ( $old_value === false ) {
|
||||
$old_value = [];
|
||||
}
|
||||
|
||||
if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$keys = \array_unique( \array_merge( \array_keys( $old_value ), \array_keys( $new_value ) ) );
|
||||
$post_types_rebuild = [];
|
||||
|
||||
foreach ( $keys as $key ) {
|
||||
$post_type = false;
|
||||
// Check if it's a key relevant to post type archives.
|
||||
foreach ( $relevant_keys as $relevant_key ) {
|
||||
if ( \strpos( $key, $relevant_key ) === 0 ) {
|
||||
$post_type = \substr( $key, \strlen( $relevant_key ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's not a relevant key or both values aren't set they haven't changed.
|
||||
if ( $post_type === false || ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value was set but now isn't, is set but wasn't or is not the same it has changed.
|
||||
if (
|
||||
! \in_array( $post_type, $post_types_rebuild, true )
|
||||
&& (
|
||||
! isset( $old_value[ $key ] )
|
||||
|| ! isset( $new_value[ $key ] )
|
||||
|| $old_value[ $key ] !== $new_value[ $key ]
|
||||
)
|
||||
) {
|
||||
$this->build_indexable( $post_type );
|
||||
$post_types_rebuild[] = $post_type;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the post type archive.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable( $post_type ) {
|
||||
$indexable = $this->repository->find_for_post_type_archive( $post_type, false );
|
||||
$indexable = $this->builder->build_for_post_type_archive( $post_type, $indexable );
|
||||
|
||||
if ( $indexable ) {
|
||||
$indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) );
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Post type change watcher.
|
||||
*/
|
||||
class Indexable_Post_Type_Change_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Holds the Post_Type_Helper instance.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* The notifications center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
private $notification_center;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Not_Admin_Ajax_Conditional::class, Admin_Conditional::class, Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Post_Type_Change_Watcher constructor.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param Post_Type_Helper $post_type_helper The post_typehelper.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options,
|
||||
Indexing_Helper $indexing_helper,
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Indexable_Helper $indexable_helper
|
||||
) {
|
||||
$this->options = $options;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->notification_center = $notification_center;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'check_post_types_public_availability' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if one or more post types change visibility.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_post_types_public_availability() {
|
||||
|
||||
// We have to make sure this is just a plain http request, no ajax/REST.
|
||||
if ( \wp_is_json_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$public_post_types = $this->post_type_helper->get_indexable_post_types();
|
||||
|
||||
$last_known_public_post_types = $this->options->get( 'last_known_public_post_types', [] );
|
||||
|
||||
// Initializing the option on the first run.
|
||||
if ( empty( $last_known_public_post_types ) ) {
|
||||
$this->options->set( 'last_known_public_post_types', $public_post_types );
|
||||
return;
|
||||
}
|
||||
|
||||
// We look for new public post types.
|
||||
$newly_made_public_post_types = \array_diff( $public_post_types, $last_known_public_post_types );
|
||||
// We look for post types that from public have been made private.
|
||||
$newly_made_non_public_post_types = \array_diff( $last_known_public_post_types, $public_post_types );
|
||||
|
||||
// Nothing to be done if no changes has been made to post types.
|
||||
if ( empty( $newly_made_public_post_types ) && ( empty( $newly_made_non_public_post_types ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the list of last known public post types in the database.
|
||||
$this->options->set( 'last_known_public_post_types', $public_post_types );
|
||||
|
||||
// There are new post types that have been made public.
|
||||
if ( $newly_made_public_post_types ) {
|
||||
|
||||
// Force a notification requesting to start the SEO data optimization.
|
||||
\delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
|
||||
\delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT );
|
||||
|
||||
$this->indexing_helper->set_reason( Indexing_Reasons::REASON_POST_TYPE_MADE_PUBLIC );
|
||||
|
||||
\do_action( 'new_public_post_type_notifications', $newly_made_public_post_types );
|
||||
}
|
||||
|
||||
// There are post types that have been made private.
|
||||
if ( $newly_made_non_public_post_types && $this->indexable_helper->should_index_indexables() ) {
|
||||
// Schedule a cron job to remove all the posts whose post type has been made private.
|
||||
$cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK );
|
||||
if ( $cleanup_not_yet_scheduled ) {
|
||||
\wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
|
||||
}
|
||||
|
||||
\do_action( 'clean_new_public_post_type_notifications', $newly_made_non_public_post_types );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Exception;
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Builders\Indexable_Link_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Loggers\Logger;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use YoastSEO_Vendor\Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* WordPress Post watcher.
|
||||
*
|
||||
* Fills the Indexable according to Post data.
|
||||
*/
|
||||
class Indexable_Post_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* The indexable hierarchy repository.
|
||||
*
|
||||
* @var Indexable_Hierarchy_Repository
|
||||
*/
|
||||
private $hierarchy_repository;
|
||||
|
||||
/**
|
||||
* The link builder.
|
||||
*
|
||||
* @var Indexable_Link_Builder
|
||||
*/
|
||||
protected $link_builder;
|
||||
|
||||
/**
|
||||
* The author archive helper.
|
||||
*
|
||||
* @var Author_Archive_Helper
|
||||
*/
|
||||
private $author_archive;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Holds the Post_Helper instance.
|
||||
*
|
||||
* @var Post_Helper
|
||||
*/
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* Holds the logger.
|
||||
*
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<string> The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Post_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Builder $builder The post builder to use.
|
||||
* @param Indexable_Hierarchy_Repository $hierarchy_repository The hierarchy repository to use.
|
||||
* @param Indexable_Link_Builder $link_builder The link builder.
|
||||
* @param Author_Archive_Helper $author_archive The author archive helper.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Post_Helper $post The post helper.
|
||||
* @param Logger $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $repository,
|
||||
Indexable_Builder $builder,
|
||||
Indexable_Hierarchy_Repository $hierarchy_repository,
|
||||
Indexable_Link_Builder $link_builder,
|
||||
Author_Archive_Helper $author_archive,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Post_Helper $post,
|
||||
Logger $logger
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->builder = $builder;
|
||||
$this->hierarchy_repository = $hierarchy_repository;
|
||||
$this->link_builder = $link_builder;
|
||||
$this->author_archive = $author_archive;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->post = $post;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp_insert_post', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'delete_post', [ $this, 'delete_indexable' ] );
|
||||
|
||||
\add_action( 'edit_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'add_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'delete_attachment', [ $this, 'delete_indexable' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the meta when a post is deleted.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_indexable( $post_id ) {
|
||||
$indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
|
||||
|
||||
// Only interested in post indexables.
|
||||
if ( ! $indexable || $indexable->object_type !== 'post' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->update_relations( $this->post->get_post( $post_id ) );
|
||||
|
||||
$this->update_has_public_posts( $indexable );
|
||||
|
||||
$this->hierarchy_repository->clear_ancestors( $indexable->id );
|
||||
$this->link_builder->delete( $indexable );
|
||||
$indexable->delete();
|
||||
\do_action( 'wpseo_indexable_deleted', $indexable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the relations when the post indexable is built.
|
||||
*
|
||||
* @param Indexable $indexable The indexable.
|
||||
* @param WP_Post $post The post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updated_indexable( $indexable, $post ) {
|
||||
// Only interested in post indexables.
|
||||
if ( $indexable->object_type !== 'post' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \is_a( $post, Indexable::class ) ) {
|
||||
\_deprecated_argument( __FUNCTION__, '17.7', 'The $old_indexable argument has been deprecated.' );
|
||||
$post = $this->post->get_post( $indexable->object_id );
|
||||
}
|
||||
|
||||
$this->update_relations( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves post meta.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable( $post_id ) {
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( $this->is_multisite_and_switched() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
|
||||
$indexable = $this->builder->build_for_id_and_type( $post_id, 'post', $indexable );
|
||||
|
||||
$post = $this->post->get_post( $post_id );
|
||||
|
||||
/*
|
||||
* Update whether an author has public posts.
|
||||
* For example this post could be set to Draft or Private,
|
||||
* which can influence if its author has any public posts at all.
|
||||
*/
|
||||
if ( $indexable ) {
|
||||
$this->update_has_public_posts( $indexable );
|
||||
}
|
||||
|
||||
// Build links for this post.
|
||||
if ( $post && $indexable && \in_array( $post->post_status, $this->post->get_public_post_statuses(), true ) ) {
|
||||
$this->link_builder->build( $indexable, $post->post_content );
|
||||
// Save indexable to persist the updated link count.
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
$this->updated_indexable( $indexable, $post );
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
$this->logger->log( LogLevel::ERROR, $exception->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the has_public_posts when the post indexable is built.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function update_has_public_posts( $indexable ) {
|
||||
// Update the author indexable's has public posts value.
|
||||
try {
|
||||
$author_indexable = $this->repository->find_by_id_and_type( $indexable->author_id, 'user' );
|
||||
if ( $author_indexable ) {
|
||||
$author_indexable->has_public_posts = $this->author_archive->author_has_public_posts( $author_indexable->object_id );
|
||||
$this->indexable_helper->save_indexable( $author_indexable );
|
||||
|
||||
if ( $this->indexable_helper->should_index_indexable( $author_indexable ) ) {
|
||||
$this->reschedule_cleanup_if_author_has_no_posts( $author_indexable );
|
||||
}
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
$this->logger->log( LogLevel::ERROR, $exception->getMessage() );
|
||||
}
|
||||
|
||||
// Update possible attachment's has public posts value.
|
||||
$this->post->update_has_public_posts_on_attachments( $indexable->object_id, $indexable->is_public );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule indexable cleanup if the author does not have any public posts.
|
||||
* This should remove the author from the indexable table, since we do not
|
||||
* want to store authors without public facing posts in the table.
|
||||
*
|
||||
* @param Indexable $author_indexable The author indexable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function reschedule_cleanup_if_author_has_no_posts( $author_indexable ) {
|
||||
if ( $author_indexable->has_public_posts === false ) {
|
||||
$cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK );
|
||||
if ( $cleanup_not_yet_scheduled ) {
|
||||
\wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the relations on post save or post status change.
|
||||
*
|
||||
* @param WP_Post $post The post that has been updated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function update_relations( $post ) {
|
||||
$related_indexables = $this->get_related_indexables( $post );
|
||||
|
||||
foreach ( $related_indexables as $indexable ) {
|
||||
// Ignore everything that is not an actual indexable.
|
||||
if ( \is_a( $indexable, Indexable::class ) ) {
|
||||
$indexable->object_last_modified = \max( $indexable->object_last_modified, $post->post_modified_gmt );
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the related indexables for given post.
|
||||
*
|
||||
* @param WP_Post $post The post to get the indexables for.
|
||||
*
|
||||
* @return Indexable[] The indexables.
|
||||
*/
|
||||
protected function get_related_indexables( $post ) {
|
||||
/**
|
||||
* The related indexables.
|
||||
*
|
||||
* @var Indexable[] $related_indexables
|
||||
*/
|
||||
$related_indexables = [];
|
||||
$related_indexables[] = $this->repository->find_by_id_and_type( $post->post_author, 'user', false );
|
||||
$related_indexables[] = $this->repository->find_for_post_type_archive( $post->post_type, false );
|
||||
$related_indexables[] = $this->repository->find_for_home_page( false );
|
||||
|
||||
$taxonomies = \get_post_taxonomies( $post->ID );
|
||||
$taxonomies = \array_filter( $taxonomies, 'is_taxonomy_viewable' );
|
||||
$term_ids = [];
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$terms = \get_the_terms( $post->ID, $taxonomy );
|
||||
|
||||
if ( empty( $terms ) || \is_wp_error( $terms ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term_ids = \array_merge( $term_ids, \wp_list_pluck( $terms, 'term_id' ) );
|
||||
}
|
||||
$related_indexables = \array_merge(
|
||||
$related_indexables,
|
||||
$this->repository->find_by_multiple_ids_and_type( $term_ids, 'term', false )
|
||||
);
|
||||
|
||||
return \array_filter( $related_indexables );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the site is multisite and switched.
|
||||
*
|
||||
* @return bool True when the site is multisite and switched
|
||||
*/
|
||||
protected function is_multisite_and_switched() {
|
||||
return \is_multisite() && \ms_is_switched();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Watcher that checks for changes in the page used as homepage.
|
||||
*
|
||||
* Watches the static homepage option and updates the permalinks accordingly.
|
||||
*/
|
||||
class Indexable_Static_Home_Page_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Static_Home_Page_Watcher constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $repository ) {
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_page_on_front', [ $this, 'update_static_homepage_permalink' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the new and previous homepage's permalink when the static home page is updated.
|
||||
*
|
||||
* @param string $old_value The previous homepage's ID.
|
||||
* @param int $value The new homepage's ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_static_homepage_permalink( $old_value, $value ) {
|
||||
if ( \is_string( $old_value ) ) {
|
||||
$old_value = (int) $old_value;
|
||||
}
|
||||
|
||||
if ( $old_value === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->update_permalink_for_page( $old_value );
|
||||
$this->update_permalink_for_page( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the permalink based on the selected homepage settings.
|
||||
*
|
||||
* @param int $page_id The page's id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function update_permalink_for_page( $page_id ) {
|
||||
if ( $page_id === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable = $this->repository->find_by_id_and_type( $page_id, 'post', false );
|
||||
|
||||
if ( $indexable === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable->permalink = \get_permalink( $page_id );
|
||||
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user