Merged in feature/from-pantheon (pull request #16)
code from pantheon * code from pantheon
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?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
|
||||
*/
|
||||
const WPSEO_FREE_PLUGIN_ID = 'wordpress-seo/wp-seo.php';
|
||||
|
||||
/**
|
||||
* A list of Yoast add-on identifiers.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,233 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use wpdb;
|
||||
use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Represents the WordPress database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* 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 wpdb $wpdb The wpdb object.
|
||||
* @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,
|
||||
wpdb $wpdb,
|
||||
Permalink_Helper $permalink_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->indexable_hierarchy_builder = $indexable_hierarchy_builder;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->indexable_hierarchy_repository = $indexable_hierarchy_repository;
|
||||
$this->permalink_helper = $permalink_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the appropriate hooks.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 || \is_null( $indexable_before->permalink ) ) {
|
||||
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 Indexable[] $child_indexables The already known child indexables.
|
||||
*
|
||||
* @return array 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.
|
||||
*/
|
||||
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 );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Indexable[] $child_indexables The child indexables.
|
||||
*
|
||||
* @return array List with object ids for the term.
|
||||
*/
|
||||
protected function get_object_ids_for_term( $term_id, $child_indexables ) {
|
||||
$filter_terms = static function( $child ) {
|
||||
return $child->object_type === 'term';
|
||||
};
|
||||
|
||||
$child_terms = \array_filter( $child_indexables, $filter_terms );
|
||||
$child_object_ids = \wp_list_pluck( $child_terms, 'object_id' );
|
||||
|
||||
// Get the term-taxonomy id's for the term and its children.
|
||||
$term_taxonomy_ids = $this->wpdb->get_col(
|
||||
$this->wpdb->prepare(
|
||||
'SELECT term_taxonomy_id
|
||||
FROM ' . $this->wpdb->term_taxonomy . '
|
||||
WHERE term_id IN( ' . \implode( ', ', \array_fill( 0, ( \count( $child_object_ids ) + 1 ), '%s' ) ) . ' )',
|
||||
$term_id,
|
||||
...$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.
|
||||
return $this->wpdb->get_col(
|
||||
$this->wpdb->prepare(
|
||||
'SELECT DISTINCT object_id
|
||||
FROM ' . $this->wpdb->term_relationships . '
|
||||
WHERE term_taxonomy_id IN( ' . \implode( ', ', \array_fill( 0, \count( $term_taxonomy_ids ), '%s' ) ) . ' )',
|
||||
...$term_taxonomy_ids
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?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\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 notifications center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
private $notification_center;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexing_Helper $indexing_helper,
|
||||
Attachment_Cleanup_Helper $attachment_cleanup,
|
||||
Yoast_Notification_Center $notification_center
|
||||
) {
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->attachment_cleanup = $attachment_cleanup;
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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['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 ( ! \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,58 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param array $old_value The old `wpseo_titles` option value.
|
||||
* @param array $new_value The new `wpseo_titles` option value.
|
||||
*
|
||||
* @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 ) {
|
||||
$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,125 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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_Builder $builder The 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.
|
||||
*/
|
||||
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' ) );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,53 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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.
|
||||
*/
|
||||
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,116 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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 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_Builder $builder The post 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.
|
||||
*/
|
||||
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 );
|
||||
$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,273 @@
|
||||
<?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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,109 @@
|
||||
<?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
|
||||
*/
|
||||
protected $post_ids_to_update = [];
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 ( \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,131 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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 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_Builder $builder The post 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.
|
||||
*/
|
||||
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' ) );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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\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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options,
|
||||
Indexing_Helper $indexing_helper,
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Yoast_Notification_Center $notification_center
|
||||
) {
|
||||
$this->options = $options;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// 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,321 @@
|
||||
<?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\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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 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,
|
||||
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->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.
|
||||
*/
|
||||
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 );
|
||||
|
||||
$indexable->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
$indexable->save();
|
||||
$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.
|
||||
*/
|
||||
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 );
|
||||
$author_indexable->save();
|
||||
|
||||
$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.
|
||||
*/
|
||||
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 );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,91 @@
|
||||
<?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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Builders\Indexable_Builder;
|
||||
use Yoast\WP\SEO\Builders\Indexable_System_Page_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Search result watcher to save the meta data to an Indexable.
|
||||
*
|
||||
* Watches the search result options to save the meta information when updated.
|
||||
*/
|
||||
class Indexable_System_Page_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_System_Page_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Builder $builder The post 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.
|
||||
*/
|
||||
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 void
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
foreach ( Indexable_System_Page_Builder::OPTION_MAPPING as $type => $options ) {
|
||||
foreach ( $options as $option ) {
|
||||
// If both values aren't set they haven't changed.
|
||||
if ( ! isset( $old_value[ $option ] ) && ! isset( $new_value[ $option ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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[ $option ] )
|
||||
|| ! isset( $new_value[ $option ] )
|
||||
|| $old_value[ $option ] !== $new_value[ $option ]
|
||||
) {
|
||||
$this->build_indexable( $type );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the search result.
|
||||
*
|
||||
* @param string $type The type of no index page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable( $type ) {
|
||||
$indexable = $this->repository->find_for_system_page( $type, false );
|
||||
$this->builder->build_for_system_page( $type, $indexable );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_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\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Taxonomy watcher.
|
||||
*
|
||||
* Responds to changes in taxonomies public availability.
|
||||
*/
|
||||
class Indexable_Taxonomy_Change_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Holds the Taxonomy_Helper instance.
|
||||
*
|
||||
* @var Taxonomy_Helper
|
||||
*/
|
||||
private $taxonomy_helper;
|
||||
|
||||
/**
|
||||
* The notifications center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
private $notification_center;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Not_Admin_Ajax_Conditional::class, Admin_Conditional::class, Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Taxonomy_Change_Watcher constructor.
|
||||
*
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Taxonomy_Helper $taxonomy_helper The taxonomy helper.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexing_Helper $indexing_helper,
|
||||
Options_Helper $options,
|
||||
Taxonomy_Helper $taxonomy_helper,
|
||||
Yoast_Notification_Center $notification_center
|
||||
) {
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
$this->options = $options;
|
||||
$this->taxonomy_helper = $taxonomy_helper;
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'check_taxonomy_public_availability' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if one or more taxonomies change visibility.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_taxonomy_public_availability() {
|
||||
|
||||
// We have to make sure this is just a plain http request, no ajax/REST.
|
||||
if ( \wp_is_json_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies();
|
||||
|
||||
$last_known_public_taxonomies = $this->options->get( 'last_known_public_taxonomies', [] );
|
||||
|
||||
// Initializing the option on the first run.
|
||||
if ( empty( $last_known_public_taxonomies ) ) {
|
||||
$this->options->set( 'last_known_public_taxonomies', $public_taxonomies );
|
||||
return;
|
||||
}
|
||||
|
||||
// We look for new public taxonomies.
|
||||
$newly_made_public_taxonomies = \array_diff( $public_taxonomies, $last_known_public_taxonomies );
|
||||
|
||||
// We look fortaxonomies that from public have been made private.
|
||||
$newly_made_non_public_taxonomies = \array_diff( $last_known_public_taxonomies, $public_taxonomies );
|
||||
|
||||
// Nothing to be done if no changes has been made to taxonomies.
|
||||
if ( empty( $newly_made_public_taxonomies ) && ( empty( $newly_made_non_public_taxonomies ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the list of last known public taxonomies in the database.
|
||||
$this->options->set( 'last_known_public_taxonomies', $public_taxonomies );
|
||||
|
||||
// There are new taxonomies that have been made public.
|
||||
if ( ! empty( $newly_made_public_taxonomies ) ) {
|
||||
|
||||
// Force a notification requesting to start the SEO data optimization.
|
||||
\delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
|
||||
\delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT );
|
||||
|
||||
$this->indexing_helper->set_reason( Indexing_Reasons::REASON_TAXONOMY_MADE_PUBLIC );
|
||||
do_action( 'new_public_taxonomy_notifications', $newly_made_public_taxonomies );
|
||||
}
|
||||
|
||||
// There are taxonomies that have been made private.
|
||||
if ( ! empty( $newly_made_non_public_taxonomies ) ) {
|
||||
// Schedule a cron job to remove all the terms whose taxonomy 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_taxonomy_notifications', $newly_made_non_public_taxonomies );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
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\Site_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Watches Terms/Taxonomies to fill the related Indexable.
|
||||
*/
|
||||
class Indexable_Term_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The indexable builder.
|
||||
*
|
||||
* @var Indexable_Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* The link builder.
|
||||
*
|
||||
* @var Indexable_Link_Builder
|
||||
*/
|
||||
protected $link_builder;
|
||||
|
||||
/**
|
||||
* Represents the site helper.
|
||||
*
|
||||
* @var Site_Helper
|
||||
*/
|
||||
protected $site;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexable_Term_Watcher constructor.
|
||||
*
|
||||
* @param Indexable_Repository $repository The repository to use.
|
||||
* @param Indexable_Builder $builder The post builder to use.
|
||||
* @param Indexable_Link_Builder $link_builder The lint builder to use.
|
||||
* @param Site_Helper $site The site helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $repository,
|
||||
Indexable_Builder $builder,
|
||||
Indexable_Link_Builder $link_builder,
|
||||
Site_Helper $site
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->builder = $builder;
|
||||
$this->link_builder = $link_builder;
|
||||
$this->site = $site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'created_term', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'edited_term', [ $this, 'build_indexable' ], \PHP_INT_MAX );
|
||||
\add_action( 'delete_term', [ $this, 'delete_indexable' ], \PHP_INT_MAX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a term from the index.
|
||||
*
|
||||
* @param int $term_id The Term ID to delete.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_indexable( $term_id ) {
|
||||
$indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false );
|
||||
|
||||
if ( ! $indexable ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable->delete();
|
||||
\do_action( 'wpseo_indexable_deleted', $indexable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the taxonomy meta data on save.
|
||||
*
|
||||
* @param int $term_id ID of the term to save data for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build_indexable( $term_id ) {
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( $this->site->is_multisite_and_switched() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$term = \get_term( $term_id );
|
||||
|
||||
if ( $term === null || \is_wp_error( $term ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \is_taxonomy_viewable( $term->taxonomy ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false );
|
||||
|
||||
// If we haven't found an existing indexable, create it. Otherwise update it.
|
||||
$indexable = $this->builder->build_for_id_and_type( $term_id, 'term', $indexable );
|
||||
|
||||
if ( ! $indexable ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update links.
|
||||
$this->link_builder->build( $indexable, $term->description );
|
||||
|
||||
$indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) );
|
||||
$indexable->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\WordPress\Wrapper;
|
||||
|
||||
/**
|
||||
* Watcher for the titles option.
|
||||
*
|
||||
* Represents the option titles watcher.
|
||||
*/
|
||||
class Option_Titles_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if one of the relevant options has been changed.
|
||||
*
|
||||
* @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 ancestors are removed.
|
||||
*/
|
||||
public function check_option( $old_value, $new_value ) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
$relevant_keys = $this->get_relevant_keys();
|
||||
if ( empty( $relevant_keys ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_types = [];
|
||||
|
||||
foreach ( $relevant_keys as $post_type => $relevant_option ) {
|
||||
// If both values aren't set they haven't changed.
|
||||
if ( ! isset( $old_value[ $relevant_option ] ) && ! isset( $new_value[ $relevant_option ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $old_value[ $relevant_option ] !== $new_value[ $relevant_option ] ) {
|
||||
$post_types[] = $post_type;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delete_ancestors( $post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant keys.
|
||||
*
|
||||
* @return array Array with the relevant keys.
|
||||
*/
|
||||
protected function get_relevant_keys() {
|
||||
$post_types = \get_post_types( [ 'public' => true ], 'names' );
|
||||
if ( ! \is_array( $post_types ) || $post_types === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$relevant_keys = [];
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$relevant_keys[ $post_type ] = 'post_types-' . $post_type . '-maintax';
|
||||
}
|
||||
|
||||
return $relevant_keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the ancestors for given post types.
|
||||
*
|
||||
* @param array $post_types The post types to remove hierarchy for.
|
||||
*
|
||||
* @return bool True when delete query was successful.
|
||||
*/
|
||||
protected function delete_ancestors( $post_types ) {
|
||||
if ( empty( $post_types ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$wpdb = Wrapper::get_wpdb();
|
||||
$total = \count( $post_types );
|
||||
$hierarchy_table = Model::get_table_name( 'Indexable_Hierarchy' );
|
||||
$indexable_table = Model::get_table_name( 'Indexable' );
|
||||
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
DELETE FROM `$hierarchy_table`
|
||||
WHERE indexable_id IN(
|
||||
SELECT id
|
||||
FROM `$indexable_table`
|
||||
WHERE object_type = 'post'
|
||||
AND object_sub_type IN( " . \implode( ', ', \array_fill( 0, $total, '%s' ) ) . ' )
|
||||
)',
|
||||
$post_types
|
||||
)
|
||||
);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Wordproof_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Watcher for the wpseo option.
|
||||
*
|
||||
* Represents the option wpseo watcher.
|
||||
*/
|
||||
class Option_Wpseo_Watcher implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Holds the WordProof helper instance.
|
||||
*
|
||||
* @var Wordproof_Helper
|
||||
*/
|
||||
protected $wordproof;
|
||||
|
||||
/**
|
||||
* The constructor for a watcher of WPSEO options.
|
||||
*
|
||||
* @param Wordproof_Helper $wordproof The WordProof helper instance.
|
||||
*/
|
||||
public function __construct( Wordproof_Helper $wordproof ) {
|
||||
$this->wordproof = $wordproof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_semrush_option_disabled' ], 10, 2 );
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_wincher_option_disabled' ], 10, 2 );
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_wordproof_option_disabled' ], 10, 2 );
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_toggle_usage_tracking' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the SEMrush integration is disabled; if so, deletes the tokens.
|
||||
*
|
||||
* We delete the tokens if the SEMrush integration is disabled, no matter if
|
||||
* the value has actually changed or not.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the SEMrush tokens have been deleted or not.
|
||||
*/
|
||||
public function check_semrush_option_disabled( $old_value, $new_value ) {
|
||||
return $this->check_token_option_disabled( 'semrush_integration_active', 'semrush_tokens', $new_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Wincher integration is disabled; if so, deletes the tokens
|
||||
* and website id.
|
||||
*
|
||||
* We delete them if the Wincher integration is disabled, no matter if the
|
||||
* value has actually changed or not.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the Wincher tokens have been deleted or not.
|
||||
*/
|
||||
public function check_wincher_option_disabled( $old_value, $new_value ) {
|
||||
$disabled = $this->check_token_option_disabled( 'wincher_integration_active', 'wincher_tokens', $new_value );
|
||||
if ( $disabled ) {
|
||||
\YoastSEO()->helpers->options->set( 'wincher_website_id', '' );
|
||||
}
|
||||
|
||||
return $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the WordProof integration is disabled; if so, deletes the tokens
|
||||
*
|
||||
* We delete them if the WordProof integration is disabled, no matter if the
|
||||
* value has actually changed or not.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the WordProof tokens have been deleted or not.
|
||||
*/
|
||||
public function check_wordproof_option_disabled( $old_value, $new_value ) {
|
||||
$disabled = $this->check_token_option_disabled( 'wordproof_integration_active', 'wordproof_tokens', $new_value );
|
||||
if ( $disabled ) {
|
||||
$this->wordproof->remove_site_options();
|
||||
}
|
||||
|
||||
return $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the usage tracking feature is toggled; if so, set an option to stop us from messing with it.
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the option is set.
|
||||
*/
|
||||
public function check_toggle_usage_tracking( $old_value, $new_value ) {
|
||||
$option_name = 'tracking';
|
||||
|
||||
if ( \array_key_exists( $option_name, $old_value ) && \array_key_exists( $option_name, $new_value ) && $old_value[ $option_name ] !== $new_value[ $option_name ] && $old_value['toggled_tracking'] === false ) {
|
||||
\YoastSEO()->helpers->options->set( 'toggled_tracking', true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed integration is disabled; if so, deletes the tokens.
|
||||
*
|
||||
* We delete the tokens if the integration is disabled, no matter if
|
||||
* the value has actually changed or not.
|
||||
*
|
||||
* @param string $integration_option The intergration option name.
|
||||
* @param string $target_option The target option to remove the tokens from.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the tokens have been deleted or not.
|
||||
*/
|
||||
protected function check_token_option_disabled( $integration_option, $target_option, $new_value ) {
|
||||
if ( \array_key_exists( $integration_option, $new_value ) && $new_value[ $integration_option ] === false ) {
|
||||
\YoastSEO()->helpers->options->set( $target_option, [] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use WP_Post;
|
||||
use WPSEO_Meta;
|
||||
use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Doing_Post_Quick_Edit_Save_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_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_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Primary_Term_Repository;
|
||||
|
||||
/**
|
||||
* Class Primary_Category_Quick_Edit_Watcher
|
||||
*/
|
||||
class Primary_Category_Quick_Edit_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Holds the primary term repository.
|
||||
*
|
||||
* @var Primary_Term_Repository
|
||||
*/
|
||||
protected $primary_term_repository;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The indexable hierarchy builder.
|
||||
*
|
||||
* @var Indexable_Hierarchy_Builder
|
||||
*/
|
||||
protected $indexable_hierarchy_builder;
|
||||
|
||||
/**
|
||||
* Primary_Category_Quick_Edit_Watcher constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Primary_Term_Repository $primary_term_repository The primary term repository.
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
* @param Indexable_Hierarchy_Builder $indexable_hierarchy_builder The indexable hierarchy repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Primary_Term_Repository $primary_term_repository,
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Indexable_Hierarchy_Builder $indexable_hierarchy_builder
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->primary_term_repository = $primary_term_repository;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->indexable_hierarchy_builder = $indexable_hierarchy_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'set_object_terms', [ $this, 'validate_primary_category' ], 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class, Doing_Post_Quick_Edit_Save_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the current primary category is still present. If not just remove the post meta for it.
|
||||
*
|
||||
* @param int $object_id Object ID.
|
||||
* @param array $terms Unused. An array of object terms.
|
||||
* @param array $tt_ids An array of term taxonomy IDs.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function validate_primary_category( $object_id, $terms, $tt_ids, $taxonomy ) {
|
||||
$post = \get_post( $object_id );
|
||||
if ( $post === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$main_taxonomy = $this->options_helper->get( 'post_types-' . $post->post_type . '-maintax' );
|
||||
if ( ! $main_taxonomy || $main_taxonomy === '0' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $main_taxonomy !== $taxonomy ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$primary_category = $this->get_primary_term_id( $post->ID, $main_taxonomy );
|
||||
if ( $primary_category === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The primary category isn't removed.
|
||||
if ( \in_array( (string) $primary_category, $tt_ids, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->remove_primary_term( $post->ID, $main_taxonomy );
|
||||
|
||||
// Rebuild the post hierarchy for this post now the primary term has been changed.
|
||||
$this->build_post_hierarchy( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary term id of a post.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param string $main_taxonomy The main taxonomy.
|
||||
*
|
||||
* @return int|false The ID of the primary term, or `false` if the post ID is invalid.
|
||||
*/
|
||||
private function get_primary_term_id( $post_id, $main_taxonomy ) {
|
||||
$primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false );
|
||||
|
||||
if ( $primary_term ) {
|
||||
return $primary_term->term_id;
|
||||
}
|
||||
|
||||
return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the primary category.
|
||||
*
|
||||
* @param int $post_id The post id to set primary taxonomy for.
|
||||
* @param string $main_taxonomy Name of the taxonomy that is set to be the primary one.
|
||||
*/
|
||||
private function remove_primary_term( $post_id, $main_taxonomy ) {
|
||||
$primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false );
|
||||
if ( $primary_term ) {
|
||||
$primary_term->delete();
|
||||
}
|
||||
|
||||
// Remove it from the post meta.
|
||||
\delete_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the hierarchy for a post.
|
||||
*
|
||||
* @param WP_Post $post The post.
|
||||
*/
|
||||
public function build_post_hierarchy( $post ) {
|
||||
if ( $this->post_type_helper->is_excluded( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $post->ID, 'post' );
|
||||
|
||||
if ( $indexable instanceof Indexable ) {
|
||||
$this->indexable_hierarchy_builder->build( $indexable );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use WP_Term;
|
||||
use WPSEO_Meta;
|
||||
use WPSEO_Primary_Term;
|
||||
use Yoast\WP\SEO\Builders\Primary_Term_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Primary_Term_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Site_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Primary_Term_Repository;
|
||||
|
||||
/**
|
||||
* Primary Term watcher.
|
||||
*
|
||||
* Watches Posts to save the primary term when set.
|
||||
*/
|
||||
class Primary_Term_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The primary term repository.
|
||||
*
|
||||
* @var Primary_Term_Repository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* Represents the site helper.
|
||||
*
|
||||
* @var Site_Helper
|
||||
*/
|
||||
protected $site;
|
||||
|
||||
/**
|
||||
* The primary term helper.
|
||||
*
|
||||
* @var Primary_Term_Helper
|
||||
*/
|
||||
protected $primary_term;
|
||||
|
||||
/**
|
||||
* The primary term builder.
|
||||
*
|
||||
* @var Primary_Term_Builder
|
||||
*/
|
||||
protected $primary_term_builder;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Migrations_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary_Term_Watcher constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It sets dependencies.
|
||||
*
|
||||
* @param Primary_Term_Repository $repository The primary term repository.
|
||||
* @param Site_Helper $site The site helper.
|
||||
* @param Primary_Term_Helper $primary_term The primary term helper.
|
||||
* @param Primary_Term_Builder $primary_term_builder The primary term builder.
|
||||
*/
|
||||
public function __construct(
|
||||
Primary_Term_Repository $repository,
|
||||
Site_Helper $site,
|
||||
Primary_Term_Helper $primary_term,
|
||||
Primary_Term_Builder $primary_term_builder
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->site = $site;
|
||||
$this->primary_term = $primary_term;
|
||||
$this->primary_term_builder = $primary_term_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'save_post', [ $this, 'save_primary_terms' ], \PHP_INT_MAX );
|
||||
\add_action( 'delete_post', [ $this, 'delete_primary_terms' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all selected primary terms.
|
||||
*
|
||||
* @param int $post_id Post ID to save primary terms for.
|
||||
*/
|
||||
public function save_primary_terms( $post_id ) {
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( $this->site->is_multisite_and_switched() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taxonomies = $this->primary_term->get_primary_term_taxonomies( $post_id );
|
||||
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$this->save_primary_term( $post_id, $taxonomy );
|
||||
}
|
||||
|
||||
$this->primary_term_builder->build( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the primary term for a specific taxonomy.
|
||||
*
|
||||
* @param int $post_id Post ID to save primary term for.
|
||||
* @param WP_Term $taxonomy Taxonomy to save primary term for.
|
||||
*/
|
||||
protected function save_primary_term( $post_id, $taxonomy ) {
|
||||
if ( isset( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] ) && \is_string( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting to an integer.
|
||||
$primary_term_id = (int) \wp_unslash( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] );
|
||||
|
||||
if ( $primary_term_id <= 0 ) {
|
||||
$primary_term = '';
|
||||
}
|
||||
else {
|
||||
$primary_term = (string) $primary_term_id;
|
||||
}
|
||||
|
||||
// We accept an empty string here because we need to save that if no terms are selected.
|
||||
if ( \check_admin_referer( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_nonce' ) !== null ) {
|
||||
$primary_term_object = new WPSEO_Primary_Term( $taxonomy->name, $post_id );
|
||||
$primary_term_object->set_primary_term( $primary_term );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes primary terms for a post.
|
||||
*
|
||||
* @param int $post_id The post to delete the terms of.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_primary_terms( $post_id ) {
|
||||
foreach ( $this->primary_term->get_primary_term_taxonomies( $post_id ) as $taxonomy ) {
|
||||
$primary_term = $this->repository->find_by_post_id_and_taxonomy( $post_id, $taxonomy->name, false );
|
||||
|
||||
if ( ! $primary_term ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$primary_term->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Notification_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Search_Engines_Discouraged_Presenter;
|
||||
use Yoast_Notification;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Shows a notification for users who have access for robots disabled.
|
||||
*
|
||||
* @class Search_Engines_Discouraged_Watcher
|
||||
*/
|
||||
class Search_Engines_Discouraged_Watcher implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The notification ID.
|
||||
*/
|
||||
const NOTIFICATION_ID = 'wpseo-search-engines-discouraged';
|
||||
|
||||
/**
|
||||
* The Yoast notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* The notification helper.
|
||||
*
|
||||
* @var Notification_Helper
|
||||
*/
|
||||
protected $notification_helper;
|
||||
|
||||
/**
|
||||
* The search engines discouraged presenter.
|
||||
*
|
||||
* @var Search_Engines_Discouraged_Presenter
|
||||
*/
|
||||
protected $presenter;
|
||||
|
||||
/**
|
||||
* The current page helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $current_page_helper;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The capability helper.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
protected $capability_helper;
|
||||
|
||||
/**
|
||||
* Search_Engines_Discouraged_Watcher constructor.
|
||||
*
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Notification_Helper $notification_helper The notification helper.
|
||||
* @param Current_Page_Helper $current_page_helper The current page helper.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Notification_Helper $notification_helper,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
Options_Helper $options_helper,
|
||||
Capability_Helper $capability_helper
|
||||
) {
|
||||
$this->notification_center = $notification_center;
|
||||
$this->notification_helper = $notification_helper;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->capability_helper = $capability_helper;
|
||||
$this->presenter = new Search_Engines_Discouraged_Presenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* On admin_init, it is checked whether the notification about search engines being discouraged should be shown.
|
||||
* On admin_notices, the notice about the search engines being discouraged will be shown when necessary.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'manage_search_engines_discouraged_notification' ] );
|
||||
|
||||
/*
|
||||
* The `admin_notices` hook fires on single site admin pages vs.
|
||||
* `network_admin_notices` which fires on multisite admin pages and
|
||||
* `user_admin_notices` which fires on multisite user admin pages.
|
||||
*/
|
||||
\add_action( 'admin_notices', [ $this, 'maybe_show_search_engines_discouraged_notice' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the search engines discouraged notification.
|
||||
*
|
||||
* Shows the notification if needed and deletes it if needed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function manage_search_engines_discouraged_notification() {
|
||||
if ( ! $this->should_show_search_engines_discouraged_notification() ) {
|
||||
$this->remove_search_engines_discouraged_notification_if_exists();
|
||||
}
|
||||
else {
|
||||
$this->maybe_add_search_engines_discouraged_notification();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the search engine discouraged notice when needed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_show_search_engines_discouraged_notice() {
|
||||
if ( ! $this->should_show_search_engines_discouraged_notice() ) {
|
||||
return;
|
||||
}
|
||||
$this->show_search_engines_discouraged_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the search engines discouraged notification should be shown.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_show_search_engines_discouraged_notification() {
|
||||
return $this->search_engines_are_discouraged() && $this->options_helper->get( 'ignore_search_engines_discouraged_notice', false ) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the search engines discouraged notification if it exists.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_search_engines_discouraged_notification_if_exists() {
|
||||
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the search engines discouraged notification if it does not exist yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_add_search_engines_discouraged_notification() {
|
||||
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 search engines are discouraged from indexing the site.
|
||||
*
|
||||
* @return bool Whether search engines are discouraged from indexing the site.
|
||||
*/
|
||||
protected function search_engines_are_discouraged() {
|
||||
return (string) \get_option( 'blog_public' ) === '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the search engines notice should be shown.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_show_search_engines_discouraged_notice() {
|
||||
$pages_to_show_notice = [
|
||||
'index.php',
|
||||
'plugins.php',
|
||||
'update-core.php',
|
||||
];
|
||||
|
||||
return (
|
||||
$this->search_engines_are_discouraged()
|
||||
&& $this->capability_helper->current_user_can( 'manage_options' )
|
||||
&& $this->options_helper->get( 'ignore_search_engines_discouraged_notice', false ) === false
|
||||
&& (
|
||||
$this->current_page_helper->is_yoast_seo_page()
|
||||
|| \in_array( $this->current_page_helper->get_current_admin_page(), $pages_to_show_notice, true )
|
||||
)
|
||||
&& $this->current_page_helper->get_current_yoast_seo_page() !== 'wpseo_dashboard'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the search engines discouraged notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function show_search_engines_discouraged_notice() {
|
||||
\printf(
|
||||
'<div id="robotsmessage" class="notice notice-error">%1$s</div>',
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe.
|
||||
$this->presenter->present()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the notification.
|
||||
*
|
||||
* @return Yoast_Notification The notification to show.
|
||||
*/
|
||||
protected function notification() {
|
||||
return new Yoast_Notification(
|
||||
$this->presenter->present(),
|
||||
[
|
||||
'type' => Yoast_Notification::ERROR,
|
||||
'id' => self::NOTIFICATION_ID,
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 1,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Notification_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Woocommerce_Beta_Editor_Presenter;
|
||||
use Yoast_Notification;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Shows a notification for users who have Woocommerce product beta editor enabled.
|
||||
*
|
||||
* @class Woocommerce_Beta_Editor_Watcher
|
||||
*/
|
||||
class Woocommerce_Beta_Editor_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The notification ID.
|
||||
*/
|
||||
const NOTIFICATION_ID = 'wpseo-woocommerce-beta-editor-warning';
|
||||
|
||||
/**
|
||||
* The short link helper.
|
||||
*
|
||||
* @var Short_Link_Helper
|
||||
*/
|
||||
protected $short_link_helper;
|
||||
|
||||
/**
|
||||
* The Yoast notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* The notification helper.
|
||||
*
|
||||
* @var Notification_Helper
|
||||
*/
|
||||
protected $notification_helper;
|
||||
|
||||
/**
|
||||
* The Woocommerce beta editor presenter.
|
||||
*
|
||||
* @var Woocommerce_Beta_Editor_Presenter
|
||||
*/
|
||||
protected $presenter;
|
||||
|
||||
/**
|
||||
* Woocommerce_Beta_Editor_Watcher constructor.
|
||||
*
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Notification_Helper $notification_helper The notification helper.
|
||||
* @param Short_Link_Helper $short_link_helper The short link helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Notification_Helper $notification_helper,
|
||||
Short_Link_Helper $short_link_helper
|
||||
) {
|
||||
$this->notification_center = $notification_center;
|
||||
$this->notification_helper = $notification_helper;
|
||||
$this->short_link_helper = $short_link_helper;
|
||||
$this->presenter = new Woocommerce_Beta_Editor_Presenter( $this->short_link_helper );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class, Not_Admin_Ajax_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* On admin_init, it is checked whether the notification about Woocommerce product beta editor enabled should be shown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'manage_woocommerce_beta_editor_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the Woocommerce product beta editor notification.
|
||||
*
|
||||
* Shows the notification if needed and deletes it if needed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function manage_woocommerce_beta_editor_notification() {
|
||||
if ( \get_option( 'woocommerce_feature_product_block_editor_enabled' ) === 'yes' ) {
|
||||
$this->maybe_add_woocommerce_beta_editor_notification();
|
||||
}
|
||||
else {
|
||||
$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Woocommerce product beta editor enabled notification if it does not exist yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_add_woocommerce_beta_editor_notification() {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the notification.
|
||||
*
|
||||
* @return Yoast_Notification The notification to show.
|
||||
*/
|
||||
protected function notification() {
|
||||
return new Yoast_Notification(
|
||||
$this->presenter->present(),
|
||||
[
|
||||
'type' => Yoast_Notification::ERROR,
|
||||
'id' => self::NOTIFICATION_ID,
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 1,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user