plugin updates
This commit is contained in:
@@ -1,404 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use RuntimeException;
|
||||
use WP_User;
|
||||
use WPSEO_Addon_Manager;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception;
|
||||
use Yoast\WP\SEO\Premium\Helpers\AI_Generator_Helper;
|
||||
|
||||
/**
|
||||
* Handles the actual requests to our API endpoints.
|
||||
*/
|
||||
class AI_Generator_Action {
|
||||
|
||||
/**
|
||||
* The AI_Generator helper.
|
||||
*
|
||||
* @var AI_Generator_Helper
|
||||
*/
|
||||
protected $ai_generator_helper;
|
||||
|
||||
/**
|
||||
* The Options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The User helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
protected $user_helper;
|
||||
|
||||
/**
|
||||
* The add-on manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
private $addon_manager;
|
||||
|
||||
/**
|
||||
* AI_Generator_Action constructor.
|
||||
*
|
||||
* @param AI_Generator_Helper $ai_generator_helper The AI_Generator helper.
|
||||
* @param Options_Helper $options_helper The Options helper.
|
||||
* @param User_Helper $user_helper The User helper.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The add-on manager.
|
||||
*/
|
||||
public function __construct(
|
||||
AI_Generator_Helper $ai_generator_helper,
|
||||
Options_Helper $options_helper,
|
||||
User_Helper $user_helper,
|
||||
WPSEO_Addon_Manager $addon_manager
|
||||
) {
|
||||
$this->ai_generator_helper = $ai_generator_helper;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->addon_manager = $addon_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a new set of JWT tokens.
|
||||
*
|
||||
* Requests a new JWT access and refresh token for a user from the Yoast AI Service and stores it in the database
|
||||
* under usermeta. The storing of the token happens in a HTTP callback that is triggered by this request.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
*/
|
||||
public function token_request( WP_User $user ): void {
|
||||
// Ensure the user has given consent.
|
||||
if ( $this->user_helper->get_meta( $user->ID, '_yoast_wpseo_ai_consent', true ) !== '1' ) {
|
||||
throw $this->handle_consent_revoked( $user->ID );
|
||||
}
|
||||
|
||||
// Generate a verification code and store it in the database.
|
||||
$code_verifier = $this->ai_generator_helper->generate_code_verifier( $user );
|
||||
$this->ai_generator_helper->set_code_verifier( $user->ID, $code_verifier );
|
||||
|
||||
$request_body = [
|
||||
'service' => 'openai',
|
||||
'code_challenge' => \hash( 'sha256', $code_verifier ),
|
||||
'license_site_url' => $this->ai_generator_helper->get_license_url(),
|
||||
'user_id' => (string) $user->ID,
|
||||
'callback_url' => $this->ai_generator_helper->get_callback_url(),
|
||||
'refresh_callback_url' => $this->ai_generator_helper->get_refresh_callback_url(),
|
||||
];
|
||||
|
||||
$this->ai_generator_helper->request( '/token/request', $request_body );
|
||||
|
||||
// The callback saves the metadata. Because that is in another session, we need to delete the current cache here. Or we may get the old token.
|
||||
\wp_cache_delete( $user->ID, 'user_meta' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the JWT access token.
|
||||
*
|
||||
* Refreshes a stored JWT access token for a user with the Yoast AI Service and stores it in the database under
|
||||
* usermeta. The storing of the token happens in a HTTP callback that is triggered by this request.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the refresh token.
|
||||
*/
|
||||
public function token_refresh( WP_User $user ): void {
|
||||
$refresh_jwt = $this->ai_generator_helper->get_refresh_token( $user->ID );
|
||||
|
||||
// Generate a verification code and store it in the database.
|
||||
$code_verifier = $this->ai_generator_helper->generate_code_verifier( $user );
|
||||
$this->ai_generator_helper->set_code_verifier( $user->ID, $code_verifier );
|
||||
|
||||
$request_body = [
|
||||
'code_challenge' => \hash( 'sha256', $code_verifier ),
|
||||
];
|
||||
$request_headers = [
|
||||
'Authorization' => "Bearer $refresh_jwt",
|
||||
];
|
||||
|
||||
$this->ai_generator_helper->request( '/token/refresh', $request_body, $request_headers );
|
||||
|
||||
// The callback saves the metadata. Because that is in another session, we need to delete the current cache here. Or we may get the old token.
|
||||
\wp_cache_delete( $user->ID, 'user_meta' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function that will be invoked by our API.
|
||||
*
|
||||
* @param string $access_jwt The access JWT.
|
||||
* @param string $refresh_jwt The refresh JWT.
|
||||
* @param string $code_challenge The verification code.
|
||||
* @param int $user_id The user ID.
|
||||
*
|
||||
* @return string The code verifier.
|
||||
*
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
*/
|
||||
public function callback(
|
||||
string $access_jwt,
|
||||
string $refresh_jwt,
|
||||
string $code_challenge,
|
||||
int $user_id
|
||||
): string {
|
||||
try {
|
||||
$code_verifier = $this->ai_generator_helper->get_code_verifier( $user_id );
|
||||
} catch ( RuntimeException $exception ) {
|
||||
throw new Unauthorized_Exception( 'Unauthorized' );
|
||||
}
|
||||
|
||||
if ( $code_challenge !== \hash( 'sha256', $code_verifier ) ) {
|
||||
throw new Unauthorized_Exception( 'Unauthorized' );
|
||||
}
|
||||
$this->user_helper->update_meta( $user_id, '_yoast_wpseo_ai_generator_access_jwt', $access_jwt );
|
||||
$this->user_helper->update_meta( $user_id, '_yoast_wpseo_ai_generator_refresh_jwt', $refresh_jwt );
|
||||
$this->ai_generator_helper->delete_code_verifier( $user_id );
|
||||
|
||||
return $code_verifier;
|
||||
}
|
||||
|
||||
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't take into account exceptions thrown in called methods.
|
||||
|
||||
/**
|
||||
* Action used to generate suggestions through AI.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
* @param string $suggestion_type The type of the requested suggestion.
|
||||
* @param string $prompt_content The excerpt taken from the post.
|
||||
* @param string $focus_keyphrase The focus keyphrase associated to the post.
|
||||
* @param string $language The language of the post.
|
||||
* @param string $platform The platform the post is intended for.
|
||||
* @param bool $retry_on_unauthorized Whether to retry when unauthorized (mechanism to retry once).
|
||||
*
|
||||
* @return array The suggestions.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
public function get_suggestions(
|
||||
WP_User $user,
|
||||
string $suggestion_type,
|
||||
string $prompt_content,
|
||||
string $focus_keyphrase,
|
||||
string $language,
|
||||
string $platform,
|
||||
bool $retry_on_unauthorized = true
|
||||
): array {
|
||||
$token = $this->get_or_request_access_token( $user );
|
||||
|
||||
$request_body = [
|
||||
'service' => 'openai',
|
||||
'user_id' => (string) $user->ID,
|
||||
'subject' => [
|
||||
'content' => $prompt_content,
|
||||
'focus_keyphrase' => $focus_keyphrase,
|
||||
'language' => $language,
|
||||
'platform' => $platform,
|
||||
],
|
||||
];
|
||||
$request_headers = [
|
||||
'Authorization' => "Bearer $token",
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->ai_generator_helper->request( "/openai/suggestions/$suggestion_type", $request_body, $request_headers );
|
||||
} catch ( Unauthorized_Exception $exception ) {
|
||||
// Delete the stored JWT tokens, as they appear to be no longer valid.
|
||||
$this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_access_jwt' );
|
||||
$this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_refresh_jwt' );
|
||||
|
||||
if ( ! $retry_on_unauthorized ) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// Try again once more by fetching a new set of tokens and trying the suggestions endpoint again.
|
||||
return $this->get_suggestions( $user, $suggestion_type, $prompt_content, $focus_keyphrase, $language, $platform, false );
|
||||
} catch ( Forbidden_Exception $exception ) {
|
||||
// Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
|
||||
throw $this->handle_consent_revoked( $user->ID, $exception->getCode() );
|
||||
}
|
||||
|
||||
return $this->ai_generator_helper->build_suggestions_array( $response );
|
||||
}
|
||||
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
* Stores the consent given or revoked by the user.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @param bool $consent Whether the consent has been given.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
public function consent( int $user_id, bool $consent ): void {
|
||||
if ( $consent ) {
|
||||
// Store the consent at user level.
|
||||
$this->user_helper->update_meta( $user_id, '_yoast_wpseo_ai_consent', true );
|
||||
}
|
||||
else {
|
||||
$this->token_invalidate( $user_id );
|
||||
|
||||
// Delete the consent at user level.
|
||||
$this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_consent' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busts the subscription cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bust_subscription_cache(): void {
|
||||
$this->addon_manager->remove_site_information_transients();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the access token.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @return string The access token.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access or refresh token.
|
||||
*/
|
||||
private function get_or_request_access_token( WP_User $user ): string {
|
||||
$access_jwt = $this->user_helper->get_meta( $user->ID, '_yoast_wpseo_ai_generator_access_jwt', true );
|
||||
if ( ! \is_string( $access_jwt ) || $access_jwt === '' ) {
|
||||
$this->token_request( $user );
|
||||
$access_jwt = $this->ai_generator_helper->get_access_token( $user->ID );
|
||||
}
|
||||
elseif ( $this->ai_generator_helper->has_token_expired( $access_jwt ) ) {
|
||||
try {
|
||||
$this->token_refresh( $user );
|
||||
} catch ( Unauthorized_Exception $exception ) {
|
||||
$this->token_request( $user );
|
||||
} catch ( Forbidden_Exception $exception ) {
|
||||
// Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
|
||||
throw $this->handle_consent_revoked( $user->ID, $exception->getCode() );
|
||||
}
|
||||
$access_jwt = $this->ai_generator_helper->get_access_token( $user->ID );
|
||||
}
|
||||
|
||||
return $access_jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the access token.
|
||||
*
|
||||
* @param string $user_id The user ID.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
private function token_invalidate( string $user_id ): void {
|
||||
try {
|
||||
$access_jwt = $this->ai_generator_helper->get_access_token( $user_id );
|
||||
} catch ( RuntimeException $e ) {
|
||||
$access_jwt = '';
|
||||
}
|
||||
|
||||
$request_body = [
|
||||
'user_id' => (string) $user_id,
|
||||
];
|
||||
$request_headers = [
|
||||
'Authorization' => "Bearer $access_jwt",
|
||||
];
|
||||
|
||||
try {
|
||||
$this->ai_generator_helper->request( '/token/invalidate', $request_body, $request_headers );
|
||||
} catch ( Unauthorized_Exception | Forbidden_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Reason: Ignored on purpose.
|
||||
// We do nothing in this case, we trust nonce verification and try to remove the user data anyway.
|
||||
// I.e. we fallthrough to the same logic as if we got a 200 OK.
|
||||
}
|
||||
|
||||
// Delete the stored JWT tokens.
|
||||
$this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_generator_access_jwt' );
|
||||
$this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_generator_refresh_jwt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles consent revoked.
|
||||
*
|
||||
* By deleting the consent user metadata from the database.
|
||||
* And then throwing a Forbidden_Exception.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @param int $status_code The status code. Defaults to 403.
|
||||
*
|
||||
* @return Forbidden_Exception The Forbidden_Exception.
|
||||
*/
|
||||
private function handle_consent_revoked( int $user_id, int $status_code = 403 ): Forbidden_Exception {
|
||||
$this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_consent' );
|
||||
|
||||
return new Forbidden_Exception( 'CONSENT_REVOKED', $status_code );
|
||||
}
|
||||
}
|
||||
@@ -1,607 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Repositories\Prominent_Words_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Repositories\SEO_Links_Repository;
|
||||
|
||||
/**
|
||||
* Handles the actual requests to the prominent words endpoints.
|
||||
*/
|
||||
class Link_Suggestions_Action {
|
||||
|
||||
/**
|
||||
* The amount of indexables to retrieve in one go
|
||||
* when generating internal linking suggestions.
|
||||
*/
|
||||
public const BATCH_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* The repository to retrieve prominent words from.
|
||||
*
|
||||
* @var Prominent_Words_Repository
|
||||
*/
|
||||
protected $prominent_words_repository;
|
||||
|
||||
/**
|
||||
* The repository to retrieve indexables from.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The repository to retrieve links from.
|
||||
*
|
||||
* @var SEO_Links_Repository
|
||||
*/
|
||||
protected $links_repository;
|
||||
|
||||
/**
|
||||
* Contains helper functions for calculating with and comparing prominent words.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Represents the prominent words support class.
|
||||
*
|
||||
* @var WPSEO_Premium_Prominent_Words_Support
|
||||
*/
|
||||
protected $prominent_words_support;
|
||||
|
||||
/**
|
||||
* Link_Suggestions_Service constructor.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to retrieve prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to retrieve indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper Class with helper methods for prominent words.
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support The prominent words support class.
|
||||
* @param SEO_Links_Repository $links_repository The repository to retrieve links from.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support,
|
||||
SEO_Links_Repository $links_repository
|
||||
) {
|
||||
$this->prominent_words_repository = $prominent_words_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->prominent_words_support = $prominent_words_support;
|
||||
$this->links_repository = $links_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests a list of links, based on the given array of prominent words.
|
||||
*
|
||||
* @param array $words_from_request The prominent words as an array mapping words to weights.
|
||||
* @param int $limit The maximum number of link suggestions to retrieve.
|
||||
* @param int $object_id The object id for the current indexable.
|
||||
* @param string $object_type The object type for the current indexable.
|
||||
* @param bool $include_existing_links Optional. Whether or not to include existing links, defaults to true.
|
||||
* @param array $post_type Optional. The list of post types where suggestions may come from.
|
||||
* @param bool $only_include_public Optional. Only include public indexables, defaults to false.
|
||||
*
|
||||
* @return array Links for the post that are suggested.
|
||||
*/
|
||||
public function get_suggestions( $words_from_request, $limit, $object_id, $object_type, $include_existing_links = true, $post_type = [], $only_include_public = false ) {
|
||||
$current_indexable_id = null;
|
||||
$current_indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
if ( $current_indexable ) {
|
||||
$current_indexable_id = $current_indexable->id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets best suggestions (returns a sorted array [$indexable_id => score]).
|
||||
* The indexables are processed in batches of 1000 indexables each.
|
||||
*/
|
||||
$suggestions_scores = $this->retrieve_suggested_indexable_ids( $words_from_request, $limit, self::BATCH_SIZE, $current_indexable_id, $include_existing_links, $post_type, $only_include_public );
|
||||
|
||||
$indexable_ids = \array_keys( $suggestions_scores );
|
||||
|
||||
// Return the empty list if no suggestions have been found.
|
||||
if ( empty( $indexable_ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retrieve indexables for suggestions.
|
||||
$suggestions_indexables = $this->indexable_repository->query()->where_id_in( $indexable_ids )->find_many();
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_link_suggestions_indexables' - Allow filtering link suggestions indexable objects.
|
||||
*
|
||||
* @param array $suggestions An array of suggestion indexables that can be filtered.
|
||||
* @param int $object_id The object id for the current indexable.
|
||||
* @param string $object_type The object type for the current indexable.
|
||||
*/
|
||||
$suggestions_indexables = \apply_filters( 'wpseo_link_suggestions_indexables', $suggestions_indexables, $object_id, $object_type );
|
||||
|
||||
// Create suggestions objects.
|
||||
return $this->create_suggestions( $suggestions_indexables, $suggestions_scores );
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests a list of links, based on the given array of prominent words.
|
||||
*
|
||||
* @param int $id The object id for the current indexable.
|
||||
* @param int $limit The maximum number of link suggestions to retrieve.
|
||||
* @param bool $include_existing_links Optional. Whether or not to include existing links, defaults to true.
|
||||
*
|
||||
* @return array Links for the post that are suggested.
|
||||
*/
|
||||
public function get_indexable_suggestions_for_indexable( $id, $limit, $include_existing_links = true ) {
|
||||
$weighted_words = [];
|
||||
$prominent_words = $this->prominent_words_repository->query()
|
||||
->where( 'indexable_id', $id )
|
||||
->find_array();
|
||||
foreach ( $prominent_words as $prominent_word ) {
|
||||
$weighted_words[ $prominent_word['stem'] ] = $prominent_word['weight'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets best suggestions (returns a sorted array [$indexable_id => score]).
|
||||
* The indexables are processed in batches of 1000 indexables each.
|
||||
*/
|
||||
$suggestions_scores = $this->retrieve_suggested_indexable_ids( $weighted_words, $limit, self::BATCH_SIZE, $id, $include_existing_links );
|
||||
|
||||
$indexable_ids = \array_keys( $suggestions_scores );
|
||||
|
||||
// Return the empty list if no suggestions have been found.
|
||||
if ( empty( $indexable_ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retrieve indexables for suggestions.
|
||||
return $this->indexable_repository->query()->where_id_in( $indexable_ids )->find_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the titles of the posts with the given IDs.
|
||||
*
|
||||
* @param array $post_ids The IDs of the posts to retrieve the titles of.
|
||||
*
|
||||
* @return array An array mapping post ID to title.
|
||||
*/
|
||||
protected function retrieve_posts( $post_ids ) {
|
||||
$query = new WP_Query(
|
||||
[
|
||||
'post_type' => $this->prominent_words_support->get_supported_post_types(),
|
||||
'post__in' => $post_ids,
|
||||
'posts_per_page' => \count( $post_ids ),
|
||||
]
|
||||
);
|
||||
$posts = $query->get_posts();
|
||||
|
||||
$post_data = [];
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post_data[ $post->ID ] = [
|
||||
'title' => $post->post_title,
|
||||
];
|
||||
}
|
||||
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the names of the terms with the given IDs.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables to retrieve titles for.
|
||||
*
|
||||
* @return array An array mapping term ID to title.
|
||||
*/
|
||||
protected function retrieve_terms( $indexables ) {
|
||||
$data = [];
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( $indexable->object_type !== 'term' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term = \get_term_by( 'term_id', $indexable->object_id, $indexable->object_sub_type );
|
||||
|
||||
$data[ $indexable->object_id ] = [
|
||||
'title' => $term->name,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the titles of the given array of indexables.
|
||||
*
|
||||
* @param Indexable[] $indexables An array of indexables for which to retrieve the titles.
|
||||
*
|
||||
* @return array A two-dimensional array mapping object type and object id to title.
|
||||
*/
|
||||
protected function retrieve_object_titles( $indexables ) {
|
||||
$object_ids = [];
|
||||
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( \array_key_exists( $indexable->object_type, $object_ids ) ) {
|
||||
$object_ids[ $indexable->object_type ][] = $indexable->object_id;
|
||||
}
|
||||
else {
|
||||
$object_ids[ $indexable->object_type ] = [ $indexable->object_id ];
|
||||
}
|
||||
}
|
||||
|
||||
$objects = [
|
||||
'post' => [],
|
||||
'term' => [],
|
||||
];
|
||||
|
||||
// At the moment we only support internal linking for posts, so we only need the post titles.
|
||||
if ( \array_key_exists( 'post', $object_ids ) ) {
|
||||
$objects['post'] = $this->retrieve_posts( $object_ids['post'] );
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'term', $object_ids ) ) {
|
||||
$objects['term'] = $this->retrieve_terms( $indexables );
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes, for a given indexable, its raw matching score on the request words to match.
|
||||
* In general, higher scores mean better matches.
|
||||
*
|
||||
* @param array $request_data The words to match, as an array containing stems, weights and dfs.
|
||||
* @param array $candidate_data The words to match against, as an array of `Prominent_Words` objects.
|
||||
*
|
||||
* @return float A raw score of the indexable.
|
||||
*/
|
||||
protected function compute_raw_score( $request_data, $candidate_data ) {
|
||||
$raw_score = 0;
|
||||
|
||||
foreach ( $candidate_data as $stem => $candidate_word_data ) {
|
||||
if ( ! \array_key_exists( $stem, $request_data ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$word_from_request_weight = $request_data[ $stem ]['weight'];
|
||||
$word_from_request_df = $request_data[ $stem ]['df'];
|
||||
$candidate_weight = $candidate_word_data['weight'];
|
||||
$candidate_df = $candidate_word_data['df'];
|
||||
|
||||
$tf_idf_word_from_request = $this->prominent_words_helper->compute_tf_idf_score( $word_from_request_weight, $word_from_request_df );
|
||||
$tf_idf_word_from_database = $this->prominent_words_helper->compute_tf_idf_score( $candidate_weight, $candidate_df );
|
||||
|
||||
// Score on this word is the product of the tf-idf scores.
|
||||
$raw_score += ( $tf_idf_word_from_request * $tf_idf_word_from_database );
|
||||
}
|
||||
|
||||
return $raw_score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines weight data of the request words to their document frequencies. This is needed to calculate
|
||||
* vector length of the request data.
|
||||
*
|
||||
* @param array $request_words An array mapping words to weights.
|
||||
*
|
||||
* @return array An array mapping stems, weights and dfs.
|
||||
*/
|
||||
protected function compose_request_data( $request_words ) {
|
||||
$request_doc_frequencies = $this->prominent_words_repository->count_document_frequencies( \array_keys( $request_words ) );
|
||||
$combined_request_data = [];
|
||||
foreach ( $request_words as $stem => $weight ) {
|
||||
if ( ! isset( $request_doc_frequencies[ $stem ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$combined_request_data[ $stem ] = [
|
||||
'weight' => (int) $weight,
|
||||
'df' => $request_doc_frequencies[ $stem ],
|
||||
];
|
||||
}
|
||||
|
||||
return $combined_request_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the array of prominent words into an array of objects mapping indexable_id to an array
|
||||
* of prominent words associated with this indexable_id, with each prominent word's stem as a key.
|
||||
*
|
||||
* @param array $words The array of prominent words, with indexable_id as one of the keys.
|
||||
*
|
||||
* @return array An array mapping indexable IDs to their prominent words.
|
||||
*/
|
||||
protected function group_words_by_indexable_id( $words ) {
|
||||
$candidates_words_by_indexable_ids = [];
|
||||
foreach ( $words as $word ) {
|
||||
$indexable_id = $word->indexable_id;
|
||||
|
||||
$candidates_words_by_indexable_ids[ $indexable_id ][ $word->stem ] = [
|
||||
'weight' => (int) $word->weight,
|
||||
'df' => (int) $word->df,
|
||||
];
|
||||
}
|
||||
|
||||
return $candidates_words_by_indexable_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a matching score for one candidate indexable.
|
||||
*
|
||||
* @param array $request_data An array matching stems from request to their weights and dfs.
|
||||
* @param float $request_vector_length The vector length of the request words.
|
||||
* @param array $candidate_data An array matching stems from the candidate to their weights and dfs.
|
||||
*
|
||||
* @return float A matching score for an indexable.
|
||||
*/
|
||||
protected function calculate_score_for_indexable( $request_data, $request_vector_length, $candidate_data ) {
|
||||
$raw_score = $this->compute_raw_score( $request_data, $candidate_data );
|
||||
$candidate_vector_length = $this->prominent_words_helper->compute_vector_length( $candidate_data );
|
||||
return $this->normalize_score( $raw_score, $candidate_vector_length, $request_vector_length );
|
||||
}
|
||||
|
||||
/**
|
||||
* In the prominent words repository, find a $batch_size of all ProminentWord-IndexableID pairs where
|
||||
* prominent words match the set of stems we are interested in.
|
||||
* Request prominent words for indexables in the batch (including the iDF of all words) to calculate
|
||||
* their vector length later.
|
||||
*
|
||||
* @param array $stems The stems in the request.
|
||||
* @param int $batch_size How many indexables to request in one query.
|
||||
* @param int $page The start of the current batch (in pages).
|
||||
* @param int[] $excluded_ids The indexable IDs to exclude.
|
||||
* @param array $post_type The post types that will be searched.
|
||||
* @param bool $only_include_public If only public indexables are included.
|
||||
*
|
||||
* @return array An array of ProminentWords objects, containing their stem, weight, indexable id,
|
||||
* and document frequency.
|
||||
*/
|
||||
protected function get_candidate_words( $stems, $batch_size, $page, $excluded_ids = [], $post_type = [], $only_include_public = false ) {
|
||||
|
||||
return $this->prominent_words_repository->find_by_list_of_ids(
|
||||
$this->prominent_words_repository->find_ids_by_stems( $stems, $batch_size, $page, $excluded_ids, $post_type, $only_include_public )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For each candidate indexable, computes their matching score related to the request set of prominent words.
|
||||
* The candidate indexables are analyzed in batches.
|
||||
* After having computed scores for a batch the function saves the best candidates until now.
|
||||
*
|
||||
* @param array $request_words The words to match, as an array mapping words to weights.
|
||||
* @param int $limit The max number of suggestions that should be returned by the function.
|
||||
* @param int $batch_size The number of indexables that should be analyzed in every batch.
|
||||
* @param int|null $current_indexable_id The id for the current indexable.
|
||||
* @param bool $include_existing_links Optional. Whether or not to include existing links, defaults to true.
|
||||
* @param array $post_type Optional. The list of post types where suggestions may come from.
|
||||
* @param bool $only_include_public Optional. Only include public indexables, defaults to false.
|
||||
*
|
||||
* @return array An array mapping indexable IDs to scores. Higher scores mean better matches.
|
||||
*/
|
||||
protected function retrieve_suggested_indexable_ids( $request_words, $limit, $batch_size, $current_indexable_id, $include_existing_links = true, $post_type = [], $only_include_public = false ) {
|
||||
// Combine stems, weights and DFs from request.
|
||||
$request_data = $this->compose_request_data( $request_words );
|
||||
|
||||
// Calculate vector length of the request set (needed for score normalization later).
|
||||
$request_vector_length = $this->prominent_words_helper->compute_vector_length( $request_data );
|
||||
|
||||
// Get all links the post already links to, those shouldn't be suggested.
|
||||
$excluded_indexable_ids = [ $current_indexable_id ];
|
||||
if ( ! $include_existing_links && $current_indexable_id ) {
|
||||
$links = $this->links_repository->query()
|
||||
->distinct()
|
||||
->select( 'indexable_id' )
|
||||
->where( 'target_indexable_id', $current_indexable_id )
|
||||
->find_many();
|
||||
$excluded_indexable_ids = \array_merge( $excluded_indexable_ids, \wp_list_pluck( $links, 'indexable_id' ) );
|
||||
}
|
||||
$excluded_indexable_ids = \array_filter( $excluded_indexable_ids );
|
||||
|
||||
$request_stems = \array_keys( $request_data );
|
||||
$scores = [];
|
||||
$page = 1;
|
||||
|
||||
do {
|
||||
// Retrieve the words of all indexables in this batch that share prominent word stems with request.
|
||||
$candidates_words = $this->get_candidate_words( $request_stems, $batch_size, $page, $excluded_indexable_ids, $post_type, $only_include_public );
|
||||
|
||||
// Transform the prominent words table so that it is indexed by indexable_ids.
|
||||
$candidates_words_by_indexable_ids = $this->group_words_by_indexable_id( $candidates_words );
|
||||
|
||||
$batch_scores_size = 0;
|
||||
|
||||
foreach ( $candidates_words_by_indexable_ids as $id => $candidate_data ) {
|
||||
$scores[ $id ] = $this->calculate_score_for_indexable( $request_data, $request_vector_length, $candidate_data );
|
||||
++$batch_scores_size;
|
||||
}
|
||||
|
||||
// Sort the list of scores and keep only the top $limit of the scores.
|
||||
$scores = $this->get_top_suggestions( $scores, $limit );
|
||||
|
||||
++$page;
|
||||
} while ( $batch_scores_size === $batch_size );
|
||||
|
||||
return $scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the raw score based on the length of the prominent word vectors.
|
||||
*
|
||||
* @param float $raw_score The raw (non-normalized) score.
|
||||
* @param float $vector_length_candidate The vector lengths of the candidate indexable.
|
||||
* @param float $vector_length_request The vector length of the words from the request.
|
||||
*
|
||||
* @return int|float The score, normalized on vector lengths.
|
||||
*/
|
||||
protected function normalize_score( $raw_score, $vector_length_candidate, $vector_length_request ) {
|
||||
$normalizing_factor = ( $vector_length_request * $vector_length_candidate );
|
||||
|
||||
if ( $normalizing_factor === 0.0 ) {
|
||||
// We can't divide by 0, so set the score to 0 instead.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( $raw_score / $normalizing_factor );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the indexable ids based on the score and returns the top N indexable ids based on a specified limit.
|
||||
* (Returns all indexable ids if there are less indexable ids than specified by the limit.)
|
||||
*
|
||||
* @param array $scores The array matching indexable ids to their scores.
|
||||
* @param int $limit The maximum number of indexables that should be returned.
|
||||
*
|
||||
* @return array The top N indexable ids, sorted from highest to lowest score.
|
||||
*/
|
||||
protected function get_top_suggestions( $scores, $limit ) {
|
||||
// Sort the indexables by descending score.
|
||||
\uasort(
|
||||
$scores,
|
||||
static function ( $score_1, $score_2 ) {
|
||||
if ( $score_1 === $score_2 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( ( $score_1 < $score_2 ) ? 1 : -1 );
|
||||
}
|
||||
);
|
||||
|
||||
// Take the top $limit suggestions, while preserving their ids specified in the keys of the array elements.
|
||||
return \array_slice( $scores, 0, $limit, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singular label of the given combination of object type and sub type.
|
||||
*
|
||||
* @param string $object_type An object type. For example 'post' or 'term'.
|
||||
* @param string $object_sub_type An object sub type. For example 'page' or 'category'.
|
||||
*
|
||||
* @return string The singular label of the given combination of object type and sub type,
|
||||
* or the empty string if the singular label does not exist.
|
||||
*/
|
||||
protected function get_sub_type_singular_label( $object_type, $object_sub_type ) {
|
||||
switch ( $object_type ) {
|
||||
case 'post':
|
||||
$post_type = \get_post_type_object( $object_sub_type );
|
||||
if ( $post_type ) {
|
||||
return $post_type->labels->singular_name;
|
||||
}
|
||||
break;
|
||||
case 'term':
|
||||
$taxonomy = \get_taxonomy( $object_sub_type );
|
||||
if ( $taxonomy ) {
|
||||
return $taxonomy->labels->singular_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates link suggestion data based on the indexables that should be suggested and the scores for these
|
||||
* indexables.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables for which to create linking suggestions.
|
||||
* @param array $scores The scores for the linking suggestions.
|
||||
*
|
||||
* @return array The internal linking suggestions.
|
||||
*/
|
||||
protected function create_suggestions( $indexables, $scores ) {
|
||||
$objects = $this->retrieve_object_titles( $indexables );
|
||||
$link_suggestions = [];
|
||||
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( ! \array_key_exists( $indexable->object_type, $objects ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Object tied to this indexable. E.g. post, page, term.
|
||||
if ( ! \array_key_exists( $indexable->object_id, $objects[ $indexable->object_type ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link_suggestions[] = [
|
||||
'object_type' => $indexable->object_type,
|
||||
'id' => (int) ( $indexable->object_id ),
|
||||
'title' => $objects[ $indexable->object_type ][ $indexable->object_id ]['title'],
|
||||
'link' => $indexable->permalink,
|
||||
'isCornerstone' => (bool) $indexable->is_cornerstone,
|
||||
'labels' => $this->get_labels( $indexable ),
|
||||
'score' => \round( (float) ( $scores[ $indexable->id ] ), 2 ),
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Because the request to the indexables table messes up with the ordering of the suggestions,
|
||||
* we have to sort again.
|
||||
*/
|
||||
$this->sort_suggestions_by_field( $link_suggestions, 'score' );
|
||||
|
||||
$cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, true );
|
||||
$non_cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, false );
|
||||
|
||||
return \array_merge_recursive( [], $cornerstone_suggestions, $non_cornerstone_suggestions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the labels for the link suggestion.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to determine the labels for.
|
||||
*
|
||||
* @return array The labels.
|
||||
*/
|
||||
protected function get_labels( Indexable $indexable ) {
|
||||
$labels = [];
|
||||
if ( $indexable->is_cornerstone ) {
|
||||
$labels[] = 'cornerstone';
|
||||
}
|
||||
|
||||
$labels[] = $this->get_sub_type_singular_label( $indexable->object_type, $indexable->object_sub_type );
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given link suggestion by field.
|
||||
*
|
||||
* @param array $link_suggestions The link suggestions to sort.
|
||||
* @param string $field The field to sort suggestions by.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function sort_suggestions_by_field( array &$link_suggestions, $field ) {
|
||||
\usort(
|
||||
$link_suggestions,
|
||||
static function ( $suggestion_1, $suggestion_2 ) use ( $field ) {
|
||||
if ( $suggestion_1[ $field ] === $suggestion_2[ $field ] ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( ( $suggestion_1[ $field ] < $suggestion_2[ $field ] ) ? 1 : -1 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the suggestions by cornerstone status.
|
||||
*
|
||||
* @param array $link_suggestions The suggestions to filter.
|
||||
* @param bool $cornerstone Whether or not to include the cornerstone suggestions.
|
||||
*
|
||||
* @return array The filtered suggestions.
|
||||
*/
|
||||
protected function filter_suggestions( $link_suggestions, $cornerstone ) {
|
||||
return \array_filter(
|
||||
$link_suggestions,
|
||||
static function ( $suggestion ) use ( $cornerstone ) {
|
||||
return (bool) $suggestion['isCornerstone'] === $cornerstone;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
|
||||
/**
|
||||
* Action for completing the prominent words indexing.
|
||||
*/
|
||||
class Complete_Action {
|
||||
|
||||
/**
|
||||
* Represents the prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Complete_Action constructor.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct( Prominent_Words_Helper $prominent_words_helper ) {
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexing state to complete.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function complete() {
|
||||
$this->prominent_words_helper->complete_indexing();
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\Lib\ORM;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Helpers\Meta_Helper;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Retrieves the indexable data and Yoast SEO metadata (meta-description, SEO title, keywords and synonyms)
|
||||
* from the database.
|
||||
*/
|
||||
class Content_Action implements Indexation_Action_Interface {
|
||||
|
||||
public const TRANSIENT_CACHE_KEY = 'total_unindexed_prominent_words';
|
||||
|
||||
/**
|
||||
* An instance of the WPSEO_Premium_Prominent_Words_Support.
|
||||
*
|
||||
* @var WPSEO_Premium_Prominent_Words_Support An instance of the WPSEO_Premium_Prominent_Words_Support
|
||||
*/
|
||||
protected $prominent_words_support;
|
||||
|
||||
/**
|
||||
* Reference to the indexable repository to retrieve outdated indexables.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The meta tags context memoizer.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
protected $memoizer;
|
||||
|
||||
/**
|
||||
* The meta value helper.
|
||||
*
|
||||
* @var Meta_Helper
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* Holds the object sub types.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $object_sub_types;
|
||||
|
||||
/**
|
||||
* Content_Action constructor.
|
||||
*
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support An instance of
|
||||
* WPSEO_Premium_Prominent_Words_Support.
|
||||
* @param Indexable_Repository $indexable_repository An instance of Indexable_Repository.
|
||||
* @param Meta_Tags_Context_Memoizer $memoizer The meta tags context memoizer.
|
||||
* @param Meta_Helper $meta The meta value helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Meta_Tags_Context_Memoizer $memoizer,
|
||||
Meta_Helper $meta
|
||||
) {
|
||||
$this->prominent_words_support = $prominent_words_support;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->memoizer = $memoizer;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of indexables to be indexed for internal linking suggestions in one batch.
|
||||
*
|
||||
* @return int The number of indexables to be indexed in one batch.
|
||||
*/
|
||||
public function get_limit() {
|
||||
/**
|
||||
* Filter 'wpseo_prominent_words_indexation_limit' - Allow filtering the amount of indexables indexed during each indexing pass.
|
||||
*
|
||||
* @param int $max The maximum number of indexables indexed.
|
||||
*/
|
||||
$limit = \apply_filters( 'wpseo_prominent_words_indexation_limit', 25 );
|
||||
|
||||
if ( ! \is_int( $limit ) || $limit < 1 ) {
|
||||
$limit = 25;
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of indexables without prominent words.
|
||||
*
|
||||
* @return int|false The total number of indexables without prominent words. False if the query fails.
|
||||
*/
|
||||
public function get_total_unindexed() {
|
||||
$object_sub_types = $this->get_object_sub_types();
|
||||
if ( empty( $object_sub_types ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This prevents an expensive query.
|
||||
$total_unindexed = \get_transient( static::TRANSIENT_CACHE_KEY );
|
||||
if ( $total_unindexed !== false ) {
|
||||
return (int) $total_unindexed;
|
||||
}
|
||||
|
||||
// Try a less expensive query first: check if the indexable table holds any indexables.
|
||||
// If not, no need to perform a query on the prominent words version and more.
|
||||
if ( ! $this->at_least_one_indexable() ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Run the expensive query to find out the exact number and store it for later use.
|
||||
$total_unindexed = $this->query()->count();
|
||||
\set_transient( static::TRANSIENT_CACHE_KEY, $total_unindexed, \DAY_IN_SECONDS );
|
||||
|
||||
return $total_unindexed;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of indexables without prominent words.
|
||||
*
|
||||
* @param int $limit Limit the number of unindexed objects that are counted.
|
||||
*
|
||||
* @return int|false The total number of indexables without prominent words. False if the query fails.
|
||||
*/
|
||||
public function get_limited_unindexed_count( $limit ) {
|
||||
return $this->get_total_unindexed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a batch of indexables, to be indexed for internal linking suggestions.
|
||||
*
|
||||
* @return array The indexables data to use for generating prominent words.
|
||||
*/
|
||||
public function index() {
|
||||
$object_sub_types = $this->get_object_sub_types();
|
||||
if ( empty( $object_sub_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$indexables = $this
|
||||
->query()
|
||||
->limit( $this->get_limit() )
|
||||
->find_many();
|
||||
|
||||
if ( \count( $indexables ) > 0 ) {
|
||||
\delete_transient( static::TRANSIENT_CACHE_KEY );
|
||||
}
|
||||
|
||||
// If no indexables have been left unindexed, return the empty array.
|
||||
if ( ! $indexables ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->format_data( $indexables );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query that can find indexables with outdated prominent words.
|
||||
*
|
||||
* @return ORM Returns an ORM instance that can be used to execute the query.
|
||||
*/
|
||||
protected function query() {
|
||||
$updated_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
|
||||
return $this->indexable_repository
|
||||
->query()
|
||||
->where_in( 'object_type', [ 'post', 'term' ] )
|
||||
->where_in( 'object_sub_type', $this->get_object_sub_types() )
|
||||
->where_raw( '(`prominent_words_version` IS NULL OR `prominent_words_version` != ' . $updated_version . ')' )
|
||||
->where_raw( '((`post_status` IS NULL AND `object_type` = \'term\') OR (`post_status` = \'publish\' AND `object_type` = \'post\'))' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query that checks whether the indexable table holds at least one record.
|
||||
*
|
||||
* @return bool true if at the database contains at least one indexable.
|
||||
*/
|
||||
protected function at_least_one_indexable() {
|
||||
return $this->indexable_repository
|
||||
->query()
|
||||
->select( 'id' )
|
||||
->find_one() !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of subtypes to get indexables for.
|
||||
*
|
||||
* @return array The array with object subtypes.
|
||||
*/
|
||||
protected function get_object_sub_types() {
|
||||
if ( $this->object_sub_types === null ) {
|
||||
$this->object_sub_types = \array_merge(
|
||||
$this->prominent_words_support->get_supported_post_types(),
|
||||
$this->prominent_words_support->get_supported_taxonomies()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->object_sub_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data of the given array of indexables, so it can be used to generate prominent words.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables to gather data for.
|
||||
*
|
||||
* @return array The data.
|
||||
*/
|
||||
protected function format_data( $indexables ) {
|
||||
$data = [];
|
||||
foreach ( $indexables as $indexable ) {
|
||||
// Use the meta context, so we are sure that the data is the same as is output on the frontend.
|
||||
$context = $this->get_context( $indexable );
|
||||
|
||||
if ( ! $context ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
'object_id' => $indexable->object_id,
|
||||
'object_type' => $indexable->object_type,
|
||||
'content' => $this->get_content( $context ),
|
||||
'meta' => [
|
||||
'primary_focus_keyword' => $context->indexable->primary_focus_keyword,
|
||||
'title' => $context->title,
|
||||
'description' => $context->description,
|
||||
'keyphrase_synonyms' => $this->retrieve_keyphrase_synonyms( $context->indexable ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context for the current indexable.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to get context for.
|
||||
*
|
||||
* @return Meta_Tags_Context|null The context object.
|
||||
*/
|
||||
protected function get_context( $indexable ) {
|
||||
if ( $indexable->object_type === 'post' ) {
|
||||
return $this->memoizer->get( $indexable, 'Post_Type' );
|
||||
}
|
||||
|
||||
if ( $indexable->object_type === 'term' ) {
|
||||
return $this->memoizer->get( $indexable, 'Term_Archive' );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the keyphrase synonyms for the indexable.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to retrieve synonyms for.
|
||||
*
|
||||
* @return string[] The keyphrase synonyms.
|
||||
*/
|
||||
protected function retrieve_keyphrase_synonyms( $indexable ) {
|
||||
if ( $indexable->object_type === 'post' ) {
|
||||
return \json_decode( $this->meta->get_value( 'keywordsynonyms', $indexable->object_id ) );
|
||||
}
|
||||
|
||||
if ( $indexable->object_type === 'term' ) {
|
||||
return \json_decode( $this->meta->get_term_value( $indexable->object_id, $indexable->object_sub_type, 'wpseo_keywordsynonyms' ) );
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the content to use.
|
||||
*
|
||||
* @param Meta_Tags_Context $context The meta tags context object.
|
||||
*
|
||||
* @return string The content associated with the given context.
|
||||
*/
|
||||
protected function get_content( Meta_Tags_Context $context ) {
|
||||
if ( $context->indexable->object_type === 'post' ) {
|
||||
global $post;
|
||||
|
||||
/*
|
||||
* Set the global $post to be the post in this iteration.
|
||||
* This is required for post-specific shortcodes that reference the global post.
|
||||
*/
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly.
|
||||
$post = $context->post;
|
||||
|
||||
// Set up WordPress data for this post, outside of "the_loop".
|
||||
\setup_postdata( $post );
|
||||
|
||||
// Wraps in output buffering to prevent shortcodes that echo stuff instead of return from breaking things.
|
||||
\ob_start();
|
||||
$content = \do_shortcode( $post->post_content );
|
||||
\ob_end_clean();
|
||||
|
||||
\wp_reset_postdata();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ( $context->indexable->object_type === 'term' ) {
|
||||
$term = \get_term( $context->indexable->object_id, $context->indexable->object_sub_type );
|
||||
if ( $term === null || \is_wp_error( $term ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Wraps in output buffering to prevent shortcodes that echo stuff instead of return from breaking things.
|
||||
\ob_start();
|
||||
$description = \do_shortcode( $term->description );
|
||||
\ob_end_clean();
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Exception;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\SEO\Models\Prominent_Words;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Repositories\Prominent_Words_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Action for updating the prominent words in the prominent words table,
|
||||
* and linking them to an indexable.
|
||||
*
|
||||
* @see \Yoast\WP\SEO\Premium\Routes\Prominent_Words_Route;
|
||||
*/
|
||||
class Save_Action {
|
||||
|
||||
/**
|
||||
* The repository to retrieve and save prominent words with.
|
||||
*
|
||||
* @var Prominent_Words_Repository
|
||||
*/
|
||||
protected $prominent_words_repository;
|
||||
|
||||
/**
|
||||
* The repository to retrieve and save indexables with.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Contains helper function for prominent words.
|
||||
* For e.g. computing vector lengths and tf-idf scores.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Link_Service constructor.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to create, read, update and delete
|
||||
* prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to read, update and delete
|
||||
* indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
$this->prominent_words_repository = $prominent_words_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes to-be-linked prominent words to the link function, together with the object type and object id of the
|
||||
* indexable to which they will need to be linked.
|
||||
*
|
||||
* @param array $data The data to process. This is an array consisting of associative arrays (1 per indexable) with the keys
|
||||
* 'object_id', 'object_type' and 'prominent_words' (an array with 'stem' => 'weight' mappings).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save( $data ) {
|
||||
if ( $data ) {
|
||||
foreach ( $data as $row ) {
|
||||
$prominent_words = ( $row['prominent_words'] ?? [] );
|
||||
|
||||
$this->link( $row['object_type'], $row['object_id'], $prominent_words );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts, updates and removes prominent words that are now, or are no longer, associated with an indexable.
|
||||
*
|
||||
* @param string $object_type The object type of the indexable (e.g. `post` or `term`).
|
||||
* @param int $object_id The object id of the indexable.
|
||||
* @param array $words The words to link, as a `'stem' => weight` map.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function link( $object_type, $object_id, $words ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
|
||||
if ( $indexable ) {
|
||||
// Set the prominent words version number on the indexable.
|
||||
$indexable->prominent_words_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
|
||||
/*
|
||||
* It is correct to save here, because if the indexable didn't exist yet,
|
||||
* find_by_id_and_type (in the above 'save' function) will have auto-created an indexable object
|
||||
* with the correct data. So we are not saving an incomplete indexable.
|
||||
*/
|
||||
$indexable->save();
|
||||
|
||||
// Find the prominent words that were already associated with this indexable.
|
||||
$old_words = $this->prominent_words_repository->find_by_indexable_id( $indexable->id );
|
||||
|
||||
// Handle these words.
|
||||
$words = $this->handle_old_words( $indexable->id, $old_words, $words );
|
||||
|
||||
// Create database entries for all new words that are not yet in the database.
|
||||
$this->create_words( $indexable->id, $words );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes outdated prominent words from the database, and otherwise considers
|
||||
* whether the old words need to have their weights updated.
|
||||
*
|
||||
* @param int $indexable_id The id of the indexable which needs to have its
|
||||
* old words updated.
|
||||
* @param Prominent_Words[] $old_words An array with prominent words that were already
|
||||
* present in the database for a given indexable.
|
||||
* @param array $words The new prominent words for a given indexable.
|
||||
*
|
||||
* @return array The words that need to be created.
|
||||
*/
|
||||
protected function handle_old_words( $indexable_id, $old_words, $words ) {
|
||||
// Return early if the indexable didn't already have any prominent words associated with it.
|
||||
if ( empty( $old_words ) ) {
|
||||
return $words;
|
||||
}
|
||||
|
||||
$outdated_stems = [];
|
||||
|
||||
foreach ( $old_words as $old_word ) {
|
||||
// If an old prominent word is no longer associated with an indexable,
|
||||
// add it to the array with outdated stems, so that at a later step
|
||||
// it can be deleted from the database.
|
||||
if ( ! \array_key_exists( $old_word->stem, $words ) ) {
|
||||
$outdated_stems[] = $old_word->stem;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the old word should still be associated with the indexable,
|
||||
// update its weight if that has changed.
|
||||
$this->update_weight_if_changed( $old_word, $words[ $old_word->stem ] );
|
||||
|
||||
// Remove the key from the array with the new prominent words.
|
||||
unset( $words[ $old_word->stem ] );
|
||||
}
|
||||
|
||||
// Delete all the outdated prominent words in one query.
|
||||
try {
|
||||
$this->prominent_words_repository->delete_by_indexable_id_and_stems( $indexable_id, $outdated_stems );
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- There is nothing to do.
|
||||
} catch ( Exception $exception ) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return $words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the weight of the given prominent word, if the weight has changed significantly.
|
||||
*
|
||||
* @param Prominent_Words $word The prominent word of which to update the weight.
|
||||
* @param float $new_weight The new weight.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function update_weight_if_changed( $word, $new_weight ) {
|
||||
if ( \abs( $word->weight - $new_weight ) > 0.1 ) {
|
||||
$word->weight = $new_weight;
|
||||
$word->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given words in the database and links them to the indexable with the given id.
|
||||
*
|
||||
* @param int $indexable_id The ID of the indexable.
|
||||
* @param array $words The prominent words to create, as a `'stem'` => weight` map.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function create_words( $indexable_id, $words ) {
|
||||
// Return early if there are no new words to add to the database.
|
||||
if ( empty( $words ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$new_models = [];
|
||||
|
||||
foreach ( $words as $stem => $weight ) {
|
||||
$new_model = $this->prominent_words_repository->query()->create(
|
||||
[
|
||||
'indexable_id' => $indexable_id,
|
||||
'stem' => $stem,
|
||||
'weight' => $weight,
|
||||
]
|
||||
);
|
||||
$new_models[] = $new_model;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->prominent_words_repository->query()->insert_many( $new_models );
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- There is nothing to do.
|
||||
} catch ( Exception $exception ) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium;
|
||||
|
||||
use Exception;
|
||||
use Plugin_Installer_Skin;
|
||||
use Plugin_Upgrader;
|
||||
use WP_Error;
|
||||
use WPSEO_Capability_Manager_Factory;
|
||||
use WPSEO_Options;
|
||||
use WPSEO_Premium_Option;
|
||||
|
||||
/**
|
||||
* Class responsible for performing the premium as an addon installer.
|
||||
*/
|
||||
class Addon_Installer {
|
||||
|
||||
/**
|
||||
* The option key for tracking the status of the installer.
|
||||
*/
|
||||
public const OPTION_KEY = 'yoast_premium_as_an_addon_installer';
|
||||
|
||||
/**
|
||||
* The minimum Yoast SEO version required.
|
||||
*/
|
||||
public const MINIMUM_YOAST_SEO_VERSION = '22.0';
|
||||
|
||||
/**
|
||||
* The base directory for the installer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_dir;
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_version = '0';
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO plugin file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_file = 'wordpress-seo/wp-seo.php';
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_dir = \WP_PLUGIN_DIR . '/wordpress-seo';
|
||||
|
||||
/**
|
||||
* Addon installer constructor.
|
||||
*
|
||||
* @param string $base_dir The base directory.
|
||||
*/
|
||||
public function __construct( $base_dir ) {
|
||||
$this->base_dir = $base_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the installer if it hasn't been done yet.
|
||||
*
|
||||
* A notice will be shown in the admin if the installer failed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install_yoast_seo_from_repository() {
|
||||
\add_action( 'admin_notices', [ $this, 'show_install_yoast_seo_notification' ] );
|
||||
\add_action( 'network_admin_notices', [ $this, 'show_install_yoast_seo_notification' ] );
|
||||
\add_action( 'plugins_loaded', [ $this, 'validate_installation_status' ] );
|
||||
if ( ! $this->get_status() ) {
|
||||
try {
|
||||
$this->install();
|
||||
} catch ( Exception $e ) {
|
||||
// Auto installation failed, the notice will be displayed.
|
||||
return;
|
||||
}
|
||||
}
|
||||
elseif ( $this->get_status() === 'started' ) {
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$this->detect_yoast_seo();
|
||||
if ( \is_plugin_active( $this->yoast_seo_file ) ) {
|
||||
// Yoast SEO is active so mark installation as successful.
|
||||
\update_option( self::OPTION_KEY, 'completed', true );
|
||||
// Enable tracking.
|
||||
if ( \class_exists( WPSEO_Options::class ) ) {
|
||||
WPSEO_Premium_Option::register_option();
|
||||
if ( WPSEO_Options::get( 'toggled_tracking' ) !== true ) {
|
||||
WPSEO_Options::set( 'tracking', true );
|
||||
}
|
||||
WPSEO_Options::set( 'should_redirect_after_install', true );
|
||||
}
|
||||
|
||||
if ( \class_exists( WPSEO_Capability_Manager_Factory::class ) ) {
|
||||
\do_action( 'wpseo_register_capabilities_premium' );
|
||||
WPSEO_Capability_Manager_Factory::get( 'premium' )->add();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the installer if it hasn't been done yet.
|
||||
* Otherwise attempts to load Yoast SEO from the vendor directory.
|
||||
*
|
||||
* @deprecated 21.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install_or_load_yoast_seo_from_vendor_directory() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 21.9' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a notification to install Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show_install_yoast_seo_notification() {
|
||||
if ( ! $this->should_show_notification() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$this->detect_yoast_seo();
|
||||
|
||||
$action = $this->get_notification_action();
|
||||
|
||||
if ( ! $action ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo (
|
||||
'<div class="error">'
|
||||
. '<p>'
|
||||
. \sprintf(
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required, %3$s: Yoast SEO Premium. */
|
||||
\esc_html__( '%1$s %2$s must be installed and activated in order to use %3$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION ),
|
||||
'Yoast SEO Premium'
|
||||
)
|
||||
. '</p>'
|
||||
. '<p>'
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
|
||||
. $action
|
||||
. '</p>'
|
||||
. '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notification action to display.
|
||||
*
|
||||
* @return false|string The notification action or false if no action should be taken.
|
||||
*/
|
||||
protected function get_notification_action() {
|
||||
$minimum_version_met = \version_compare( $this->yoast_seo_version, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '>=' );
|
||||
$network_active = \is_plugin_active_for_network( \WPSEO_PREMIUM_BASENAME );
|
||||
$yoast_seo_active = ( $network_active ) ? \is_plugin_active_for_network( $this->yoast_seo_file ) : \is_plugin_active( $this->yoast_seo_file );
|
||||
|
||||
if ( $minimum_version_met && $yoast_seo_active ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $minimum_version_met ) {
|
||||
$permission = 'activate_plugins';
|
||||
}
|
||||
elseif ( $this->yoast_seo_version !== '0' ) {
|
||||
$permission = 'update_plugins';
|
||||
}
|
||||
else {
|
||||
$permission = 'install_plugins';
|
||||
}
|
||||
|
||||
if ( \current_user_can( $permission ) ) {
|
||||
switch ( $permission ) {
|
||||
case 'activate_plugins':
|
||||
if ( $network_active ) {
|
||||
$base_url = \network_admin_url( 'plugins.php?action=activate&plugin=' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sNetwork Activate %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
}
|
||||
else {
|
||||
$base_url = \self_admin_url( 'plugins.php?action=activate&plugin=' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sActivate %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
}
|
||||
$url = \wp_nonce_url( $base_url, 'activate-plugin_' . $this->yoast_seo_file );
|
||||
break;
|
||||
case 'update_plugins':
|
||||
$url = \wp_nonce_url( \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . $this->yoast_seo_file ), 'upgrade-plugin_' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sUpgrade %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
break;
|
||||
case 'install_plugins':
|
||||
$url = \wp_nonce_url( \self_admin_url( 'update.php?action=install-plugin&plugin=wordpress-seo' ), 'install-plugin_wordpress-seo' );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sInstall %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
break;
|
||||
}
|
||||
return \sprintf(
|
||||
\esc_html( $button_content ),
|
||||
'Yoast SEO',
|
||||
'<a class="button" href="' . \esc_url( $url ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( \is_multisite() ) {
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required. */
|
||||
$message = \__( 'Please contact a network administrator to install %1$s %2$s.', 'wordpress-seo-premium' );
|
||||
}
|
||||
else {
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required. */
|
||||
$message = \__( 'Please contact an administrator to install %1$s %2$s.', 'wordpress-seo-premium' );
|
||||
}
|
||||
return \sprintf(
|
||||
\esc_html( $message ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Yoast SEO is at a minimum required version.
|
||||
*
|
||||
* @return bool True if Yoast SEO is at a minimal required version
|
||||
*/
|
||||
public static function is_yoast_seo_up_to_date() {
|
||||
return ( \defined( 'WPSEO_VERSION' ) && \version_compare( \WPSEO_VERSION, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '>=' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the installation status if Yoast SEO is not installed or outdated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate_installation_status() {
|
||||
if ( ! self::is_yoast_seo_up_to_date() ) {
|
||||
\delete_option( self::OPTION_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the installer.
|
||||
*
|
||||
* This uses a separate option from our options framework as it needs to be available
|
||||
* before Yoast SEO has been loaded.
|
||||
*
|
||||
* @return false|string false if the installer hasn't been started.
|
||||
* "started" if it has but hasn't completed.
|
||||
* "completed" if it has been completed.
|
||||
*/
|
||||
protected function get_status() {
|
||||
return \get_option( self::OPTION_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs to premium as an addon.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the installer failed.
|
||||
*/
|
||||
protected function install() {
|
||||
if ( $this->get_status() ) {
|
||||
return;
|
||||
}
|
||||
// Mark the installer as having been started but not completed.
|
||||
\update_option( self::OPTION_KEY, 'started', true );
|
||||
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$this->detect_yoast_seo();
|
||||
// Either the plugin is not installed or is installed and too old.
|
||||
if ( \version_compare( $this->yoast_seo_version, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '<' ) ) {
|
||||
include_once \ABSPATH . 'wp-includes/pluggable.php';
|
||||
include_once \ABSPATH . 'wp-admin/includes/file.php';
|
||||
include_once \ABSPATH . 'wp-admin/includes/misc.php';
|
||||
require_once \ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
|
||||
// The class is defined inline to avoid problems with the autoloader when extending a WP class.
|
||||
$skin = new class() extends Plugin_Installer_Skin {
|
||||
|
||||
/**
|
||||
* Suppresses the header.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function header() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the footer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function footer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the errors.
|
||||
*
|
||||
* @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Flags unused params which are required via the interface. Invalid.
|
||||
*
|
||||
* @param string|WP_Error $errors Errors.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error( $errors ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the feedback.
|
||||
*
|
||||
* @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Flags unused params which are required via the interface. Invalid.
|
||||
*
|
||||
* @param string $feedback Message data.
|
||||
* @param array<string> ...$args Optional text replacements.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function feedback( $feedback, ...$args ) {
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the minimum version is available, otherwise we'll download the zip from SVN trunk (which should be the latest RC).
|
||||
$url = 'https://downloads.wordpress.org/plugin/wordpress-seo.' . self::MINIMUM_YOAST_SEO_VERSION . '.zip';
|
||||
$check_result = \wp_remote_retrieve_response_code( \wp_remote_head( $url ) );
|
||||
if ( $check_result !== 200 ) {
|
||||
$url = 'https://downloads.wordpress.org/plugin/wordpress-seo.zip';
|
||||
}
|
||||
|
||||
$upgrader = new Plugin_Upgrader( $skin );
|
||||
$installed = $upgrader->install( $url );
|
||||
if ( \is_wp_error( $installed ) || ! $installed ) {
|
||||
throw new Exception( 'Could not automatically install Yoast SEO' );
|
||||
}
|
||||
}
|
||||
|
||||
$this->ensure_yoast_seo_is_activated();
|
||||
$this->transfer_auto_update_settings();
|
||||
// Mark the installer as having been completed.
|
||||
\update_option( self::OPTION_KEY, 'completed', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the Yoast SEO plugin file and version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function detect_yoast_seo() {
|
||||
// Make sure Yoast SEO isn't already installed in another directory.
|
||||
foreach ( \get_plugins() as $file => $plugin ) {
|
||||
// Use text domain to identify the plugin as it's the closest thing to a slug.
|
||||
if (
|
||||
isset( $plugin['TextDomain'] ) && $plugin['TextDomain'] === 'wordpress-seo'
|
||||
&& isset( $plugin['Name'] ) && $plugin['Name'] === 'Yoast SEO'
|
||||
) {
|
||||
$this->yoast_seo_file = $file;
|
||||
$this->yoast_seo_version = ( $plugin['Version'] ?? '0' );
|
||||
$this->yoast_seo_dir = \WP_PLUGIN_DIR . '/' . \dirname( $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If Yoast SEO could not be activated.
|
||||
*/
|
||||
protected function ensure_yoast_seo_is_activated() {
|
||||
if ( ! \is_plugin_active( $this->yoast_seo_file ) ) {
|
||||
$network_active = \is_plugin_active_for_network( \WPSEO_PREMIUM_BASENAME );
|
||||
// If we're not active at all it means we're being activated.
|
||||
if ( ! $network_active && ! \is_plugin_active( \WPSEO_PREMIUM_BASENAME ) ) {
|
||||
// So set network active to whether or not we're in the network admin.
|
||||
$network_active = \is_network_admin();
|
||||
}
|
||||
// Activate Yoast SEO. If Yoast SEO Premium is network active then make sure Yoast SEO is as well.
|
||||
$activation = \activate_plugin( $this->yoast_seo_file, '', $network_active );
|
||||
if ( \is_wp_error( $activation ) ) {
|
||||
throw new Exception( \esc_html( 'Could not activate Yoast SEO: ' . $activation->get_error_message() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the auto update settings for Yoast SEO Premium to Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function transfer_auto_update_settings() {
|
||||
$auto_updates = (array) \get_site_option( 'auto_update_plugins', [] );
|
||||
|
||||
if ( \in_array( \WPSEO_PREMIUM_BASENAME, $auto_updates, true ) ) {
|
||||
$auto_updates[] = $this->yoast_seo_file;
|
||||
$auto_updates = \array_unique( $auto_updates );
|
||||
\update_site_option( 'auto_update_plugins', $auto_updates );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether or not the notification to install Yoast SEO should be shown.
|
||||
*
|
||||
* This is copied from the Yoast_Admin_And_Dashboard_Conditional which we can't use as Yoast SEO may not be installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_show_notification() {
|
||||
global $pagenow;
|
||||
|
||||
// Do not output on plugin / theme upgrade pages or when WordPress is upgrading.
|
||||
if ( ( \defined( 'IFRAME_REQUEST' ) && \IFRAME_REQUEST ) || \wp_installing() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* IFRAME_REQUEST is not defined on these pages,
|
||||
* though these action pages do show when upgrading themes or plugins.
|
||||
*/
|
||||
$actions = [ 'do-theme-upgrade', 'do-plugin-upgrade', 'do-core-upgrade', 'do-core-reinstall' ];
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['action'] ) && \in_array( $_GET['action'], $actions, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- Reason: We are not processing form information, only a strpos is done in the input.
|
||||
if ( $pagenow === 'admin.php' && isset( $_GET['page'] ) && \strpos( $_GET['page'], 'wpseo' ) === 0 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$target_pages = [
|
||||
'index.php',
|
||||
'plugins.php',
|
||||
'update-core.php',
|
||||
'options-permalink.php',
|
||||
];
|
||||
|
||||
return \in_array( $pagenow, $target_pages, true );
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
|
||||
/**
|
||||
* Conditional that is met when the AI editor integration should be active.
|
||||
*/
|
||||
class Ai_Editor_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* Holds the Post_Conditional.
|
||||
*
|
||||
* @var Post_Conditional
|
||||
*/
|
||||
private $post_conditional;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Constructs Ai_Editor_Conditional.
|
||||
*
|
||||
* @param Post_Conditional $post_conditional The Post_Conditional.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct( Post_Conditional $post_conditional, Current_Page_Helper $current_page_helper ) {
|
||||
$this->post_conditional = $post_conditional;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` when the AI editor integration should be active.
|
||||
*
|
||||
* @return bool `true` when the AI editor integration should be active.
|
||||
*/
|
||||
public function is_met() {
|
||||
return $this->post_conditional->is_met() || $this->is_elementor_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` when the page is the elementor editor.
|
||||
*
|
||||
* @return bool `true` when the page is the elementor editor.
|
||||
*/
|
||||
private function is_elementor_editor() {
|
||||
if ( $this->current_page_helper->get_current_admin_page() !== 'post.php' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['action'] ) && \is_string( $_GET['action'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing.
|
||||
if ( \wp_unslash( $_GET['action'] ) === 'elementor' ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when the Algolia integration is enabled.
|
||||
*/
|
||||
class Algolia_Enabled_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Algolia_Enabled_Conditional constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this conditional is met.
|
||||
*
|
||||
* @return bool Whether or not the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
return $this->options_helper->get( 'algolia_integration_active' ) === true;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Cornerstone_Enabled_Conditional class.
|
||||
*/
|
||||
class Cornerstone_Enabled_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Cornerstone_Enabled_Conditional constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` when the cornerstone content feature is enabled.
|
||||
*
|
||||
* @return bool `true` when the cornerstone content feature is enabled.
|
||||
*/
|
||||
public function is_met() {
|
||||
return $this->options_helper->get( 'enable_cornerstone_content' );
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when Easy Digital Downloads is active.
|
||||
*/
|
||||
class EDD_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* Returns `true` when the Easy Digital Downloads plugin is installed and activated.
|
||||
*
|
||||
* @return bool `true` when the Easy Digital Downloads plugin is installed and activated.
|
||||
*/
|
||||
public function is_met() {
|
||||
return \class_exists( 'Easy_Digital_Downloads' );
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use WPSEO_Metabox_Analysis_Inclusive_Language;
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Enabled_Conditional class.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Inclusive_Language_Enabled_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* Returns `true` when the inclusive language analysis is enabled.
|
||||
*
|
||||
* @return bool `true` when the inclusive language analysis is enabled.
|
||||
*/
|
||||
public function is_met() {
|
||||
$analysis_inclusive_language = new WPSEO_Metabox_Analysis_Inclusive_Language();
|
||||
return $analysis_inclusive_language->is_enabled();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when on a term overview page or during an ajax request.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Term_Overview_Or_Ajax_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* Returns whether or not this conditional is met.
|
||||
*
|
||||
* @return bool Whether or not the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
global $pagenow;
|
||||
return $pagenow === 'edit-tags.php' || \wp_doing_ajax();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast admin.
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Yoast_Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Main;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when on either on a Yoast SEO admin page or on the Yoast introductions rest route.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Yoast_Admin_Or_Introductions_Route_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* Holds the Yoast_Admin_Conditional.
|
||||
*
|
||||
* @var Yoast_Admin_Conditional
|
||||
*/
|
||||
private $yoast_admin_conditional;
|
||||
|
||||
/**
|
||||
* Constructs the instance.
|
||||
*
|
||||
* @param Yoast_Admin_Conditional $yoast_admin_conditional The Yoast_Admin_Conditional.
|
||||
*/
|
||||
public function __construct( Yoast_Admin_Conditional $yoast_admin_conditional ) {
|
||||
$this->yoast_admin_conditional = $yoast_admin_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this conditional is met.
|
||||
*
|
||||
* @return bool Whether the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
if ( $this->yoast_admin_conditional->is_met() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->is_post_request() && $this->is_introductions_rest_request() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request method is POST.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_post_request() {
|
||||
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request URI starts with the prefix, Yoast API V1 and introductions.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_introductions_rest_request() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Variable is only used in a case-insensitive comparison.
|
||||
return \stripos( $_SERVER['REQUEST_URI'], '/' . \rest_get_url_prefix() . '/' . Main::API_V1_NAMESPACE . '/introductions/' ) === 0;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Config;
|
||||
|
||||
use Yoast\WP\SEO\Config\Badge_Group_Names as New_Badge_Group_Names;
|
||||
|
||||
/**
|
||||
* Class Badge_Group_Names.
|
||||
*
|
||||
* This class defines groups for "new" badges, with the version in which those groups are no longer considered
|
||||
* to be "new".
|
||||
*/
|
||||
class Badge_Group_Names extends New_Badge_Group_Names {
|
||||
|
||||
public const GROUP_GLOBAL_TEMPLATES = 'global-templates';
|
||||
|
||||
/**
|
||||
* Constant describing when certain groups of new badges will no longer be shown.
|
||||
*/
|
||||
public const GROUP_NAMES = [
|
||||
self::GROUP_GLOBAL_TEMPLATES => '16.5-beta0',
|
||||
];
|
||||
|
||||
/**
|
||||
* Badge_Group_Names constructor.
|
||||
*
|
||||
* @param string|null $version Optional. The current version number.
|
||||
*/
|
||||
public function __construct( $version = null ) {
|
||||
parent::__construct( $version );
|
||||
|
||||
if ( ! $version ) {
|
||||
$version = \WPSEO_PREMIUM_VERSION;
|
||||
}
|
||||
$this->version = $version;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Config\Migrations;
|
||||
|
||||
use Yoast\WP\Lib\Migrations\Migration;
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Class WpYoastPremiumImprovedInternalLinking
|
||||
*
|
||||
* @package Yoast\WP\SEO\Config\Migrations
|
||||
*/
|
||||
class WpYoastPremiumImprovedInternalLinking extends Migration {
|
||||
|
||||
/**
|
||||
* The plugin this migration belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin = 'premium';
|
||||
|
||||
/**
|
||||
* Migration up.
|
||||
*/
|
||||
public function up() {
|
||||
$table_name = $this->get_table_name();
|
||||
$adapter = $this->get_adapter();
|
||||
|
||||
if ( ! $adapter->has_table( $table_name ) ) {
|
||||
$table = $this->create_table( $table_name );
|
||||
|
||||
$table->column(
|
||||
'stem',
|
||||
'string',
|
||||
[
|
||||
'null' => true,
|
||||
'limit' => 191,
|
||||
]
|
||||
);
|
||||
$table->column(
|
||||
'indexable_id',
|
||||
'integer',
|
||||
[
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'limit' => 11,
|
||||
]
|
||||
);
|
||||
$table->column( 'weight', 'float' );
|
||||
|
||||
$table->finish();
|
||||
}
|
||||
|
||||
if ( ! $adapter->has_index( $table_name, 'stem', [ 'name' => 'stem' ] ) ) {
|
||||
$this->add_index(
|
||||
$table_name,
|
||||
[
|
||||
'stem',
|
||||
],
|
||||
[
|
||||
'name' => 'stem',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $adapter->has_index( $table_name, 'indexable_id', [ 'name' => 'indexable_id' ] ) ) {
|
||||
$this->add_index(
|
||||
$table_name,
|
||||
[
|
||||
'indexable_id',
|
||||
],
|
||||
[
|
||||
'name' => 'indexable_id',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration down.
|
||||
*/
|
||||
public function down() {
|
||||
$table_name = $this->get_table_name();
|
||||
if ( $this->get_adapter()->has_table( $table_name ) ) {
|
||||
$this->drop_table( $table_name );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table name to use.
|
||||
*
|
||||
* @return string The table name to use.
|
||||
*/
|
||||
protected function get_table_name() {
|
||||
return Model::get_table_name( 'Prominent_Words' );
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Config\Migrations;
|
||||
|
||||
use Yoast\WP\Lib\Migrations\Migration;
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* AddIndexOnIndexableIdAndStem class.
|
||||
*/
|
||||
class AddIndexOnIndexableIdAndStem extends Migration {
|
||||
|
||||
/**
|
||||
* The plugin this migration belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin = 'premium';
|
||||
|
||||
/**
|
||||
* The columns on which an index should be added.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $columns_with_index = [
|
||||
'indexable_id',
|
||||
'stem',
|
||||
];
|
||||
|
||||
/**
|
||||
* Migration up. Adds a combined index on 'indexable_id' and 'stem'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up() {
|
||||
$table_name = $this->get_table_name();
|
||||
$adapter = $this->get_adapter();
|
||||
|
||||
if ( ! $adapter->has_table( $table_name ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the index if it doesn't exist already.
|
||||
if ( ! $adapter->has_index( $table_name, $this->columns_with_index, [ 'name' => 'indexable_id_and_stem' ] ) ) {
|
||||
$this->add_index(
|
||||
$this->get_table_name(),
|
||||
$this->columns_with_index,
|
||||
[ 'name' => 'indexable_id_and_stem' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration down. Removes the combined index on 'indexable_id' and 'stem'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down() {
|
||||
$table_name = $this->get_table_name();
|
||||
$adapter = $this->get_adapter();
|
||||
|
||||
if ( ! $adapter->has_table( $table_name ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the index if it exists.
|
||||
if ( $adapter->has_index( $table_name, $this->columns_with_index, [ 'name' => 'indexable_id_and_stem' ] ) ) {
|
||||
|
||||
$this->remove_index(
|
||||
$this->get_table_name(),
|
||||
$this->columns_with_index,
|
||||
[ 'name' => 'indexable_id_and_stem' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table name to use for storing prominent words.
|
||||
*
|
||||
* @return string The table name to use.
|
||||
*/
|
||||
protected function get_table_name() {
|
||||
return Model::get_table_name( 'Prominent_Words' );
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Database;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Initializers\Migration_Runner;
|
||||
|
||||
/**
|
||||
* Triggers premium database migrations and handles results.
|
||||
*/
|
||||
class Migration_Runner_Premium extends Migration_Runner {
|
||||
|
||||
/**
|
||||
* Runs this initializer.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->run_premium_migrations();
|
||||
|
||||
// The below action is used when queries fail, this may happen in a multisite environment when switch_to_blog is used.
|
||||
\add_action( '_yoast_run_migrations', [ $this, 'run_premium_migrations' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Premium migrations.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception When a migration errored.
|
||||
*/
|
||||
public function run_premium_migrations() {
|
||||
$this->run_migrations( 'premium', \WPSEO_PREMIUM_VERSION );
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use Yoast\WP\SEO\Premium\Helpers\Zapier_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Handles the actual requests to the Zapier endpoints.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Action {
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
protected $zapier_helper;
|
||||
|
||||
/**
|
||||
* The Indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Zapier_Action constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
* @param Indexable_Repository $indexable_repository The Indexable repository.
|
||||
*/
|
||||
public function __construct( Zapier_Helper $zapier_helper, Indexable_Repository $indexable_repository ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->zapier_helper = $zapier_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes Zapier and stores the passed URL for later usage.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $url The URL to subscribe.
|
||||
* @param string $api_key The API key from Zapier to check against the one stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function subscribe( $url, $api_key ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
if ( $this->zapier_helper->is_connected() ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'Subscribing failed. A subscription already exists.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$subscription_data = $this->zapier_helper->subscribe_url( $url );
|
||||
|
||||
if ( ! $subscription_data ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'Subscribing failed.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => $subscription_data,
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes Zapier based on the passed ID.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $id The ID to unsubscribe.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function unsubscribe( $id ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_subscribed_id( $id ) ) {
|
||||
return (object) [
|
||||
'message' => \sprintf( 'Unsubscribing failed. Subscription with ID `%s` does not exist.', $id ),
|
||||
'status' => 404,
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! $this->zapier_helper->unsubscribe_id( $id ) ) {
|
||||
return (object) [
|
||||
'message' => 'Unsubscribing failed. Unable to delete subscription.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'message' => \sprintf( 'Successfully unsubscribed subscription with ID `%s`.', $id ),
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the API key submitted by Zapier.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $api_key The API key from Zapier to check against the one
|
||||
* stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function check_api_key( $api_key ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key is valid.',
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an array of the last published post URLs.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $api_key The API key from Zapier to check against the one
|
||||
* stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function perform_list( $api_key ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$latest_post = \get_posts(
|
||||
[
|
||||
'numberposts' => 1,
|
||||
]
|
||||
);
|
||||
$zapier_data = [];
|
||||
foreach ( $latest_post as $item ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $item->ID, 'post' );
|
||||
if ( $indexable ) {
|
||||
$zapier_data[] = (object) $this->zapier_helper->get_data_for_zapier( $indexable );
|
||||
}
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => $zapier_data,
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Zapier is connected.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function is_connected() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return (object) [
|
||||
'data' => [
|
||||
'is_connected' => $this->zapier_helper->is_connected(),
|
||||
],
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the API key in the DB.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $api_key The API key to be reset.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function reset_api_key( $api_key ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$this->zapier_helper->reset_api_key_and_subscription();
|
||||
$new_api_key = $this->zapier_helper->get_or_generate_zapier_api_key();
|
||||
|
||||
return (object) [
|
||||
'data' => [
|
||||
'zapier_api_key' => $new_api_key,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class represents the fetching of a full name for a user who has filled in a Facebook profile url. The class
|
||||
* will try to fetch the full name via the Facebook following plugin (widget). If the user has chosen to disallow
|
||||
* following of his profile, there isn't returned any name - only an empty string.
|
||||
*
|
||||
* To prevent doing request all the time, the obtained name will be stored as user meta for the user.
|
||||
*
|
||||
* @deprecated 20.3
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WPSEO_Facebook_Profile {
|
||||
|
||||
public const TRANSIENT_NAME = 'yoast_facebook_profiles';
|
||||
|
||||
/**
|
||||
* URL providing us the full name belonging to the user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $facebook_endpoint = 'https://www.facebook.com/plugins/follow.php?href=';
|
||||
|
||||
/**
|
||||
* Sets the AJAX action hook, to catch the AJAX request for getting the name on Facebook.
|
||||
*
|
||||
* @deprecated 20.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_hooks() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.3' );
|
||||
add_action( 'wp_ajax_wpseo_get_facebook_name', [ $this, 'ajax_get_facebook_name' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user id and prints the full Facebook name.
|
||||
*
|
||||
* @deprecated 20.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_facebook_name() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.3' );
|
||||
if ( wp_doing_ajax() ) {
|
||||
check_ajax_referer( 'get_facebook_name' );
|
||||
|
||||
$user_id = (int) filter_input( INPUT_GET, 'user_id' );
|
||||
$facebook_profile = $this->get_facebook_profile( $user_id );
|
||||
|
||||
// Only try to get the name when the user has a profile set.
|
||||
if ( $facebook_profile !== '' ) {
|
||||
wp_die( esc_html( $this->get_name( $facebook_profile ) ) );
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Facebook profile url from the user profile.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $user_id The user to get the Facebook profile field for.
|
||||
*
|
||||
* @return string URL or empty string if the field is not set or empty.
|
||||
*/
|
||||
private function get_facebook_profile( $user_id ) {
|
||||
$facebook_profile = get_the_author_meta( 'facebook', $user_id );
|
||||
|
||||
if ( ! empty( $facebook_profile ) ) {
|
||||
return $facebook_profile;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name used on Facebook from the transient cache, if the name isn't
|
||||
* fetched already get it from the Facebook follow widget.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $facebook_profile The profile to get.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_name( $facebook_profile ) {
|
||||
$cached_facebook_name = $this->get_cached_name( $facebook_profile );
|
||||
if ( $cached_facebook_name !== false ) {
|
||||
return $cached_facebook_name;
|
||||
}
|
||||
|
||||
$facebook_name = $this->get_name_from_facebook( $facebook_profile );
|
||||
|
||||
$this->set_cached_name( $facebook_profile, $facebook_name );
|
||||
|
||||
return $facebook_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored name from the user meta.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $facebook_profile The Facebook profile to look for.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
private function get_cached_name( $facebook_profile ) {
|
||||
$facebook_profiles = get_transient( self::TRANSIENT_NAME );
|
||||
if ( is_array( $facebook_profiles ) && array_key_exists( $facebook_profile, $facebook_profiles ) ) {
|
||||
return $facebook_profiles[ $facebook_profile ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fetched Facebook name to the user meta.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $facebook_profile The Facebook profile belonging to the name.
|
||||
* @param string $facebook_name The name the user got on Facebook.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_cached_name( $facebook_profile, $facebook_name ) {
|
||||
$facebook_profiles = get_transient( self::TRANSIENT_NAME );
|
||||
|
||||
$facebook_profiles[ $facebook_profile ] = $facebook_name;
|
||||
|
||||
set_transient( self::TRANSIENT_NAME, $facebook_profiles, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Do request to Facebook to get the HTML for the follow widget.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $facebook_profile The profile URL to lookup.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_name_from_facebook( $facebook_profile ) {
|
||||
$response = wp_remote_get(
|
||||
$this->facebook_endpoint . $facebook_profile,
|
||||
[
|
||||
'headers' => [ 'Accept-Language' => 'en_US' ],
|
||||
]
|
||||
);
|
||||
|
||||
if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
|
||||
return $this->extract_name_from_response(
|
||||
wp_remote_retrieve_body( $response )
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to extract the full name from the response.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $response_body The response HTML to lookup for the full name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extract_name_from_response( $response_body ) {
|
||||
$full_name_regex = '/<div class="pluginButton pluginButtonInline pluginConnectButtonDisconnected" title="Follow(.*)'s public updates">/i';
|
||||
|
||||
if ( preg_match( $full_name_regex, $response_body, $matches ) ) {
|
||||
if ( ! empty( $matches[1] ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Zapier_Helper;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when the Zapier integration is enabled.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Enabled_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
private $zapier;
|
||||
|
||||
/**
|
||||
* Zapier_Enabled_Conditional constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Zapier_Helper $zapier The Zapier helper.
|
||||
*/
|
||||
public function __construct( Zapier_Helper $zapier ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO Premium 20.7' );
|
||||
|
||||
$this->zapier = $zapier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this conditional is met.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether or not the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO Premium 20.7' );
|
||||
|
||||
return $this->zapier->is_enabled();
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* Class Zapier_Helper
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Yoast\WP\SEO\Helpers
|
||||
*/
|
||||
class Zapier_Helper {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The meta surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
protected $meta_surface;
|
||||
|
||||
/**
|
||||
* Zapier_Helper constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Meta_Surface $meta_surface The Meta surface.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, Meta_Surface $meta_surface ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->options = $options;
|
||||
$this->meta_surface = $meta_surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a subscription exists in the database.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether a subscription exists in the database.
|
||||
*/
|
||||
public function is_connected() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$subscription = $this->options->get( 'zapier_subscription' );
|
||||
|
||||
if ( \is_array( $subscription )
|
||||
&& ! empty( $subscription['id'] )
|
||||
&& \filter_var( $subscription['url'], \FILTER_VALIDATE_URL )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Zapier integration is currently enabled.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether the integration is enabled.
|
||||
*/
|
||||
public function is_enabled() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return (bool) $this->options->get( 'zapier_integration_active', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored Zapier API Key.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The Zapier API Key.
|
||||
*/
|
||||
public function get_or_generate_zapier_api_key() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$zapier_api_key = $this->options->get( 'zapier_api_key' );
|
||||
|
||||
if ( empty( $zapier_api_key ) ) {
|
||||
$zapier_api_key = \wp_generate_password( 32, false );
|
||||
$this->options->set( 'zapier_api_key', $zapier_api_key );
|
||||
}
|
||||
|
||||
return $zapier_api_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stored Zapier API Key and subscription data.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset_api_key_and_subscription() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->options->set( 'zapier_api_key', '' );
|
||||
$this->options->set( 'zapier_subscription', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string matches the API key in the DB, if present.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $api_key The API key to test.
|
||||
*
|
||||
* @return bool Whether the API key is valid or not.
|
||||
*/
|
||||
public function is_valid_api_key( $api_key ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return ( ! empty( $api_key ) && $this->options->get( 'zapier_api_key' ) === $api_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Zapier hook URL of the trigger if present, null otherwise.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string|null The hook URL, null if not set.
|
||||
*/
|
||||
public function get_trigger_url() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( $this->is_connected() ) {
|
||||
$subscription = $this->options->get( 'zapier_subscription', [] );
|
||||
|
||||
return $subscription['url'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the submitted id is present in the subscriptions.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $id The id to be tested.
|
||||
*
|
||||
* @return bool Whether the id is present in the subscriptions.
|
||||
*/
|
||||
public function is_subscribed_id( $id ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( $this->is_connected() ) {
|
||||
$subscription = $this->options->get( 'zapier_subscription', [] );
|
||||
|
||||
return $subscription['id'] === $id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes the submitted id.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $id The id to be unsubscribed.
|
||||
*
|
||||
* @return bool Whether the unsubscription was successful.
|
||||
*/
|
||||
public function unsubscribe_id( $id ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( $this->is_connected() && $this->is_subscribed_id( $id ) ) {
|
||||
return $this->options->set( 'zapier_subscription', [] );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subscription with the submitted URL.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $url The URL to be subscribed.
|
||||
*
|
||||
* @return array|bool The subscription data (id and URL) if successful, false otherwise.
|
||||
*/
|
||||
public function subscribe_url( $url ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->is_connected() ) {
|
||||
$subscription_data = [
|
||||
'id' => \wp_generate_password( 32, false ),
|
||||
'url' => \esc_url_raw( $url, [ 'http', 'https' ] ),
|
||||
];
|
||||
|
||||
if ( $this->options->set( 'zapier_subscription', $subscription_data ) ) {
|
||||
return $subscription_data;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the data for Zapier.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Indexable $indexable The indexable from which the data must be extracted.
|
||||
*
|
||||
* @return array[] The array of data ready to be sent to Zapier.
|
||||
*/
|
||||
public function get_data_for_zapier( Indexable $indexable ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$post = \get_post( $indexable->object_id );
|
||||
if ( ! $post ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$meta = $this->meta_surface->for_indexable( $indexable );
|
||||
|
||||
$open_graph_image = '';
|
||||
if ( \count( $meta->open_graph_images ) > 0 ) {
|
||||
$open_graph_image_array = \reset( $meta->open_graph_images );
|
||||
$open_graph_image = $open_graph_image_array['url'];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $indexable->permalink,
|
||||
'post_type' => $post->post_type,
|
||||
'post_title' => \html_entity_decode( $post->post_title ),
|
||||
'author' => \get_the_author_meta( 'display_name', $post->post_author ),
|
||||
'tags' => \html_entity_decode( \implode( ', ', \wp_get_post_tags( $post->ID, [ 'fields' => 'names' ] ) ) ),
|
||||
'categories' => \html_entity_decode( \implode( ', ', \wp_get_post_categories( $post->ID, [ 'fields' => 'names' ] ) ) ),
|
||||
'primary_category' => \html_entity_decode( \yoast_get_primary_term( 'category', $post ) ),
|
||||
'meta_description' => \html_entity_decode( $meta->description ),
|
||||
'open_graph_title' => \html_entity_decode( $meta->open_graph_title ),
|
||||
'open_graph_description' => \html_entity_decode( $meta->open_graph_description ),
|
||||
'open_graph_image' => $open_graph_image,
|
||||
'twitter_title' => \html_entity_decode( $meta->twitter_title ),
|
||||
'twitter_description' => \html_entity_decode( $meta->twitter_description ),
|
||||
'twitter_image' => $meta->twitter_image,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the post type is supported by the Zapier integration.
|
||||
*
|
||||
* The Zapier integration should be visible and working only for post types
|
||||
* that support the Yoast Metabox. We filter out attachments regardless of
|
||||
* the Yoast SEO settings, anyway.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $post_type The post type to be checked.
|
||||
*
|
||||
* @return bool Whether the post type is supported by the Zapier integration.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return $post_type !== 'attachment' && WPSEO_Utils::is_metabox_active( $post_type, 'post_type' );
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Url_Helper;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Class Crawl_Cleanup_Permalinks.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Crawl_Cleanup_Permalinks implements Initializer_Interface {
|
||||
|
||||
/**
|
||||
* The current page helper
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The URL helper.
|
||||
*
|
||||
* @var Url_Helper
|
||||
*/
|
||||
private $url_helper;
|
||||
|
||||
/**
|
||||
* Crawl Cleanup Permalinks constructor.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Current_Page_Helper $current_page_helper The current page helper.
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
* @param Url_Helper $url_helper The URL helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Current_Page_Helper $current_page_helper,
|
||||
Options_Helper $options_helper,
|
||||
Url_Helper $url_helper
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks' );
|
||||
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->url_helper = $url_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks::initialize()' );
|
||||
|
||||
// We need to hook after 10 because otherwise our options helper isn't available yet.
|
||||
\add_action( 'plugins_loaded', [ $this, 'register_hooks' ], 15 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks our required hooks.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks::register_hooks()' );
|
||||
|
||||
if ( $this->options_helper->get( 'clean_campaign_tracking_urls' ) && ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
\add_action( 'template_redirect', [ $this, 'utm_redirect' ], 0 );
|
||||
}
|
||||
if ( $this->options_helper->get( 'clean_permalinks' ) && ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
\add_action( 'template_redirect', [ $this, 'clean_permalinks' ], 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks::get_conditionals()' );
|
||||
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect utm variables away.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function utm_redirect() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks::utm_redirect()' );
|
||||
|
||||
// Prevents WP CLI from throwing an error.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) || \strpos( $_SERVER['REQUEST_URI'], '?' ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
if ( ! \stripos( $_SERVER['REQUEST_URI'], 'utm_' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
$parsed = \wp_parse_url( $_SERVER['REQUEST_URI'] );
|
||||
|
||||
$query = \explode( '&', $parsed['query'] );
|
||||
$utms = [];
|
||||
$other_args = [];
|
||||
|
||||
foreach ( $query as $query_arg ) {
|
||||
if ( \stripos( $query_arg, 'utm_' ) === 0 ) {
|
||||
$utms[] = $query_arg;
|
||||
continue;
|
||||
}
|
||||
$other_args[] = $query_arg;
|
||||
}
|
||||
|
||||
if ( empty( $utms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$other_args_str = '';
|
||||
if ( \count( $other_args ) > 0 ) {
|
||||
$other_args_str = '?' . \implode( '&', $other_args );
|
||||
}
|
||||
|
||||
$new_path = $parsed['path'] . $other_args_str . '#' . \implode( '&', $utms );
|
||||
|
||||
$message = \sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium */
|
||||
\__( '%1$s: redirect utm variables to #', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
|
||||
\wp_safe_redirect( \trailingslashit( $this->url_helper->recreate_current_url( false ) ) . \ltrim( $new_path, '/' ), 301, $message );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unneeded query variables from the URL.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clean_permalinks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks::clean_permalinks()' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We're not processing anything yet...
|
||||
if ( \is_robots() || \get_query_var( 'sitemap' ) || empty( $_GET ) || \is_user_logged_in() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_url = $this->url_helper->recreate_current_url();
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\allowlist_permalink_vars' - Allows plugins to register their own variables not to clean.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 19.2.0
|
||||
*
|
||||
* @param array $allowed_extravars The list of the allowed vars (empty by default).
|
||||
*/
|
||||
$allowed_extravars = \apply_filters( 'Yoast\WP\SEO\allowlist_permalink_vars', [] );
|
||||
|
||||
if ( $this->options_helper->get( 'clean_permalinks_extra_variables' ) !== '' ) {
|
||||
$allowed_extravars = \array_merge( $allowed_extravars, \explode( ',', $this->options_helper->get( 'clean_permalinks_extra_variables' ) ) );
|
||||
}
|
||||
|
||||
$allowed_query = [];
|
||||
|
||||
// @todo parse_str changes spaces in param names into `_`, we should find a better way to support them.
|
||||
\wp_parse_str( \wp_parse_url( $current_url, \PHP_URL_QUERY ), $query );
|
||||
|
||||
if ( ! empty( $allowed_extravars ) ) {
|
||||
foreach ( $allowed_extravars as $get ) {
|
||||
$get = \trim( $get );
|
||||
if ( isset( $query[ $get ] ) ) {
|
||||
$allowed_query[ $get ] = \rawurlencode_deep( $query[ $get ] );
|
||||
unset( $query[ $get ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we had only allowed params, let's just bail out, no further processing needed.
|
||||
if ( \count( $query ) === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_query;
|
||||
|
||||
$proper_url = '';
|
||||
|
||||
if ( \is_singular() ) {
|
||||
global $post;
|
||||
$proper_url = \get_permalink( $post->ID );
|
||||
|
||||
$page = \get_query_var( 'page' );
|
||||
if ( $page && $page !== 1 ) {
|
||||
$the_post = \get_post( $post->ID );
|
||||
$page_count = \substr_count( $the_post->post_content, '<!--nextpage-->' );
|
||||
$proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . $page );
|
||||
if ( $page > ( $page_count + 1 ) ) {
|
||||
$proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . ( $page_count + 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Fix reply to comment links, whoever decided this should be a GET variable?
|
||||
// phpcs:ignore WordPress.Security -- We know this is scary.
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && \preg_match( '`(\?replytocom=[^&]+)`', \sanitize_text_field( $_SERVER['REQUEST_URI'] ), $matches ) ) {
|
||||
$proper_url .= \str_replace( '?replytocom=', '#comment-', $matches[0] );
|
||||
}
|
||||
unset( $matches );
|
||||
|
||||
// Prevent cleaning out posts & page previews for people capable of viewing them.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We know this is scary.
|
||||
if ( isset( $_GET['preview'] ) && isset( $_GET['preview_nonce'] ) && \current_user_can( 'edit_post' ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
elseif ( \is_front_page() ) {
|
||||
if ( $this->current_page_helper->is_home_posts_page() ) {
|
||||
$proper_url = \home_url( '/' );
|
||||
}
|
||||
elseif ( $this->current_page_helper->is_home_static_page() ) {
|
||||
$proper_url = \get_permalink( $GLOBALS['post']->ID );
|
||||
}
|
||||
}
|
||||
elseif ( $this->current_page_helper->is_posts_page() ) {
|
||||
$proper_url = \get_permalink( \get_option( 'page_for_posts' ) );
|
||||
}
|
||||
elseif ( \is_category() || \is_tag() || \is_tax() ) {
|
||||
$term = $wp_query->get_queried_object();
|
||||
if ( \is_feed() ) {
|
||||
$proper_url = \get_term_feed_link( $term->term_id, $term->taxonomy );
|
||||
}
|
||||
else {
|
||||
$proper_url = \get_term_link( $term, $term->taxonomy );
|
||||
}
|
||||
}
|
||||
elseif ( \is_search() ) {
|
||||
$s = \get_search_query();
|
||||
$proper_url = \get_bloginfo( 'url' ) . '/?s=' . \rawurlencode( $s );
|
||||
}
|
||||
elseif ( \is_404() ) {
|
||||
if ( \is_multisite() && ! \is_subdomain_install() && \is_main_site() ) {
|
||||
if ( $current_url === \get_bloginfo( 'url' ) . '/blog/' || $current_url === \get_bloginfo( 'url' ) . '/blog' ) {
|
||||
if ( $this->current_page_helper->is_home_static_page() ) {
|
||||
$proper_url = \get_permalink( \get_option( 'page_for_posts' ) );
|
||||
}
|
||||
else {
|
||||
$proper_url = \get_bloginfo( 'url' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! empty( $proper_url ) && $wp_query->query_vars['paged'] !== 0 && $wp_query->post_count !== 0 ) {
|
||||
if ( \is_search() ) {
|
||||
$proper_url = \get_bloginfo( 'url' ) . '/page/' . $wp_query->query_vars['paged'] . '/?s=' . \rawurlencode( \get_search_query() );
|
||||
}
|
||||
else {
|
||||
$proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . 'page/' . $wp_query->query_vars['paged'] );
|
||||
}
|
||||
}
|
||||
|
||||
$proper_url = \add_query_arg( $allowed_query, $proper_url );
|
||||
|
||||
if ( ! empty( $proper_url ) && $current_url !== $proper_url ) {
|
||||
\header( 'Content-Type: redirect', true );
|
||||
\header_remove( 'Content-Type' );
|
||||
\header_remove( 'Last-Modified' );
|
||||
\header_remove( 'X-Pingback' );
|
||||
|
||||
$message = \sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium */
|
||||
\__( '%1$s: unregistered URL parameter removed', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
|
||||
\wp_safe_redirect( $proper_url, 301, $message );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Option;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
|
||||
use Yoast_Form;
|
||||
|
||||
/**
|
||||
* Crawl_Settings_Integration class
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Crawl_Settings_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for the head clean up piece.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $basic_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for the feeds clean up.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $feed_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for permalink cleanup settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $permalink_cleanup_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for search cleanup settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $search_cleanup_settings;
|
||||
|
||||
/**
|
||||
* Holds the settings + labels for unused resources settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unused_resources_settings;
|
||||
|
||||
/**
|
||||
* The shortlinker.
|
||||
*
|
||||
* @var WPSEO_Shortlinker
|
||||
*/
|
||||
private $shortlinker;
|
||||
|
||||
/**
|
||||
* The options' helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Crawl_Settings_Integration constructor.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param WPSEO_Shortlinker $shortlinker The shortlinker.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, WPSEO_Shortlinker $shortlinker ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration' );
|
||||
|
||||
$this->options_helper = $options_helper;
|
||||
$this->shortlinker = $shortlinker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* In this case: when on an admin page.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration::get_conditionals()' );
|
||||
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an action to add a new tab to the General page.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration::register_hooks()' );
|
||||
|
||||
$this->register_setting_labels();
|
||||
|
||||
\add_action( 'wpseo_settings_tab_crawl_cleanup_network', [ $this, 'add_crawl_settings_tab_content_network' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration::enqueue_assets()' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_dashboard' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_enqueue_script( 'wp-seo-premium-crawl-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content to the Crawl Cleanup tab.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Yoast_Form $yform The yoast form object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_crawl_settings_tab_content( $yform ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4' );
|
||||
|
||||
$this->add_crawl_settings( $yform, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content to the Crawl Cleanup network tab.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Yoast_Form $yform The yoast form object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_crawl_settings_tab_content_network( $yform ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration::add_crawl_settings_tab_content_network( $yform )' );
|
||||
|
||||
$this->add_crawl_settings( $yform, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the settings to their labels.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_setting_labels() {
|
||||
$this->feed_settings = [
|
||||
'remove_feed_global' => \__( 'Global feed', 'wordpress-seo-premium' ),
|
||||
'remove_feed_global_comments' => \__( 'Global comment feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_post_comments' => \__( 'Post comments feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_authors' => \__( 'Post authors feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_post_types' => \__( 'Post type feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_categories' => \__( 'Category feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_tags' => \__( 'Tag feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_custom_taxonomies' => \__( 'Custom taxonomy feeds', 'wordpress-seo-premium' ),
|
||||
'remove_feed_search' => \__( 'Search results feeds', 'wordpress-seo-premium' ),
|
||||
'remove_atom_rdf_feeds' => \__( 'Atom/RDF feeds', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$this->basic_settings = [
|
||||
'remove_shortlinks' => \__( 'Shortlinks', 'wordpress-seo-premium' ),
|
||||
'remove_rest_api_links' => \__( 'REST API links', 'wordpress-seo-premium' ),
|
||||
'remove_rsd_wlw_links' => \__( 'RSD / WLW links', 'wordpress-seo-premium' ),
|
||||
'remove_oembed_links' => \__( 'oEmbed links', 'wordpress-seo-premium' ),
|
||||
'remove_generator' => \__( 'Generator tag', 'wordpress-seo-premium' ),
|
||||
'remove_pingback_header' => \__( 'Pingback HTTP header', 'wordpress-seo-premium' ),
|
||||
'remove_powered_by_header' => \__( 'Powered by HTTP header', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$this->permalink_cleanup_settings = [
|
||||
'clean_campaign_tracking_urls' => \__( 'Campaign tracking URL parameters', 'wordpress-seo-premium' ),
|
||||
'clean_permalinks' => \__( 'Unregistered URL parameters', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$this->search_cleanup_settings = [
|
||||
'search_cleanup' => \__( 'Filter search terms', 'wordpress-seo-premium' ),
|
||||
'search_cleanup_emoji' => \__( 'Filter searches with emojis and other special characters', 'wordpress-seo-premium' ),
|
||||
'search_cleanup_patterns' => \__( 'Filter searches with common spam patterns', 'wordpress-seo-premium' ),
|
||||
'deny_search_crawling' => \__( 'Prevent search engines from crawling site search URLs', 'wordpress-seo-premium' ),
|
||||
'redirect_search_pretty_urls' => \__( 'Redirect pretty URLs for search pages to raw format', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$this->unused_resources_settings = [
|
||||
'remove_emoji_scripts' => \__( 'Emoji scripts', 'wordpress-seo-premium' ),
|
||||
'deny_wp_json_crawling' => \__( 'Prevent search engines from crawling /wp-json/', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the settings sections.
|
||||
*
|
||||
* @param Yoast_Form $yform The Yoast form class.
|
||||
* @param bool $is_network Whether we're on the network site.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_crawl_settings( $yform, $is_network ) {
|
||||
$this->print_toggles( $this->basic_settings, $yform, $is_network, \__( 'Basic crawl settings', 'wordpress-seo-premium' ), \__( 'Remove links added by WordPress to the header and <head>.', 'wordpress-seo-premium' ) );
|
||||
|
||||
$this->print_toggles( $this->feed_settings, $yform, $is_network, \__( 'Feed crawl settings', 'wordpress-seo-premium' ), \__( "Remove feed links added by WordPress that aren't needed for this site.", 'wordpress-seo-premium' ) );
|
||||
$this->print_toggles( $this->unused_resources_settings, $yform, $is_network, \__( 'Remove unused resources', 'wordpress-seo-premium' ), \__( 'WordPress loads lots of resources, some of which your site might not need. If you’re not using these, removing them can speed up your pages and save resources.', 'wordpress-seo-premium' ) );
|
||||
|
||||
$first_search_setting = \array_slice( $this->search_cleanup_settings, 0, 1 );
|
||||
$rest_search_settings = \array_slice( $this->search_cleanup_settings, 1 );
|
||||
$search_settings_toggles = [
|
||||
'off' => \__( 'Disabled', 'wordpress-seo-premium' ),
|
||||
'on' => \__( 'Enabled', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$this->print_toggles( $first_search_setting, $yform, $is_network, \__( 'Search cleanup settings', 'wordpress-seo-premium' ), \__( 'Clean up and filter searches to prevent search spam.', 'wordpress-seo-premium' ), $search_settings_toggles );
|
||||
|
||||
if ( ! $is_network ) {
|
||||
echo '<div id="search_character_limit_container" class="yoast-crawl-single-setting">';
|
||||
$yform->number(
|
||||
'search_character_limit',
|
||||
\__( 'Max number of characters to allow in searches', 'wordpress-seo-premium' ),
|
||||
[
|
||||
'min' => 1,
|
||||
'max' => 1000,
|
||||
]
|
||||
);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
$this->print_toggles( $rest_search_settings, $yform, $is_network, '', '', $search_settings_toggles );
|
||||
|
||||
$permalink_warning = \sprintf(
|
||||
/* Translators: %1$s expands to an opening anchor tag for a link leading to the Yoast SEO page of the Permalink Cleanup features, %2$s expands to a closing anchor tag. */
|
||||
\esc_html__(
|
||||
'These are expert features, so make sure you know what you\'re doing before removing the parameters. %1$sRead more about how your site can be affected%2$s.',
|
||||
'wordpress-seo-premium'
|
||||
),
|
||||
'<a href="' . \esc_url( $this->shortlinker->build_shortlink( 'https://yoa.st/permalink-cleanup' ) ) . '" target="_blank" rel="noopener noreferrer">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
$this->print_toggles( $this->permalink_cleanup_settings, $yform, $is_network, \__( 'Permalink cleanup settings', 'wordpress-seo-premium' ), \__( 'Remove unwanted URL parameters from your URLs.', 'wordpress-seo-premium' ), [], $permalink_warning );
|
||||
|
||||
if ( ! $is_network && ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
echo '<div id="clean_permalinks_extra_variables_container" class="yoast-crawl-single-setting">';
|
||||
$yform->textinput( 'clean_permalinks_extra_variables', \__( 'Additional URL parameters to allow', 'wordpress-seo-premium' ) );
|
||||
echo '<p class="desc label yoast-extra-variables-label">';
|
||||
\esc_html_e( 'Please use a comma to separate multiple URL parameters.', 'wordpress-seo-premium' );
|
||||
echo '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
else {
|
||||
// Also add the original option as hidden, so as not to lose any values if it's disabled and the form is saved.
|
||||
$yform->hidden( 'clean_permalinks_extra_variables', 'clean_permalinks_extra_variables' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a list of toggles for an array of settings with labels.
|
||||
*
|
||||
* @param array $settings The settings being displayed.
|
||||
* @param Yoast_Form $yform The Yoast form class.
|
||||
* @param bool $is_network Whether we're on the network site.
|
||||
* @param string $title Optional title for the settings being displayed.
|
||||
* @param string $description Optional description of the settings being displayed.
|
||||
* @param array $toggles Optional naming of the toggle buttons.
|
||||
* @param string $warning Optional warning to be displayed above the toggles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function print_toggles( array $settings, Yoast_Form $yform, $is_network = false, $title = '', $description = '', $toggles = [], $warning = '' ) {
|
||||
if ( ! empty( $title ) ) {
|
||||
echo '<h3 class="yoast-crawl-settings">', \esc_html( $title ), '</h3>';
|
||||
}
|
||||
if ( ! $is_network && ! empty( $description ) ) {
|
||||
echo '<p class="yoast-crawl-settings-explanation">', \esc_html( $description ), '</p>';
|
||||
}
|
||||
|
||||
if ( ! empty( $warning ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in Alert_Presenter.
|
||||
echo new Alert_Presenter( $warning, 'warning' );
|
||||
}
|
||||
|
||||
if ( empty( $toggles ) ) {
|
||||
$toggles = [
|
||||
'off' => \__( 'Keep', 'wordpress-seo-premium' ),
|
||||
'on' => \__( 'Remove', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
$setting_prefix = '';
|
||||
|
||||
if ( $is_network ) {
|
||||
$setting_prefix = WPSEO_Option::ALLOW_KEY_PREFIX;
|
||||
$toggles = [
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
'on' => \__( 'Allow Control', 'wordpress-seo' ),
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
'off' => \__( 'Disable', 'wordpress-seo' ),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ( $settings as $setting => $label ) {
|
||||
$attr = [];
|
||||
$variable = $setting_prefix . $setting;
|
||||
|
||||
if ( $this->should_feature_be_disabled_permalink( $setting, $is_network ) ) {
|
||||
$attr = [
|
||||
'disabled' => true,
|
||||
];
|
||||
$variable = $setting_prefix . $setting . '_disabled';
|
||||
|
||||
// Also add the original option as hidden, so as not to lose any values if it's disabled and the form is saved.
|
||||
$yform->hidden( $setting_prefix . $setting, $setting_prefix . $setting );
|
||||
}
|
||||
elseif ( $this->should_feature_be_disabled_multisite( $setting ) ) {
|
||||
$attr = [
|
||||
'disabled' => true,
|
||||
'preserve_disabled_value' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$yform->toggle_switch(
|
||||
$variable,
|
||||
$toggles,
|
||||
$label,
|
||||
'',
|
||||
$attr
|
||||
);
|
||||
if ( $setting === 'remove_feed_global_comments' && ! $is_network ) {
|
||||
echo '<p class="yoast-crawl-settings-help">';
|
||||
echo \esc_html__( 'By removing Global comments feed, Post comments feeds will be removed too.', 'wordpress-seo-premium' );
|
||||
echo '</p>';
|
||||
}
|
||||
if ( $this->should_feature_be_disabled_permalink( $setting, $is_network ) ) {
|
||||
echo '<p class="yoast-crawl-settings-help">';
|
||||
if ( \current_user_can( 'manage_options' ) ) {
|
||||
\printf(
|
||||
/* translators: 1: Link start tag to the Permalinks settings page, 2: Link closing tag. */
|
||||
\esc_html__( 'This feature is disabled when your site is not using %1$spretty permalinks%2$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . \esc_url( \admin_url( 'options-permalink.php' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
else {
|
||||
echo \esc_html__( 'This feature is disabled when your site is not using pretty permalinks.', 'wordpress-seo-premium' );
|
||||
}
|
||||
echo '</p>';
|
||||
}
|
||||
elseif ( $this->should_feature_be_disabled_multisite( $setting ) ) {
|
||||
echo '<p>';
|
||||
\esc_html_e( 'This feature is not available for multisites.', 'wordpress-seo-premium' );
|
||||
echo '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the feature should be disabled due to non-pretty permalinks.
|
||||
*
|
||||
* @param string $setting The setting to be displayed.
|
||||
* @param bool $is_network Whether we're on the network site.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_feature_be_disabled_permalink( $setting, $is_network ) {
|
||||
return (
|
||||
\in_array( $setting, [ 'clean_permalinks', 'clean_campaign_tracking_urls' ], true )
|
||||
&& ! $is_network
|
||||
&& empty( \get_option( 'permalink_structure' ) )
|
||||
&& ! $this->is_control_disabled( $setting )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the feature should be disabled due to the site being a multisite.
|
||||
*
|
||||
* @param string $setting The setting to be displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_feature_be_disabled_multisite( $setting ) {
|
||||
return (
|
||||
\in_array( $setting, [ 'deny_search_crawling', 'deny_wp_json_crawling' ], true )
|
||||
&& \is_multisite()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given control should be disabled, because of the network admin.
|
||||
*
|
||||
* @param string $variable The variable within the option to check whether its control should be disabled.
|
||||
*
|
||||
* @return bool True if control should be disabled, false otherwise.
|
||||
*/
|
||||
protected function is_control_disabled( $variable ) {
|
||||
return ! $this->options_helper->get( 'allow_' . $variable, true );
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Integrations_Page class
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Integrations_Page implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Workouts_Integration constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Options;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Zapier_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter;
|
||||
|
||||
/**
|
||||
* Shows a notification telling the user that zapier integration will be removed.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Notification_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the name of the user meta key.
|
||||
*
|
||||
* The value of this database field holds whether the user has dismissed this notice or not.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const USER_META_DISMISSED = 'is_dismissed_zapier_notice';
|
||||
|
||||
/**
|
||||
* The capability helper.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var Zapier_Enabled_Conditional
|
||||
*/
|
||||
private $zapier_enable_conditional;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapier_Notification_Integration constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
* @param Zapier_Enabled_Conditional $zapier_enable_conditional The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
Capability_Helper $capability_helper,
|
||||
Zapier_Enabled_Conditional $zapier_enable_conditional
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->capability_helper = $capability_helper;
|
||||
$this->zapier_enable_conditional = $zapier_enable_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
\add_action( 'admin_notices', [ $this, 'zapier_notice' ] );
|
||||
\add_action( 'wp_ajax_dismiss_zapier_notice', [ $this, 'dismiss_zapier_notice' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notice if zapier is enabled and it's not being dismissed before.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function zapier_notice() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->capability_helper->current_user_can( 'wpseo_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_notice_dismissed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_zapier_connected = WPSEO_Options::get( 'zapier_subscription', [] );
|
||||
|
||||
if ( $is_zapier_connected ) {
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
/* translators: %1$s for Yoast SEO */
|
||||
$title = \sprintf( \__( 'Zapier integration will be removed from %1$s', 'wordpress-seo-premium' ), 'Yoast SEO' );
|
||||
$content = \sprintf(
|
||||
/* translators: %1$s and %2$s expands to the link to https://yoast.com/features/zapier, %3$s for Yoast SEO, %4$s for support email. */
|
||||
\esc_html__( 'The %1$sZapier integration%2$s (on the Integrations page) will be removed from %3$s in 20.7 (release date May 9th). If you have any questions, please reach out to %4$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'http://yoa.st/zapier-removal-notification' ) . '" target="_blank">',
|
||||
'</a>',
|
||||
'Yoast SEO',
|
||||
'support@yoast.com'
|
||||
);
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output of the title escaped in the Notice_Presenter.
|
||||
echo new Notice_Presenter(
|
||||
$title,
|
||||
$content,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
'yoast-zapier-notice'
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// Enable permanently dismissing the notice.
|
||||
echo '<script>
|
||||
jQuery( document ).ready( function() {
|
||||
jQuery( "body" ).on( "click", "#yoast-zapier-notice .notice-dismiss", function() {
|
||||
const data = { "action": "dismiss_zapier_notice", "nonce": "' . \esc_attr( \wp_create_nonce( 'dismiss_zapier_notice' ) ) . '" };
|
||||
jQuery.post( ajaxurl, data, function( response ) {
|
||||
jQuery( this ).parent( "#yoast-zapier-notice" ).hide();
|
||||
});
|
||||
} );
|
||||
} );
|
||||
</script>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the notice dismissed by the user.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_notice_dismissed() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return \get_user_meta( \get_current_user_id(), self::USER_META_DISMISSED, true ) === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the notice.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dismiss_zapier_notice() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! \check_ajax_referer( 'dismiss_zapier_notice', 'nonce', false ) || ! $this->capability_helper->current_user_can( 'wpseo_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
\update_user_meta( \get_current_user_id(), self::USER_META_DISMISSED, true );
|
||||
return WPSEO_Options::set( 'is_dismissed_zapier_notice', true );
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Schema_Templates\Block_Patterns\Block_Pattern;
|
||||
|
||||
/**
|
||||
* Registers the block patterns needed for the Premium Schema blocks.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Block_Patterns implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The block patterns to register.
|
||||
*
|
||||
* @var Block_Pattern[]
|
||||
*/
|
||||
protected $block_patterns = [];
|
||||
|
||||
/**
|
||||
* Block_Patterns integration constructor.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Block_Pattern ...$block_patterns The block patterns to register.
|
||||
*/
|
||||
public function __construct( Block_Pattern ...$block_patterns ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
$this->block_patterns = $block_patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block patterns with WordPress.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block_patterns() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block pattern category with WordPress.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block_pattern_category() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Wordpress_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds the block categories for the Jobs Posting block.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Job_Posting_Block implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Represents the WordPress helper.
|
||||
*
|
||||
* @var Wordpress_Helper
|
||||
*/
|
||||
protected $wordpress_helper;
|
||||
|
||||
/**
|
||||
* Job_Posting_Block constructor.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Wordpress_Helper $wordpress_helper The WordPress helper.
|
||||
*/
|
||||
public function __construct( Wordpress_Helper $wordpress_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
$this->wordpress_helper = $wordpress_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Yoast block categories.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $categories The categories to filter.
|
||||
*
|
||||
* @return array The filtered categories.
|
||||
*/
|
||||
public function add_block_categories( $categories ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Blocks;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Loads the Premium schema block templates into Gutenberg.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Schema_Blocks implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Schema_Blocks constructor.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the Premium structured data blocks templates.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $templates The templates from Yoast SEO.
|
||||
*
|
||||
* @return array All the templates that should be loaded.
|
||||
*/
|
||||
public function add_premium_templates( $templates ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the schema blocks css file.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Crawl_Cleanup_Basic.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Crawl_Cleanup_Basic implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Crawl Cleanup Basic integration constructor.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic' );
|
||||
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic::register_hooks()' );
|
||||
|
||||
// Remove HTTP headers we don't want.
|
||||
\add_action( 'wp', [ $this, 'clean_headers' ], 0 );
|
||||
|
||||
if ( $this->is_true( 'remove_shortlinks' ) ) {
|
||||
// Remove shortlinks.
|
||||
\remove_action( 'wp_head', 'wp_shortlink_wp_head' );
|
||||
\remove_action( 'template_redirect', 'wp_shortlink_header', 11 );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_rest_api_links' ) ) {
|
||||
// Remove REST API links.
|
||||
\remove_action( 'wp_head', 'rest_output_link_wp_head' );
|
||||
\remove_action( 'template_redirect', 'rest_output_link_header', 11 );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_rsd_wlw_links' ) ) {
|
||||
// Remove RSD and WLW Manifest links.
|
||||
\remove_action( 'wp_head', 'rsd_link' );
|
||||
\remove_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
|
||||
\remove_action( 'wp_head', 'wlwmanifest_link' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_oembed_links' ) ) {
|
||||
// Remove JSON+XML oEmbed links.
|
||||
\remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_generator' ) ) {
|
||||
\remove_action( 'wp_head', 'wp_generator' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_emoji_scripts' ) ) {
|
||||
// Remove emoji scripts and additional stuff they cause.
|
||||
\remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
||||
\remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
\remove_action( 'admin_print_styles', 'print_emoji_styles' );
|
||||
\add_filter( 'wp_resource_hints', [ $this, 'resource_hints_plain_cleanup' ], 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic::get_conditionals()' );
|
||||
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes X-Pingback and X-Powered-By headers as they're unneeded.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clean_headers() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic::clean_headers()' );
|
||||
|
||||
if ( \headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_powered_by_header' ) ) {
|
||||
\header_remove( 'X-Powered-By' );
|
||||
}
|
||||
if ( $this->is_true( 'remove_pingback_header' ) ) {
|
||||
\header_remove( 'X-Pingback' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the core s.w.org hint as it's only used for emoji stuff we don't use.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $hints The hints we're adding to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function resource_hints_plain_cleanup( $hints ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic::resource_hints_plain_cleanup( $hints )' );
|
||||
|
||||
foreach ( $hints as $key => $hint ) {
|
||||
if ( \strpos( $hint, '//s.w.org' ) !== false ) {
|
||||
unset( $hints[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of an option is set to true.
|
||||
*
|
||||
* @param string $option_name The option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_true( $option_name ) {
|
||||
return $this->options_helper->get( $option_name ) === true;
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Adds actions that cleanup unwanted rss feed links.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Crawl_Cleanup_Rss implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Crawl Cleanup RSS integration constructor.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss' );
|
||||
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss::get_conditionals()' );
|
||||
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our RSS related hooks.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss::register_hooks()' );
|
||||
|
||||
if ( $this->is_true( 'remove_feed_global' ) ) {
|
||||
\add_action( 'feed_links_show_posts_feed', '__return_false' );
|
||||
}
|
||||
|
||||
if ( $this->is_true( 'remove_feed_global_comments' ) ) {
|
||||
\add_action( 'feed_links_show_comments_feed', '__return_false' );
|
||||
}
|
||||
|
||||
\add_action( 'wp', [ $this, 'maybe_disable_feeds' ] );
|
||||
\add_action( 'wp', [ $this, 'maybe_redirect_feeds' ], -10000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable feeds on selected cases.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_disable_feeds() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss::maybe_disable_feeds()' );
|
||||
|
||||
if ( \is_singular() && $this->is_true( 'remove_feed_post_comments' )
|
||||
|| ( \is_author() && $this->is_true( 'remove_feed_authors' ) )
|
||||
|| ( \is_category() && $this->is_true( 'remove_feed_categories' ) )
|
||||
|| ( \is_tag() && $this->is_true( 'remove_feed_tags' ) )
|
||||
|| ( \is_tax() && $this->is_true( 'remove_feed_custom_taxonomies' ) )
|
||||
|| ( \is_post_type_archive() && $this->is_true( 'remove_feed_post_types' ) )
|
||||
|| ( \is_search() && $this->is_true( 'remove_feed_search' ) ) ) {
|
||||
\remove_action( 'wp_head', 'feed_links_extra', 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect feeds we don't want away.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect_feeds() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss::maybe_redirect_feeds()' );
|
||||
|
||||
global $wp_query;
|
||||
|
||||
if ( ! \is_feed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \in_array( \get_query_var( 'feed' ), [ 'atom', 'rdf' ], true ) && $this->is_true( 'remove_atom_rdf_feeds' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable Atom/RDF feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
// Only if we're on the global feed, the query is _just_ `'feed' => 'feed'`, hence this check.
|
||||
if ( ( $wp_query->query === [ 'feed' => 'feed' ]
|
||||
|| $wp_query->query === [ 'feed' => 'atom' ]
|
||||
|| $wp_query->query === [ 'feed' => 'rdf' ] )
|
||||
&& $this->is_true( 'remove_feed_global' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable the RSS feed for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_comment_feed() && ! ( \is_singular() || \is_attachment() ) && $this->is_true( 'remove_feed_global_comments' ) ) {
|
||||
$this->redirect_feed( \home_url(), 'We disable comment feeds for performance reasons.' );
|
||||
}
|
||||
elseif ( \is_comment_feed()
|
||||
&& \is_singular()
|
||||
&& ( $this->is_true( 'remove_feed_post_comments' ) || $this->is_true( 'remove_feed_global_comments' ) ) ) {
|
||||
$url = \get_permalink( \get_queried_object() );
|
||||
$this->redirect_feed( $url, 'We disable post comment feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_author() && $this->is_true( 'remove_feed_authors' ) ) {
|
||||
$author_id = (int) \get_query_var( 'author' );
|
||||
$url = \get_author_posts_url( $author_id );
|
||||
$this->redirect_feed( $url, 'We disable author feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( ( \is_category() && $this->is_true( 'remove_feed_categories' ) )
|
||||
|| ( \is_tag() && $this->is_true( 'remove_feed_tags' ) )
|
||||
|| ( \is_tax() && $this->is_true( 'remove_feed_custom_taxonomies' ) ) ) {
|
||||
$term = \get_queried_object();
|
||||
$url = \get_term_link( $term, $term->taxonomy );
|
||||
if ( \is_wp_error( $url ) ) {
|
||||
$url = \home_url();
|
||||
}
|
||||
$this->redirect_feed( $url, 'We disable taxonomy feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( ( \is_post_type_archive() ) && $this->is_true( 'remove_feed_post_types' ) ) {
|
||||
$url = \get_post_type_archive_link( $this->get_queried_post_type() );
|
||||
$this->redirect_feed( $url, 'We disable post type feeds for performance reasons.' );
|
||||
}
|
||||
|
||||
if ( \is_search() && $this->is_true( 'remove_feed_search' ) ) {
|
||||
$url = \trailingslashit( \home_url() ) . '?s=' . \get_search_query();
|
||||
$this->redirect_feed( $url, 'We disable search RSS feeds for performance reasons.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a cache control header.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $expiration The expiration time.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cache_control_header( $expiration ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss::cache_control_header( $expiration )' );
|
||||
|
||||
\header_remove( 'Expires' );
|
||||
|
||||
// The cacheability of the current request. 'public' allows caching, 'private' would not allow caching by proxies like CloudFlare.
|
||||
$cacheability = 'public';
|
||||
$format = '%1$s, max-age=%2$d, s-maxage=%2$d, stale-while-revalidate=120, stale-if-error=14400';
|
||||
|
||||
if ( \is_user_logged_in() ) {
|
||||
$expiration = 0;
|
||||
$cacheability = 'private';
|
||||
$format = '%1$s, max-age=%2$d';
|
||||
}
|
||||
|
||||
\header( \sprintf( 'Cache-Control: ' . $format, $cacheability, $expiration ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect a feed result to somewhere else.
|
||||
*
|
||||
* @param string $url The location we're redirecting to.
|
||||
* @param string $reason The reason we're redirecting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_feed( $url, $reason ) {
|
||||
\header_remove( 'Content-Type' );
|
||||
\header_remove( 'Last-Modified' );
|
||||
|
||||
$this->cache_control_header( 7 * \DAY_IN_SECONDS );
|
||||
|
||||
\wp_safe_redirect( $url, 301, 'Yoast SEO: ' . $reason );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the queried post type.
|
||||
*
|
||||
* @return string The queried post type.
|
||||
*/
|
||||
private function get_queried_post_type() {
|
||||
$post_type = \get_query_var( 'post_type' );
|
||||
if ( \is_array( $post_type ) ) {
|
||||
$post_type = \reset( $post_type );
|
||||
}
|
||||
return $post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of an option is set to true.
|
||||
*
|
||||
* @param string $option_name The option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_true( $option_name ) {
|
||||
return $this->options_helper->get( $option_name ) === true;
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Front_End;
|
||||
|
||||
use WP_Query;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Crawl_Cleanup_Searches.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Crawl_Cleanup_Searches implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Patterns to match against to find spam.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $patterns = [
|
||||
'/[:()【】[]]+/u',
|
||||
'/(TALK|QQ)\:/iu',
|
||||
];
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper
|
||||
*/
|
||||
private $redirect_helper;
|
||||
|
||||
/**
|
||||
* Crawl_Cleanup_Searches integration constructor.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
* @param Redirect_Helper $redirect_helper The redirect helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Redirect_Helper $redirect_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches' );
|
||||
|
||||
$this->options_helper = $options_helper;
|
||||
$this->redirect_helper = $redirect_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches::register_hooks()' );
|
||||
|
||||
if ( $this->options_helper->get( 'search_cleanup' ) ) {
|
||||
\add_filter( 'pre_get_posts', [ $this, 'validate_search' ] );
|
||||
}
|
||||
if ( $this->options_helper->get( 'redirect_search_pretty_urls' ) && ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
\add_action( 'template_redirect', [ $this, 'maybe_redirect_searches' ], 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches::get_conditionals()' );
|
||||
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we want to allow this search to happen.
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_Query $query The main query.
|
||||
*
|
||||
* @return WP_Query
|
||||
*/
|
||||
public function validate_search( WP_Query $query ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches::validate_search( WP_Query $query )' );
|
||||
|
||||
if ( ! $query->is_search() ) {
|
||||
return $query;
|
||||
}
|
||||
// First check against emoji and patterns we might not want.
|
||||
$this->check_unwanted_patterns( $query );
|
||||
|
||||
// Then limit characters if still needed.
|
||||
$this->limit_characters();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect pretty search URLs to the "raw" equivalent
|
||||
*
|
||||
* @deprecated 20.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect_searches() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.4', 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches::maybe_redirect_searches()' );
|
||||
|
||||
if ( ! \is_search() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && \stripos( $_SERVER['REQUEST_URI'], '/search/' ) === 0 ) {
|
||||
$args = [];
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
$parsed = \wp_parse_url( $_SERVER['REQUEST_URI'] );
|
||||
|
||||
if ( ! empty( $parsed['query'] ) ) {
|
||||
\wp_parse_str( $parsed['query'], $args );
|
||||
}
|
||||
|
||||
$args['s'] = \get_search_query();
|
||||
|
||||
$proper_url = \home_url( '/' );
|
||||
|
||||
if ( \intval( \get_query_var( 'paged' ) ) > 1 ) {
|
||||
$proper_url .= \sprintf( 'page/%s/', \get_query_var( 'paged' ) );
|
||||
unset( $args['paged'] );
|
||||
}
|
||||
|
||||
$proper_url = \add_query_arg( \array_map( 'rawurlencode_deep', $args ), $proper_url );
|
||||
|
||||
if ( ! empty( $parsed['fragment'] ) ) {
|
||||
$proper_url .= '#' . \rawurlencode( $parsed['fragment'] );
|
||||
}
|
||||
|
||||
$this->redirect_away( 'We redirect pretty URLs to the raw format.', $proper_url );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check query against unwanted search patterns.
|
||||
*
|
||||
* @param WP_Query $query The main WordPress query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function check_unwanted_patterns( WP_Query $query ) {
|
||||
$s = \rawurldecode( $query->query_vars['s'] );
|
||||
if ( $this->options_helper->get( 'search_cleanup_emoji' ) && $this->has_emoji( $s ) ) {
|
||||
$this->redirect_away( 'We don\'t allow searches with emojis and other special characters.' );
|
||||
}
|
||||
|
||||
if ( ! $this->options_helper->get( 'search_cleanup_patterns' ) ) {
|
||||
return;
|
||||
}
|
||||
foreach ( $this->patterns as $pattern ) {
|
||||
$outcome = \preg_match( $pattern, $s, $matches );
|
||||
if ( $outcome && $matches !== [] ) {
|
||||
$this->redirect_away( 'Your search matched a common spam pattern.' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the homepage for invalid searches.
|
||||
*
|
||||
* @param string $reason The reason for redirecting away.
|
||||
* @param string $to_url The URL to redirect to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_away( $reason, $to_url = '' ) {
|
||||
if ( empty( $to_url ) ) {
|
||||
$to_url = \get_home_url();
|
||||
}
|
||||
|
||||
$this->redirect_helper->do_safe_redirect( $to_url, 301, 'Yoast Search Filtering: ' . $reason );
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the number of characters in the search query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function limit_characters() {
|
||||
// We retrieve the search term unescaped because we want to count the characters properly. We make sure to escape it afterwards, if we do something with it.
|
||||
$unescaped_s = \get_search_query( false );
|
||||
|
||||
// We then unslash the search term, again because we want to count the characters properly. We make sure to slash it afterwards, if we do something with it.
|
||||
$raw_s = \wp_unslash( $unescaped_s );
|
||||
if ( \mb_strlen( $raw_s, 'UTF-8' ) > $this->options_helper->get( 'search_character_limit' ) ) {
|
||||
$new_s = \mb_substr( $raw_s, 0, $this->options_helper->get( 'search_character_limit' ), 'UTF-8' );
|
||||
\set_query_var( 's', \wp_slash( \esc_attr( $new_s ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a text string contains an emoji or not.
|
||||
*
|
||||
* @param string $text The text string to detect emoji in.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function has_emoji( $text ) {
|
||||
$emojis_regex = '/([^-\p{L}\x00-\x7F]+)/u';
|
||||
\preg_match( $emojis_regex, $text, $matches );
|
||||
|
||||
return ! empty( $matches );
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use WPSEO_Admin_Utils;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Zapier_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Zapier_Helper;
|
||||
|
||||
/**
|
||||
* Class to manage the Zapier integration in the Classic editor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Classic_Editor implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
protected $zapier_helper;
|
||||
|
||||
/**
|
||||
* Zapier constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
*/
|
||||
public function __construct( Zapier_Helper $zapier_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->zapier_helper = $zapier_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Zapier_Enabled_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
\add_action( 'wpseo_publishbox_misc_actions', [ $this, 'add_publishbox_text' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Zapier text to the Classic Editor publish box.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_Post $post The current post object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_publishbox_text( $post ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! \is_a( $post, 'WP_Post' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->zapier_helper->is_post_type_supported( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="misc-pub-section yoast yoast-seo-score yoast-zapier-text">
|
||||
<span class="yoast-logo svg"></span>
|
||||
<span>
|
||||
<?php
|
||||
if ( $this->zapier_helper->is_connected() ) {
|
||||
\printf(
|
||||
/* translators: 1: Zapier, 2: Link start tag, 3: Zapier, 4: Link closing tag. */
|
||||
\esc_html__( 'You’re successfully connected to %1$s. Publishing a post will trigger automated actions based on your Zap’s configuration. %2$sManage your Zap in %3$s%4$s.', 'wordpress-seo-premium' ),
|
||||
'Zapier',
|
||||
'<a href="' . \esc_url( 'https://zapier.com/app/zaps' ) . '" target="_blank">',
|
||||
'Zapier',
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The content is already escaped.
|
||||
WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
|
||||
);
|
||||
}
|
||||
else {
|
||||
\printf(
|
||||
/* translators: 1: Link start tag, 2: Yoast SEO, 3: Zapier, 4: Link closing tag. */
|
||||
\esc_html__( '%1$sConnect %2$s with %3$s%4$s to instantly share your published posts with 2000+ destinations such as Twitter, Facebook and more.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_integrations' ) ) . '" target="_blank">',
|
||||
'Yoast SEO',
|
||||
'Zapier',
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The content is already escaped.
|
||||
WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
|
||||
);
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WP_Error;
|
||||
use Yoast\WP\SEO\Helpers\Meta_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Zapier_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Zapier_Helper;
|
||||
|
||||
/**
|
||||
* Class to manage the triggering of the Zapier integration.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Trigger implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The meta helper.
|
||||
*
|
||||
* @var Meta_Helper
|
||||
*/
|
||||
protected $meta_helper;
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
protected $zapier_helper;
|
||||
|
||||
/**
|
||||
* Zapier constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Meta_Helper $meta_helper The meta helper.
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
*/
|
||||
public function __construct( Meta_Helper $meta_helper, Zapier_Helper $zapier_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->meta_helper = $meta_helper;
|
||||
$this->zapier_helper = $zapier_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Zapier_Enabled_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
\add_action( 'wpseo_save_indexable', [ $this, 'maybe_call_zapier' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if Zapier should be triggered.
|
||||
*
|
||||
* Zapier should be triggered only if:
|
||||
* - we have a connection established
|
||||
* - the item is a post (in the Indexable sense, as opposed to taxonomies etc.)
|
||||
* - the item status is 'publish'
|
||||
* - we are not serving a REST request (to avoid triggering on the first request by the block editor)
|
||||
* - if the item hasn't been sent before
|
||||
* - if the post_date is recent (so we are not just updating a post published before enabling Zapier)
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Indexable $indexable The indexable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_call_zapier( Indexable $indexable ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( ! $this->zapier_helper->is_connected()
|
||||
|| $indexable->object_type !== 'post'
|
||||
|| $indexable->post_status !== 'publish'
|
||||
|| \defined( 'REST_REQUEST' ) && \REST_REQUEST
|
||||
|| $this->meta_helper->get_value( 'zapier_trigger_sent', $indexable->object_id ) === '1' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All dates are GMT to prevent failing checks due to timezone differences.
|
||||
$post = \get_post( $indexable->object_id );
|
||||
$published_datetime_gmt = \strtotime( $post->post_date_gmt . ' +0000' );
|
||||
$half_an_hour_ago_datetime_gmt = ( \time() - ( \MINUTE_IN_SECONDS * 30 ) );
|
||||
if ( ! $this->zapier_helper->is_post_type_supported( $post->post_type )
|
||||
|| $published_datetime_gmt < $half_an_hour_ago_datetime_gmt ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->call_zapier( $indexable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the Zapier trigger hook.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Indexable $indexable The indexable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function call_zapier( Indexable $indexable ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$trigger_url = $this->zapier_helper->get_trigger_url();
|
||||
$zapier_data = $this->zapier_helper->get_data_for_zapier( $indexable );
|
||||
|
||||
$response = \wp_remote_post(
|
||||
$trigger_url,
|
||||
[
|
||||
'body' => $zapier_data,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! $response instanceof WP_Error ) {
|
||||
// Need to cast the new value to a string as booleans aren't supported.
|
||||
$this->meta_helper->set_value( 'zapier_trigger_sent', '1', $indexable->object_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Admin_Utils;
|
||||
use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Zapier_Helper;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
|
||||
use Yoast_Feature_Toggle;
|
||||
|
||||
/**
|
||||
* Zapier integration class for managing the toggle and the connection setup.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The Zapier dashboard URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ZAPIER_DASHBOARD_URL = 'https://zapier.com/app/zaps';
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
protected $zapier_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Yoast_Admin_And_Dashboard_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapier constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Zapier_Helper $zapier_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->zapier_helper = $zapier_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
// Add the Zapier toggle to the Integrations tab in the admin.
|
||||
\add_action( 'Yoast\WP\SEO\admin_integration_after', [ $this, 'toggle_after' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_filter( 'wpseo_premium_integrations_page_data', [ $this, 'enhance_integrations_page_data' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required assets.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_integrations' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset_manager->enqueue_style( 'monorepo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional content to be displayed after the Zapier toggle.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Yoast_Feature_Toggle $integration The integration feature we've shown the toggle for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function toggle_after( $integration ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( $integration->setting !== 'zapier_integration_active' ) {
|
||||
return;
|
||||
}
|
||||
if ( $this->zapier_helper->is_connected() ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped in function.
|
||||
echo $this->get_connected_content();
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped in function.
|
||||
echo $this->get_not_connected_content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional content to be displayed when Zapier is connected.
|
||||
*
|
||||
* @return string The additional content.
|
||||
*/
|
||||
private function get_connected_content() {
|
||||
$alert = new Alert_Presenter(
|
||||
\sprintf(
|
||||
/* translators: 1: Yoast SEO, 2: Zapier. */
|
||||
\esc_html__( '%1$s is successfully connected to %2$s!', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
'Zapier'
|
||||
),
|
||||
'success'
|
||||
);
|
||||
|
||||
$output = '<div id="zapier-connection">';
|
||||
$output .= $alert->present();
|
||||
$output .= '<p><a href="' . self::ZAPIER_DASHBOARD_URL . '" class="yoast-button yoast-button--primary" type="button" target="_blank">' . \sprintf(
|
||||
/* translators: %s: Zapier. */
|
||||
\esc_html__( 'Go to your %s Dashboard', 'wordpress-seo-premium' ),
|
||||
'Zapier'
|
||||
) . WPSEO_Admin_Utils::get_new_tab_message() . '</a></p>';
|
||||
$output .= '<p>' . \sprintf(
|
||||
/* translators: 1: Zapier, 2: The Zapier API Key. */
|
||||
\esc_html__( '%1$s uses this API Key: %2$s', 'wordpress-seo-premium' ),
|
||||
'Zapier',
|
||||
'<strong>' . $this->zapier_helper->get_or_generate_zapier_api_key() . '</strong>'
|
||||
) . '</p>';
|
||||
$output .= '<p><button name="zapier_api_key_reset" value="1" type="submit" class="yoast-button yoast-button--secondary">' . \esc_html__( 'Reset API Key', 'wordpress-seo-premium' ) . '</button></p>';
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional content to be displayed when Zapier is not connected.
|
||||
*
|
||||
* @return string The additional content.
|
||||
*/
|
||||
private function get_not_connected_content() {
|
||||
$content = \sprintf(
|
||||
/* translators: 1: Yoast SEO, 2: Zapier, 3: Emphasis open tag, 4: Emphasis close tag. */
|
||||
\esc_html__( '%1$s is not connected to %2$s. To set up a connection, make sure you click %3$sSave changes%4$s first, then copy the given API key below and use it to %3$screate%4$s and %3$sturn on%4$s a Zap within your %2$s account.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
'Zapier',
|
||||
'<em>',
|
||||
'</em>'
|
||||
);
|
||||
|
||||
$content .= '<br/><br/>';
|
||||
$content .= ' ' . \sprintf(
|
||||
/* translators: 1: Yoast SEO. */
|
||||
\esc_html__( 'Please note that you can only create 1 Zap with a trigger event from %1$s. Within this Zap you can choose one or more actions.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO'
|
||||
);
|
||||
|
||||
$alert = new Alert_Presenter(
|
||||
$content,
|
||||
'info'
|
||||
);
|
||||
|
||||
$output = '<div id="zapier-connection">';
|
||||
$output .= $alert->present();
|
||||
$output .= '<div class="yoast-field-group">';
|
||||
$output .= '<div class="yoast-field-group__title yoast-field-group__title--light">';
|
||||
$output .= '<label for="zapier-api-key">' . \sprintf(
|
||||
/* translators: %s: Zapier. */
|
||||
\esc_html__( '%s will ask for an API key. Use this one:', 'wordpress-seo-premium' ),
|
||||
'Zapier'
|
||||
) . '</label>';
|
||||
$output .= '</div>';
|
||||
$output .= '<div class="yoast-field-group__inline">';
|
||||
$output .= '<input class="yoast-field-group__inputfield" readonly type="text" id="zapier-api-key" name="wpseo[zapier_api_key]" value="' . $this->zapier_helper->get_or_generate_zapier_api_key() . '">';
|
||||
$output .= '<button type="button" class="yoast-button yoast-button--secondary" id="copy-zapier-api-key" data-clipboard-target="#zapier-api-key">' . \esc_html__( 'Copy to clipboard', 'wordpress-seo-premium' ) . '</button><br />';
|
||||
$output .= '</div>';
|
||||
$output .= '</div>';
|
||||
$output .= '<p><a href="' . self::ZAPIER_DASHBOARD_URL . '" class="yoast-button yoast-button--primary" type="button" target="_blank">' . \sprintf(
|
||||
/* translators: %s: Zapier. */
|
||||
\esc_html__( 'Create a Zap in %s', 'wordpress-seo-premium' ),
|
||||
'Zapier'
|
||||
) . WPSEO_Admin_Utils::get_new_tab_message() . '</a></p>';
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the array for the integrations page script with additional data.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $data The array to add data to.
|
||||
*
|
||||
* @return array The enhances data.
|
||||
*/
|
||||
public function enhance_integrations_page_data( $data ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO Premium 20.7' );
|
||||
|
||||
if ( ! \is_array( $data ) ) {
|
||||
$data = [ $data ];
|
||||
}
|
||||
|
||||
$data['zapierKey'] = $this->zapier_helper->get_or_generate_zapier_api_key();
|
||||
$data['zapierUrl'] = self::ZAPIER_DASHBOARD_URL;
|
||||
$data['zapierIsConnected'] = $this->zapier_helper->is_connected();
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Watcher for the wpseo option on Premium.
|
||||
*
|
||||
* Represents the option wpseo watcher for Premium.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Premium_Option_Wpseo_Watcher implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Watcher constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_zapier_option_disabled' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Zapier integration is disabled; if so, deletes the data.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $old_value The old value of the option.
|
||||
* @param array $new_value The new value of the option.
|
||||
*
|
||||
* @return bool Whether the Zapier data has been deleted or not.
|
||||
*/
|
||||
public function check_zapier_option_disabled( $old_value, $new_value ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
if ( \array_key_exists( 'zapier_integration_active', $new_value )
|
||||
&& $old_value['zapier_integration_active'] === true
|
||||
&& $new_value['zapier_integration_active'] === false ) {
|
||||
$this->options->set( 'zapier_subscription', [] );
|
||||
$this->options->set( 'zapier_api_key', '' );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Watchers;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Zapier_Enabled_Conditional;
|
||||
|
||||
/**
|
||||
* Watcher for resetting the Zapier API key.
|
||||
*
|
||||
* Represents the Zapier API key reset watcher for Premium.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_APIKey_Reset_Watcher implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Watcher constructor.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
|
||||
return [ Zapier_Enabled_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Zapier API key must be reset; if so, deletes the data.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool Whether the Zapier data has been deleted or not.
|
||||
*/
|
||||
public function zapier_api_key_reset() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated.
|
||||
if ( \current_user_can( 'manage_options' ) && isset( $_POST['zapier_api_key_reset'] ) && $_POST['zapier_api_key_reset'] === '1' ) {
|
||||
$this->options->set( 'zapier_api_key', '' );
|
||||
$this->options->set( 'zapier_subscription', [] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Routes;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Premium\Actions\Zapier_Action;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Zapier_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Registers the route for the Zapier integration.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Zapier_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The Zapier route prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = 'zapier';
|
||||
|
||||
/**
|
||||
* The subscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/subscribe';
|
||||
|
||||
/**
|
||||
* The unsubscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const UNSUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/unsubscribe';
|
||||
|
||||
/**
|
||||
* The check route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CHECK_API_KEY_ROUTE = self::ROUTE_PREFIX . '/check';
|
||||
|
||||
/**
|
||||
* The perform list route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PERFORM_LIST = self::ROUTE_PREFIX . '/list';
|
||||
|
||||
/**
|
||||
* The is_connected route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const IS_CONNECTED = self::ROUTE_PREFIX . '/is_connected';
|
||||
|
||||
/**
|
||||
* The reset_api_key route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const RESET_API_KEY = self::ROUTE_PREFIX . '/reset_api_key';
|
||||
|
||||
/**
|
||||
* Instance of the Zapier_Action.
|
||||
*
|
||||
* @var Zapier_Action
|
||||
*/
|
||||
protected $zapier_action;
|
||||
|
||||
/**
|
||||
* Zapier_Route constructor.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Zapier_Action $zapier_action The action to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( Zapier_Action $zapier_action ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$this->zapier_action = $zapier_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$subscribe_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'url' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The callback URL to use.',
|
||||
],
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'subscribe' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::SUBSCRIBE_ROUTE, $subscribe_route_args );
|
||||
|
||||
$unsubscribe_route_args = [
|
||||
'methods' => 'DELETE',
|
||||
'args' => [
|
||||
'id' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The ID of the subscription to unsubscribe.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'unsubscribe' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::UNSUBSCRIBE_ROUTE, $unsubscribe_route_args );
|
||||
|
||||
$check_api_key_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'check_api_key' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::CHECK_API_KEY_ROUTE, $check_api_key_route_args );
|
||||
|
||||
$perform_list_route_args = [
|
||||
'methods' => 'GET',
|
||||
'args' => [
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'perform_list' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::PERFORM_LIST, $perform_list_route_args );
|
||||
|
||||
$is_connected_route_args = [
|
||||
'methods' => 'GET',
|
||||
'args' => [],
|
||||
'callback' => [ $this, 'is_connected' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::IS_CONNECTED, $is_connected_route_args );
|
||||
|
||||
$reset_api_key_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to reset.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'reset_api_key' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::RESET_API_KEY, $reset_api_key_route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the subscribe action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the subscribe action.
|
||||
*/
|
||||
public function subscribe( WP_REST_Request $request ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$subscription = $this->zapier_action->subscribe( $request['url'], $request['api_key'] );
|
||||
$response = $subscription->data;
|
||||
|
||||
if ( empty( $response ) && \property_exists( $subscription, 'message' ) ) {
|
||||
$response = $subscription->message;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, $subscription->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the unsubscribe action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the unsubscribe action.
|
||||
*/
|
||||
public function unsubscribe( WP_REST_Request $request ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$subscription = $this->zapier_action->unsubscribe( $request['id'] );
|
||||
|
||||
return new WP_REST_Response( $subscription->message, $subscription->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the check_api_key action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the check_api_key action.
|
||||
*/
|
||||
public function check_api_key( WP_REST_Request $request ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$check = $this->zapier_action->check_api_key( $request['api_key'] );
|
||||
|
||||
return new WP_REST_Response( $check->message, $check->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the perform_list action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the perform_list action.
|
||||
*/
|
||||
public function perform_list( WP_REST_Request $request ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$response = $this->zapier_action->perform_list( $request['api_key'] );
|
||||
|
||||
return new WP_REST_Response( $response->data, $response->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the is_connected action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WP_REST_Response The response of the is_connected action.
|
||||
*/
|
||||
public function is_connected() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$response = $this->zapier_action->is_connected();
|
||||
|
||||
return new WP_REST_Response( [ 'json' => $response->data ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the reset_api_key action.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the reset_api_key action.
|
||||
*/
|
||||
public function reset_api_key( WP_REST_Request $request ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
$result = $this->zapier_action->reset_api_key( $request['api_key'] );
|
||||
|
||||
return new WP_REST_Response( [ 'json' => $result->data ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is authorised to query the connection status or reset the key.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore Just a wrapper for a WordPress function.
|
||||
*
|
||||
* @return bool Whether the user is authorised to query the connection status or reset the key.
|
||||
*/
|
||||
public function check_permissions() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return \current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which these routes should be active.
|
||||
*
|
||||
* @deprecated 20.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The list of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
|
||||
return [ Zapier_Enabled_Conditional::class ];
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* Holds the names of the block pattern categories.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Block_Pattern_Categories {
|
||||
|
||||
public const YOAST_JOB_POSTING = 'yoast_job_posting';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* Holds the names of the block pattern keywords.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Block_Pattern_Keywords {
|
||||
|
||||
public const YOAST_JOB_POSTING = [ 'yoast', 'job', 'posting', 'vacancy' ];
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A Gutenberg block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class Block_Pattern {
|
||||
|
||||
/**
|
||||
* Returns the block pattern configuration.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string[] The configuration.
|
||||
*/
|
||||
public function get_configuration() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return [
|
||||
'title' => '',
|
||||
'content' => '',
|
||||
'categories' => [],
|
||||
'keywords' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
abstract public function get_title();
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
abstract public function get_content();
|
||||
|
||||
/**
|
||||
* Gets the categories of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string[] The categories of this block pattern.
|
||||
*/
|
||||
abstract public function get_categories();
|
||||
|
||||
/**
|
||||
* Gets the keywords of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string[] The keywords of this block pattern.
|
||||
*/
|
||||
abstract public function get_keywords();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A minimal job posting, containing required blocks only.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class Job_Posting_Base_Pattern extends Block_Pattern {
|
||||
|
||||
/**
|
||||
* Includes this Job Posting block pattern in the Yoast Job Posting block pattern category.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The categories under which this block pattern should be shown.
|
||||
*/
|
||||
public function get_categories() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keywords of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The keywords that help users discover the pattern while searching.
|
||||
*/
|
||||
public function get_keywords() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A job posting containing all the required and recommended blocks, shown in one column (with a three-column header).
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Job_Posting_One_Column extends Job_Posting_Base_Pattern {
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
public function get_name() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return 'yoast/job-posting/one-column';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
public function get_title() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return 'Three-column header and one centered column of text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
public function get_content() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return '<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:html -->
|
||||
<strong>Employment</strong>
|
||||
<!-- /wp:html -->
|
||||
|
||||
<!-- wp:yoast/job-employment-type {"employmentType":"FULL_TIME"} -->
|
||||
<div class="yoast-job-block__employment "><div><span data-id="employmentType" data-value="FULL_TIME">Full time</span></div></div>
|
||||
<!-- /wp:yoast/job-employment-type --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:html -->
|
||||
<strong>Base salary</strong>
|
||||
<!-- /wp:html -->
|
||||
|
||||
<!-- wp:yoast/job-salary -->
|
||||
<div class=""><!-- wp:yoast/job-base-salary {"currency":"USD","value":"4000","unit":"MONTH"} -->
|
||||
<div class="yoast-job-block__salary "><div class="yoast-schema-flex"><span data-id="currency" data-value="USD">USD</span> 4000 / <span data-id="unit" data-value="MONTH">month</span></div></div>
|
||||
<!-- /wp:yoast/job-base-salary --></div>
|
||||
<!-- /wp:yoast/job-salary --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column {"width":"66.66%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:66.66%"><!-- wp:paragraph {"style":{"typography":{"fontSize":24}}} -->
|
||||
<p style="font-size:24px">Our company is growing! And we’re searching for an ambitious employee! Do you believe that a hard work is fundamental for your business? If you do, we’re probably looking for you!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-the-job">About the job</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:yoast/job-description -->
|
||||
<div class="yoast-job-block__description "><p data-id="description">You’ll be part of an interdisciplinary team and together you’ll work on challenging, varied projects. You’ll get the freedom and responsibility to reach your full potential!</p></div>
|
||||
<!-- /wp:yoast/job-description -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-you">About you</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Do you you have a passion for your job? Are you aware of current trends in your field? Do you love diving into details?</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>We’re looking for someone who is proactive, patient and smart, has an eye for details, and is a great communicator and motivator. If you also have a sense of humor and an enthusiasm for participating in discussions, you’re probably a fit!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:yoast/job-requirements {"title_level":2} -->
|
||||
<h2 data-id="title">To summarize</h2><div class="yoast-job-block__requirements "><ul data-id="requirements"><li>You enjoy working in a fast-paced team environment.</li><li>You don’t ever think “good enough” is good enough.</li><li>You are available for 40 hours per week.</li><li>You speak and write English fluently (preferably with at least proficiency level C1).</li></ul></div>
|
||||
<!-- /wp:yoast/job-requirements -->
|
||||
|
||||
<!-- wp:yoast/job-benefits {"title_level":2,"className":"yoast-job-block__benefits"} -->
|
||||
<h2 data-id="title">What we’re offering</h2><div class="yoast-job-block__benefits yoast-job-block__benefits"><ul data-id="benefits"><li>A challenging job in a fast-growing, dynamic, ambitious and international atmosphere.</li><li>25 vacation days (on the base of 40 hours).</li><li>You’ll be able to spend 10% of your salary on education.</li><li>We have a really fun company culture with lots of team building activities.</li><li>Are you interested? Then please send your application to this@emailaddress.com before January 1, 2022. Do you have any questions? We’ll be happy to answer them.</li></ul></div>
|
||||
<!-- /wp:yoast/job-benefits -->
|
||||
|
||||
<!-- wp:yoast/job-application-closing-date {"closingDate":"2022-01-01"} -->
|
||||
<div class="yoast-job-block__application-closing-date "><span data-id="title">Apply before</span> <time datetime="2022-01-01">January 1, 2022</time></div>
|
||||
<!-- /wp:yoast/job-application-closing-date --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"width":"33.33%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:33.33%"><!-- wp:yoast/job-location -->
|
||||
<div class=""><!-- wp:yoast/office-location -->
|
||||
<div class="yoast-job-block__location "><!-- wp:yoast/job-location-address -->
|
||||
<div class="yoast-job-block__location__address "><span data-id="address">350 5th Avenue</span></div>
|
||||
<!-- /wp:yoast/job-location-address -->
|
||||
|
||||
<!-- wp:yoast/job-location-city -->
|
||||
<div class="yoast-job-block__location__city "><span data-id="city">New York</span></div>
|
||||
<!-- /wp:yoast/job-location-city -->
|
||||
|
||||
<!-- wp:yoast/job-location-postal-code -->
|
||||
<div class="yoast-job-block__location__postal-code "><span data-id="postal-code">NY 10118</span></div>
|
||||
<!-- /wp:yoast/job-location-postal-code -->
|
||||
|
||||
<!-- wp:yoast/job-location-country -->
|
||||
<div class="yoast-job-block__location__country "><span data-id="country">United States of America</span></div>
|
||||
<!-- /wp:yoast/job-location-country --></div>
|
||||
<!-- /wp:yoast/office-location --></div>
|
||||
<!-- /wp:yoast/job-location --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->';
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A job posting containing all the required and recommended blocks, shown in two columns (with a two-column header).
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Job_Posting_Two_Columns extends Job_Posting_Base_Pattern {
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
public function get_name() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return 'yoast/job-posting/two-columns';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
public function get_title() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return 'Two-column header and two columns of text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
public function get_content() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
return '<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:html -->
|
||||
<strong>Employment</strong>
|
||||
<!-- /wp:html -->
|
||||
|
||||
<!-- wp:yoast/job-employment-type {"employmentType":"FULL_TIME"} -->
|
||||
<div class="yoast-job-block__employment "><div><span data-id="employmentType" data-value="FULL_TIME">Full time</span></div></div>
|
||||
<!-- /wp:yoast/job-employment-type --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:html -->
|
||||
<strong>Salary range</strong>
|
||||
<!-- /wp:html -->
|
||||
|
||||
<!-- wp:yoast/job-salary -->
|
||||
<div class=""><!-- wp:yoast/job-salary-range {"currency":"USD","minValue":"1000","maxValue":"2000","unit":"MONTH"} -->
|
||||
<div class="yoast-job-block__salary "><div class="yoast-schema-flex"><span data-id="currency" data-value="USD">USD</span> 1000 - 2000 / <span data-id="unit" data-value="MONTH">month</span></div></div>
|
||||
<!-- /wp:yoast/job-salary-range --></div>
|
||||
<!-- /wp:yoast/job-salary --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:html -->
|
||||
<strong>Location</strong>
|
||||
<!-- /wp:html -->
|
||||
|
||||
<!-- wp:yoast/job-location -->
|
||||
<div class=""><!-- wp:yoast/office-location -->
|
||||
<div class="yoast-job-block__location "><!-- wp:yoast/job-location-address -->
|
||||
<div class="yoast-job-block__location__address "><span data-id="address">350 5th Avenue</span></div>
|
||||
<!-- /wp:yoast/job-location-address -->
|
||||
|
||||
<!-- wp:yoast/job-location-postal-code -->
|
||||
<div class="yoast-job-block__location__postal-code "><span data-id="postal-code">10118</span></div>
|
||||
<!-- /wp:yoast/job-location-postal-code -->
|
||||
|
||||
<!-- wp:yoast/job-location-city -->
|
||||
<div class="yoast-job-block__location__city "><span data-id="city">New York</span></div>
|
||||
<!-- /wp:yoast/job-location-city -->
|
||||
|
||||
<!-- wp:yoast/job-location-region -->
|
||||
<div class="yoast-job-block__location__region "><span data-id="region">NY</span></div>
|
||||
<!-- /wp:yoast/job-location-region -->
|
||||
|
||||
<!-- wp:yoast/job-location-country -->
|
||||
<div class="yoast-job-block__location__country "><span data-id="country">United States of America</span></div>
|
||||
<!-- /wp:yoast/job-location-country --></div>
|
||||
<!-- /wp:yoast/office-location --></div>
|
||||
<!-- /wp:yoast/job-location --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:paragraph {"style":{"typography":{"fontSize":24}}} -->
|
||||
<p style="font-size:24px">Our company is growing! And we’re searching for an ambitious employee! Do you believe that a hard work is fundamental for your business? If you do, we’re probably looking for you!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-the-job">About the job</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:yoast/job-description -->
|
||||
<div class="yoast-job-block__description "><p data-id="description">You’ll be part of a interdisciplinary team and together you’ll work on challenging, varied projects. You’ll get the freedom and responsibility to reach your full potential!</p></div>
|
||||
<!-- /wp:yoast/job-description -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-you">About you</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Do you you have a passion for your job? Are you aware of current trends in your field? Do you love diving into details?</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>We’re looking for someone who is proactive, patient and smart, has an eye for details, and is a great communicator and motivator. If you also have a sense of humor and an enthusiasm for participating in discussions, you’re probably a fit!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:yoast/job-requirements {"title_level":2} -->
|
||||
<h2 data-id="title">Requirements</h2><div class="yoast-job-block__requirements "><ul data-id="requirements"><li>You enjoy working in a fast-paced team environment.</li><li>You don’t ever think “good enough” is good enough.</li><li>You are available for 40 hours per week.</li><li>You speak and write English fluently (preferably with at least proficiency level C1).</li></ul></div>
|
||||
<!-- /wp:yoast/job-requirements -->
|
||||
|
||||
<!-- wp:yoast/job-benefits {"title_level":2} -->
|
||||
<h2 data-id="title">Benefits</h2><div class="yoast-job-block__benefits "><ul data-id="benefits"><li>A challenging job in a fast-growing, dynamic, ambitious and international atmosphere.</li><li>25 vacation days (on the base of 40 hours).</li><li>You’ll be able to spend 10% of your salary on education.</li><li>We have a really fun company culture with lots of team building activities.</li></ul></div>
|
||||
<!-- /wp:yoast/job-benefits -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Are you interested? Then please send your application to this@emailaddress.com before January 1, 2022. Do you have any questions? We’ll be happy to answer them.</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:yoast/job-application-closing-date {"closingDate":"2022-01-01"} -->
|
||||
<div class="yoast-job-block__application-closing-date "><span data-id="title">Apply before</span> <time datetime="2022-01-01">January 1, 2022</time></div>
|
||||
<!-- /wp:yoast/job-application-closing-date -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p></p>
|
||||
<!-- /wp:paragraph -->';
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception for attempting a mutation on properties that are made readonly through magic getters and setters.
|
||||
*/
|
||||
class Forbidden_Property_Mutation_Exception extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a Forbidden_Property_Mutation_Exception exception when an attempt is made
|
||||
* to assign a value to an immutable property.
|
||||
*
|
||||
* @param string $property_name The name of the immutable property.
|
||||
*
|
||||
* @return Forbidden_Property_Mutation_Exception The exception.
|
||||
*/
|
||||
public static function cannot_set_because_property_is_immutable( $property_name ) {
|
||||
return new self( \sprintf( 'Setting property $%s is not supported.', $property_name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Forbidden_Property_Mutation_Exception exception when an attempt is made to unset an immutable property.
|
||||
*
|
||||
* @param string $property_name The name of the immutable property.
|
||||
*
|
||||
* @return Forbidden_Property_Mutation_Exception The exception.
|
||||
*/
|
||||
public static function cannot_unset_because_property_is_immutable( $property_name ) {
|
||||
return new self( \sprintf( 'Unsetting property $%s is not supported.', $property_name ) );
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 400 - Bad request response.
|
||||
*/
|
||||
class Bad_Request_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 403 - Forbidden response.
|
||||
*/
|
||||
class Forbidden_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 500 - Internal server error response.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Internal_Server_Error_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 404 - not found response.
|
||||
*/
|
||||
class Not_Found_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class to manage a 402 - payment required response.
|
||||
*/
|
||||
class Payment_Required_Exception extends Remote_Request_Exception {
|
||||
|
||||
/**
|
||||
* The missing plugin licenses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $missing_licenses;
|
||||
|
||||
/**
|
||||
* Payment_Required_Exception constructor.
|
||||
*
|
||||
* @param string $message The error message.
|
||||
* @param int $code The error status code.
|
||||
* @param Throwable $previous The previously thrown exception.
|
||||
* @param array $missing_licenses The missing plugin licenses.
|
||||
*/
|
||||
public function __construct( $message = '', $code = 0, $previous = null, $missing_licenses = [] ) {
|
||||
$this->missing_licenses = $missing_licenses;
|
||||
parent::__construct( $message, $code, $previous );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the missing plugin licences.
|
||||
*
|
||||
* @return array The missing plugin licenses.
|
||||
*/
|
||||
public function get_missing_licenses() {
|
||||
return $this->missing_licenses;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class Remote_Request_Exception
|
||||
*/
|
||||
abstract class Remote_Request_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 408 - request timeout exception
|
||||
*/
|
||||
class Request_Timeout_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 503 - service unavailable response.
|
||||
*/
|
||||
class Service_Unavailable_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 429 - Too many requests response.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Too_Many_Requests_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 401 - unauthorized response.
|
||||
*/
|
||||
class Unauthorized_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Premium\Addon_Installer;
|
||||
use Yoast\WP\SEO\Premium\Main;
|
||||
|
||||
/**
|
||||
* Retrieves the main instance.
|
||||
*
|
||||
* @phpcs:disable WordPress.NamingConventions -- Should probably be renamed, but leave for now.
|
||||
*
|
||||
* @return Main The main instance.
|
||||
*/
|
||||
function YoastSEOPremium() {
|
||||
// phpcs:enable
|
||||
|
||||
static $main;
|
||||
if ( did_action( 'wpseo_loaded' ) ) {
|
||||
$should_load = Addon_Installer::is_yoast_seo_up_to_date();
|
||||
if ( $main === null && $should_load ) {
|
||||
// Ensure free is loaded as loading premium will fail without it.
|
||||
YoastSEO();
|
||||
$main = new Main();
|
||||
$main->load();
|
||||
}
|
||||
}
|
||||
else {
|
||||
add_action( 'wpseo_loaded', 'YoastSEOPremium' );
|
||||
}
|
||||
|
||||
return $main;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,357 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use RuntimeException;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception;
|
||||
use Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception;
|
||||
|
||||
/**
|
||||
* Class AI_Generator_Helper
|
||||
*
|
||||
* @package Yoast\WP\SEO\Helpers
|
||||
*/
|
||||
class AI_Generator_Helper {
|
||||
|
||||
/**
|
||||
* The API base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_url = 'https://ai.yoa.st/api/v1';
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The User helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
protected $user_helper;
|
||||
|
||||
/**
|
||||
* AI_Generator_Helper constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param User_Helper $user_helper The User helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, User_Helper $user_helper ) {
|
||||
$this->options_helper = $options;
|
||||
$this->user_helper = $user_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random code verifier for a user. The code verifier is used in communication with the Yoast AI API
|
||||
* to ensure that the callback that is sent for both the token and refresh request are handled by the same site that requested the tokens.
|
||||
* Each code verifier should only be used once.
|
||||
* This all helps with preventing access tokens from one site to be sent to another and it makes a mitm attack more difficult to execute.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @return string The code verifier.
|
||||
*/
|
||||
public function generate_code_verifier( WP_User $user ) {
|
||||
$random_string = \substr( \str_shuffle( '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ), 1, 10 );
|
||||
|
||||
return \hash( 'sha256', $user->user_email . $random_string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily stores the code verifier. We expect the callback that consumes this verifier to reach us within a couple of seconds.
|
||||
* So, we throw away the code after 5 minutes: when we know the callback isn't coming.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @param string $code_verifier The code verifier.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_code_verifier( int $user_id, string $code_verifier ): void {
|
||||
$user_id_string = (string) $user_id;
|
||||
\set_transient( "yoast_wpseo_ai_generator_code_verifier_$user_id_string", $code_verifier, ( \MINUTE_IN_SECONDS * 5 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the code verifier.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
*
|
||||
* @return string The code verifier.
|
||||
*
|
||||
* @throws RuntimeException Unable to retrieve the code verifier.
|
||||
*/
|
||||
public function get_code_verifier( int $user_id ): string {
|
||||
$user_id_string = (string) $user_id;
|
||||
$code_verifier = \get_transient( "yoast_wpseo_ai_generator_code_verifier_$user_id_string" );
|
||||
if ( ! \is_string( $code_verifier ) || $code_verifier === '' ) {
|
||||
throw new RuntimeException( 'Unable to retrieve the code verifier.' );
|
||||
}
|
||||
|
||||
return $code_verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the code verifier.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_code_verifier( int $user_id ): void {
|
||||
$user_id_string = (string) $user_id;
|
||||
\delete_transient( "yoast_wpseo_ai_generator_code_verifier_$user_id_string" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the licence URL.
|
||||
*
|
||||
* @return string The licence URL.
|
||||
*/
|
||||
public function get_license_url() {
|
||||
return WPSEO_Utils::get_home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback URL to be used by the API to send back the access token, refresh token and code challenge.
|
||||
*
|
||||
* @return array The callbacks URLs.
|
||||
*/
|
||||
public function get_callback_url() {
|
||||
return \get_rest_url( null, 'yoast/v1/ai_generator/callback' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback URL to be used by the API to send back the refreshed JWTs once they expire.
|
||||
*
|
||||
* @return array The callbacks URLs.
|
||||
*/
|
||||
public function get_refresh_callback_url() {
|
||||
return \get_rest_url( null, 'yoast/v1/ai_generator/refresh_callback' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the request using WordPress internals.
|
||||
*
|
||||
* @param string $action_path The path to the desired action.
|
||||
* @param array $request_body The request body.
|
||||
* @param array $request_headers The request headers.
|
||||
*
|
||||
* @return object The response object.
|
||||
*
|
||||
* @throws Bad_Request_Exception When the request fails for any other reason.
|
||||
* @throws Forbidden_Exception When the response code is 403.
|
||||
* @throws Internal_Server_Error_Exception When the response code is 500.
|
||||
* @throws Not_Found_Exception When the response code is 404.
|
||||
* @throws Payment_Required_Exception When the response code is 402.
|
||||
* @throws Request_Timeout_Exception When the response code is 408.
|
||||
* @throws Service_Unavailable_Exception When the response code is 503.
|
||||
* @throws Too_Many_Requests_Exception When the response code is 429.
|
||||
* @throws Unauthorized_Exception When the response code is 401.
|
||||
*/
|
||||
public function request( $action_path, $request_body = [], $request_headers = [] ) {
|
||||
// Our API expects JSON.
|
||||
// The request times out after 30 seconds.
|
||||
$request_headers = \array_merge( $request_headers, [ 'Content-Type' => 'application/json' ] );
|
||||
$request_arguments = [
|
||||
'timeout' => 30,
|
||||
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Reason: We don't want the debug/pretty possibility.
|
||||
'body' => \wp_json_encode( $request_body ),
|
||||
'headers' => $request_headers,
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\ai_api_url' - Replaces the default URL for the AI API with a custom one.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 21.0
|
||||
* @internal
|
||||
*
|
||||
* @param string $url The default URL for the AI API.
|
||||
*/
|
||||
$api_url = \apply_filters( 'Yoast\WP\SEO\ai_api_url', $this->base_url );
|
||||
$response = \wp_remote_post( $api_url . $action_path, $request_arguments );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
throw new Bad_Request_Exception( $response->get_error_message(), $response->get_error_code() );
|
||||
}
|
||||
|
||||
[ $response_code, $response_message, $missing_licenses ] = $this->parse_response( $response );
|
||||
|
||||
switch ( $response_code ) {
|
||||
case 200:
|
||||
return (object) $response;
|
||||
case 401:
|
||||
throw new Unauthorized_Exception( $response_message, $response_code );
|
||||
case 402:
|
||||
throw new Payment_Required_Exception( $response_message, $response_code, null, $missing_licenses );
|
||||
case 403:
|
||||
throw new Forbidden_Exception( $response_message, $response_code );
|
||||
case 404:
|
||||
throw new Not_Found_Exception( $response_message, $response_code );
|
||||
case 408:
|
||||
throw new Request_Timeout_Exception( $response_message, $response_code );
|
||||
case 429:
|
||||
throw new Too_Many_Requests_Exception( $response_message, $response_code );
|
||||
case 500:
|
||||
throw new Internal_Server_Error_Exception( $response_message, $response_code );
|
||||
case 503:
|
||||
throw new Service_Unavailable_Exception( $response_message, $response_code );
|
||||
default:
|
||||
throw new Bad_Request_Exception( $response_message, $response_code );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the list of 5 suggestions to return.
|
||||
*
|
||||
* @param object $response The response from the API.
|
||||
*
|
||||
* @return array The array of suggestions.
|
||||
*/
|
||||
public function build_suggestions_array( $response ): array {
|
||||
$suggestions = [];
|
||||
$json = \json_decode( $response->body );
|
||||
if ( $json === null || ! isset( $json->choices ) ) {
|
||||
return $suggestions;
|
||||
}
|
||||
foreach ( $json->choices as $suggestion ) {
|
||||
$suggestions[] = $suggestion->text;
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response from the API.
|
||||
*
|
||||
* @param array|WP_Error $response The response from the API.
|
||||
*
|
||||
* @return array The response code and message.
|
||||
*/
|
||||
public function parse_response( $response ) {
|
||||
$response_code = ( \wp_remote_retrieve_response_code( $response ) !== '' ) ? \wp_remote_retrieve_response_code( $response ) : 0;
|
||||
$response_message = \esc_html( \wp_remote_retrieve_response_message( $response ) );
|
||||
$missing_licenses = [];
|
||||
|
||||
if ( $response_code !== 200 && $response_code !== 0 ) {
|
||||
$json_body = \json_decode( \wp_remote_retrieve_body( $response ) );
|
||||
if ( $json_body !== null ) {
|
||||
$response_message = ( $json_body->error_code ?? $this->map_message_to_code( $json_body->message ) );
|
||||
if ( $response_code === 402 ) {
|
||||
$missing_licenses = isset( $json_body->missing_licenses ) ? (array) $json_body->missing_licenses : [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ $response_code, $response_message, $missing_licenses ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the token has expired.
|
||||
*
|
||||
* @param string $jwt The JWT.
|
||||
*
|
||||
* @return bool Whether the token has expired.
|
||||
*/
|
||||
public function has_token_expired( string $jwt ): bool {
|
||||
$parts = \explode( '.', $jwt );
|
||||
if ( \count( $parts ) !== 3 ) {
|
||||
// Headers, payload and signature parts are not detected.
|
||||
return true;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Reason: Decoding the payload of the JWT.
|
||||
$payload = \base64_decode( $parts[1] );
|
||||
$json = \json_decode( $payload );
|
||||
if ( $json === null || ! isset( $json->exp ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $json->exp < \time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the access JWT.
|
||||
*
|
||||
* @param string $user_id The user ID.
|
||||
*
|
||||
* @return string The access JWT.
|
||||
*
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
public function get_access_token( string $user_id ): string {
|
||||
$access_jwt = $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_generator_access_jwt', true );
|
||||
if ( ! \is_string( $access_jwt ) || $access_jwt === '' ) {
|
||||
throw new RuntimeException( 'Unable to retrieve the access token.' );
|
||||
}
|
||||
|
||||
return $access_jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the refresh JWT.
|
||||
*
|
||||
* @param string $user_id The user ID.
|
||||
*
|
||||
* @return string The access JWT.
|
||||
*
|
||||
* @throws RuntimeException Unable to retrieve the refresh token.
|
||||
*/
|
||||
public function get_refresh_token( $user_id ) {
|
||||
$refresh_jwt = $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_generator_refresh_jwt', true );
|
||||
if ( ! \is_string( $refresh_jwt ) || $refresh_jwt === '' ) {
|
||||
throw new RuntimeException( 'Unable to retrieve the refresh token.' );
|
||||
}
|
||||
|
||||
return $refresh_jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI Generator feature is active.
|
||||
*
|
||||
* @return bool Whether the feature is active.
|
||||
*/
|
||||
public function is_ai_generator_enabled() {
|
||||
return $this->options_helper->get( 'enable_ai_generator', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the message to a code.
|
||||
*
|
||||
* @param string $message The message.
|
||||
*
|
||||
* @return string The code.
|
||||
*/
|
||||
private function map_message_to_code( $message ) {
|
||||
if ( \strpos( $message, 'must NOT have fewer than 1 characters' ) !== false ) {
|
||||
return 'NOT_ENOUGH_CONTENT';
|
||||
}
|
||||
if ( \strpos( $message, 'Client timeout' ) !== false ) {
|
||||
return 'CLIENT_TIMEOUT';
|
||||
}
|
||||
if ( \strpos( $message, 'Server timeout' ) !== false ) {
|
||||
return 'SERVER_TIMEOUT';
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
/**
|
||||
* Class Current_Page_Helper.
|
||||
*/
|
||||
class Current_Page_Helper {
|
||||
|
||||
/**
|
||||
* Determine whether the current page is the homepage and shows posts.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_home_posts_page() {
|
||||
return ( \is_home() && \get_option( 'show_on_front' ) !== 'page' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current page is a static homepage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_home_static_page() {
|
||||
return ( \is_front_page() && \get_option( 'show_on_front' ) === 'page' && \is_page( \get_option( 'page_on_front' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this is the posts page, regardless of whether it's the frontpage or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_posts_page() {
|
||||
return ( \is_home() && ! \is_front_page() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current post id.
|
||||
* Returns 0 if no post id is found.
|
||||
*
|
||||
* @return int The post id.
|
||||
*/
|
||||
public function get_current_post_id() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
|
||||
if ( isset( $_GET['post'] ) && \is_string( $_GET['post'] ) && (int) \wp_unslash( $_GET['post'] ) > 0 ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer, also this is a helper function.
|
||||
return (int) \wp_unslash( $_GET['post'] );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current post type.
|
||||
*
|
||||
* @return string The post type.
|
||||
*/
|
||||
public function get_current_post_type() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['post_type'] ) && \is_string( $_GET['post_type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return \sanitize_text_field( \wp_unslash( $_GET['post_type'] ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: should be done outside the helper function.
|
||||
if ( isset( $_POST['post_type'] ) && \is_string( $_POST['post_type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: should be done outside the helper function.
|
||||
return \sanitize_text_field( \wp_unslash( $_POST['post_type'] ) );
|
||||
}
|
||||
|
||||
$post_id = $this->get_current_post_id();
|
||||
|
||||
if ( $post_id ) {
|
||||
return \get_post_type( $post_id );
|
||||
}
|
||||
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current taxonomy.
|
||||
*
|
||||
* @return string The taxonomy.
|
||||
*/
|
||||
public function get_current_taxonomy() {
|
||||
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ! \in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'POST' ], true ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification -- Reason: We are not processing form information.
|
||||
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: should be done outside the helper function.
|
||||
if ( isset( $_POST['taxonomy'] ) && \is_string( $_POST['taxonomy'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: should be done outside the helper function.
|
||||
return \sanitize_text_field( \wp_unslash( $_POST['taxonomy'] ) );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['taxonomy'] ) && \is_string( $_GET['taxonomy'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return \sanitize_text_field( \wp_unslash( $_GET['taxonomy'] ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Helper.
|
||||
*/
|
||||
class Prominent_Words_Helper {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Helper constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the tf-idf (term frequency - inverse document frequency) score of a prominent word in a document.
|
||||
* The document frequency should be 1 or higher, if it is not, it is assumed to be 1.
|
||||
*
|
||||
* @param int $term_frequency How many times the word occurs in the document.
|
||||
* @param int $doc_frequency In how many documents this word occurs.
|
||||
*
|
||||
* @return float The tf-idf score of a prominent word.
|
||||
*/
|
||||
public function compute_tf_idf_score( $term_frequency, $doc_frequency ) {
|
||||
// Set doc frequency to a minimum of 1, to avoid division by 0.
|
||||
$doc_frequency = \max( 1, $doc_frequency );
|
||||
|
||||
return ( $term_frequency * ( 1 / $doc_frequency ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the vector length for the given prominent words, applying Pythagoras's Theorem on the weights.
|
||||
*
|
||||
* @param array $prominent_words The prominent words, as an array mapping stems to `weight` and `df` (document frequency).
|
||||
*
|
||||
* @return float Vector length for the prominent words.
|
||||
*/
|
||||
public function compute_vector_length( $prominent_words ) {
|
||||
$sum_of_squares = 0;
|
||||
|
||||
foreach ( $prominent_words as $stem => $word ) {
|
||||
$doc_frequency = 1;
|
||||
if ( \array_key_exists( 'df', $word ) ) {
|
||||
$doc_frequency = $word['df'];
|
||||
}
|
||||
|
||||
$tf_idf = $this->compute_tf_idf_score( $word['weight'], $doc_frequency );
|
||||
$sum_of_squares += ( $tf_idf ** 2 );
|
||||
}
|
||||
|
||||
return \sqrt( $sum_of_squares );
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the prominent words indexing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function complete_indexing() {
|
||||
$this->set_indexing_completed( true );
|
||||
\set_transient( 'total_unindexed_prominent_words', '0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prominent_words_indexing_completed option.
|
||||
*
|
||||
* @param bool $indexing_completed Whether or not the prominent words indexing has completed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_indexing_completed( $indexing_completed ) {
|
||||
$this->options_helper->set( 'prominent_words_indexing_completed', $indexing_completed );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean that indicates whether the prominent words indexing has completed.
|
||||
*
|
||||
* @return bool Whether the prominent words indexing has completed.
|
||||
*/
|
||||
public function is_indexing_completed() {
|
||||
return $this->options_helper->get( 'prominent_words_indexing_completed' );
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use Yoast\WP\SEO\Premium\Addon_Installer;
|
||||
|
||||
/**
|
||||
* Helper class to check the status of the Free and Premium versions.
|
||||
*/
|
||||
class Version_Helper {
|
||||
|
||||
/**
|
||||
* Checks whether Free is active and set to a version later than the minimum required.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_free_upgraded() {
|
||||
return ( \defined( 'WPSEO_VERSION' ) && \version_compare( \WPSEO_VERSION, Addon_Installer::MINIMUM_YOAST_SEO_VERSION . '-RC0', '>' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a new update is available for Premium.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_premium_update_available() {
|
||||
$plugin_updates = \get_plugin_updates();
|
||||
return isset( $plugin_updates[ \WPSEO_PREMIUM_BASENAME ] );
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Index_Now_Key class
|
||||
*/
|
||||
class Index_Now_Key implements Initializer_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Holds the IndexNow key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Index_Now_Key initializer constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
\add_action( 'init', [ $this, 'add_rewrite_rule' ], 1 );
|
||||
\add_action( 'plugins_loaded', [ $this, 'load' ], 15 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load() {
|
||||
if ( $this->options_helper->get( 'enable_index_now' ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->key = $this->options_helper->get( 'index_now_key' );
|
||||
if ( $this->key === '' ) {
|
||||
$this->generate_key();
|
||||
}
|
||||
\add_action( 'wp', [ $this, 'output_key' ], 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the rewrite rule for the IndexNow key txt file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_rewrite_rule() {
|
||||
if ( $this->options_helper->get( 'enable_index_now' ) !== true ) {
|
||||
return;
|
||||
}
|
||||
global $wp;
|
||||
|
||||
$wp->add_query_var( 'yoast_index_now_key' );
|
||||
\add_rewrite_rule( '^yoast-index-now-([a-zA-Z0-9-]+)\.txt$', 'index.php?yoast_index_now_key=$matches[1]', 'top' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the key when it matches the key in the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function output_key() {
|
||||
$key_in_url = \get_query_var( 'yoast_index_now_key' );
|
||||
if ( empty( $key_in_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $key_in_url === $this->key ) {
|
||||
// Remove all headers.
|
||||
\header_remove();
|
||||
// Only send plain text header.
|
||||
\header( 'Content-Type: text/plain;charset=UTF-8' );
|
||||
echo \esc_html( $this->key );
|
||||
die;
|
||||
}
|
||||
|
||||
// Trying keys? Good luck.
|
||||
global $wp_query;
|
||||
$wp_query->set_404();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an IndexNow key.
|
||||
*
|
||||
* Adapted from wp_generate_password to include dash (-) and not be filtered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function generate_key() {
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-';
|
||||
|
||||
for ( $i = 0; $i < 100; $i++ ) {
|
||||
$this->key .= \substr( $chars, \wp_rand( 0, ( \strlen( $chars ) - 1 ) ), 1 );
|
||||
}
|
||||
$this->options_helper->set( 'index_now_key', $this->key );
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
use Yoast\WP\SEO\Introductions\Application\Current_Page_Trait;
|
||||
use Yoast\WP\SEO\Introductions\Domain\Introduction_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Yoast_Admin_Or_Introductions_Route_Conditional;
|
||||
|
||||
/**
|
||||
* Initializes Premium introductions.
|
||||
*/
|
||||
class Introductions_Initializer implements Initializer_Interface {
|
||||
|
||||
use Current_Page_Trait;
|
||||
|
||||
public const SCRIPT_HANDLE = 'wp-seo-premium-introductions';
|
||||
|
||||
/**
|
||||
* Holds the current page helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the introductions.
|
||||
*
|
||||
* @var Introduction_Interface
|
||||
*/
|
||||
private $introductions;
|
||||
|
||||
/**
|
||||
* Constructs the new features integration.
|
||||
*
|
||||
* @param Current_Page_Helper $current_page_helper The current page helper.
|
||||
* @param Introduction_Interface ...$introductions The introductions.
|
||||
*/
|
||||
public function __construct( Current_Page_Helper $current_page_helper, Introduction_Interface ...$introductions ) {
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->introductions = $introductions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* In this case: when on an admin page.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Yoast_Admin_Or_Introductions_Route_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the action to enqueue the needed script(s).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
if ( $this->is_on_installation_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_filter( 'wpseo_introductions', [ $this, 'add_introductions' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Premium introductions.
|
||||
*
|
||||
* @param Introduction_Interface[] $introductions The introductions.
|
||||
*
|
||||
* @return array The merged introductions.
|
||||
*/
|
||||
public function add_introductions( $introductions ) {
|
||||
// Safety check and bail.
|
||||
if ( ! \is_array( $introductions ) ) {
|
||||
return $introductions;
|
||||
}
|
||||
|
||||
return \array_merge( $introductions, $this->introductions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_script( self::SCRIPT_HANDLE );
|
||||
\wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'wpseoPremiumIntroductions',
|
||||
[
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use WPSEO_Capability_Manager_Factory;
|
||||
use WPSEO_Premium;
|
||||
use WPSEO_Premium_Register_Capabilities;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Class Plugin.
|
||||
*/
|
||||
class Plugin implements Initializer_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Plugin constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the redirect handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
\add_action( 'plugins_loaded', [ $this, 'load' ], 15 );
|
||||
|
||||
$wpseo_premium_capabilities = new WPSEO_Premium_Register_Capabilities();
|
||||
$wpseo_premium_capabilities->register_hooks();
|
||||
|
||||
\register_deactivation_hook( \WPSEO_PREMIUM_FILE, [ $this, 'wpseo_premium_deactivate' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* The premium setup
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load() {
|
||||
new WPSEO_Premium();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up Premium on deactivation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wpseo_premium_deactivate() {
|
||||
\do_action( 'wpseo_register_capabilities_premium' );
|
||||
WPSEO_Capability_Manager_Factory::get( 'premium' )->remove();
|
||||
if ( $this->options_helper->get( 'toggled_tracking' ) !== true ) {
|
||||
$this->options_helper->set( 'tracking', false );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,675 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Premium_Redirect_Option;
|
||||
use WPSEO_Redirect_Option;
|
||||
use WPSEO_Redirect_Util;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Class Redirect_Handler.
|
||||
*/
|
||||
class Redirect_Handler implements Initializer_Interface {
|
||||
|
||||
/**
|
||||
* Array where the redirects will stored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $redirects;
|
||||
|
||||
/**
|
||||
* The matches parts of the URL in case of a matched regex redirect.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $url_matches = [];
|
||||
|
||||
/**
|
||||
* Is the current page being redirected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_redirected = false;
|
||||
|
||||
/**
|
||||
* The URL that is called at the moment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $request_url = '';
|
||||
|
||||
/**
|
||||
* Sets the error template to include.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template_file_path;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the redirect handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
// Only handle the redirect when the option for php redirects is enabled.
|
||||
if ( ! $this->load_php_redirects() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \function_exists( 'is_plugin_active_for_network' ) ) {
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
// If the plugin is network activated, we wait for the plugins to be loaded before initializing.
|
||||
if ( \is_plugin_active_for_network( \WPSEO_PREMIUM_BASENAME ) ) {
|
||||
\add_action( 'plugins_loaded', [ $this, 'handle_redirects' ], 16 );
|
||||
}
|
||||
else {
|
||||
$this->handle_redirects();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 410 status code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function do_410() {
|
||||
$is_include_hook_set = $this->set_template_include_hook( '410' );
|
||||
|
||||
if ( ! $is_include_hook_set ) {
|
||||
$this->set_404();
|
||||
}
|
||||
|
||||
$this->status_header( 410 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 451 status code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function do_451() {
|
||||
$is_include_hook_set = $this->set_template_include_hook( '451' );
|
||||
|
||||
if ( ! $is_include_hook_set ) {
|
||||
$this->set_404();
|
||||
}
|
||||
|
||||
$this->status_header( 451, 'Unavailable For Legal Reasons' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template that should be included.
|
||||
*
|
||||
* @param string $template The template that will included before executing hook.
|
||||
*
|
||||
* @return string Returns the template that should be included.
|
||||
*/
|
||||
public function set_template_include( $template ) {
|
||||
if ( ! empty( $this->template_file_path ) ) {
|
||||
return $this->template_file_path;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the $regex vars with URL matches.
|
||||
*
|
||||
* @param string[] $matches Array with the matches from the matching redirect.
|
||||
*
|
||||
* @return string The replaced URL.
|
||||
*/
|
||||
public function format_regex_redirect_url( $matches ) {
|
||||
$arr_key = \substr( $matches[0], 1 );
|
||||
|
||||
if ( isset( $this->url_matches[ $arr_key ] ) ) {
|
||||
return $this->url_matches[ $arr_key ];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the wp_query to 404 when this is an object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_404() {
|
||||
$wp_query = $this->get_wp_query();
|
||||
$wp_query->is_404 = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current URL matches a normal redirect.
|
||||
*
|
||||
* @param string $request_url The request url to look for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_normal_redirects( $request_url ) {
|
||||
// Setting the redirects.
|
||||
$redirects = $this->get_redirects( WPSEO_Redirect_Option::OPTION_PLAIN );
|
||||
$this->redirects = $this->normalize_redirects( $redirects );
|
||||
|
||||
$request_url = $this->normalize_url( $request_url );
|
||||
|
||||
// Get the URL and doing the redirect.
|
||||
$redirect_url = $this->find_url( $request_url );
|
||||
|
||||
if ( empty( $redirect_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->normalize_url( $redirect_url['url'] ) === $request_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->is_redirected = true;
|
||||
$this->do_redirect( $redirect_url['url'], $redirect_url['type'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the url by trimming the slashes. If the given URL is a slash only,
|
||||
* it will do nothing. By normalizing the URL there is a basis for matching multiple
|
||||
* variants (Like: url, /url, /url/, url/).
|
||||
*
|
||||
* @param string $url The URL to normalize.
|
||||
*
|
||||
* @return string The modified url.
|
||||
*/
|
||||
protected function normalize_url( $url ) {
|
||||
if ( $url === '/' ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return \trim( $url, '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current URL matches a regex.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_regex_redirects() {
|
||||
// Setting the redirects.
|
||||
$this->redirects = $this->get_redirects( WPSEO_Redirect_Option::OPTION_REGEX );
|
||||
|
||||
foreach ( $this->redirects as $regex => $redirect ) {
|
||||
// Check if the URL matches the $regex.
|
||||
$this->match_regex_redirect( $regex, $redirect );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request URL matches one of the regex redirects.
|
||||
*
|
||||
* @param string $regex The reqular expression to match.
|
||||
* @param array $redirect The URL that might be matched with the regex.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function match_regex_redirect( $regex, array $redirect ) {
|
||||
/*
|
||||
* Escape the ` because we use ` to delimit the regex to prevent faulty redirects.
|
||||
*
|
||||
* Explicitly chosen not to use `preg_quote` because we need to be able to parse
|
||||
* user provided regular expression syntax.
|
||||
*/
|
||||
$regex = \str_replace( '`', '\\`', $regex );
|
||||
|
||||
// Suppress warning: a faulty redirect will give a warning and not an exception. So we can't catch it.
|
||||
// See issue: https://github.com/Yoast/wordpress-seo-premium/issues/662.
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
if ( @\preg_match( "`{$regex}`", $this->request_url, $this->url_matches ) === 1 ) {
|
||||
|
||||
// Replace the $regex vars with URL matches.
|
||||
$redirect_url = \preg_replace_callback(
|
||||
'/\$[0-9]+/',
|
||||
[ $this, 'format_regex_redirect_url' ],
|
||||
$redirect['url']
|
||||
);
|
||||
|
||||
$this->do_redirect( $redirect_url, $redirect['type'] );
|
||||
}
|
||||
|
||||
// Reset url_matches.
|
||||
$this->url_matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirects from the options.
|
||||
*
|
||||
* @param string $option The option name that wil be fetched.
|
||||
*
|
||||
* @return array Returns the redirects for the given option.
|
||||
*/
|
||||
protected function get_redirects( $option ) {
|
||||
static $redirects;
|
||||
|
||||
if ( ! isset( $redirects[ $option ] ) ) {
|
||||
$redirects[ $option ] = \get_option( $option, false );
|
||||
}
|
||||
|
||||
if ( ! empty( $redirects[ $option ] ) ) {
|
||||
return $redirects[ $option ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the redirect.
|
||||
*
|
||||
* @param string $redirect_url The target URL.
|
||||
* @param string $redirect_type The type of the redirect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_redirect( $redirect_url, $redirect_type ) {
|
||||
$redirect_url = $this->parse_target_url( $redirect_url );
|
||||
|
||||
// Prevents redirecting to itself.
|
||||
if ( $this->home_url( $this->request_url ) === $redirect_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirect_types_without_target = [ 410, 451 ];
|
||||
if ( \in_array( $redirect_type, $redirect_types_without_target, true ) ) {
|
||||
$this->handle_redirect_without_target( $redirect_type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect( $redirect_url, $redirect_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a redirect has been executed.
|
||||
*
|
||||
* @return bool Whether a redirect has been executed.
|
||||
*/
|
||||
protected function is_redirected() {
|
||||
return $this->is_redirected === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should load the PHP redirects.
|
||||
*
|
||||
* If Apache or NginX configuration is selected, don't load PHP redirects.
|
||||
*
|
||||
* @return bool True if PHP redirects should be loaded and used.
|
||||
*/
|
||||
protected function load_php_redirects() {
|
||||
|
||||
if ( \defined( 'WPSEO_DISABLE_PHP_REDIRECTS' ) && \WPSEO_DISABLE_PHP_REDIRECTS === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( \defined( 'WP_CLI' ) && \WP_CLI === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = \get_option( 'wpseo_redirect', false );
|
||||
if ( $options === false ) {
|
||||
// If the option is not set, save it, to prevent a query for a non-existing option on every page load.
|
||||
\add_action( 'wp_head', [ $this, 'save_default_redirect_options' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the PHP redirects are disabled intentionally, return false.
|
||||
if ( ! empty( $options['disable_php_redirect'] ) && $options['disable_php_redirect'] === 'on' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PHP redirects are the enabled method of redirecting.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the default redirects options to the DB.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_default_redirect_options() {
|
||||
$redirect_option = WPSEO_Premium_Redirect_Option::get_instance();
|
||||
\update_option( 'wpseo_redirect', $redirect_option->get_defaults(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_request_uri() {
|
||||
$request_uri = '';
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We sanitize after decoding.
|
||||
$request_uri = \sanitize_text_field( \rawurldecode( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
|
||||
}
|
||||
|
||||
return $this->strip_subdirectory( $request_uri );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the redirects by raw url decoding the origin.
|
||||
*
|
||||
* @param array $redirects The redirects to normalize.
|
||||
*
|
||||
* @return array The normalized redirects.
|
||||
*/
|
||||
protected function normalize_redirects( $redirects ) {
|
||||
$normalized_redirects = [];
|
||||
|
||||
foreach ( $redirects as $origin => $redirect ) {
|
||||
$normalized_redirects[ \rawurldecode( $origin ) ] = $redirect;
|
||||
}
|
||||
|
||||
return $normalized_redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request URL and sanitize the slashes for it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_request_url() {
|
||||
$this->request_url = $this->get_request_uri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the URL in the redirects.
|
||||
*
|
||||
* @param string $url The needed URL.
|
||||
*
|
||||
* @return bool|string The found url or false if not found.
|
||||
*/
|
||||
protected function find_url( $url ) {
|
||||
$redirect_url = $this->search( $url );
|
||||
if ( ! empty( $redirect_url ) ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
return $this->find_url_fallback( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the given URL in the redirects array.
|
||||
*
|
||||
* @param string $url The URL to search for.
|
||||
*
|
||||
* @return string|bool The found url or false if not found.
|
||||
*/
|
||||
protected function search( $url ) {
|
||||
if ( ! empty( $this->redirects[ $url ] ) ) {
|
||||
return $this->redirects[ $url ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for alternatives with slashes if requested URL isn't found.
|
||||
*
|
||||
* This will add a slash if there isn't a slash or it will remove a trailing slash when there isn't one.
|
||||
*
|
||||
* @todo Discuss: Maybe we should add slashes to all the values we handle instead of using a fallback.
|
||||
*
|
||||
* @param string $url The URL that have to be matched.
|
||||
*
|
||||
* @return bool|string The found url or false if not found.
|
||||
*/
|
||||
protected function find_url_fallback( $url ) {
|
||||
$no_trailing_slash = \rtrim( $url, '/' );
|
||||
|
||||
$checks = [
|
||||
'no_trailing_slash' => $no_trailing_slash,
|
||||
'trailing_slash' => $no_trailing_slash . '/',
|
||||
];
|
||||
|
||||
foreach ( $checks as $check ) {
|
||||
$redirect_url = $this->search( $check );
|
||||
if ( ! empty( $redirect_url ) ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the target URL.
|
||||
*
|
||||
* @param string $target_url The URL to parse. When there isn't found a scheme, just parse it based on the home URL.
|
||||
*
|
||||
* @return string The parsed url.
|
||||
*/
|
||||
protected function parse_target_url( $target_url ) {
|
||||
if ( $this->has_url_scheme( $target_url ) ) {
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
$target_url = $this->trailingslashit( $target_url );
|
||||
$target_url = $this->format_for_multisite( $target_url );
|
||||
|
||||
return $this->home_url( $target_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given url has a scheme.
|
||||
*
|
||||
* @param string $url The url to check.
|
||||
*
|
||||
* @return bool True when url has scheme.
|
||||
*/
|
||||
protected function has_url_scheme( $url ) {
|
||||
$scheme = \wp_parse_url( $url, \PHP_URL_SCHEME );
|
||||
|
||||
return ! empty( $scheme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the target URL ends with a slash and adds one if necessary.
|
||||
*
|
||||
* @param string $target_url The url to format.
|
||||
*
|
||||
* @return string The url with trailing slash.
|
||||
*/
|
||||
protected function trailingslashit( $target_url ) {
|
||||
// Adds slash to target URL when permalink structure ends with a slash.
|
||||
if ( $this->requires_trailing_slash( $target_url ) ) {
|
||||
return \trailingslashit( $target_url );
|
||||
}
|
||||
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the target url for the multisite if needed.
|
||||
*
|
||||
* @param string $target_url The url to format.
|
||||
*
|
||||
* @return string The formatted url.
|
||||
*/
|
||||
protected function format_for_multisite( $target_url ) {
|
||||
if ( ! \is_multisite() ) {
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
$blog_details = \get_blog_details();
|
||||
if ( $blog_details && ! empty( $blog_details->path ) ) {
|
||||
$blog_path = \ltrim( $blog_details->path, '/' );
|
||||
if ( ! empty( $blog_path ) && \strpos( $target_url, $blog_path ) === 0 ) {
|
||||
$target_url = \substr( $target_url, \strlen( $blog_path ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirect URL by given URL.
|
||||
*
|
||||
* @param string $redirect_url The URL that has to be redirected.
|
||||
*
|
||||
* @return string The redirect url.
|
||||
*/
|
||||
protected function home_url( $redirect_url ) {
|
||||
$redirect_url = $this->strip_subdirectory( $redirect_url );
|
||||
|
||||
return \home_url( $redirect_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the subdirectory from the given url.
|
||||
*
|
||||
* @param string $url The url to strip the subdirectory from.
|
||||
*
|
||||
* @return string The url with the stripped subdirectory.
|
||||
*/
|
||||
protected function strip_subdirectory( $url ) {
|
||||
return WPSEO_Redirect_Util::strip_base_url_path_from_url( $this->get_home_url(), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL PATH from the home url.
|
||||
*
|
||||
* @return string|null The url path or null if there isn't one.
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
return \home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hook for setting the template include. This is the file that we want to show.
|
||||
*
|
||||
* @param string $template_to_set The template to look for.
|
||||
*
|
||||
* @return bool True when template should be included.
|
||||
*/
|
||||
protected function set_template_include_hook( $template_to_set ) {
|
||||
$this->template_file_path = $this->get_query_template( $template_to_set );
|
||||
if ( ! empty( $this->template_file_path ) ) {
|
||||
\add_filter( 'template_include', [ $this, 'set_template_include' ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the WordPress status_header function.
|
||||
*
|
||||
* @param int $code HTTP status code.
|
||||
* @param string $description Optional. A custom description for the HTTP status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function status_header( $code, $description = '' ) {
|
||||
\status_header( $code, $description );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of WP_Query.
|
||||
*
|
||||
* @return WP_Query Instance of WP_Query.
|
||||
*/
|
||||
protected function get_wp_query() {
|
||||
global $wp_query;
|
||||
|
||||
if ( \is_object( $wp_query ) ) {
|
||||
return $wp_query;
|
||||
}
|
||||
|
||||
return new WP_Query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the redirects without a target by setting the needed hooks.
|
||||
*
|
||||
* @param string $redirect_type The type of the redirect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_redirect_without_target( $redirect_type ) {
|
||||
if ( $redirect_type === 410 ) {
|
||||
\add_action( 'wp', [ $this, 'do_410' ] );
|
||||
}
|
||||
|
||||
if ( $redirect_type === 451 ) {
|
||||
\add_action( 'wp', [ $this, 'do_451' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method for doing the actual redirect.
|
||||
*
|
||||
* @param string $location The path to redirect to.
|
||||
* @param int $status Status code to use.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect( $location, $status = 302 ) {
|
||||
if ( ! \function_exists( 'wp_redirect' ) ) {
|
||||
require_once \ABSPATH . 'wp-includes/pluggable.php';
|
||||
}
|
||||
|
||||
\wp_redirect( $location, $status, 'Yoast SEO Premium' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a target URL requires a trailing slash.
|
||||
*
|
||||
* @param string $target_url The target URL to check.
|
||||
*
|
||||
* @return bool True when trailing slash is required.
|
||||
*/
|
||||
protected function requires_trailing_slash( $target_url ) {
|
||||
return WPSEO_Redirect_Util::requires_trailing_slash( $target_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query template.
|
||||
*
|
||||
* @param string $filename Filename without extension.
|
||||
*
|
||||
* @return string Full path to template file.
|
||||
*/
|
||||
protected function get_query_template( $filename ) {
|
||||
return \get_query_template( $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually handles redirects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_redirects() {
|
||||
// Set the requested URL.
|
||||
$this->set_request_url();
|
||||
|
||||
// Check the normal redirects.
|
||||
$this->handle_normal_redirects( $this->request_url );
|
||||
|
||||
// Check the regex redirects.
|
||||
if ( $this->is_redirected() === false ) {
|
||||
$this->handle_regex_redirects();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Declares compatibility with the WooCommerce HPOS feature.
|
||||
*/
|
||||
class Woocommerce implements Initializer_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Hooks into WooCommerce.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
\add_action( 'before_woocommerce_init', [ $this, 'declare_custom_order_tables_compatibility' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares compatibility with the WooCommerce HPOS feature.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function declare_custom_order_tables_compatibility() {
|
||||
if ( \class_exists( FeaturesUtil::class ) ) {
|
||||
FeaturesUtil::declare_compatibility( 'custom_order_tables', \WPSEO_PREMIUM_FILE, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use WP_CLI;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Wp_Cli_Initializer class
|
||||
*/
|
||||
class Wp_Cli_Initializer implements Initializer_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
if ( \defined( 'WP_CLI' ) && \WP_CLI ) {
|
||||
\add_action( 'plugins_loaded', [ $this, 'wpseo_cli_init' ], 20 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the WP-CLI integration.
|
||||
*
|
||||
* The WP-CLI integration needs PHP 5.3 support, which should be automatically
|
||||
* enforced by the check for the WP_CLI constant. As WP-CLI itself only runs
|
||||
* on PHP 5.3+, the constant should only be set when requirements are met.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wpseo_cli_init() {
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect list',
|
||||
'WPSEO_CLI_Redirect_List_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect create',
|
||||
'WPSEO_CLI_Redirect_Create_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect update',
|
||||
'WPSEO_CLI_Redirect_Update_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect delete',
|
||||
'WPSEO_CLI_Redirect_Delete_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect has',
|
||||
'WPSEO_CLI_Redirect_Has_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
|
||||
WP_CLI::add_command(
|
||||
'yoast redirect follow',
|
||||
'WPSEO_CLI_Redirect_Follow_Command',
|
||||
[ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Abstract_OpenGraph_Integration.
|
||||
*/
|
||||
abstract class Abstract_OpenGraph_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The name or prefix for the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = '';
|
||||
|
||||
/**
|
||||
* The name or prefix for the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = '';
|
||||
|
||||
/**
|
||||
* The name or prefix for the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = '';
|
||||
|
||||
/**
|
||||
* The name or prefix for the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = '';
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Open_Graph_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social title from the options.
|
||||
*
|
||||
* @param string $title The default title.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_title( $title ) {
|
||||
$social_title = $this->options->get( $this::OPTION_TITLES_KEY_TITLE );
|
||||
|
||||
if ( ! empty( $social_title ) ) {
|
||||
$title = $social_title;
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social description from the options.
|
||||
*
|
||||
* @param string $description The default description.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_description( $description ) {
|
||||
$social_description = $this->options->get( $this::OPTION_TITLES_KEY_DESCRIPTION );
|
||||
|
||||
if ( ! empty( $social_description ) ) {
|
||||
$description = $social_description;
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social image ID from the options.
|
||||
*
|
||||
* @param int $id The default image ID.
|
||||
*
|
||||
* @return mixed|int The filtered value.
|
||||
*/
|
||||
public function filter_image_id( $id ) {
|
||||
$social_id = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE_ID );
|
||||
|
||||
if ( ! empty( $social_id ) ) {
|
||||
$id = $social_id;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social image URL from the options.
|
||||
*
|
||||
* @param string $url The default image URL.
|
||||
*
|
||||
* @return mixed|int The filtered value.
|
||||
*/
|
||||
public function filter_image( $url ) {
|
||||
$social_url = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE );
|
||||
|
||||
if ( ! empty( $social_url ) ) {
|
||||
$url = $social_url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social title for the subtype from the options.
|
||||
*
|
||||
* @param string $title The default title.
|
||||
* @param string $object_subtype The subtype of the current indexable.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_title_for_subtype( $title, $object_subtype ) {
|
||||
$social_title = $this->options->get( $this::OPTION_TITLES_KEY_TITLE . $object_subtype );
|
||||
|
||||
if ( ! empty( $social_title ) ) {
|
||||
$title = $social_title;
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social description for the subtype from the options.
|
||||
*
|
||||
* @param string $description The default description.
|
||||
* @param string $object_subtype The subtype of the current indexable.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_description_for_subtype( $description, $object_subtype ) {
|
||||
$social_description = $this->options->get( $this::OPTION_TITLES_KEY_DESCRIPTION . $object_subtype );
|
||||
|
||||
if ( ! empty( $social_description ) ) {
|
||||
$description = $social_description;
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social image ID for the subtype from the options.
|
||||
*
|
||||
* @param int $id The default image ID.
|
||||
* @param string $object_subtype The subtype of the current indexable.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_image_id_for_subtype( $id, $object_subtype ) {
|
||||
$social_id = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE_ID . $object_subtype );
|
||||
|
||||
if ( ! empty( $social_id ) ) {
|
||||
$id = $social_id;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the relevant social image URL for the subtype from the options.
|
||||
*
|
||||
* @param string $url The default image URL.
|
||||
* @param string $object_subtype The subtype of the current indexable.
|
||||
*
|
||||
* @return mixed|string The filtered value.
|
||||
*/
|
||||
public function filter_image_for_subtype( $url, $object_subtype ) {
|
||||
$social_url = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE . $object_subtype );
|
||||
|
||||
if ( ! empty( $social_url ) ) {
|
||||
$url = $social_url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\User_Profile_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository;
|
||||
|
||||
/**
|
||||
* Ai_Consent_Integration class.
|
||||
*/
|
||||
class Ai_Consent_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $asset_manager;
|
||||
|
||||
/**
|
||||
* Represents the add-on manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
private $addon_manager;
|
||||
|
||||
/**
|
||||
* Represents the options manager.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Represents the user helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
private $user_helper;
|
||||
|
||||
/**
|
||||
* Represents the wistia embed permission repository.
|
||||
*
|
||||
* @var Wistia_Embed_Permission_Repository
|
||||
*/
|
||||
private $wistia_embed_permission_repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ User_Profile_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param User_Helper $user_helper The user helper.
|
||||
* @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The wistia embed permission
|
||||
* repository.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Options_Helper $options_helper,
|
||||
User_Helper $user_helper,
|
||||
Wistia_Embed_Permission_Repository $wistia_embed_permission_repository
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->wistia_embed_permission_repository = $wistia_embed_permission_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Hide AI feature option in user profile if the user is not allowed to use it.
|
||||
if ( \current_user_can( 'edit_posts' ) ) {
|
||||
\add_action( 'wpseo_user_profile_additions', [ $this, 'render_user_profile' ], 12 );
|
||||
}
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$this->asset_manager->enqueue_style( 'premium-ai-generator' );
|
||||
|
||||
\wp_enqueue_script( 'wp-seo-premium-manage-ai-consent-button' );
|
||||
$user_id = $this->user_helper->get_current_user_id();
|
||||
\wp_localize_script(
|
||||
'wp-seo-premium-manage-ai-consent-button',
|
||||
'wpseoPremiumManageAiConsentButton',
|
||||
[
|
||||
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
|
||||
// Note: this is passing the Free plugin URL! As the image is located in there.
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_FILE ),
|
||||
'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( $user_id ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the AI consent button for the user profile.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_user_profile() {
|
||||
echo '<label for="ai-generator-consent-button">',
|
||||
\esc_html__( 'AI features', 'wordpress-seo-premium' ),
|
||||
'</label>',
|
||||
'<div id="ai-generator-consent" style="display:inline-block; margin-top: 28px; padding-left:5px;"></div>';
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Ai_Editor_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction;
|
||||
|
||||
/**
|
||||
* Ai_Generator_Integration class.
|
||||
*/
|
||||
class Ai_Generator_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Represents the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $asset_manager;
|
||||
|
||||
/**
|
||||
* Represents the add-on manager.
|
||||
*
|
||||
* @var WPSEO_Addon_Manager
|
||||
*/
|
||||
private $addon_manager;
|
||||
|
||||
/**
|
||||
* Represents the options manager.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Represents the user helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
private $user_helper;
|
||||
|
||||
/**
|
||||
* Represents the introductions seen repository.
|
||||
*
|
||||
* @var Introductions_Seen_Repository
|
||||
*/
|
||||
private $introductions_seen_repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Ai_Editor_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
|
||||
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param User_Helper $user_helper The user helper.
|
||||
* @param Introductions_Seen_Repository $introductions_seen_repository The introductions seen repository.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Options_Helper $options_helper,
|
||||
User_Helper $user_helper,
|
||||
Introductions_Seen_Repository $introductions_seen_repository
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->introductions_seen_repository = $introductions_seen_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! $this->options_helper->get( 'enable_ai_generator', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
// Enqueue after Elementor_Premium integration, which re-registers the assets.
|
||||
\add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subscription status for Yoast SEO Premium and Yoast WooCommerce SEO.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_product_subscriptions() {
|
||||
return [
|
||||
'premiumSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ),
|
||||
'wooCommerceSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$user_id = $this->user_helper->get_current_user_id();
|
||||
|
||||
\wp_enqueue_script( 'wp-seo-premium-ai-generator' );
|
||||
\wp_localize_script(
|
||||
'wp-seo-premium-ai-generator',
|
||||
'wpseoPremiumAiGenerator',
|
||||
[
|
||||
'adminUrl' => \admin_url( 'admin.php' ),
|
||||
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
|
||||
'productSubscriptions' => $this->get_product_subscriptions(),
|
||||
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, Ai_Generate_Titles_And_Descriptions_Introduction::ID ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
'postType' => \get_post_type(),
|
||||
]
|
||||
);
|
||||
$this->asset_manager->enqueue_style( 'premium-ai-generator' );
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_Query;
|
||||
use wpdb;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Cornerstone_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Presenters\Icons\Checkmark_Icon_Presenter;
|
||||
use Yoast\WP\SEO\Premium\Presenters\Icons\Cross_Icon_Presenter;
|
||||
|
||||
/**
|
||||
* Cornerstone_Column_Integration class.
|
||||
*/
|
||||
class Cornerstone_Column_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CORNERSTONE_COLUMN_NAME = 'wpseo-cornerstone';
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* The database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* The admin columns cache.
|
||||
*
|
||||
* @var Admin_Columns_Cache_Integration
|
||||
*/
|
||||
protected $admin_columns_cache;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Posts_Overview_Or_Ajax_Conditional::class,
|
||||
Cornerstone_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cornerstone_Column_Integration constructor
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
* @param wpdb $wpdb The wpdb object.
|
||||
* @param Admin_Columns_Cache_Integration $admin_columns_cache The admin columns cache.
|
||||
*/
|
||||
public function __construct(
|
||||
Post_Type_Helper $post_type_helper,
|
||||
wpdb $wpdb,
|
||||
Admin_Columns_Cache_Integration $admin_columns_cache
|
||||
) {
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->admin_columns_cache = $admin_columns_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'posts_clauses', [ $this, 'order_by_cornerstone' ], 1, 2 );
|
||||
\add_action( 'admin_init', [ $this, 'register_init_hooks' ] );
|
||||
|
||||
// Adds a filter to exclude the attachments from the cornerstone column.
|
||||
\add_filter( 'wpseo_cornerstone_column_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
|
||||
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks that need to be registered after `init` due to all post types not yet being registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_init_hooks() {
|
||||
$public_post_types = \apply_filters( 'wpseo_cornerstone_column_post_types', $this->post_type_helper->get_accessible_post_types() );
|
||||
|
||||
if ( ! \is_array( $public_post_types ) || empty( $public_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $public_post_types as $post_type ) {
|
||||
\add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'add_cornerstone_column' ] );
|
||||
\add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
|
||||
\add_filter( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets needed for the integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the columns for the post overview.
|
||||
*
|
||||
* @param array $columns Array with columns.
|
||||
*
|
||||
* @return array The extended array with columns.
|
||||
*/
|
||||
public function add_cornerstone_column( $columns ) {
|
||||
if ( ! \is_array( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns[ self::CORNERSTONE_COLUMN_NAME ] = \sprintf(
|
||||
'<span class="yoast-column-cornerstone yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
|
||||
\esc_attr__( 'Is this cornerstone content?', 'wordpress-seo-premium' ),
|
||||
/* translators: Hidden accessibility text. */
|
||||
\esc_html__( 'Cornerstone content', 'wordpress-seo-premium' )
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query pieces to allow ordering column by cornerstone.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_cornerstone( $pieces, $query ) {
|
||||
if ( $query->get( 'orderby' ) !== self::CORNERSTONE_COLUMN_NAME ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
return $this->build_sort_query_pieces( $pieces, $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the pieces for a sorting query.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array Modified Query pieces.
|
||||
*/
|
||||
protected function build_sort_query_pieces( $pieces, $query ) {
|
||||
// We only want our code to run in the main WP query.
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
// Get the order query variable - ASC or DESC.
|
||||
$order = \strtoupper( $query->get( 'order' ) );
|
||||
|
||||
// Make sure the order setting qualifies. If not, set default as ASC.
|
||||
if ( ! \in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
|
||||
$order = 'ASC';
|
||||
}
|
||||
|
||||
$table = Model::get_table_name( 'Indexable' );
|
||||
|
||||
$pieces['join'] .= " LEFT JOIN $table AS yoast_indexable ON yoast_indexable.object_id = {$this->wpdb->posts}.ID AND yoast_indexable.object_type = 'post' ";
|
||||
$pieces['orderby'] = "yoast_indexable.is_cornerstone $order, FIELD( {$this->wpdb->posts}.post_status, 'publish' ) $order, {$pieces['orderby']}";
|
||||
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $column_name Column to display the content for.
|
||||
* @param int $post_id Post to display the column content for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function column_content( $column_name, $post_id ) {
|
||||
$indexable = $this->admin_columns_cache->get_indexable( $post_id );
|
||||
// Nothing to output if we don't have the value.
|
||||
if ( empty( $indexable ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
if ( $column_name === self::CORNERSTONE_COLUMN_NAME ) {
|
||||
if ( $indexable->is_cornerstone === true ) {
|
||||
echo new Checkmark_Icon_Presenter( 20 );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo new Cross_Icon_Presenter( 20 );
|
||||
}
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sortable columns.
|
||||
*
|
||||
* @param array $columns Array with sortable columns.
|
||||
*
|
||||
* @return array The extended array with sortable columns.
|
||||
*/
|
||||
public function column_sort( $columns ) {
|
||||
$columns[ self::CORNERSTONE_COLUMN_NAME ] = self::CORNERSTONE_COLUMN_NAME;
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Taxonomy_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Cornerstone_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Term_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Premium\Presenters\Icons\Checkmark_Icon_Presenter;
|
||||
use Yoast\WP\SEO\Premium\Presenters\Icons\Cross_Icon_Presenter;
|
||||
|
||||
/**
|
||||
* Cornerstone_Taxonomy_Column_Integration class.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Cornerstone_Taxonomy_Column_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CORNERSTONE_COLUMN_NAME = 'wpseo-cornerstone';
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper instance.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Returns the posted/get taxonomy value if it is set.
|
||||
*
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct( Current_Page_Helper $current_page_helper ) {
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Term_Overview_Or_Ajax_Conditional::class,
|
||||
Cornerstone_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'register_init_hooks' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks that need to be registered after `init` due to all post types not yet being registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_init_hooks() {
|
||||
$taxonomy = $this->current_page_helper->get_current_taxonomy();
|
||||
$is_product = $this->current_page_helper->get_current_post_type() === 'product';
|
||||
$is_product_cat = $taxonomy === 'product_cat';
|
||||
$is_product_tag = $taxonomy === 'product_tag';
|
||||
|
||||
if ( ( $is_product && ( $is_product_cat || $is_product_tag ) ) || ( ! $is_product && $taxonomy ) ) {
|
||||
\add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, 'add_cornerstone_column' ] );
|
||||
\add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, 'column_content' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets needed for the integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the cornerstone column for the term overview.
|
||||
*
|
||||
* @param array $columns Array with columns.
|
||||
*
|
||||
* @return array The extended array with columns.
|
||||
*/
|
||||
public function add_cornerstone_column( $columns ) {
|
||||
if ( ! \is_array( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns[ self::CORNERSTONE_COLUMN_NAME ] = \sprintf(
|
||||
'<span class="yoast-tooltip yoast-tooltip-n yoast-tooltip-alt" data-label="%1$s">
|
||||
<span class="yoast-column-cornerstone yoast-column-header-has-tooltip">
|
||||
<span class="screen-reader-text">%2$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
\esc_attr__( 'Is this cornerstone content?', 'wordpress-seo-premium' ),
|
||||
/* translators: Hidden accessibility text. */
|
||||
\esc_html__( 'Cornerstone content', 'wordpress-seo-premium' )
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $content The current content of the column.
|
||||
* @param string $column_name The name of the column.
|
||||
* @param int $term_id ID of requested taxonomy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_content( $content, $column_name, $term_id ) {
|
||||
$is_cornerstone = (int) WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $this->current_page_helper->get_current_taxonomy(), 'is_cornerstone' );
|
||||
|
||||
if ( $column_name === self::CORNERSTONE_COLUMN_NAME ) {
|
||||
if ( $is_cornerstone ) {
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
echo new Checkmark_Icon_Presenter( 20 );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo new Cross_Icon_Presenter( 20 );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_Query;
|
||||
use wpdb;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Score_Icon_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Inclusive_Language_Enabled_Conditional;
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Column_Integration class.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Inclusive_Language_Column_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const INCLUSIVE_LANGUAGE_COLUMN_NAME = 'wpseo-inclusive-language';
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
protected $post_type_helper;
|
||||
|
||||
/**
|
||||
* The score icon helper.
|
||||
*
|
||||
* @var Score_Icon_Helper
|
||||
*/
|
||||
protected $score_icon_helper;
|
||||
|
||||
/**
|
||||
* The database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* The admin columns cache.
|
||||
*
|
||||
* @var Admin_Columns_Cache_Integration
|
||||
*/
|
||||
protected $admin_columns_cache;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Posts_Overview_Or_Ajax_Conditional::class,
|
||||
Inclusive_Language_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Column_Integration constructor
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
* @param Score_Icon_Helper $score_icon_helper The score icon helper.
|
||||
* @param wpdb $wpdb The wpdb object.
|
||||
* @param Admin_Columns_Cache_Integration $admin_columns_cache The admin columns cache.
|
||||
*/
|
||||
public function __construct(
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Score_Icon_Helper $score_icon_helper,
|
||||
wpdb $wpdb,
|
||||
Admin_Columns_Cache_Integration $admin_columns_cache
|
||||
) {
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->score_icon_helper = $score_icon_helper;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->admin_columns_cache = $admin_columns_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'posts_clauses', [ $this, 'order_by_inclusive_language_score' ], 1, 2 );
|
||||
\add_action( 'admin_init', [ $this, 'register_init_hooks' ] );
|
||||
|
||||
// Adds a filter to exclude the attachments from the inclusive language column.
|
||||
\add_filter( 'wpseo_inclusive_language_column_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
|
||||
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks that need to be registered after `init` due to all post types not yet being registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_init_hooks() {
|
||||
$public_post_types = \apply_filters( 'wpseo_inclusive_language_column_post_types', $this->post_type_helper->get_accessible_post_types() );
|
||||
|
||||
if ( ! \is_array( $public_post_types ) || empty( $public_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $public_post_types as $post_type ) {
|
||||
\add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'add_inclusive_language_column' ] );
|
||||
\add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
|
||||
\add_filter( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets needed for the integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inclusive language column for the post overview.
|
||||
*
|
||||
* @param array $columns Array with columns.
|
||||
*
|
||||
* @return array The extended array with columns.
|
||||
*/
|
||||
public function add_inclusive_language_column( $columns ) {
|
||||
if ( ! \is_array( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns[ self::INCLUSIVE_LANGUAGE_COLUMN_NAME ] = \sprintf(
|
||||
'<span class="yoast-column-inclusive-language yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
|
||||
\esc_attr__( 'Inclusive language score', 'wordpress-seo-premium' ),
|
||||
\esc_html__( 'Inclusive language score', 'wordpress-seo-premium' )
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query pieces to allow ordering column by inclusive language score.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_inclusive_language_score( $pieces, $query ) {
|
||||
if ( $query->get( 'orderby' ) !== self::INCLUSIVE_LANGUAGE_COLUMN_NAME ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
return $this->build_sort_query_pieces( $pieces, $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the pieces for a sorting query.
|
||||
*
|
||||
* @param array $pieces Array of Query pieces.
|
||||
* @param WP_Query $query The Query on which to apply.
|
||||
*
|
||||
* @return array Modified Query pieces.
|
||||
*/
|
||||
protected function build_sort_query_pieces( $pieces, $query ) {
|
||||
// We only want our code to run in the main WP query.
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
// Get the order query variable - ASC or DESC.
|
||||
$order = \strtoupper( $query->get( 'order' ) );
|
||||
|
||||
// Make sure the order setting qualifies. If not, set default as ASC.
|
||||
if ( ! \in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
|
||||
$order = 'ASC';
|
||||
}
|
||||
|
||||
$table = Model::get_table_name( 'Indexable' );
|
||||
|
||||
$pieces['join'] .= " LEFT JOIN $table AS yoast_indexable ON yoast_indexable.object_id = {$this->wpdb->posts}.ID AND yoast_indexable.object_type = 'post' ";
|
||||
$pieces['orderby'] = "yoast_indexable.inclusive_language_score $order, {$pieces['orderby']}";
|
||||
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $column_name Column to display the content for.
|
||||
* @param int $post_id Post to display the column content for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function column_content( $column_name, $post_id ) {
|
||||
$indexable = $this->admin_columns_cache->get_indexable( $post_id );
|
||||
// Nothing to output if we don't have the value.
|
||||
if ( empty( $indexable ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $column_name === self::INCLUSIVE_LANGUAGE_COLUMN_NAME ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped through the Score_Icon_Helper.
|
||||
echo $this->score_icon_helper->for_inclusive_language( $indexable->inclusive_language_score );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sortable columns.
|
||||
*
|
||||
* @param array $columns Array with sortable columns.
|
||||
*
|
||||
* @return array The extended array with sortable columns.
|
||||
*/
|
||||
public function column_sort( $columns ) {
|
||||
$columns[ self::INCLUSIVE_LANGUAGE_COLUMN_NAME ] = self::INCLUSIVE_LANGUAGE_COLUMN_NAME;
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Meta;
|
||||
use WPSEO_Rank;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Inclusive_Language_Enabled_Conditional;
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Filter_Integration class.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Inclusive_Language_Filter_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Posts_Overview_Or_Ajax_Conditional::class,
|
||||
Inclusive_Language_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Creates the inclusive language score filter dropdown -- with priority 20 to fire after the SEO/readability filter.
|
||||
\add_action( 'restrict_manage_posts', [ $this, 'posts_filter_dropdown_inclusive_language' ], 20 );
|
||||
// Adds the inclusive language score filter to the list of active filters -- if selected for filtering.
|
||||
\add_filter( 'wpseo_change_applicable_filters', [ $this, 'add_inclusive_language_filter' ] );
|
||||
// Adds the inclusive language score meta column to the order by part of the query -- if selected for ordering.
|
||||
\add_filter( 'wpseo_change_order_by', [ $this, 'add_inclusive_language_order_by' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dropdown that allows filtering on inclusive language score.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function posts_filter_dropdown_inclusive_language() {
|
||||
$ranks = WPSEO_Rank::get_all_inclusive_language_ranks();
|
||||
|
||||
echo '<label class="screen-reader-text" for="wpseo-inclusive-language-filter">'
|
||||
/* translators: Hidden accessibility text. */
|
||||
. \esc_html__( 'Filter by Inclusive Language Score', 'wordpress-seo-premium' )
|
||||
. '</label>';
|
||||
echo '<select name="inclusive_language_filter" id="wpseo-inclusive-language-filter">';
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( '', \__( 'All Inclusive Language Scores', 'wordpress-seo-premium' ) );
|
||||
|
||||
foreach ( $ranks as $rank ) {
|
||||
$selected = \selected( $this->get_current_inclusive_language_filter(), $rank->get_rank(), false );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_inclusive_language_labels(), $selected );
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an <option> element.
|
||||
*
|
||||
* @param string $value The option's value.
|
||||
* @param string $label The option's label.
|
||||
* @param string $selected HTML selected attribute for an option.
|
||||
*
|
||||
* @return string The generated <option> element.
|
||||
*/
|
||||
protected function generate_option( $value, $label, $selected = '' ) {
|
||||
return '<option ' . $selected . ' value="' . \esc_attr( $value ) . '">' . \esc_html( $label ) . '</option>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current inclusive language score filter value from the $_GET variable.
|
||||
*
|
||||
* @return string|null The sanitized inclusive language score filter value or null when the variable is not set in $_GET.
|
||||
*/
|
||||
public function get_current_inclusive_language_filter() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['inclusive_language_filter'] ) && \is_string( $_GET['inclusive_language_filter'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return \sanitize_text_field( \wp_unslash( $_GET['inclusive_language_filter'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the inclusive language score filter to the meta query, based on the passed inclusive language filter.
|
||||
*
|
||||
* @param string $inclusive_language_filter The inclusive language filter to use to determine what further filter to apply.
|
||||
*
|
||||
* @return array The inclusive language score filter.
|
||||
*/
|
||||
public function determine_inclusive_language_filters( $inclusive_language_filter ) {
|
||||
$rank = new WPSEO_Rank( $inclusive_language_filter );
|
||||
|
||||
return $this->create_inclusive_language_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an inclusive language score filter.
|
||||
*
|
||||
* @param number $low The lower boundary of the score.
|
||||
* @param number $high The higher boundary of the score.
|
||||
*
|
||||
* @return array The inclusive language score filter.
|
||||
*/
|
||||
protected function create_inclusive_language_score_filter( $low, $high ) {
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'inclusive_language_score',
|
||||
'value' => [ $low, $high ],
|
||||
'type' => 'numeric',
|
||||
'compare' => 'BETWEEN',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inclusive language filter to the list of active filters -- if it has been used for filtering.
|
||||
*
|
||||
* @param array $active_filters The currently active filters.
|
||||
* @return array The active filters, including the inclusive language filter -- if it has been used for filtering.
|
||||
*/
|
||||
public function add_inclusive_language_filter( $active_filters ) {
|
||||
$inclusive_language_filter = $this->get_current_inclusive_language_filter();
|
||||
|
||||
if ( \is_string( $inclusive_language_filter ) && $inclusive_language_filter !== '' ) {
|
||||
$active_filters = \array_merge(
|
||||
$active_filters,
|
||||
$this->determine_inclusive_language_filters( $inclusive_language_filter )
|
||||
);
|
||||
}
|
||||
|
||||
return $active_filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inclusive language score field to the order by part of the query -- if it has been selected during filtering.
|
||||
*
|
||||
* @param array $order_by The current order by statement.
|
||||
* @param string $order_by_column The column to use for ordering.
|
||||
* @return array The order by.
|
||||
*/
|
||||
public function add_inclusive_language_order_by( $order_by, $order_by_column = '' ) {
|
||||
if ( $order_by === [] && $order_by_column === Inclusive_Language_Column_Integration::INCLUSIVE_LANGUAGE_COLUMN_NAME ) {
|
||||
return [
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'inclusive_language_score',
|
||||
'orderby' => 'meta_value_num',
|
||||
];
|
||||
}
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Taxonomy_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Score_Icon_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Inclusive_Language_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Term_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Column_Integration class.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Inclusive_Language_Taxonomy_Column_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const INCLUSIVE_LANGUAGE_COLUMN_NAME = 'wpseo-inclusive-language';
|
||||
|
||||
/**
|
||||
* The score icon helper.
|
||||
*
|
||||
* @var Score_Icon_Helper
|
||||
*/
|
||||
protected $score_icon_helper;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper instance.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Term_Overview_Or_Ajax_Conditional::class,
|
||||
Inclusive_Language_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive_Language_Column_Integration constructor
|
||||
*
|
||||
* @param Score_Icon_Helper $score_icon_helper The score icon helper.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Score_Icon_Helper $score_icon_helper,
|
||||
Current_Page_Helper $current_page_helper
|
||||
) {
|
||||
$this->score_icon_helper = $score_icon_helper;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'register_init_hooks' ] );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks that need to be registered after `init` due to all post types not yet being registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_init_hooks() {
|
||||
$taxonomy = $this->current_page_helper->get_current_taxonomy();
|
||||
$is_product = $this->current_page_helper->get_current_post_type() === 'product';
|
||||
$is_product_cat = $taxonomy === 'product_cat';
|
||||
$is_product_tag = $taxonomy === 'product_tag';
|
||||
|
||||
if ( ( $is_product && ( $is_product_cat || $is_product_tag ) ) || ( ! $is_product && $taxonomy ) ) {
|
||||
\add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, 'add_inclusive_language_column' ] );
|
||||
\add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, 'column_content' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets needed for the integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inclusive language column for the term overview.
|
||||
*
|
||||
* @param array $columns Array with columns.
|
||||
*
|
||||
* @return array The extended array with columns.
|
||||
*/
|
||||
public function add_inclusive_language_column( $columns ) {
|
||||
if ( ! \is_array( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns[ self::INCLUSIVE_LANGUAGE_COLUMN_NAME ] = \sprintf(
|
||||
'<span class="yoast-tooltip yoast-tooltip-n yoast-tooltip-alt" data-label="%1$s">
|
||||
<span class="yoast-column-inclusive-language yoast-column-header-has-tooltip">
|
||||
<span class="screen-reader-text">%2$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
\esc_attr__( 'Inclusive language score', 'wordpress-seo-premium' ),
|
||||
\esc_html__( 'Inclusive language score', 'wordpress-seo-premium' )
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $content The current content of the column.
|
||||
* @param string $column_name The name of the column.
|
||||
* @param int $term_id ID of requested taxonomy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_content( $content, $column_name, $term_id ) {
|
||||
$score = (int) WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $this->current_page_helper->get_current_taxonomy(), 'inclusive_language_score' );
|
||||
|
||||
if ( $column_name === self::INCLUSIVE_LANGUAGE_COLUMN_NAME ) {
|
||||
return $this->score_icon_helper->for_inclusive_language( $score );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Keyword_Integration class
|
||||
*/
|
||||
class Keyword_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_posts_for_focus_keyword', [ $this, 'add_posts_for_focus_keyword' ], 10, 3 );
|
||||
\add_filter( 'wpseo_posts_for_related_keywords', [ $this, 'add_posts_for_related_keywords' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the array of posts that share their focus keywords with the post's related keywords by adding posts' ids with the same related keywords.
|
||||
*
|
||||
* @param array $usage The array of posts' ids that share their focus keywords with the post.
|
||||
* @param int $post_id The id of the post we're finding the usage of related keywords for.
|
||||
*
|
||||
* @return array The filtered array of posts' ids.
|
||||
*/
|
||||
public function add_posts_for_related_keywords( $usage, $post_id ) {
|
||||
$additional_keywords = \json_decode( WPSEO_Meta::get_value( 'focuskeywords', $post_id ), true );
|
||||
|
||||
if ( empty( $additional_keywords ) ) {
|
||||
return $usage;
|
||||
}
|
||||
|
||||
foreach ( $additional_keywords as $additional_keyword ) {
|
||||
if ( isset( $additional_keyword['keyword'] ) ) {
|
||||
$keyword = $additional_keyword['keyword'];
|
||||
|
||||
$usage[ $keyword ] = WPSEO_Meta::keyword_usage( $keyword, $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
return $usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the array of posts that share their focus keywords with the post's focus keywords by adding posts' ids with the same related keywords.
|
||||
*
|
||||
* @param array $post_ids The array of posts' ids that share their related keywords with the post.
|
||||
* @param string $keyword The keyword to search for.
|
||||
* @param int $post_id The id of the post the keyword is associated to.
|
||||
*
|
||||
* @return array The filtered array of posts' ids.
|
||||
*/
|
||||
public function add_posts_for_focus_keyword( $post_ids, $keyword, $post_id ) {
|
||||
$query = [
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_yoast_wpseo_focuskeywords',
|
||||
'value' => \sprintf( '"keyword":"%s"', $keyword ),
|
||||
'compare' => 'LIKE',
|
||||
],
|
||||
],
|
||||
'post__not_in' => [ $post_id ],
|
||||
'fields' => 'ids',
|
||||
'post_type' => 'any',
|
||||
|
||||
/*
|
||||
* We only need to return zero, one or two results:
|
||||
* - Zero: keyword hasn't been used before
|
||||
* - One: Keyword has been used once before
|
||||
* - Two or more: Keyword has been used twice or more before
|
||||
*/
|
||||
'posts_per_page' => 2,
|
||||
];
|
||||
$get_posts = new WP_Query( $query );
|
||||
return \array_merge( $post_ids, $get_posts->posts );
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Options;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Metabox_Formatter_Integration class
|
||||
*/
|
||||
class Metabox_Formatter_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_social_template_post_type', [ $this, 'get_template_for_post_type' ], 10, 3 );
|
||||
\add_filter( 'wpseo_social_template_taxonomy', [ $this, 'get_template_for_taxonomy' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a template for a post type.
|
||||
*
|
||||
* @param string $template The default template.
|
||||
* @param string $template_option_name The subname of the option in which the template you want to get is saved.
|
||||
* @param string $post_type The name of the post type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_for_post_type( $template, $template_option_name, $post_type ) {
|
||||
$needed_option = \sprintf( 'social-%s-%s', $template_option_name, $post_type );
|
||||
|
||||
if ( WPSEO_Options::get( $needed_option, '' ) !== '' ) {
|
||||
return WPSEO_Options::get( $needed_option );
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a template for a taxonomy.
|
||||
*
|
||||
* @param string $template The default template.
|
||||
* @param string $template_option_name The subname of the option in which the template you want to get is saved.
|
||||
* @param string $taxonomy The name of the taxonomy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_for_taxonomy( $template, $template_option_name, $taxonomy ) {
|
||||
$needed_option = \sprintf( 'social-%s-tax-%s', $template_option_name, $taxonomy );
|
||||
return WPSEO_Options::get( $needed_option, $template );
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Plugin_Links_Integration class
|
||||
*/
|
||||
class Plugin_Links_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'plugin_action_links_' . \WPSEO_BASENAME, [ $this, 'remove_yoast_seo_action_link' ], 10 );
|
||||
\add_filter( 'network_admin_plugin_action_links_' . \WPSEO_BASENAME, [ $this, 'remove_yoast_seo_action_link' ], 10 );
|
||||
|
||||
\add_filter( 'plugin_action_links_' . \WPSEO_PREMIUM_BASENAME, [ $this, 'add_yoast_seo_premium_action_link' ], 10 );
|
||||
\add_filter( 'network_admin_plugin_action_links_' . \WPSEO_PREMIUM_BASENAME, [ $this, 'add_yoast_seo_premium_action_link' ], 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the upgrade link from Yoast SEO free.
|
||||
*
|
||||
* @param string[] $links The action links.
|
||||
*
|
||||
* @return string[] The action link with the upgrade link removed.
|
||||
*/
|
||||
public function remove_yoast_seo_action_link( $links ) {
|
||||
$link_to_remove = $this->get_upgrade_link();
|
||||
return \array_filter(
|
||||
$links,
|
||||
static function ( $link ) use ( $link_to_remove ) {
|
||||
return $link !== $link_to_remove;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the upgrade link to the premium actions.
|
||||
*
|
||||
* @param string[] $links The action links.
|
||||
*
|
||||
* @return string[] The action link with the upgrade link added.
|
||||
*/
|
||||
public function add_yoast_seo_premium_action_link( $links ) {
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
|
||||
if ( ! $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
|
||||
\array_unshift( $links, $this->get_upgrade_link() );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upgrade link.
|
||||
*
|
||||
* @return string The upgrade link.
|
||||
*/
|
||||
protected function get_upgrade_link() {
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
return '<a style="font-weight: bold;" href="' . \esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/activate-my-yoast' ) ) . '" target="_blank">' . \__( 'Activate your subscription', 'wordpress-seo' ) . '</a>';
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use WPSEO_Language_Utils;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Language_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Url_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Routes\Prominent_Words_Route;
|
||||
|
||||
/**
|
||||
* Class Indexing_Integration.
|
||||
*/
|
||||
class Indexing_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Number of prominent words to index per indexable
|
||||
* when a language has function word support.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const PER_INDEXABLE_LIMIT = 20;
|
||||
|
||||
/**
|
||||
* Number of prominent words to index per indexable
|
||||
* when a language does not have function word support.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT = 30;
|
||||
|
||||
/**
|
||||
* All indexing actions.
|
||||
*
|
||||
* @var Indexation_Action_Interface[]
|
||||
*/
|
||||
protected $indexing_actions;
|
||||
|
||||
/**
|
||||
* Represents the language helper.
|
||||
*
|
||||
* @var Language_Helper
|
||||
*/
|
||||
protected $language_helper;
|
||||
|
||||
/**
|
||||
* Represents the url helper.
|
||||
*
|
||||
* @var Url_Helper
|
||||
*/
|
||||
protected $url_helper;
|
||||
|
||||
/**
|
||||
* Represents the prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Holds the total number of unindexed objects.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $total_unindexed;
|
||||
|
||||
/**
|
||||
* WPSEO_Premium_Prominent_Words_Recalculation constructor.
|
||||
*
|
||||
* @param Content_Action $content_indexation_action The content indexing action.
|
||||
* @param Indexable_Post_Indexation_Action $post_indexation_action The post indexing action.
|
||||
* @param Indexable_Term_Indexation_Action $term_indexation_action The term indexing action.
|
||||
* @param Indexable_General_Indexation_Action $general_indexation_action The general indexing action.
|
||||
* @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action The post type archive indexing action.
|
||||
* @param Language_Helper $language_helper The language helper.
|
||||
* @param Url_Helper $url_helper The url helper.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Action $content_indexation_action,
|
||||
Indexable_Post_Indexation_Action $post_indexation_action,
|
||||
Indexable_Term_Indexation_Action $term_indexation_action,
|
||||
Indexable_General_Indexation_Action $general_indexation_action,
|
||||
Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action,
|
||||
Language_Helper $language_helper,
|
||||
Url_Helper $url_helper,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
$this->language_helper = $language_helper;
|
||||
$this->url_helper = $url_helper;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
|
||||
// Indexation actions are used to calculate the number of unindexed objects.
|
||||
$this->indexing_actions = [
|
||||
// Get the number of indexables that haven't had their prominent words indexed yet.
|
||||
$content_indexation_action,
|
||||
|
||||
// Take posts and terms into account that do not have indexables yet.
|
||||
// These need to be counted again here (in addition to being counted in Free) because them being unindexed
|
||||
// means that the above prominent words unindexed count couldn't detect these posts/terms for prominent words indexing.
|
||||
$post_indexation_action,
|
||||
$term_indexation_action,
|
||||
$general_indexation_action,
|
||||
$post_type_archive_indexation_action,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
|
||||
|
||||
\add_filter( 'wpseo_indexing_data', [ $this, 'adapt_indexing_data' ] );
|
||||
\add_filter( 'wpseo_indexing_get_unindexed_count', [ $this, 'get_unindexed_count' ] );
|
||||
\add_filter( 'wpseo_indexing_get_limited_unindexed_count', [ $this, 'get_limited_unindexed_count' ], 10, 2 );
|
||||
\add_filter( 'wpseo_indexing_endpoints', [ $this, 'add_endpoints' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Migrations_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the endpoints to call.
|
||||
*
|
||||
* @param array $endpoints The endpoints to extend.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
public function add_endpoints( $endpoints ) {
|
||||
$endpoints['get_content'] = Prominent_Words_Route::FULL_GET_CONTENT_ROUTE;
|
||||
$endpoints['complete_words'] = Prominent_Words_Route::FULL_COMPLETE_ROUTE;
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the indexing data as sent to the JavaScript side of the
|
||||
* indexing process.
|
||||
*
|
||||
* Adds the appropriate prominent words endpoints and other settings.
|
||||
*
|
||||
* @param array $data The data to be adapted.
|
||||
*
|
||||
* @return array The adapted indexing data.
|
||||
*/
|
||||
public function adapt_indexing_data( $data ) {
|
||||
$site_locale = \get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
$data['locale'] = $site_locale;
|
||||
$data['language'] = $language;
|
||||
|
||||
$data['morphologySupported'] = $this->language_helper->is_word_form_recognition_active( $language );
|
||||
|
||||
$per_indexable_limit = self::PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT;
|
||||
if ( $this->language_helper->has_function_word_support( $language ) ) {
|
||||
$per_indexable_limit = self::PER_INDEXABLE_LIMIT;
|
||||
}
|
||||
|
||||
$data['prominentWords'] = [
|
||||
'endpoint' => Prominent_Words_Route::FULL_SAVE_ROUTE,
|
||||
'perIndexableLimit' => $per_indexable_limit,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
if ( ! isset( $_GET['page'] )
|
||||
|| ( $_GET['page'] !== 'wpseo_tools' && $_GET['page'] !== 'wpseo_workouts' && $_GET['page'] !== 'wpseo_dashboard' )
|
||||
|| ( $_GET['page'] === 'wpseo_tools' && isset( $_GET['tool'] ) )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_completed = ( (int) $this->get_unindexed_count( 0 ) === 0 );
|
||||
$this->prominent_words_helper->set_indexing_completed( $is_completed );
|
||||
|
||||
\wp_enqueue_script( 'yoast-premium-prominent-words-indexation' );
|
||||
\wp_localize_script( 'yoast-premium-prominent-words-indexation', 'wpseoPremiumIndexationData', [ 'licensedURL' => $this->url_helper->network_safe_home_url() ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of unindexed objects.
|
||||
*
|
||||
* @param int $unindexed_count The unindexed count.
|
||||
*
|
||||
* @return int The total number of indexables to recalculate.
|
||||
*/
|
||||
public function get_unindexed_count( $unindexed_count ) {
|
||||
foreach ( $this->indexing_actions as $indexing_action ) {
|
||||
$unindexed_count += $indexing_action->get_total_unindexed();
|
||||
}
|
||||
return $unindexed_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a limited number of unindexed objects.
|
||||
*
|
||||
* @param int $unindexed_count The unindexed count.
|
||||
* @param int $limit Limit the number of unindexed objects that are counted.
|
||||
*
|
||||
* @return int The total number of unindexed objects.
|
||||
*/
|
||||
public function get_limited_unindexed_count( $unindexed_count, $limit ) {
|
||||
foreach ( $this->indexing_actions as $indexing_action ) {
|
||||
$unindexed_count += $indexing_action->get_limited_unindexed_count( $limit - $unindexed_count + 1 );
|
||||
if ( $unindexed_count > $limit ) {
|
||||
return $unindexed_count;
|
||||
}
|
||||
}
|
||||
|
||||
return $unindexed_count;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action;
|
||||
|
||||
/**
|
||||
* Adds a hidden field to the metabox for storing the calculated words and also
|
||||
* handles the value of it after posting.
|
||||
*/
|
||||
class Metabox_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Represents the prominent words save action.
|
||||
*
|
||||
* @var Save_Action
|
||||
*/
|
||||
protected $save_action;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Metabox constructor.
|
||||
*
|
||||
* @param Save_Action $save_action The prominent words save action.
|
||||
*/
|
||||
public function __construct( Save_Action $save_action ) {
|
||||
$this->save_action = $save_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the register_hooks function of the Integration interface.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_words_for_linking_hidden_field' ] );
|
||||
\add_filter( 'update_post_metadata', [ $this, 'save_prominent_words_for_post' ], 10, 4 );
|
||||
|
||||
\add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_words_for_linking_hidden_field' ] );
|
||||
\add_action( 'edit_term', [ $this, 'save_prominent_words_for_term' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hidden field for the prominent words to the metabox.
|
||||
*
|
||||
* @param array $field_defs The definitions for the input fields.
|
||||
*
|
||||
* @return array The definitions for the input fields.
|
||||
*/
|
||||
public function add_words_for_linking_hidden_field( $field_defs ) {
|
||||
if ( \is_array( $field_defs ) ) {
|
||||
$field_defs['words_for_linking'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'words_for_linking',
|
||||
'label' => '',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value of the _yoast_wpseo_words_for_linking hidden field to the prominent_words table, not postmeta.
|
||||
* Added to the 'update_post_metadata' filter.
|
||||
*
|
||||
* @param false|null $check Whether to allow updating metadata for the given type.
|
||||
* @param int $object_id The post id.
|
||||
* @param string $meta_key The key of the metadata.
|
||||
* @param mixed $meta_value The value of the metadata.
|
||||
*
|
||||
* @return false|null Non-null value if meta data should not be updated.
|
||||
* Null if the metadata should be updated as normal.
|
||||
*/
|
||||
public function save_prominent_words_for_post( $check, $object_id, $meta_key, $meta_value ) {
|
||||
if ( $meta_key !== '_yoast_wpseo_words_for_linking' ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
// If the save was triggered with an empty meta value, don't update the prominent words.
|
||||
if ( empty( $meta_value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Decode from stringified JSON.
|
||||
$words_for_linking = \json_decode( $meta_value, true );
|
||||
// 2. Save prominent words using the existing functionality.
|
||||
$this->save_action->link( 'post', $object_id, $words_for_linking );
|
||||
|
||||
// 3. Return non-null value so we don't save prominent words to the `post_meta` table.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the prominent words for a term.
|
||||
*
|
||||
* @param int $term_id The term id to save the words for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_prominent_words_for_term( $term_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- The nonce is already validated.
|
||||
if ( ! isset( $_POST['wpseo_words_for_linking'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$words_for_linking = [];
|
||||
if ( ! empty( $_POST['wpseo_words_for_linking'] ) ) {
|
||||
$prominent_words = \sanitize_text_field( \wp_unslash( $_POST['wpseo_words_for_linking'] ) );
|
||||
// phpcs:enable
|
||||
$words_for_linking = \json_decode( $prominent_words, true );
|
||||
}
|
||||
|
||||
$this->save_action->link( 'term', $term_id, $words_for_linking );
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Take related keyphrases into account when filtering posts on keyphrase.
|
||||
*
|
||||
* phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Related_Keyphrase_Filter_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The conditionals that should be met for this integration to be active.
|
||||
*
|
||||
* @return string[] A list of fully qualified class names of the `Conditional`s that should be met.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the WordPress hooks needed for this integration to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_change_keyphrase_filter_in_request', [ $this, 'add_related_keyphrase_filter' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the keyphrase filter to also take related keyphrases into account.
|
||||
*
|
||||
* @param array $keyphrase_filter The current keyphrase filter.
|
||||
* @param string $keyphrase The keyphrase.
|
||||
*
|
||||
* @return array The new keyphrase filter,
|
||||
*/
|
||||
public function add_related_keyphrase_filter( $keyphrase_filter, $keyphrase ) {
|
||||
return [
|
||||
'relation' => 'OR',
|
||||
$keyphrase_filter,
|
||||
$this->get_related_keyphrase_filter( $keyphrase ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter to use within the WP Meta Query to filter
|
||||
* on related keyphrase.
|
||||
*
|
||||
* @param string $focus_keyphrase The focus keyphrase to filter on.
|
||||
*
|
||||
* @return array The filter.
|
||||
*/
|
||||
private function get_related_keyphrase_filter( $focus_keyphrase ) {
|
||||
return [
|
||||
'post_type' => \get_query_var( 'post_type', 'post' ),
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'focuskeywords',
|
||||
'value' => '"keyword":"' . \sanitize_text_field( $focus_keyphrase ) . '"',
|
||||
'compare' => 'LIKE',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Metabox;
|
||||
use WPSEO_Options;
|
||||
use WPSEO_Post_Type;
|
||||
use WPSEO_Taxonomy;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
|
||||
/**
|
||||
* Replacement_Variables_Integration class
|
||||
*/
|
||||
class Replacement_Variables_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the replacement variables styles and component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_premium_load_emoji_picker' - Allow changing whether the emoji picker is loaded for the meta description and SEO title fields.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 19.0
|
||||
*
|
||||
* @param bool $load Whether to load the emoji picker.
|
||||
*/
|
||||
if ( ! \apply_filters( 'wpseo_premium_load_emoji_picker', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_elementor_action = false;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['action'] ) && \is_string( $_GET['action'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing.
|
||||
$is_elementor_action = ( \wp_unslash( $_GET['action'] ) === 'elementor' );
|
||||
}
|
||||
|
||||
$is_settings_page = false;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing.
|
||||
$is_settings_page = ( \wp_unslash( $_GET['page'] ) === 'wpseo_page_settings' );
|
||||
}
|
||||
|
||||
if ( ! $is_settings_page && ! $is_elementor_action && ! $this->load_metabox( $this->get_current_page() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_enqueue_script( 'yoast-seo-premium-draft-js-plugins' );
|
||||
|
||||
\wp_enqueue_style( 'yoast-seo-premium-draft-js-plugins' );
|
||||
|
||||
$draft_js_external_script_location = 'https://yoast.com/shared-assets/scripts/wp-seo-premium-draft-js-plugins-source-2.0.0.min.js';
|
||||
|
||||
if ( \file_exists( \WPSEO_PREMIUM_PATH . 'assets/js/external/draft-js-emoji-picker.min.js' ) ) {
|
||||
$draft_js_external_script_location = \plugins_url( 'wordpress-seo-premium/assets/js/external/draft-js-emoji-picker.min.js' );
|
||||
}
|
||||
|
||||
\wp_enqueue_script(
|
||||
'yoast-seo-premium-draft-js-plugins-external',
|
||||
$draft_js_external_script_location,
|
||||
[
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
],
|
||||
\WPSEO_PREMIUM_VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the metabox related scripts should be loaded.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $current_page The page we are on.
|
||||
*
|
||||
* @return bool True when it should be loaded.
|
||||
*/
|
||||
protected function load_metabox( $current_page ) {
|
||||
$page_helper = new Current_Page_Helper();
|
||||
// When the current page is a term related one.
|
||||
if ( WPSEO_Taxonomy::is_term_edit( $current_page ) || WPSEO_Taxonomy::is_term_overview( $current_page ) ) {
|
||||
return WPSEO_Options::get( 'display-metabox-tax-' . $page_helper->get_current_taxonomy() );
|
||||
}
|
||||
|
||||
// When the current page isn't a post related one.
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) || WPSEO_Metabox::is_post_overview( $current_page ) ) {
|
||||
return WPSEO_Post_Type::has_metabox_enabled( $page_helper->get_current_post_type() );
|
||||
}
|
||||
|
||||
// Make sure ajax integrations are loaded.
|
||||
return \wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the pagenow variable.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The value of pagenow.
|
||||
*/
|
||||
private function get_current_page() {
|
||||
global $pagenow;
|
||||
|
||||
return $pagenow;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Settings_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class Settings_Integration.
|
||||
*/
|
||||
class Settings_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Admin_Asset_Manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $current_page_helper;
|
||||
|
||||
/**
|
||||
* Constructs Settings_Integration.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Current_Page_Helper $current_page_helper ) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Settings_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Are we on the settings page?
|
||||
if ( $this->current_page_helper->get_current_yoast_seo_page() === 'wpseo_page_settings' ) {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$this->asset_manager->enqueue_style( 'premium-settings' );
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Thank_You_Page_Integration class
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Thank_You_Page_Integration implements Integration_Interface {
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Thank_You_Page_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'admin_menu', [ $this, 'add_submenu_page' ], 9 );
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'admin_init', [ $this, 'maybe_redirect' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the installation success page if an installation has just occured.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_redirect() {
|
||||
if ( ! $this->options_helper->get( 'should_redirect_after_install' ) ) {
|
||||
return;
|
||||
}
|
||||
$this->options_helper->set( 'should_redirect_after_install', false );
|
||||
|
||||
if ( ! empty( $this->options_helper->get( 'activation_redirect_timestamp' ) ) ) {
|
||||
return;
|
||||
}
|
||||
$this->options_helper->set( 'activation_redirect_timestamp', \time() );
|
||||
|
||||
\wp_safe_redirect( \admin_url( 'admin.php?page=wpseo_installation_successful' ), 302, 'Yoast SEO Premium' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the workouts submenu page.
|
||||
*
|
||||
* @param array $submenu_pages The Yoast SEO submenu pages.
|
||||
*
|
||||
* @return array the filtered submenu pages.
|
||||
*/
|
||||
public function add_submenu_page( $submenu_pages ) {
|
||||
\add_submenu_page(
|
||||
'',
|
||||
\__( 'Installation Successful', 'wordpress-seo-premium' ),
|
||||
'',
|
||||
'manage_options',
|
||||
'wpseo_installation_successful',
|
||||
[ $this, 'render_page' ]
|
||||
);
|
||||
|
||||
return $submenu_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets on the Thank you page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_installation_successful' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_style( 'monorepo' );
|
||||
\wp_enqueue_style( 'yoast-seo-premium-thank-you' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the thank you page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page() {
|
||||
require \WPSEO_PREMIUM_PATH . 'classes/views/thank-you.php';
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Version_Helper;
|
||||
use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter;
|
||||
|
||||
/**
|
||||
* Integration to display a notification urging to update Premium when a new version is available.
|
||||
*/
|
||||
class Update_Premium_Notification implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options' helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The version helper.
|
||||
*
|
||||
* @var Version_Helper
|
||||
*/
|
||||
private $version_helper;
|
||||
|
||||
/**
|
||||
* The capability helper.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* The Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update_Premium_Notification constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Version_Helper $version_helper The version helper.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Version_Helper $version_helper,
|
||||
Capability_Helper $capability_helper,
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
Current_Page_Helper $current_page_helper
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->version_helper = $version_helper;
|
||||
$this->capability_helper = $capability_helper;
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_notices', [ $this, 'maybe_display_notification' ] );
|
||||
\add_action( 'wp_ajax_dismiss_update_premium_notification', [ $this, 'dismiss_update_premium_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notice if Free is newer than the minimum required version and Premium has an update available.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_display_notification() {
|
||||
if ( $this->current_page_helper->get_current_admin_page() === 'update.php' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->notice_was_dismissed_on_current_premium_version() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->capability_helper->current_user_can( 'wpseo_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether Free is set to a version later than the minimum required and a Premium update is a available.
|
||||
if ( $this->version_helper->is_free_upgraded() && $this->version_helper->is_premium_update_available() ) {
|
||||
$this->admin_asset_manager->enqueue_style( 'monorepo' );
|
||||
|
||||
$is_plugins_page = $this->current_page_helper->get_current_admin_page() === 'plugins.php';
|
||||
$content = \sprintf(
|
||||
/* translators: 1: Yoast SEO Premium, 2 and 3: opening and closing anchor tag. */
|
||||
\esc_html__( 'Please %2$supdate %1$s to the latest version%3$s to ensure you can fully use all Premium settings and features.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium',
|
||||
( $is_plugins_page ) ? '' : '<a href="' . \esc_url( \self_admin_url( 'plugins.php' ) ) . '">',
|
||||
( $is_plugins_page ) ? '' : '</a>'
|
||||
);
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output of the title escaped in the Notice_Presenter.
|
||||
echo new Notice_Presenter(
|
||||
/* translators: 1: Yoast SEO Premium */
|
||||
\sprintf( \__( 'Update to the latest version of %1$s!', 'wordpress-seo-premium' ), 'Yoast SEO Premium' ),
|
||||
$content,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
'yoast-update-premium-notification'
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// Enable permanently dismissing the notice.
|
||||
echo "<script>
|
||||
function dismiss_update_premium_notification(){
|
||||
var data = {
|
||||
'action': 'dismiss_update_premium_notification',
|
||||
};
|
||||
|
||||
jQuery.post( ajaxurl, data, function( response ) {
|
||||
jQuery( '#yoast-update-premium-notification' ).hide();
|
||||
});
|
||||
}
|
||||
|
||||
jQuery( document ).ready( function() {
|
||||
jQuery( 'body' ).on( 'click', '#yoast-update-premium-notification .notice-dismiss', function() {
|
||||
dismiss_update_premium_notification();
|
||||
} );
|
||||
} );
|
||||
</script>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the old premium notice.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dismiss_update_premium_notification() {
|
||||
return $this->options_helper->set( 'dismiss_update_premium_notification', \WPSEO_PREMIUM_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the notification was dismissed in the current Premium version.
|
||||
*
|
||||
* @return bool Whether the notification was dismissed in the current Premium version.
|
||||
*/
|
||||
protected function notice_was_dismissed_on_current_premium_version() {
|
||||
$dismissed_notification_version = $this->options_helper->get( 'dismiss_update_premium_notification', '' );
|
||||
if ( ! empty( $dismissed_notification_version ) ) {
|
||||
return \version_compare( $dismissed_notification_version, \WPSEO_PREMIUM_VERSION, '>=' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_User;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class User_Profile_Integration.
|
||||
*
|
||||
* This class deals with the interface for and the storing of user Schema fields. The output of these fields is done
|
||||
* by this class's sibling frontend integration. Note that the output is done "as is", so all the sanitization happens in
|
||||
* this class.
|
||||
*/
|
||||
class User_Profile_Integration implements Integration_Interface {
|
||||
|
||||
public const NONCE_FIELD_ACTION = 'show_user_profile';
|
||||
public const NONCE_FIELD_NAME = 'wpseo_premium_user_profile_schema_nonce';
|
||||
|
||||
/**
|
||||
* Holds the schema fields we're adding to the user profile.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $fields;
|
||||
|
||||
/**
|
||||
* User_Profile_Integration constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->set_fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'show_user_profile', [ $this, 'user_profile' ], 5 );
|
||||
\add_action( 'edit_user_profile', [ $this, 'user_profile' ], 5 );
|
||||
|
||||
\add_action( 'personal_options_update', [ $this, 'process_user_option_update' ] );
|
||||
\add_action( 'edit_user_profile_update', [ $this, 'process_user_option_update' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fields and their labels and descriptions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_fields() {
|
||||
$this->fields = [
|
||||
'basicInfo' => [
|
||||
'label' => \__( 'Basic information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'honorificPrefix' => [
|
||||
'label' => \__( 'Honorific prefix', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sDr%2$s, %1$sMs%2$s, %1$sMr%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'honorificSuffix' => [
|
||||
'label' => \__( 'Honorific suffix', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sMD%2$s, %1$sPhD%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'birthDate' => [
|
||||
'label' => \__( 'Birth date', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Use format: %1$sYYYY-MM-DD%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'date',
|
||||
],
|
||||
'gender' => [
|
||||
'label' => \__( 'Gender', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sfemale%2$s, %1$smale%2$s, %1$snon-binary%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'extraInfo' => [
|
||||
'label' => \__( 'Extra information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'award' => [
|
||||
'label' => \__( 'Awards', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sMost likely to succeed - 1991, Smartest in class - 1990%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'knowsAbout' => [
|
||||
'label' => \__( 'Expertise in', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sPHP, JavaScript, 90\'s rock music%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'knowsLanguage' => [
|
||||
'label' => \__( 'Language(s) spoken', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sEnglish, French, Dutch%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'jobInfo' => [
|
||||
'label' => \__( 'Employer information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'jobTitle' => [
|
||||
'label' => \__( 'Job title', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$ssoftware engineer%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'worksFor' => [
|
||||
'label' => \__( 'Employer name', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sAcme inc%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a form to add Schema fields to a user.
|
||||
*
|
||||
* @param WP_User $user The current page's user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function user_profile( $user ) {
|
||||
\wp_nonce_field( self::NONCE_FIELD_ACTION, self::NONCE_FIELD_NAME );
|
||||
|
||||
echo '<h2 id="yoast-seo-schema">', \esc_html__( 'Yoast SEO Schema enhancements', 'wordpress-seo-premium' ), '</h2>';
|
||||
echo '<p>', \esc_html__( 'The info you add below is added to the data Yoast SEO outputs in its schema.org output, for instance when you\'re the author of a page. Please only add the info you feel good sharing publicly.', 'wordpress-seo-premium' ), '</p>';
|
||||
|
||||
$user_schema = \get_user_meta( $user->ID, 'wpseo_user_schema', true );
|
||||
|
||||
echo '<div class="yoast yoast-settings">';
|
||||
foreach ( $this->fields as $key => $field ) {
|
||||
if ( $field['type'] === 'group' ) {
|
||||
echo '<h2>', \esc_html( $field['label'] ), '</h2>';
|
||||
continue;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $key is set in the code above, not by a user.
|
||||
echo '<label for="wpseo_user_schema_', $key, '">', \esc_html( $field['label'] ), '</label>';
|
||||
$val = '';
|
||||
if ( isset( $user_schema[ $key ] ) ) {
|
||||
$val = $user_schema[ $key ];
|
||||
}
|
||||
if ( $field['type'] === 'array' && \is_array( $val ) ) {
|
||||
$val = \implode( ', ', $val );
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $key is set in the code above, not by a user.
|
||||
echo '<input class="yoast-settings__text regular-text" type="text" id="wpseo_user_schema_', $key, '" name="wpseo_user_schema[', $key, ']" value="', \esc_attr( $val ), '"/>';
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $field['description'] is set in the code above, not by a user.
|
||||
echo '<p>', $field['description'], '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user metas that (might) have been set on the user profile page.
|
||||
*
|
||||
* @param int $user_id User ID of the updated user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_user_option_update( $user_id ) {
|
||||
// I'm keeping this to conform to the original logic.
|
||||
if ( ! isset( $_POST[ self::NONCE_FIELD_NAME ] ) || ! \is_string( $_POST[ self::NONCE_FIELD_NAME ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\check_admin_referer( self::NONCE_FIELD_ACTION, self::NONCE_FIELD_NAME );
|
||||
|
||||
\update_user_meta( $user_id, 'wpseo_user_schema', $this->get_posted_user_fields() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the posted user fields and sanitizes them.
|
||||
*
|
||||
* As we output these values straight from the database both on frontend and backend, this sanitization is quite important.
|
||||
*
|
||||
* @return array The posted user fields, restricted to allowed fields.
|
||||
*/
|
||||
private function get_posted_user_fields() {
|
||||
$user_schema = [];
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified in process_user_option_update.
|
||||
if ( isset( $_POST['wpseo_user_schema'] ) && \is_array( $_POST['wpseo_user_schema'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified in process_user_option_update.
|
||||
$user_schema = \array_map( 'sanitize_text_field', \wp_unslash( $_POST['wpseo_user_schema'] ) );
|
||||
}
|
||||
|
||||
foreach ( $this->fields as $key => $object ) {
|
||||
switch ( $object['type'] ) {
|
||||
case 'array':
|
||||
$user_schema[ $key ] = \explode( ',', $user_schema[ $key ] );
|
||||
// Trim each item in the comma separated array.
|
||||
foreach ( $user_schema[ $key ] as $index => $item ) {
|
||||
$user_schema[ $key ][ $index ] = \trim( $item );
|
||||
}
|
||||
// Remove empty items.
|
||||
$user_schema[ $key ] = \array_filter( $user_schema[ $key ] );
|
||||
|
||||
if ( $user_schema[ $key ] === [] || $user_schema[ $key ][0] === '' ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
$date = \explode( '-', $user_schema[ $key ] );
|
||||
if ( \count( $date ) !== 3 || ! \checkdate( (int) $date[1], (int) $date[2], (int) $date[0] ) ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ( empty( $user_schema[ $key ] ) ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
// Nothing further to be done for strings.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $user_schema;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Premium_Asset_JS_L10n;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_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\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* WorkoutsIntegration class
|
||||
*/
|
||||
class Workouts_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository The indexable repository.
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* The shortlinker.
|
||||
*
|
||||
* @var WPSEO_Shortlinker
|
||||
*/
|
||||
private $shortlinker;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
private $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Workouts_Integration constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexables repository.
|
||||
* @param WPSEO_Shortlinker $shortlinker The shortlinker.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $indexable_repository,
|
||||
WPSEO_Shortlinker $shortlinker,
|
||||
Options_Helper $options_helper,
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->shortlinker = $shortlinker;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved.
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_workouts' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workouts_option = $this->options_helper->get( 'workouts' );
|
||||
|
||||
$indexable_ids_in_workouts = [ 0 ];
|
||||
if ( isset( $workouts_option['orphaned']['indexablesByStep'] )
|
||||
&& \is_array( $workouts_option['orphaned']['indexablesByStep'] )
|
||||
&& isset( $workouts_option['cornerstone']['indexablesByStep'] )
|
||||
&& \is_array( $workouts_option['cornerstone']['indexablesByStep'] )
|
||||
) {
|
||||
foreach ( [ 'orphaned', 'cornerstone' ] as $workout ) {
|
||||
foreach ( $workouts_option[ $workout ]['indexablesByStep'] as $step => $indexables ) {
|
||||
if ( $step === 'removed' ) {
|
||||
continue;
|
||||
}
|
||||
foreach ( $indexables as $indexable_id ) {
|
||||
$indexable_ids_in_workouts[] = $indexable_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$orphaned = $this->get_orphaned( $indexable_ids_in_workouts );
|
||||
|
||||
$premium_localization = new WPSEO_Premium_Asset_JS_L10n();
|
||||
$premium_localization->localize_script( 'yoast-seo-premium-workouts' );
|
||||
\wp_enqueue_script( 'yoast-seo-premium-workouts' );
|
||||
\wp_localize_script(
|
||||
'yoast-seo-premium-workouts',
|
||||
'wpseoPremiumWorkoutsData',
|
||||
[
|
||||
'cornerstoneGuide' => $this->shortlinker->build_shortlink( 'https://yoa.st/4el' ),
|
||||
'orphanedGuide' => $this->shortlinker->build_shortlink( 'https://yoa.st/4fa' ),
|
||||
'orphanedUpdateContent' => $this->shortlinker->build_shortlink( 'https://yoa.st/4h9' ),
|
||||
'cornerstoneOn' => $this->options_helper->get( 'enable_cornerstone_content' ),
|
||||
'seoDataOptimizationNeeded' => ! $this->prominent_words_helper->is_indexing_completed(),
|
||||
'orphaned' => $orphaned,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public indexable sub types.
|
||||
*
|
||||
* @return array The sub types.
|
||||
*/
|
||||
protected function get_public_sub_types() {
|
||||
$object_sub_types = \array_values(
|
||||
\array_merge(
|
||||
$this->post_type_helper->get_public_post_types(),
|
||||
\get_taxonomies( [ 'public' => true ] )
|
||||
)
|
||||
);
|
||||
|
||||
$excluded_post_types = \apply_filters( 'wpseo_indexable_excluded_post_types', [ 'attachment' ] );
|
||||
$object_sub_types = \array_diff( $object_sub_types, $excluded_post_types );
|
||||
return $object_sub_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the orphaned indexables.
|
||||
*
|
||||
* @param array $indexable_ids_in_orphaned_workout The orphaned indexable ids.
|
||||
* @param int $limit The limit.
|
||||
*
|
||||
* @return array The orphaned indexables.
|
||||
*/
|
||||
protected function get_orphaned( array $indexable_ids_in_orphaned_workout, $limit = 10 ) {
|
||||
$orphaned = $this->indexable_repository->query()
|
||||
->where_raw( '( incoming_link_count is NULL OR incoming_link_count < 3 )' )
|
||||
->where_raw( '( post_status = \'publish\' OR post_status IS NULL )' )
|
||||
->where_raw( '( is_robots_noindex = FALSE OR is_robots_noindex IS NULL )' )
|
||||
->where_raw( 'NOT ( object_sub_type = \'page\' AND permalink = %s )', [ \home_url( '/' ) ] )
|
||||
->where_in( 'object_sub_type', $this->get_public_sub_types() )
|
||||
->where_in( 'object_type', [ 'post' ] )
|
||||
->where_not_in( 'id', $indexable_ids_in_orphaned_workout )
|
||||
->order_by_asc( 'created_at' )
|
||||
->limit( $limit )
|
||||
->find_many();
|
||||
$orphaned = \array_map( [ $this->indexable_repository, 'ensure_permalink' ], $orphaned );
|
||||
return $orphaned;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Alerts;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Alerts\Abstract_Dismissable_Alert;
|
||||
|
||||
/**
|
||||
* Registers a dismissible alert.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Ai_Generator_Tip_Notification extends Abstract_Dismissable_Alert {
|
||||
|
||||
/**
|
||||
* Holds the alert identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $alert_identifier = 'wpseo_premium_ai_generator';
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Blocks\Dynamic_Block;
|
||||
|
||||
/**
|
||||
* Estimated_Reading_Time_Block class.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Estimated_Reading_Time_Block extends Dynamic_Block {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'estimated-reading-time';
|
||||
|
||||
/**
|
||||
* Holds the clock icon HTML.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $clock_icon = '<span class="yoast-reading-time__icon"><svg aria-hidden="true" focusable="false" data-icon="clock" width="20" height="20" fill="none" stroke="currentColor" style="display:inline-block;vertical-align:-0.1em" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></span><span class="yoast-reading-time__spacer" style="display:inline-block;width:1em"></span>';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* Registers the block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block() {
|
||||
\register_block_type(
|
||||
'yoast-seo/' . $this->block_name,
|
||||
[
|
||||
'editor_script' => $this->script,
|
||||
'render_callback' => [ $this, 'present' ],
|
||||
'attributes' => [
|
||||
'className' => [
|
||||
'default' => '',
|
||||
'type' => 'string',
|
||||
],
|
||||
'estimatedReadingTime' => [
|
||||
'type' => 'number',
|
||||
'default' => 0,
|
||||
],
|
||||
'descriptiveText' => [
|
||||
'type' => 'string',
|
||||
'default' => \__( 'Estimated reading time:', 'wordpress-seo-premium' ) . ' ',
|
||||
],
|
||||
'showDescriptiveText' => [
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
],
|
||||
'showIcon' => [
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes, $content = '' ) {
|
||||
|
||||
$content = \preg_replace(
|
||||
'/<span class="yoast-reading-time__time-unit">.*<\/span>/',
|
||||
'<span class="yoast-reading-time__time-unit"> ' . \sprintf( \_n( 'minute', 'minutes', $attributes['estimatedReadingTime'], 'wordpress-seo-premium' ), $attributes['estimatedReadingTime'] ) . '</span>',
|
||||
$content,
|
||||
1
|
||||
);
|
||||
if ( $attributes['showIcon'] ) {
|
||||
// Replace 15.7 icon placeholder.
|
||||
$content = \preg_replace(
|
||||
'/ICON_PLACEHOLDER/',
|
||||
$this->clock_icon,
|
||||
$content,
|
||||
1
|
||||
);
|
||||
|
||||
// Replace the 15.8+ icon placeholder.
|
||||
return \preg_replace(
|
||||
'/<span class="yoast-reading-time__icon"><\/span>/',
|
||||
$this->clock_icon,
|
||||
$content,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Related content block class.
|
||||
*/
|
||||
class Related_Links_Block implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\register_block_type( 'yoast-seo/related-links', [ 'editor_script' => 'wp-seo-premium-blocks' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use wpdb;
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Analytics\Domain\To_Be_Cleaned_Indexable_Bucket;
|
||||
use Yoast\WP\SEO\Analytics\Domain\To_Be_Cleaned_Indexable_Count;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository;
|
||||
|
||||
/**
|
||||
* Adds cleanup hooks.
|
||||
*/
|
||||
class Cleanup_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The indexable cleanup repository.
|
||||
*
|
||||
* @var Indexable_Cleanup_Repository
|
||||
*/
|
||||
private $indexable_cleanup_repository;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Indexable_Cleanup_Repository $indexable_cleanup_repository The indexable cleanup repository.
|
||||
*/
|
||||
public function __construct( Indexable_Cleanup_Repository $indexable_cleanup_repository ) {
|
||||
$this->indexable_cleanup_repository = $indexable_cleanup_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array The array of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_cleanup_tasks', [ $this, 'add_cleanup_tasks' ] );
|
||||
\add_action( 'wpseo_add_cleanup_counts_to_indexable_bucket', [ $this, 'add_cleanup_counts' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cleanup tasks for the cleanup integration.
|
||||
*
|
||||
* @param array $tasks Array of tasks to be added.
|
||||
*
|
||||
* @return array An associative array of tasks to be added to the cleanup integration.
|
||||
*/
|
||||
public function add_cleanup_tasks( $tasks ) {
|
||||
return \array_merge(
|
||||
$tasks,
|
||||
[
|
||||
'clean_orphaned_indexables_prominent_words' => function ( $limit ) {
|
||||
return $this->cleanup_orphaned_from_table( 'Prominent_Words', 'indexable_id', $limit );
|
||||
},
|
||||
'clean_old_prominent_word_entries' => function ( $limit ) {
|
||||
return $this->cleanup_old_prominent_words( $limit );
|
||||
},
|
||||
'clean_old_prominent_word_version_numbers' => function ( $limit ) {
|
||||
return $this->cleanup_old_prominent_word_version_numbers( $limit );
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cleanup counts to the data bucket object.
|
||||
*
|
||||
* @param To_Be_Cleaned_Indexable_Bucket $to_be_cleaned_indexable_bucket The bucket with current indexable count data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_cleanup_counts( To_Be_Cleaned_Indexable_Bucket $to_be_cleaned_indexable_bucket ): void {
|
||||
$to_be_cleaned_indexable_bucket->add_to_be_cleaned_indexable_count( new To_Be_Cleaned_Indexable_Count( 'orphaned_indexables_prominent_words', $this->indexable_cleanup_repository->count_orphaned_from_table( 'Prominent_Words', 'indexable_id' ) ) );
|
||||
$to_be_cleaned_indexable_bucket->add_to_be_cleaned_indexable_count( new To_Be_Cleaned_Indexable_Count( 'orphaned_prominent_word_entries', $this->count_old_prominent_words() ) );
|
||||
$to_be_cleaned_indexable_bucket->add_to_be_cleaned_indexable_count( new To_Be_Cleaned_Indexable_Count( 'orphaned_prominent_word_version_numbers', $this->count_old_prominent_word_version_numbers() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans orphaned rows from a yoast table.
|
||||
*
|
||||
* @param string $table The table to cleanup.
|
||||
* @param string $column The table column the cleanup will rely on.
|
||||
* @param int $limit The limit we'll apply to the queries.
|
||||
*
|
||||
* @return int The number of deleted rows.
|
||||
*/
|
||||
public function cleanup_orphaned_from_table( $table, $column, $limit ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = Model::get_table_name( $table );
|
||||
$indexable_table = Model::get_table_name( 'Indexable' );
|
||||
|
||||
// Warning: If this query is changed, make sure to update the query in cleanup_orphaned_from_table in Free as well.
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT table_to_clean.{$column}
|
||||
FROM {$table} table_to_clean
|
||||
LEFT JOIN {$indexable_table} AS indexable_table
|
||||
ON table_to_clean.{$column} = indexable_table.id
|
||||
WHERE indexable_table.id IS NULL
|
||||
AND table_to_clean.{$column} IS NOT NULL
|
||||
LIMIT %d",
|
||||
$limit
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared.
|
||||
$orphans = $wpdb->get_col( $query );
|
||||
|
||||
if ( empty( $orphans ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared.
|
||||
return \intval( $wpdb->query( "DELETE FROM $table WHERE {$column} IN( " . \implode( ',', $orphans ) . ' ) ' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up old style prominent words from the database.
|
||||
*
|
||||
* @param int $limit The maximum amount of old prominent words to clean up in one go. Defaults to 1000.
|
||||
*
|
||||
* @return int The number of deleted rows.
|
||||
*/
|
||||
public function cleanup_old_prominent_words( $limit = 1000 ) {
|
||||
global $wpdb;
|
||||
|
||||
$taxonomy_ids = $this->retrieve_prominent_word_taxonomies( $wpdb, $limit );
|
||||
|
||||
if ( \count( $taxonomy_ids ) === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$nr_of_deleted_rows = $this->delete_prominent_word_taxonomies_and_terms( $wpdb, $taxonomy_ids );
|
||||
|
||||
if ( $nr_of_deleted_rows === false ) {
|
||||
// Failed query.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $nr_of_deleted_rows;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
/**
|
||||
* Count up old style prominent words from the database.
|
||||
*
|
||||
* @return int The number of old prominent word rows.
|
||||
*/
|
||||
public function count_old_prominent_words() {
|
||||
global $wpdb;
|
||||
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT count(term_taxonomy_id) FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s",
|
||||
[ 'yst_prominent_words' ]
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared.
|
||||
return $wpdb->get_col( $query )[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of prominent word taxonomy IDs.
|
||||
*
|
||||
* @param wpdb $wpdb The WordPress database object.
|
||||
* @param int $limit The maximum amount of prominent word taxonomies to retrieve.
|
||||
*
|
||||
* @return string[] A list of prominent word taxonomy IDs (of size 'limit').
|
||||
*/
|
||||
protected function retrieve_prominent_word_taxonomies( $wpdb, $limit ) {
|
||||
return $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s LIMIT %d",
|
||||
[ 'yst_prominent_words', $limit ]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given list of taxonomies and their terms.
|
||||
*
|
||||
* @param wpdb $wpdb The WordPress database object.
|
||||
* @param string[] $taxonomy_ids The IDs of the taxonomies to remove and their corresponding terms.
|
||||
*
|
||||
* @return bool|int `false` if the query failed, the amount of rows deleted otherwise.
|
||||
*/
|
||||
protected function delete_prominent_word_taxonomies_and_terms( $wpdb, $taxonomy_ids ) {
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE t, tr, tt FROM {$wpdb->term_taxonomy} tt
|
||||
LEFT JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
|
||||
LEFT JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
||||
WHERE tt.term_taxonomy_id IN ( " . \implode( ', ', \array_fill( 0, \count( $taxonomy_ids ), '%s' ) ) . ' )',
|
||||
$taxonomy_ids
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the old prominent word versions from the postmeta table in the database.
|
||||
*
|
||||
* @param int $limit The maximum number of prominent word version numbers to clean in one go.
|
||||
*
|
||||
* @return bool|int The number of cleaned up prominent word version numbers, or `false` if the query failed.
|
||||
*/
|
||||
protected function cleanup_old_prominent_word_version_numbers( $limit ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input.
|
||||
$query = $wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s LIMIT %d",
|
||||
[ '_yst_prominent_words_version', $limit ]
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared.
|
||||
return $wpdb->query( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts up the old prominent word versions from the postmeta table in the database.
|
||||
*
|
||||
* @return bool|int The number of prominent word version numbers.
|
||||
*/
|
||||
protected function count_old_prominent_word_version_numbers() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input.
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT count(*) FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
[ '_yst_prominent_words_version' ]
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared.
|
||||
return $wpdb->get_col( $query )[0];
|
||||
}
|
||||
|
||||
// phpcs:enable
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Front_End;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Robots_Txt_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Handles adding the rules to `robots.txt`.
|
||||
*/
|
||||
class Robots_Txt_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Instantiates the `robots.txt` integration.
|
||||
*
|
||||
* @param Options_Helper $options_helper Options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Robots_Txt_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( \is_multisite() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->options_helper->get( 'deny_ccbot_crawling' ) ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_ccbot' ], 10, 1 );
|
||||
}
|
||||
if ( $this->options_helper->get( 'deny_google_extended_crawling' ) ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_google_extended_bot' ], 10, 1 );
|
||||
}
|
||||
if ( $this->options_helper->get( 'deny_gptbot_crawling' ) ) {
|
||||
\add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_gptbot' ], 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for Common Crawl CCBot agents to `robots.txt`.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_ccbot( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( 'CCBot', '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for Google-Extended agents to `robots.txt`.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_google_extended_bot( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( 'Google-Extended', '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disallow rule for OpenAI GPTBot agents to `robots.txt`.
|
||||
*
|
||||
* @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_disallow_gptbot( Robots_Txt_Helper $robots_txt_helper ) {
|
||||
$robots_txt_helper->add_disallow( 'GPTBot', '/' );
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use WP_Admin_Bar;
|
||||
use WPSEO_Metabox_Analysis_Readability;
|
||||
use WPSEO_Metabox_Analysis_SEO;
|
||||
use WPSEO_Options;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Robots_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Frontend_Inspector class
|
||||
*/
|
||||
class Frontend_Inspector implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The identifier used for the frontend inspector submenu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FRONTEND_INSPECTOR_SUBMENU_IDENTIFIER = 'wpseo-frontend-inspector';
|
||||
|
||||
/**
|
||||
* Holds the Robots_Helper.
|
||||
*
|
||||
* @var Robots_Helper
|
||||
*/
|
||||
protected $robots_helper;
|
||||
|
||||
/**
|
||||
* Constructs a Frontend_Inspector.
|
||||
*
|
||||
* @param Robots_Helper $robots_helper The Robots_Helper.
|
||||
*/
|
||||
public function __construct( Robots_Helper $robots_helper ) {
|
||||
$this->robots_helper = $robots_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
|
||||
\add_action( 'wpseo_add_adminbar_submenu', [ $this, 'add_frontend_inspector_submenu' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the frontend inspector submenu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar The admin bar.
|
||||
* @param string $menu_identifier The menu identifier.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_frontend_inspector_submenu( WP_Admin_Bar $wp_admin_bar, $menu_identifier ) {
|
||||
if ( ! \is_admin() ) {
|
||||
$menu_args = [
|
||||
'parent' => $menu_identifier,
|
||||
'id' => self::FRONTEND_INSPECTOR_SUBMENU_IDENTIFIER,
|
||||
'title' => \sprintf(
|
||||
'%1$s <span class="yoast-badge yoast-beta-badge">%2$s</span>',
|
||||
\__( 'Front-end SEO inspector', 'wordpress-seo-premium' ),
|
||||
\__( 'Beta', 'wordpress-seo-premium' )
|
||||
),
|
||||
'href' => '#wpseo-frontend-inspector',
|
||||
'meta' => [
|
||||
'tabindex' => '0',
|
||||
],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the workouts app.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! \is_admin_bar_showing() || ! WPSEO_Options::get( 'enable_admin_bar_menu' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the current user can't write posts, this is all of no use, so let's not output an admin menu.
|
||||
if ( ! \current_user_can( 'edit_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$analysis_readability = new WPSEO_Metabox_Analysis_Readability();
|
||||
$current_page_meta = \YoastSEO()->meta->for_current_page();
|
||||
$indexable = $current_page_meta->indexable;
|
||||
$page_type = $current_page_meta->page_type;
|
||||
|
||||
$is_seo_analysis_active = $analysis_seo->is_enabled();
|
||||
$is_readability_analysis_active = $analysis_readability->is_enabled();
|
||||
$display_metabox = true;
|
||||
|
||||
switch ( $page_type ) {
|
||||
case 'Home_Page':
|
||||
case 'Post_Type_Archive':
|
||||
case 'Date_Archive':
|
||||
case 'Error_Page':
|
||||
case 'Fallback':
|
||||
case 'Search_Result_Page':
|
||||
break;
|
||||
case 'Static_Home_Page':
|
||||
case 'Static_Posts_Page':
|
||||
case 'Post_Type':
|
||||
$display_metabox = WPSEO_Options::get( 'display-metabox-pt-' . $indexable->object_sub_type );
|
||||
break;
|
||||
case 'Term_Archive':
|
||||
$display_metabox = WPSEO_Options::get( 'display-metabox-tax-' . $indexable->object_sub_type );
|
||||
break;
|
||||
case 'Author_Archive':
|
||||
$display_metabox = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! $display_metabox ) {
|
||||
$is_seo_analysis_active = false;
|
||||
$is_readability_analysis_active = false;
|
||||
}
|
||||
|
||||
\wp_enqueue_script( 'yoast-seo-premium-frontend-inspector' );
|
||||
\wp_localize_script(
|
||||
'yoast-seo-premium-frontend-inspector',
|
||||
'wpseoScriptData',
|
||||
[
|
||||
'frontendInspector' => [
|
||||
'isIndexable' => $this->robots_helper->is_indexable( $indexable ),
|
||||
'indexable' => [
|
||||
'is_robots_noindex' => $indexable->is_robots_noindex,
|
||||
'primary_focus_keyword' => $indexable->primary_focus_keyword,
|
||||
'primary_focus_keyword_score' => $indexable->primary_focus_keyword_score,
|
||||
'readability_score' => $indexable->readability_score,
|
||||
],
|
||||
'contentAnalysisActive' => $is_readability_analysis_active,
|
||||
'keywordAnalysisActive' => $is_seo_analysis_active,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use WP_Post;
|
||||
use WPSEO_Remote_Request;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Request_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Index_Now_Ping class.
|
||||
*/
|
||||
class Index_Now_Ping implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The request helper.
|
||||
*
|
||||
* @var Request_Helper
|
||||
*/
|
||||
private $request_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* The IndexNow endpoint URL we're using.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $endpoint;
|
||||
|
||||
/**
|
||||
* Index_Now_Ping integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The option helper.
|
||||
* @param Request_Helper $request_helper The request helper.
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Request_Helper $request_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->request_helper = $request_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\indexnow_endpoint' - Allow changing the Indexnow endpoint.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 18.8
|
||||
*
|
||||
* @param string $endpoint The IndexNow endpoint URL.
|
||||
*/
|
||||
$this->endpoint = \apply_filters( 'Yoast\WP\SEO\indexnow_endpoint', 'https://api.indexnow.org/indexnow' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks this integration acts on.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( $this->options_helper->get( 'enable_index_now' ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \wp_get_environment_type() !== 'production' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Please note that the name transition_post_status is misleading.
|
||||
* The hook does not only fire on a post status transition but also when a post is updated
|
||||
* while the status is not changed from one to another at all.
|
||||
*/
|
||||
\add_action( 'transition_post_status', [ $this, 'ping_index_now' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pings IndexNow for changes.
|
||||
*
|
||||
* @param string $new_status The new status for the post.
|
||||
* @param string $old_status The old status for the post.
|
||||
* @param WP_Post $post The post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ping_index_now( $new_status, $old_status, $post ) {
|
||||
if ( $new_status !== 'publish' && $old_status !== 'publish' ) {
|
||||
// If we're not transitioning to or from a published status, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// The block editor saves published posts twice, we want to ping only on the first request.
|
||||
if ( $new_status === 'publish' && $this->request_helper->is_rest_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \in_array( $post->post_type, $this->post_type_helper->get_accessible_post_types(), true )
|
||||
|| ! $this->post_type_helper->is_indexable( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bail out if last ping was less than two minutes ago.
|
||||
$indexnow_last_ping = \get_post_meta( $post->ID, '_yoast_indexnow_last_ping', true );
|
||||
if ( \is_numeric( $indexnow_last_ping ) && \abs( \time() - ( (int) $indexnow_last_ping ) ) < 120 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $this->options_helper->get( 'index_now_key' );
|
||||
$permalink = $this->get_permalink( $post );
|
||||
$urls = [ $permalink ];
|
||||
|
||||
if ( $post->post_type === 'post' ) {
|
||||
$urls[] = \get_home_url();
|
||||
}
|
||||
|
||||
if ( ! empty( \get_option( 'permalink_structure' ) ) ) {
|
||||
$key_location = \trailingslashit( \get_home_url() ) . 'yoast-index-now-' . $key . '.txt';
|
||||
}
|
||||
else {
|
||||
$key_location = \add_query_arg( 'yoast_index_now_key', $key, \trailingslashit( \get_home_url() ) );
|
||||
}
|
||||
|
||||
$content = (object) [
|
||||
'host' => \wp_parse_url( \get_home_url(), \PHP_URL_HOST ),
|
||||
'key' => $key,
|
||||
'keyLocation' => $key_location,
|
||||
'urlList' => $urls,
|
||||
];
|
||||
|
||||
// Set a 'content-type' header of 'application/json' and an identifying source header.
|
||||
// The "false" on the end of the x-source-info header determines whether this is a manual submission or not.
|
||||
$request_args = [
|
||||
'headers' => [
|
||||
'content-type' => 'application/json; charset=utf-8',
|
||||
'x-source-info' => 'https://yoast.com/wordpress/plugins/seo-premium/' . \WPSEO_PREMIUM_VERSION . '/false',
|
||||
],
|
||||
];
|
||||
|
||||
$request = new WPSEO_Remote_Request( $this->endpoint, $request_args );
|
||||
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- This is being sent to an API, not displayed.
|
||||
$request->set_body( \wp_json_encode( $content ) );
|
||||
$request->send();
|
||||
|
||||
\update_post_meta( $post->ID, '_yoast_indexnow_last_ping', \time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the (former) permalink for a post.
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
*
|
||||
* @return string Permalink.
|
||||
*/
|
||||
private function get_permalink( WP_Post $post ) {
|
||||
if ( \in_array( $post->post_status, [ 'trash', 'draft', 'pending', 'future' ], true ) ) {
|
||||
if ( $post->post_status === 'trash' ) {
|
||||
// Fix the post_name.
|
||||
$post->post_name = \preg_replace( '/__trashed$/', '', $post->post_name );
|
||||
}
|
||||
// Force post_status to publish briefly, so we get the correct URL.
|
||||
$post->post_status = 'publish';
|
||||
}
|
||||
|
||||
return \get_permalink( $post );
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action;
|
||||
|
||||
/**
|
||||
* Adds prominent words to the missing indexables bucket.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Missing_Indexables_Count_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The content indexable action.
|
||||
*
|
||||
* @var Content_Action
|
||||
*/
|
||||
private $content_action;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Content_Action $content_action The action.
|
||||
*/
|
||||
public function __construct( Content_Action $content_action ) {
|
||||
$this->content_action = $content_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_indexable_collector_add_indexation_actions', [ $this, 'add_index_action' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Content_Action to the indexable collector.
|
||||
*
|
||||
* @param array<Indexation_Action_Interface> $indexation_actions The current indexation actions.
|
||||
* @return array<Indexation_Action_Interface>
|
||||
*/
|
||||
public function add_index_action( $indexation_actions ) {
|
||||
$indexation_actions[] = $this->content_action;
|
||||
return $indexation_actions;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
/**
|
||||
* Class OpenGraph_Author_Archive.
|
||||
*/
|
||||
class OpenGraph_Author_Archive extends Abstract_OpenGraph_Integration {
|
||||
|
||||
/**
|
||||
* The name of the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = 'social-title-author-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-author-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-author-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = 'social-image-url-author-wpseo';
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_title_user', [ $this, 'filter_title' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_description_user', [ $this, 'filter_description' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_user', [ $this, 'filter_image_id' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_user', [ $this, 'filter_image' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
/**
|
||||
* Class OpenGraph_Date_Archive.
|
||||
*/
|
||||
class OpenGraph_Date_Archive extends Abstract_OpenGraph_Integration {
|
||||
|
||||
/**
|
||||
* The name of the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = 'social-title-archive-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-archive-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-archive-wpseo';
|
||||
|
||||
/**
|
||||
* The name of the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = 'social-image-url-archive-wpseo';
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_title_date-archive', [ $this, 'filter_title' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_description_date-archive', [ $this, 'filter_description' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_date-archive', [ $this, 'filter_image_id' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_date-archive', [ $this, 'filter_image' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
/**
|
||||
* Class OpenGraph_Post_Type.
|
||||
*/
|
||||
class OpenGraph_Post_Type extends Abstract_OpenGraph_Integration {
|
||||
|
||||
/**
|
||||
* The prefix for the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = 'social-title-';
|
||||
|
||||
/**
|
||||
* The prefix for the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = 'social-image-url-';
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_title_post', [ $this, 'filter_title_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_description_post', [ $this, 'filter_description_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_post', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_post', [ $this, 'filter_image_for_subtype' ], 10, 2 );
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
/**
|
||||
* Class OpenGraph_PostType_Archive.
|
||||
*/
|
||||
class OpenGraph_PostType_Archive extends Abstract_OpenGraph_Integration {
|
||||
|
||||
/**
|
||||
* The prefix for the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = 'social-title-ptarchive-';
|
||||
|
||||
/**
|
||||
* The prefix for the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-ptarchive-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-ptarchive-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = 'social-image-url-ptarchive-';
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_title_post-type-archive', [ $this, 'filter_title_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_description_post-type-archive', [ $this, 'filter_description_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_post-type-archive', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_post-type-archive', [ $this, 'filter_image_for_subtype' ], 10, 2 );
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
/**
|
||||
* Class OpenGraph_Term_Archive.
|
||||
*/
|
||||
class OpenGraph_Term_Archive extends Abstract_OpenGraph_Integration {
|
||||
|
||||
/**
|
||||
* The prefix for the social title option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_TITLE = 'social-title-tax-';
|
||||
|
||||
/**
|
||||
* The prefix for the social description option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-tax-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image ID option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-tax-';
|
||||
|
||||
/**
|
||||
* The prefix for the social image URL option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_TITLES_KEY_IMAGE = 'social-image-url-tax-';
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_title_term', [ $this, 'filter_title_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_description_term', [ $this, 'filter_description_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_term', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\open_graph_image_term', [ $this, 'filter_image_for_subtype' ], 10, 2 );
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
|
||||
/**
|
||||
* Integration to add organization details to the Schema.
|
||||
*/
|
||||
class Organization_Schema_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Constant holding the mapping between database option and actual schema name.
|
||||
*/
|
||||
public const ORGANIZATION_DETAILS_MAPPING = [
|
||||
'org-description' => 'description',
|
||||
'org-email' => 'email',
|
||||
'org-phone' => 'telephone',
|
||||
'org-legal-name' => 'legalName',
|
||||
'org-founding-date' => 'foundingDate',
|
||||
'org-number-employees' => 'numberOfEmployees',
|
||||
'org-vat-id' => 'vatID',
|
||||
'org-tax-id' => 'taxID',
|
||||
'org-iso' => 'iso6523Code',
|
||||
'org-duns' => 'duns',
|
||||
'org-leicode' => 'leiCode',
|
||||
'org-naics' => 'naics',
|
||||
];
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array<object> The conditionals to check.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Organization_Schema_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_schema_organization', [ $this, 'filter_organization_schema' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the organization schema.
|
||||
*
|
||||
* @param array<string,array<string|int>> $profiles The organization schema data.
|
||||
* @return array<string,array<string|int>> The filtered organization schema data.
|
||||
*/
|
||||
public function filter_organization_schema( $profiles ) {
|
||||
$options = [];
|
||||
$exclude = [ 'org-number-employees' ];
|
||||
if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
|
||||
\array_push( $exclude, 'org-vat-id', 'org-tax-id', 'org-phone', 'org-email' );
|
||||
}
|
||||
foreach ( self::ORGANIZATION_DETAILS_MAPPING as $option_name => $schema_name ) {
|
||||
$options[ $option_name ] = $this->options_helper->get( $option_name );
|
||||
if ( $options[ $option_name ] && ! \in_array( $option_name, $exclude, true ) ) {
|
||||
$profiles[ $schema_name ] = $options[ $option_name ];
|
||||
}
|
||||
}
|
||||
|
||||
$profiles = $this->add_employees_number( $profiles, $options['org-number-employees'] );
|
||||
|
||||
return $profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds employees number to the organization schema tree.
|
||||
*
|
||||
* @param array<string,array<string|int>> $profiles The organization schema tree.
|
||||
* @param array<string,array<string|int>> $employees The option for employees number.
|
||||
* @return array<string,array<string|int>> The modified organization schema tree.
|
||||
*/
|
||||
public function add_employees_number( $profiles, $employees ) {
|
||||
if ( ! $employees ) {
|
||||
return $profiles;
|
||||
}
|
||||
|
||||
$profiles['numberOfEmployees'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
];
|
||||
|
||||
$range = \explode( '-', $employees );
|
||||
|
||||
if ( \count( $range ) === 2 ) {
|
||||
$profiles['numberOfEmployees']['minValue'] = $range[0];
|
||||
$profiles['numberOfEmployees']['maxValue'] = $range[1];
|
||||
}
|
||||
else {
|
||||
$profiles['numberOfEmployees']['value'] = $employees;
|
||||
}
|
||||
|
||||
return $profiles;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user