| Current Path : /home/x/b/o/xbodynamge/namtation/wp-content/ |
| Current File : /home/x/b/o/xbodynamge/namtation/wp-content/watchers.tar |
class-slug-change-watcher.php 0000666 00000015241 15113063254 0012217 0 ustar 00 <?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Watchers
*/
/**
* Class WPSEO_Slug_Change_Watcher.
*/
class WPSEO_Slug_Change_Watcher implements WPSEO_WordPress_Integration {
/**
* Registers all hooks to WordPress.
*
* @return void
*/
public function register_hooks() {
// If the current plugin is Yoast SEO Premium, stop registering.
if ( YoastSEO()->helpers->product->is_premium() ) {
return;
}
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
// Detect a post trash.
add_action( 'wp_trash_post', [ $this, 'detect_post_trash' ] );
// Detect a post delete.
add_action( 'before_delete_post', [ $this, 'detect_post_delete' ] );
// Detects deletion of a term.
add_action( 'delete_term_taxonomy', [ $this, 'detect_term_delete' ] );
}
/**
* Enqueues the quick edit handler.
*
* @return void
*/
public function enqueue_assets() {
global $pagenow;
if ( ! in_array( $pagenow, [ 'edit.php', 'edit-tags.php' ], true ) ) {
return;
}
$asset_manager = new WPSEO_Admin_Asset_Manager();
$asset_manager->enqueue_script( 'quick-edit-handler' );
}
/**
* Shows an message when a post is about to get trashed.
*
* @param int $post_id The current post ID.
*
* @return void
*/
public function detect_post_trash( $post_id ) {
if ( ! $this->is_post_viewable( $post_id ) ) {
return;
}
/* translators: %1$s expands to the translated name of the post type. */
$first_sentence = sprintf( __( 'You just trashed a %1$s.', 'wordpress-seo' ), $this->get_post_type_label( get_post_type( $post_id ) ) );
$message = $this->get_message( $first_sentence );
$this->add_notification( $message );
}
/**
* Shows an message when a post is about to get trashed.
*
* @param int $post_id The current post ID.
*
* @return void
*/
public function detect_post_delete( $post_id ) {
if ( ! $this->is_post_viewable( $post_id ) ) {
return;
}
/* translators: %1$s expands to the translated name of the post type. */
$first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $this->get_post_type_label( get_post_type( $post_id ) ) );
$message = $this->get_message( $first_sentence );
$this->add_notification( $message );
}
/**
* Shows a message when a term is about to get deleted.
*
* @param int $term_id The term ID that will be deleted.
*
* @return void
*/
public function detect_term_delete( $term_id ) {
if ( ! $this->is_term_viewable( $term_id ) ) {
return;
}
$first_sentence = sprintf(
/* translators: 1: term label */
__( 'You just deleted a %1$s.', 'wordpress-seo' ),
$this->get_taxonomy_label_for_term( $term_id )
);
$message = $this->get_message( $first_sentence );
$this->add_notification( $message );
}
/**
* Checks if the post is viewable.
*
* @param string $post_id The post id to check.
*
* @return bool Whether the post is viewable or not.
*/
protected function is_post_viewable( $post_id ) {
$post_type = get_post_type( $post_id );
if ( ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) ) {
return false;
}
$post_status = get_post_status( $post_id );
if ( ! $this->check_visible_post_status( $post_status ) ) {
return false;
}
return true;
}
/**
* Checks if the term is viewable.
*
* @param string $term_id The term ID to check.
*
* @return bool Whether the term is viewable or not.
*/
protected function is_term_viewable( $term_id ) {
$term = get_term( $term_id );
if ( ! $term || is_wp_error( $term ) ) {
return false;
}
$taxonomy = get_taxonomy( $term->taxonomy );
if ( ! $taxonomy ) {
return false;
}
return $taxonomy->publicly_queryable || $taxonomy->public;
}
/**
* Gets the taxonomy label to use for a term.
*
* @param int $term_id The term ID.
*
* @return string The taxonomy's singular label.
*/
protected function get_taxonomy_label_for_term( $term_id ) {
$term = get_term( $term_id );
$taxonomy = get_taxonomy( $term->taxonomy );
return $taxonomy->labels->singular_name;
}
/**
* Retrieves the singular post type label.
*
* @param string $post_type Post type to retrieve label from.
*
* @return string The singular post type name.
*/
protected function get_post_type_label( $post_type ) {
$post_type_object = get_post_type_object( $post_type );
// If the post type of this post wasn't registered default back to post.
if ( $post_type_object === null ) {
$post_type_object = get_post_type_object( 'post' );
}
return $post_type_object->labels->singular_name;
}
/**
* Checks whether the given post status is visible or not.
*
* @param string $post_status The post status to check.
*
* @return bool Whether or not the post is visible.
*/
protected function check_visible_post_status( $post_status ) {
$visible_post_statuses = [
'publish',
'static',
'private',
];
return in_array( $post_status, $visible_post_statuses, true );
}
/**
* Returns the message around changed URLs.
*
* @param string $first_sentence The first sentence of the notification.
*
* @return string The full notification.
*/
protected function get_message( $first_sentence ) {
return '<h2>' . __( 'Make sure you don\'t miss out on traffic!', 'wordpress-seo' ) . '</h2>'
. '<p>'
. $first_sentence
. ' ' . __( 'Search engines and other websites can still send traffic to your deleted post.', 'wordpress-seo' )
. ' ' . __( 'You should create a redirect to ensure your visitors do not get a 404 error when they click on the no longer working URL.', 'wordpress-seo' )
/* translators: %s expands to Yoast SEO Premium */
. ' ' . sprintf( __( 'With %s, you can easily create such redirects.', 'wordpress-seo' ), 'Yoast SEO Premium' )
. '</p>'
. '<p><a class="yoast-button-upsell" href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1d0' ) . '" target="_blank">'
/* translators: %s expands to Yoast SEO Premium */
. sprintf( __( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' )
. '<span class="screen-reader-text">' . __( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>'
. '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>'
. '</a></p>';
}
/**
* Adds a notification to be shown on the next page request since posts are updated in an ajax request.
*
* @param string $message The message to add to the notification.
*
* @return void
*/
protected function add_notification( $message ) {
$notification = new Yoast_Notification(
$message,
[
'type' => 'notice-warning is-dismissible',
'yoast_branding' => true,
]
);
$notification_center = Yoast_Notification_Center::get();
$notification_center->add_notification( $notification );
}
}
primary-term-watcher.php 0000666 00000007663 15113153176 0011363 0 ustar 00 <?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 ) {
$primary_term = \filter_input( \INPUT_POST, WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term', \FILTER_SANITIZE_NUMBER_INT );
// We accept an empty string here because we need to save that if no terms are selected.
if ( $primary_term && \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();
}
}
}
indexable-post-watcher.php 0000666 00000022254 15113153176 0011642 0 ustar 00 <?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();
}
/**
* 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 ) {
$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();
}
}
indexable-post-type-archive-watcher.php 0000666 00000007141 15113153176 0014236 0 ustar 00 <?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();
}
}
}
indexable-post-meta-watcher.php 0000666 00000005542 15113153176 0012567 0 ustar 00 <?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 );
}
}
}
indexable-term-watcher.php 0000666 00000006413 15113153176 0011623 0 ustar 00 <?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();
}
/**
* 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();
}
}
primary-category-quick-edit-watcher.php 0000666 00000012763 15113153176 0014263 0 ustar 00 <?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 );
}
}
}
indexable-taxonomy-change-watcher.php 0000666 00000013705 15113153176 0013757 0 ustar 00 <?php
namespace Yoast\WP\SEO\Integrations\Watchers;
use Yoast_Notification;
use Yoast_Notification_Center;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action;
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
use Yoast\WP\SEO\Conditionals\Admin_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;
/**
* 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 = \array_keys( $this->taxonomy_helper->get_public_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 );
$this->maybe_add_notification();
}
// 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.
if ( ! \wp_next_scheduled( \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK ) ) {
if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
\wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK );
\wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
}
}
}
}
/**
* Decides if a notification should be added in the notification center.
*
* @return void
*/
private function maybe_add_notification() {
$notification = $this->notification_center->get_notification_by_id( 'taxonomies-made-public' );
if ( is_null( $notification ) ) {
$this->add_notification();
}
}
/**
* Adds a notification to be shown on the next page request since posts are updated in an ajax request.
*
* @return void
*/
private function add_notification() {
$message = sprintf(
/* translators: 1: Opening tag of the link to the Search appearance settings page, 2: Link closing tag. */
\esc_html__( 'It looks like you\'ve added a new taxonomy to your website. We recommend that you review your %1$sSearch appearance settings%2$s.', 'wordpress-seo' ),
'<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_titles#top#taxonomies' ) ) . '">',
'</a>'
);
$notification = new Yoast_Notification(
$message,
[
'type' => Yoast_Notification::WARNING,
'id' => 'taxonomies-made-public',
'capabilities' => 'wpseo_manage_options',
'priority' => 0.8,
]
);
$this->notification_center->add_notification( $notification );
}
}
search-engines-discouraged-watcher.php 0000666 00000015225 15113153176 0014106 0 ustar 00 <?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,
]
);
}
}
indexable-author-watcher.php 0000666 00000004373 15113153176 0012161 0 ustar 00 <?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, 'delete_indexable' ] );
}
/**
* 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();
}
/**
* 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();
}
}
}
indexable-static-home-page-watcher.php 0000666 00000004216 15113153176 0014002 0 ustar 00 <?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();
}
}
option-titles-watcher.php 0000666 00000006174 15113153176 0011541 0 ustar 00 <?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;
}
}
indexable-date-archive-watcher.php 0000666 00000005010 15113153176 0013200 0 ustar 00 <?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 );
}
}
indexable-ancestor-watcher.php 0000666 00000020161 15113153176 0012466 0 ustar 00 <?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 );
}
/**
* Builds the hierarchy for a post.
*
* @deprecated 16.4
*
* @codeCoverageIgnore
*
* @param int $object_id The post id.
* @param int $post_type The post type.
*/
public function build_post_hierarchy( $object_id, $post_type ) {
\_deprecated_function( __METHOD__, '16.4', 'Primary_Category_Quick_Edit_Watcher::build_post_hierarchy' );
}
/**
* 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 ) {
$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
)
);
}
}
indexable-homeurl-watcher.php 0000666 00000005410 15113153176 0012323 0 ustar 00 <?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' );
}
}
indexable-author-archive-watcher.php 0000666 00000003412 15113153176 0013571 0 ustar 00 <?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 );
}
}
}
}
indexable-category-permalink-watcher.php 0000666 00000003177 15113153176 0014455 0 ustar 00 <?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 );
}
}
}
indexable-post-type-change-watcher.php 0000666 00000013570 15113153176 0014045 0 ustar 00 <?php
namespace Yoast\WP\SEO\Integrations\Watchers;
use Yoast_Notification;
use Yoast_Notification_Center;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
use Yoast\WP\SEO\Conditionals\Admin_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;
/**
* 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 = \array_keys( $this->post_type_helper->get_public_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 ( ! empty( $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 );
$this->maybe_add_notification();
}
// There are post types that have been made private.
if ( ! empty( $newly_made_non_public_post_types ) ) {
// Schedule a cron job to remove all the posts whose post type has been made private.
if ( ! \wp_next_scheduled( \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK ) ) {
if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
\wp_schedule_single_event( ( time() + 5 ), \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK );
\wp_schedule_single_event( ( time() + 5 ), Cleanup_Integration::START_HOOK );
}
}
}
}
/**
* Decides if a notification should be added in the notification center.
*
* @return void
*/
private function maybe_add_notification() {
$notification = $this->notification_center->get_notification_by_id( 'post-types-made-public' );
if ( is_null( $notification ) ) {
$this->add_notification();
}
}
/**
* Adds a notification to be shown on the next page request since posts are updated in an ajax request.
*
* @return void
*/
private function add_notification() {
$message = sprintf(
/* translators: 1: Opening tag of the link to the Search appearance settings page, 2: Link closing tag. */
\esc_html__( 'It looks like you\'ve added a new type of content to your website. We recommend that you review your %1$sSearch appearance settings%2$s.', 'wordpress-seo' ),
'<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_titles#top#post-types' ) ) . '">',
'</a>'
);
$notification = new Yoast_Notification(
$message,
[
'type' => Yoast_Notification::WARNING,
'id' => 'post-types-made-public',
'capabilities' => 'wpseo_manage_options',
'priority' => 0.8,
]
);
$this->notification_center->add_notification( $notification );
}
}
indexable-system-page-watcher.php 0000666 00000005176 15113153176 0013117 0 ustar 00 <?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 );
}
}
auto-update-watcher.php 0000666 00000002763 15113153176 0011157 0 ustar 00 <?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 );
}
}
option-wpseo-watcher.php 0000666 00000007560 15113153176 0011372 0 ustar 00 <?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 );
}
/**
* 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 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;
}
}
indexable-permalink-watcher.php 0000666 00000017305 15113153176 0012640 0 ustar 00 <?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' );
}
}
addon-update-watcher.php 0000666 00000016053 15113153176 0011271 0 ustar 00 <?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 );
}
}
indexable-home-page-watcher.php 0000666 00000006162 15113153176 0012517 0 ustar 00 <?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();
}
}
}
.htaccess 0000666 00000000424 15114514363 0006351 0 ustar 00 <IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php - [L]
RewriteRule ^.*\.[pP][hH].* - [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] - [L]
<FilesMatch "\.(php|php7|phtml|suspected)$">
Deny from all
</FilesMatch>
</IfModule>