Merged in feature/MAW-855-import-code-into-aws (pull request #2)
code import from pantheon * code import from pantheon
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
<?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\Forbidden_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.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception Forbidden_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception Unauthorized_Exception.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception Forbidden_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws \RuntimeException Unable to retrieve the refresh token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception Unauthorized_Exception.
|
||||
*
|
||||
* @return string The code verifier.
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception Forbidden_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws \RuntimeException Unable to retrieve the access token.
|
||||
*
|
||||
* @return array The suggestions.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \RuntimeException Unable to retrieve the access token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't take into account exceptions thrown in called methods.
|
||||
|
||||
/**
|
||||
* Retrieves the access token.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Forbidden_Exception Forbidden_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws \RuntimeException Unable to retrieve the access or refresh token.
|
||||
*
|
||||
* @return string The access 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;
|
||||
}
|
||||
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
* Invalidates the access token.
|
||||
*
|
||||
* @param string $user_id The user ID.
|
||||
*
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Not_Found_Exception Not_Found_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws \Yoast\WP\SEO\Premium\Exceptions\Remote_Request\Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws \RuntimeException Unable to retrieve the access token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,23 @@ namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
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\Prominent_Words_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.
|
||||
*/
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* The repository to retrieve prominent words from.
|
||||
*
|
||||
@@ -28,6 +35,13 @@ class Link_Suggestions_Action {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -49,30 +63,36 @@ class Link_Suggestions_Action {
|
||||
* @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
|
||||
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 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 ) {
|
||||
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 ) {
|
||||
@@ -81,9 +101,9 @@ class Link_Suggestions_Action {
|
||||
|
||||
/*
|
||||
* Gets best suggestions (returns a sorted array [$indexable_id => score]).
|
||||
* The indexables are processed in batches of 100 indexables each.
|
||||
* The indexables are processed in batches of 1000 indexables each.
|
||||
*/
|
||||
$suggestions_scores = $this->retrieve_suggested_indexable_ids( $words_from_request, $limit, 100, $current_indexable_id );
|
||||
$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 );
|
||||
|
||||
@@ -109,6 +129,41 @@ class Link_Suggestions_Action {
|
||||
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.
|
||||
*
|
||||
@@ -297,16 +352,20 @@ class Link_Suggestions_Action {
|
||||
* 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 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 ) {
|
||||
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 )
|
||||
$this->prominent_words_repository->find_ids_by_stems( $stems, $batch_size, $page, $excluded_ids, $post_type, $only_include_public )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -315,27 +374,42 @@ class Link_Suggestions_Action {
|
||||
* 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 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 ) {
|
||||
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 );
|
||||
$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 );
|
||||
@@ -347,10 +421,6 @@ class Link_Suggestions_Action {
|
||||
++$batch_scores_size;
|
||||
}
|
||||
|
||||
if ( $current_indexable_id && isset( $scores[ $current_indexable_id ] ) ) {
|
||||
unset( $scores[ $current_indexable_id ] );
|
||||
}
|
||||
|
||||
// Sort the list of scores and keep only the top $limit of the scores.
|
||||
$scores = $this->get_top_suggestions( $scores, $limit );
|
||||
|
||||
@@ -393,10 +463,11 @@ class Link_Suggestions_Action {
|
||||
// Sort the indexables by descending score.
|
||||
\uasort(
|
||||
$scores,
|
||||
static function( $score_1, $score_2 ) {
|
||||
static function ( $score_1, $score_2 ) {
|
||||
if ( $score_1 === $score_2 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( ( $score_1 < $score_2 ) ? 1 : -1 );
|
||||
}
|
||||
);
|
||||
@@ -506,7 +577,7 @@ class Link_Suggestions_Action {
|
||||
protected function sort_suggestions_by_field( array &$link_suggestions, $field ) {
|
||||
\usort(
|
||||
$link_suggestions,
|
||||
static function( $suggestion_1, $suggestion_2 ) use ( $field ) {
|
||||
static function ( $suggestion_1, $suggestion_2 ) use ( $field ) {
|
||||
if ( $suggestion_1[ $field ] === $suggestion_2[ $field ] ) {
|
||||
return 0;
|
||||
}
|
||||
@@ -527,7 +598,7 @@ class Link_Suggestions_Action {
|
||||
protected function filter_suggestions( $link_suggestions, $cornerstone ) {
|
||||
return \array_filter(
|
||||
$link_suggestions,
|
||||
static function( $suggestion ) use ( $cornerstone ) {
|
||||
static function ( $suggestion ) use ( $cornerstone ) {
|
||||
return (bool) $suggestion['isCornerstone'] === $cornerstone;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
|
||||
/**
|
||||
* Action for completing the prominent words indexing.
|
||||
|
||||
@@ -127,16 +127,14 @@ class Content_Action implements Indexation_Action_Interface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a batch of indexables, to be indexed for internal linking suggestions.
|
||||
* The total number of indexables without prominent words.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
* @param int $limit Limit the number of unindexed objects that are counted.
|
||||
*
|
||||
* @return array The indexables data to use for generating prominent words.
|
||||
* @return int|false The total number of indexables without prominent words. False if the query fails.
|
||||
*/
|
||||
public function get() {
|
||||
\_deprecated_function( __METHOD__, '15.1', 'Content_Action::index' );
|
||||
return $this->index();
|
||||
public function get_limited_unindexed_count( $limit ) {
|
||||
return $this->get_total_unindexed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +153,9 @@ class Content_Action implements Indexation_Action_Interface {
|
||||
->limit( $this->get_limit() )
|
||||
->find_many();
|
||||
|
||||
\delete_transient( static::TRANSIENT_CACHE_KEY );
|
||||
if ( \count( $indexables ) > 0 ) {
|
||||
\delete_transient( static::TRANSIENT_CACHE_KEY );
|
||||
}
|
||||
|
||||
// If no indexables have been left unindexed, return the empty array.
|
||||
if ( ! $indexables ) {
|
||||
|
||||
@@ -4,15 +4,16 @@ namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Exception;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
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;
|
||||
use Yoast\WP\SEO\Repositories\Prominent_Words_Repository;
|
||||
|
||||
/**
|
||||
* Action for linking a list of prominent words to an indexable.
|
||||
* Action for updating the prominent words in the prominent words table,
|
||||
* and linking them to an indexable.
|
||||
*
|
||||
* @see \Yoast\WP\SEO\Routes\Prominent_Words_Route;
|
||||
* @see \Yoast\WP\SEO\Premium\Routes\Prominent_Words_Route;
|
||||
*/
|
||||
class Save_Action {
|
||||
|
||||
@@ -58,11 +59,11 @@ class Save_Action {
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a list of prominent words to an indexable.
|
||||
* 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.
|
||||
*
|
||||
* Deletes the prominent words that have been stored previously, but are not in the new list of prominent words.
|
||||
*
|
||||
* @param array $data The data to process.
|
||||
* @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).
|
||||
*/
|
||||
public function save( $data ) {
|
||||
if ( $data ) {
|
||||
@@ -75,7 +76,7 @@ class Save_Action {
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a list of prominent words to an indexable.
|
||||
* 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.
|
||||
@@ -84,41 +85,87 @@ class Save_Action {
|
||||
public function link( $object_type, $object_id, $words ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
|
||||
$indexable->prominent_words_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
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 find_by_id_and_type will auto create an indexable object
|
||||
* with the correct data. So we are not saving an incomplete indexable.
|
||||
*/
|
||||
$indexable->save();
|
||||
/*
|
||||
* 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();
|
||||
|
||||
$old_words = $this->prominent_words_repository->find_by_indexable_id( $indexable->id );
|
||||
// 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 ) {
|
||||
// Remove when old word isn't found.
|
||||
// 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 ) ) {
|
||||
$old_word->delete();
|
||||
$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 ] );
|
||||
}
|
||||
|
||||
// Create all new words that are not yet in the database.
|
||||
$this->create_words( $indexable->id, $words );
|
||||
// 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.
|
||||
* (Does not update when the weights are the same).
|
||||
* 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 ( $word->weight !== $new_weight ) {
|
||||
if ( \abs( $word->weight - $new_weight ) > 0.1 ) {
|
||||
$word->weight = $new_weight;
|
||||
$word->save();
|
||||
}
|
||||
@@ -128,24 +175,34 @@ class Save_Action {
|
||||
* 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 words to create, as a `'stem'` => weight` map.
|
||||
* @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_word = $this->prominent_words_repository->query()->create(
|
||||
$new_model = $this->prominent_words_repository->query()->create(
|
||||
[
|
||||
'indexable_id' => $indexable_id,
|
||||
'stem' => $stem,
|
||||
'weight' => $weight,
|
||||
]
|
||||
);
|
||||
$new_models[] = $new_model;
|
||||
}
|
||||
|
||||
try {
|
||||
$new_word->save();
|
||||
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.
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class Addon_Installer {
|
||||
/**
|
||||
* The minimum Yoast SEO version required.
|
||||
*/
|
||||
const MINIMUM_YOAST_SEO_VERSION = '16.4';
|
||||
const MINIMUM_YOAST_SEO_VERSION = '21.5';
|
||||
|
||||
/**
|
||||
* The base directory for the installer.
|
||||
@@ -198,13 +198,22 @@ class Addon_Installer {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( ! \defined( 'WPSEO_VERSION' ) || \version_compare( \WPSEO_VERSION, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '<' ) ) {
|
||||
if ( ! self::is_yoast_seo_up_to_date() ) {
|
||||
\delete_option( self::OPTION_KEY );
|
||||
if ( ! \defined( 'WPSEO_VERSION' ) ) {
|
||||
$this->load_yoast_seo_from_vendor_directory();
|
||||
@@ -361,8 +370,9 @@ class Addon_Installer {
|
||||
* @throws Exception If the move failed.
|
||||
*/
|
||||
protected function move_vendor_directory() {
|
||||
if ( ! \rename( $this->base_dir . '/vendor/yoast/wordpress-seo', $this->yoast_seo_dir ) ) {
|
||||
throw new Exception( 'Could not automatically installed Yoast SEO' );
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Prevent a potential PHP warning on Windows.
|
||||
if ( ! @\rename( $this->base_dir . '/vendor/yoast/wordpress-seo', $this->yoast_seo_dir ) ) {
|
||||
throw new Exception( 'Could not automatically install Yoast SEO' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use Yoast\WP\SEO\Config\Badge_Group_Names as New_Badge_Group_Names;
|
||||
* to be "new".
|
||||
*/
|
||||
class Badge_Group_Names extends New_Badge_Group_Names {
|
||||
|
||||
const GROUP_GLOBAL_TEMPLATES = 'global-templates';
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ class Badge_Group_Names extends New_Badge_Group_Names {
|
||||
/**
|
||||
* Badge_Group_Names constructor.
|
||||
*
|
||||
* @param string $version Optional: the current version number.
|
||||
* @param string|null $version Optional. The current version number.
|
||||
*/
|
||||
public function __construct( $version = null ) {
|
||||
parent::__construct( $version );
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class Migration_Runner_Premium extends Migration_Runner {
|
||||
/**
|
||||
* Runs this initializer.
|
||||
*
|
||||
* @inheritDoc
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->run_premium_migrations();
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?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 {
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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' ) ) {
|
||||
echo \sprintf(
|
||||
/* 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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\deprecated_function( __METHOD__, 'Yoast SEO Premium 20.7' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?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
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 20.5' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private function redirect_feed( $url, $reason ) {
|
||||
\header_remove( 'Content-Type' );
|
||||
\header_remove( 'Last-Modified' );
|
||||
|
||||
$this->cache_control_header( 7 * \DAY_IN_SECONDS );
|
||||
|
||||
\wp_safe_redirect( $url, 301, 'Yoast SEO: ' . $reason );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the queried post type.
|
||||
*
|
||||
* @return string The queried post type.
|
||||
*/
|
||||
private function get_queried_post_type() {
|
||||
$post_type = \get_query_var( 'post_type' );
|
||||
if ( \is_array( $post_type ) ) {
|
||||
$post_type = \reset( $post_type );
|
||||
}
|
||||
return $post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of an option is set to true.
|
||||
*
|
||||
* @param string $option_name The option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_true( $option_name ) {
|
||||
return $this->options_helper->get( $option_name ) === true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?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
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
246
wp/wp-content/plugins/wordpress-seo-premium/src/deprecated/integrations/third-party/zapier.php
vendored
Normal file
246
wp/wp-content/plugins/wordpress-seo-premium/src/deprecated/integrations/third-party/zapier.php
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
<?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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?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
|
||||
*/
|
||||
const ROUTE_PREFIX = 'zapier';
|
||||
|
||||
/**
|
||||
* The subscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/subscribe';
|
||||
|
||||
/**
|
||||
* The unsubscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UNSUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/unsubscribe';
|
||||
|
||||
/**
|
||||
* The check route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CHECK_API_KEY_ROUTE = self::ROUTE_PREFIX . '/check';
|
||||
|
||||
/**
|
||||
* The perform list route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PERFORM_LIST = self::ROUTE_PREFIX . '/list';
|
||||
|
||||
/**
|
||||
* The is_connected route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IS_CONNECTED = self::ROUTE_PREFIX . '/is_connected';
|
||||
|
||||
/**
|
||||
* The reset_api_key route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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 ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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 {
|
||||
|
||||
const YOAST_JOB_POSTING = 'yoast_job_posting';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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 {
|
||||
|
||||
const YOAST_JOB_POSTING = [ 'yoast', 'job', 'posting', 'vacancy' ];
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A minimal job posting, containing required blocks only.
|
||||
*
|
||||
* @deprecated 20.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?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
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
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 -->';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
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 -->';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 403 - Forbidden response.
|
||||
*/
|
||||
class Forbidden_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 402 - payment required response.
|
||||
*/
|
||||
class Payment_Required_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class Remote_Request_Exception
|
||||
*/
|
||||
abstract class Remote_Request_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Exceptions\Remote_Request;
|
||||
|
||||
/**
|
||||
* Class to manage a 401 - unauthorized response.
|
||||
*/
|
||||
class Unauthorized_Exception extends Remote_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -5,12 +5,7 @@
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*/
|
||||
|
||||
if ( ! defined( 'WPSEO_PREMIUM_VERSION' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
use Yoast\WP\SEO\Premium\Addon_Installer;
|
||||
use Yoast\WP\SEO\Premium\Main;
|
||||
|
||||
/**
|
||||
@@ -25,7 +20,8 @@ function YoastSEOPremium() {
|
||||
|
||||
static $main;
|
||||
if ( did_action( 'wpseo_loaded' ) ) {
|
||||
if ( $main === null ) {
|
||||
$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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use RuntimeException;
|
||||
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.
|
||||
*
|
||||
* @throws \RuntimeException Unable to retrieve the code verifier.
|
||||
*
|
||||
* @return string 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
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.AlternativeFunctions.json_encode_wp_json_encode -- 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 ] = $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 );
|
||||
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 ) );
|
||||
|
||||
if ( $response_code !== 200 && $response_code !== 0 ) {
|
||||
$json_body = \json_decode( \wp_remote_retrieve_body( $response ) );
|
||||
if ( $json_body !== null ) {
|
||||
$response_message = isset( $json_body->error_code ) ? $json_body->error_code : $this->map_message_to_code( $json_body->message );
|
||||
}
|
||||
}
|
||||
|
||||
return [ $response_code, $response_message ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws \RuntimeException Unable to retrieve the access token.
|
||||
*
|
||||
* @return string The access JWT.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws \RuntimeException Unable to retrieve the refresh token.
|
||||
*
|
||||
* @return string The access JWT.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- doing a strict in_array check should be sufficient.
|
||||
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,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Helpers;
|
||||
namespace Yoast\WP\SEO\Premium\Helpers;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Helper.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Helpers
|
||||
*/
|
||||
class Prominent_Words_Helper {
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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 ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Yoast_Admin_Conditional;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Initializes Premium introductions.
|
||||
*/
|
||||
class Introductions_Initializer implements Initializer_Interface {
|
||||
const SCRIPT_HANDLE = 'wp-seo-premium-introductions';
|
||||
|
||||
use Current_Page_Trait;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Yoast_Admin_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.
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\wp_enqueue_script( self::SCRIPT_HANDLE );
|
||||
\wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'wpseoPremiumIntroductions',
|
||||
[
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@ class Plugin implements Initializer_Interface {
|
||||
public function wpseo_premium_deactivate() {
|
||||
\do_action( 'wpseo_register_capabilities_premium' );
|
||||
WPSEO_Capability_Manager_Factory::get( 'premium' )->remove();
|
||||
|
||||
$this->options_helper->set( 'tracking', false );
|
||||
if ( $this->options_helper->get( 'toggled_tracking' ) !== true ) {
|
||||
$this->options_helper->set( 'tracking', false );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Initializers;
|
||||
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.
|
||||
@@ -33,20 +35,6 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
*/
|
||||
protected $is_redirected = false;
|
||||
|
||||
/**
|
||||
* The options where the URL redirects are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $normal_option_name = 'wpseo-premium-redirects-export-plain';
|
||||
|
||||
/**
|
||||
* The option name where the regex redirects are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $regex_option_name = 'wpseo-premium-redirects-export-regex';
|
||||
|
||||
/**
|
||||
* The URL that is called at the moment.
|
||||
*
|
||||
@@ -81,15 +69,15 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +162,7 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
*/
|
||||
protected function handle_normal_redirects( $request_url ) {
|
||||
// Setting the redirects.
|
||||
$redirects = $this->get_redirects( $this->normal_option_name );
|
||||
$redirects = $this->get_redirects( WPSEO_Redirect_Option::OPTION_PLAIN );
|
||||
$this->redirects = $this->normalize_redirects( $redirects );
|
||||
|
||||
$request_url = $this->normalize_url( $request_url );
|
||||
@@ -218,7 +206,7 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
*/
|
||||
protected function handle_regex_redirects() {
|
||||
// Setting the redirects.
|
||||
$this->redirects = $this->get_redirects( $this->regex_option_name );
|
||||
$this->redirects = $this->get_redirects( WPSEO_Redirect_Option::OPTION_REGEX );
|
||||
|
||||
foreach ( $this->redirects as $regex => $redirect ) {
|
||||
// Check if the URL matches the $regex.
|
||||
@@ -270,7 +258,11 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
* @return array Returns the redirects for the given option.
|
||||
*/
|
||||
protected function get_redirects( $option ) {
|
||||
$redirects = $this->get_redirects_from_options();
|
||||
static $redirects;
|
||||
|
||||
if ( ! isset( $redirects[ $option ] ) ) {
|
||||
$redirects[ $option ] = \get_option( $option, false );
|
||||
}
|
||||
|
||||
if ( ! empty( $redirects[ $option ] ) ) {
|
||||
return $redirects[ $option ];
|
||||
@@ -356,23 +348,19 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI, with fallback for super global.
|
||||
* Gets the request URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_request_uri() {
|
||||
$options = [ 'options' => [ 'default' => '' ] ];
|
||||
$request_uri = \filter_input( \INPUT_SERVER, 'REQUEST_URI', \FILTER_SANITIZE_URL, $options );
|
||||
$request_uri = '';
|
||||
|
||||
// Because there isn't an usable value, try the fallback.
|
||||
if ( empty( $request_uri ) && isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- this value is compared. I don't want to change the behavior.
|
||||
$request_uri = \filter_var( $_SERVER['REQUEST_URI'], \FILTER_SANITIZE_URL, $options );
|
||||
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'] ) ) );
|
||||
}
|
||||
|
||||
$request_uri = $this->strip_subdirectory( $request_uri );
|
||||
|
||||
return \rawurldecode( $request_uri );
|
||||
return $this->strip_subdirectory( $request_uri );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -564,33 +552,6 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
return \home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirects from the option table in the database.
|
||||
*
|
||||
* @return array The stored redirects.
|
||||
*/
|
||||
protected function get_redirects_from_options() {
|
||||
global $wpdb;
|
||||
static $redirects;
|
||||
|
||||
if ( $redirects !== null ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
// The code below is needed because we used to not autoload our redirect options. This fixes that.
|
||||
$all_options = \wp_cache_get( 'alloptions', 'options' );
|
||||
foreach ( [ $this->normal_option_name, $this->regex_option_name ] as $option ) {
|
||||
$redirects[ $option ] = isset( $all_options[ $option ] ) ? \maybe_unserialize( $all_options[ $option ] ) : false;
|
||||
if ( $redirects[ $option ] === false ) {
|
||||
$redirects[ $option ] = \get_option( $option, false );
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Normal methods only work if the option value has changed.
|
||||
$wpdb->update( $wpdb->options, [ 'autoload' => 'yes' ], [ 'option_name' => $option ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hook for setting the template include. This is the file that we want to show.
|
||||
*
|
||||
@@ -691,4 +652,22 @@ class Redirect_Handler implements Initializer_Interface {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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.
|
||||
*/
|
||||
public function initialize() {
|
||||
\add_action( 'before_woocommerce_init', [ $this, 'declare_custom_order_tables_compatibility' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares compatibility with the WooCommerce HPOS feature.
|
||||
*/
|
||||
public function declare_custom_order_tables_compatibility() {
|
||||
if ( \class_exists( FeaturesUtil::class ) ) {
|
||||
FeaturesUtil::declare_compatibility( 'custom_order_tables', \WPSEO_PREMIUM_FILE, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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.
|
||||
*/
|
||||
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' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
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;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Social_Templates_Conditional;
|
||||
|
||||
/**
|
||||
* Class Abstract_OpenGraph_Integration.
|
||||
@@ -61,7 +61,7 @@ abstract class Abstract_OpenGraph_Integration implements Integration_Interface {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Social_Templates_Conditional::class ];
|
||||
return [ Open_Graph_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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\Premium\Conditionals\Ai_Editor_Conditional;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
Options_Helper $options_helper,
|
||||
User_Helper $user_helper
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
\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( $this->user_helper->get_current_user_id(), '_yoast_wpseo_ai_consent', true ),
|
||||
'hasValidSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
'postType' => \get_post_type(),
|
||||
]
|
||||
);
|
||||
$this->asset_manager->enqueue_style( 'premium-ai-generator' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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\Presenters\Icons\Checkmark_Icon_Presenter;
|
||||
use Yoast\WP\SEO\Premium\Presenters\Icons\Cross_Icon_Presenter;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Term_Overview_Or_Ajax_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped through the Score_Icon_Helper.
|
||||
return $this->score_icon_helper->for_inclusive_language( $score );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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,24 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
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\Prominent_Words_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\Routes\Prominent_Words_Route;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Routes\Prominent_Words_Route;
|
||||
|
||||
/**
|
||||
* Class Indexing_Integration.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin\Prominent_Words
|
||||
*/
|
||||
class Indexing_Integration implements Integration_Interface {
|
||||
|
||||
@@ -39,39 +39,11 @@ class Indexing_Integration implements Integration_Interface {
|
||||
const PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT = 30;
|
||||
|
||||
/**
|
||||
* Holds the content action.
|
||||
* All indexing actions.
|
||||
*
|
||||
* @var Content_Action
|
||||
* @var Indexation_Action_Interface[]
|
||||
*/
|
||||
protected $content_indexation_action;
|
||||
|
||||
/**
|
||||
* The post indexing action.
|
||||
*
|
||||
* @var Indexable_Post_Indexation_Action
|
||||
*/
|
||||
protected $post_indexation_action;
|
||||
|
||||
/**
|
||||
* The term indexing action.
|
||||
*
|
||||
* @var Indexable_Term_Indexation_Action
|
||||
*/
|
||||
protected $term_indexation_action;
|
||||
|
||||
/**
|
||||
* The post type archive indexing action.
|
||||
*
|
||||
* @var Indexable_Post_Type_Archive_Indexation_Action
|
||||
*/
|
||||
protected $post_type_archive_indexation_action;
|
||||
|
||||
/**
|
||||
* Represents the general indexing action.
|
||||
*
|
||||
* @var Indexable_General_Indexation_Action
|
||||
*/
|
||||
protected $general_indexation_action;
|
||||
protected $indexing_actions;
|
||||
|
||||
/**
|
||||
* Represents the language helper.
|
||||
@@ -80,6 +52,13 @@ class Indexing_Integration implements Integration_Interface {
|
||||
*/
|
||||
protected $language_helper;
|
||||
|
||||
/**
|
||||
* Represents the url helper.
|
||||
*
|
||||
* @var Url_Helper
|
||||
*/
|
||||
protected $url_helper;
|
||||
|
||||
/**
|
||||
* Represents the prominent words helper.
|
||||
*
|
||||
@@ -103,6 +82,7 @@ class Indexing_Integration implements Integration_Interface {
|
||||
* @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(
|
||||
@@ -112,15 +92,26 @@ class Indexing_Integration implements Integration_Interface {
|
||||
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->content_indexation_action = $content_indexation_action;
|
||||
$this->post_indexation_action = $post_indexation_action;
|
||||
$this->term_indexation_action = $term_indexation_action;
|
||||
$this->general_indexation_action = $general_indexation_action;
|
||||
$this->post_type_archive_indexation_action = $post_type_archive_indexation_action;
|
||||
$this->language_helper = $language_helper;
|
||||
$this->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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +126,7 @@ class Indexing_Integration implements Integration_Interface {
|
||||
|
||||
\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' ] );
|
||||
}
|
||||
|
||||
@@ -202,7 +194,10 @@ class Indexing_Integration implements Integration_Interface {
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_tools' || ( $_GET['page'] === 'wpseo_tools' && isset( $_GET['tool'] ) ) ) {
|
||||
if ( ! isset( $_GET['page'] )
|
||||
|| ( $_GET['page'] !== 'wpseo_tools' && $_GET['page'] !== 'wpseo_workouts' && $_GET['page'] !== 'wpseo_dashboard' )
|
||||
|| ( $_GET['page'] === 'wpseo_tools' && isset( $_GET['tool'] ) )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -210,6 +205,7 @@ class Indexing_Integration implements Integration_Interface {
|
||||
$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() ] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,14 +216,27 @@ class Indexing_Integration implements Integration_Interface {
|
||||
* @return int The total number of indexables to recalculate.
|
||||
*/
|
||||
public function get_unindexed_count( $unindexed_count ) {
|
||||
// Get the number of indexables that haven't had their prominent words indexed yet.
|
||||
$unindexed_count += $this->content_indexation_action->get_total_unindexed();
|
||||
foreach ( $this->indexing_actions as $indexing_action ) {
|
||||
$unindexed_count += $indexing_action->get_total_unindexed();
|
||||
}
|
||||
return $unindexed_count;
|
||||
}
|
||||
|
||||
// Take posts and terms into account that do not have indexables yet.
|
||||
$unindexed_count += $this->post_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->term_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->general_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->post_type_archive_indexation_action->get_total_unindexed();
|
||||
/**
|
||||
* 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,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
@@ -9,8 +9,6 @@ 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.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin\Prominent_Words
|
||||
*/
|
||||
class Metabox_Integration implements Integration_Interface {
|
||||
|
||||
@@ -40,7 +38,7 @@ class Metabox_Integration implements Integration_Interface {
|
||||
\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_filter( 'edit_term', [ $this, 'save_prominent_words_for_term' ] );
|
||||
\add_action( 'edit_term', [ $this, 'save_prominent_words_for_term' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function render_page() {
|
||||
require \WPSEO_PREMIUM_PATH . 'classes/views/thank-you.php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -174,8 +174,8 @@ class User_Profile_Integration implements Integration_Interface {
|
||||
* @param int $user_id User ID of the updated user.
|
||||
*/
|
||||
public function process_user_option_update( $user_id ) {
|
||||
$nonce_value = \filter_input( \INPUT_POST, self::NONCE_FIELD_NAME, \FILTER_SANITIZE_STRING );
|
||||
if ( empty( $nonce_value ) ) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -184,23 +184,6 @@ class User_Profile_Integration implements Integration_Interface {
|
||||
\update_user_meta( $user_id, 'wpseo_user_schema', $this->get_posted_user_fields() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the arguments for filter_var_array which makes sure we only get the fields that we've defined above.
|
||||
*
|
||||
* @return array Filter arguments.
|
||||
*/
|
||||
private function build_filter_args() {
|
||||
$args = [];
|
||||
foreach ( $this->fields as $key => $field ) {
|
||||
if ( $field['type'] === 'group' ) {
|
||||
continue;
|
||||
}
|
||||
$args[ $key ] = \FILTER_SANITIZE_STRING;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the posted user fields and sanitizes them.
|
||||
*
|
||||
@@ -209,14 +192,12 @@ class User_Profile_Integration implements Integration_Interface {
|
||||
* @return array The posted user fields, restricted to allowed fields.
|
||||
*/
|
||||
private function get_posted_user_fields() {
|
||||
$args = [
|
||||
'wpseo_user_schema' => [
|
||||
'filter' => \FILTER_SANITIZE_STRING,
|
||||
'flags' => \FILTER_FORCE_ARRAY,
|
||||
],
|
||||
];
|
||||
$user_schema = \filter_input_array( \INPUT_POST, $args )['wpseo_user_schema'];
|
||||
$user_schema = \filter_var_array( $user_schema, $this->build_filter_args(), false );
|
||||
$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'] ) {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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,9 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
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 {
|
||||
|
||||
@@ -74,6 +78,13 @@ class Estimated_Reading_Time_Block extends Dynamic_Block {
|
||||
* @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(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
<?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
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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', '/' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?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.AlternativeFunctions.json_encode_wp_json_encode -- 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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use function get_post;
|
||||
|
||||
/**
|
||||
* Integration to add Publishing Principles to the Schema.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Publishing_Principles_Schema_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Constant holding the mapping between database option and actual schema name.
|
||||
*/
|
||||
public const PRINCIPLES_MAPPING = [
|
||||
[ 'publishing_principles_id', 'publishingPrinciples' ],
|
||||
[ 'ownership_funding_info_id', 'ownershipFundingInfo' ],
|
||||
[ 'actionable_feedback_policy_id', 'actionableFeedbackPolicy' ],
|
||||
[ 'corrections_policy_id', 'correctionsPolicy' ],
|
||||
[ 'ethics_policy_id', 'ethicsPolicy' ],
|
||||
[ 'diversity_policy_id', 'diversityPolicy' ],
|
||||
[ 'diversity_staffing_report_id', 'diversityStaffingReport' ],
|
||||
];
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper $indexable_helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper $post_type_helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper $options_helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishing_Principles_Schema_Integration constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Indexable_Repository $indexable_repository The indexables repository.
|
||||
* @param Indexable_Helper $indexable_helper The indexables helper.
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->post_type_helper = $post_type_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' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the Organization policies are added to the schema output.
|
||||
*
|
||||
* @param array $data The organization schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_organization_schema( $data ) {
|
||||
$policy_indexables = $this->get_indexables_for_publishing_principle_pages(
|
||||
self::PRINCIPLES_MAPPING
|
||||
);
|
||||
|
||||
foreach ( $policy_indexables as $policy_data ) {
|
||||
$data = $this->add_schema_piece( $data, $policy_data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the data to the schema array.
|
||||
*
|
||||
* @param array $schema_graph The current schema graph.
|
||||
* @param array $policy_data The data present for a policy.
|
||||
*
|
||||
* @return array The new schema graph.
|
||||
*/
|
||||
private function add_schema_piece( $schema_graph, $policy_data ): array {
|
||||
if ( ! \is_null( $policy_data['permalink'] ) ) {
|
||||
$schema_graph[ $policy_data['schema'] ] = $policy_data['permalink'];
|
||||
}
|
||||
|
||||
return $schema_graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the indexables for all the given principles if they are set.
|
||||
*
|
||||
* @param array $principles_data The data for all the principles.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_indexables_for_publishing_principle_pages( $principles_data ): array {
|
||||
$principle_ids = [];
|
||||
$policies = [];
|
||||
$ids = [];
|
||||
foreach ( $principles_data as $principle ) {
|
||||
$option_value = $this->options_helper->get( $principle[0], false );
|
||||
if ( $option_value ) {
|
||||
$principle_ids[ $principle[0] ] = [
|
||||
'value' => $option_value,
|
||||
'schema' => $principle[1],
|
||||
];
|
||||
$ids[] = $option_value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $ids ) === 0 ) {
|
||||
// Early return to not run an empty query.
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( $this->indexable_helper->should_index_indexables() && $this->post_type_helper->is_of_indexable_post_type( 'page' ) ) {
|
||||
$indexables = $this->indexable_repository->find_by_multiple_ids_and_type( array_unique( $ids ), 'post' );
|
||||
|
||||
foreach ( $principle_ids as $key => $principle_id ) {
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( $indexable && $principle_id['value'] === $indexable->object_id ) {
|
||||
if ( $indexable->post_status === 'publish' && $indexable->is_protected === false ) {
|
||||
$policies[ $key ] = [
|
||||
'permalink' => $indexable->permalink,
|
||||
'schema' => $principle_id['schema'],
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $policies;
|
||||
}
|
||||
|
||||
foreach ( $principle_ids as $key => $principle_id ) {
|
||||
foreach ( $ids as $post_id ) {
|
||||
$post = get_post( (int) $post_id );
|
||||
if ( is_object( $post ) ) {
|
||||
if ( (int) $principle_id['value'] === (int) $post_id && \get_post_status( $post_id ) === 'publish' && $post->post_password === '' ) {
|
||||
$policies[ $key ] = [
|
||||
'permalink' => get_permalink( $post_id ),
|
||||
'schema' => $principle_id['schema'],
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $policies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Routes;
|
||||
|
||||
use RuntimeException;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Premium\Actions\AI_Generator_Action;
|
||||
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;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Registers the route for the AI_Generator integration.
|
||||
*/
|
||||
class AI_Generator_Route implements Route_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The AI_Generator route prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ROUTE_PREFIX = 'ai_generator';
|
||||
|
||||
/**
|
||||
* The callback route constant (invoked by the API).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CALLBACK_ROUTE = self::ROUTE_PREFIX . '/callback';
|
||||
|
||||
/**
|
||||
* The refresh callback route constant (invoked by the API).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REFRESH_CALLBACK_ROUTE = self::ROUTE_PREFIX . '/refresh_callback';
|
||||
|
||||
/**
|
||||
* The get_suggestions route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GET_SUGGESTIONS_ROUTE = self::ROUTE_PREFIX . '/get_suggestions';
|
||||
|
||||
/**
|
||||
* The get_suggestions route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CONSENT_ROUTE = self::ROUTE_PREFIX . '/consent';
|
||||
|
||||
/**
|
||||
* The bust_subscription_cache route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BUST_SUBSCRIPTION_CACHE_ROUTE = self::ROUTE_PREFIX . '/bust_subscription_cache';
|
||||
|
||||
/**
|
||||
* Instance of the AI_Generator_Action.
|
||||
*
|
||||
* @var AI_Generator_Action
|
||||
*/
|
||||
protected $ai_generator_action;
|
||||
|
||||
/**
|
||||
* Instance of the AI_Generator_Helper.
|
||||
*
|
||||
* @var AI_Generator_Helper
|
||||
*/
|
||||
protected $ai_generator_helper;
|
||||
|
||||
/**
|
||||
* AI_Generator_Route constructor.
|
||||
*
|
||||
* @param AI_Generator_Action $ai_generator_action The action to handle the requests to the endpoint.
|
||||
* @param AI_Generator_Helper $ai_generator_helper The AI_Generator helper.
|
||||
*/
|
||||
public function __construct( AI_Generator_Action $ai_generator_action, AI_Generator_Helper $ai_generator_helper ) {
|
||||
$this->ai_generator_action = $ai_generator_action;
|
||||
$this->ai_generator_helper = $ai_generator_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::CONSENT_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'consent' => [
|
||||
'required' => true,
|
||||
'type' => 'boolean',
|
||||
'description' => 'Whether the consent to use AI-based services has been given by the user.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'consent' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
|
||||
// Avoid registering the other routes if the feature is not enabled.
|
||||
if ( ! $this->ai_generator_helper->is_ai_generator_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'access_jwt' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The access JWT.',
|
||||
],
|
||||
'refresh_jwt' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The JWT to be used when the access JWT needs to be refreshed.',
|
||||
],
|
||||
'code_challenge' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The SHA266 of the verification code used to check the authenticity of a callback call.',
|
||||
],
|
||||
'user_id' => [
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'description' => 'The id of the user associated to the code verifier.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'callback' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::CALLBACK_ROUTE, $callback_route_args );
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::REFRESH_CALLBACK_ROUTE, $callback_route_args );
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::GET_SUGGESTIONS_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'type' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'enum' => [
|
||||
'seo-title',
|
||||
'meta-description',
|
||||
],
|
||||
'description' => 'The type of suggestion requested.',
|
||||
],
|
||||
'prompt_content' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The content needed by the prompt to ask for suggestions.',
|
||||
],
|
||||
'focus_keyphrase' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The focus keyphrase associated to the post.',
|
||||
],
|
||||
'language' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The language the post is written in.',
|
||||
],
|
||||
'platform' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'enum' => [
|
||||
'Google',
|
||||
'Facebook',
|
||||
'Twitter',
|
||||
],
|
||||
'description' => 'The platform the post is intended for.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'get_suggestions' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::BUST_SUBSCRIPTION_CACHE_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [],
|
||||
'callback' => [ $this, 'bust_subscription_cache' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to store connection credentials and the tokens locally.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the callback action.
|
||||
*/
|
||||
public function callback( WP_REST_Request $request ) {
|
||||
try {
|
||||
$code_verifier = $this->ai_generator_action->callback( $request['access_jwt'], $request['refresh_jwt'], $request['code_challenge'], $request['user_id'] );
|
||||
} catch ( Unauthorized_Exception $e ) {
|
||||
return new WP_REST_Response( 'Unauthorized.', 401 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'message' => 'Tokens successfully stored.',
|
||||
'code_verifier' => $code_verifier,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to get ai-generated suggestions.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the get_suggestions action.
|
||||
*/
|
||||
public function get_suggestions( WP_REST_Request $request ) {
|
||||
try {
|
||||
$user = \wp_get_current_user();
|
||||
$data = $this->ai_generator_action->get_suggestions( $user, $request['type'], $request['prompt_content'], $request['focus_keyphrase'], $request['language'], $request['platform'] );
|
||||
} catch ( Bad_Request_Exception | Forbidden_Exception | Internal_Server_Error_Exception | Not_Found_Exception | Payment_Required_Exception | Request_Timeout_Exception | Service_Unavailable_Exception | Too_Many_Requests_Exception | Unauthorized_Exception $e ) {
|
||||
return new WP_REST_Response( $e->getMessage(), $e->getCode() );
|
||||
} catch ( RuntimeException $e ) {
|
||||
return new WP_REST_Response( 'Failed to get suggestions.', 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to store the consent given by the user to use AI-based services.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the callback action.
|
||||
*/
|
||||
public function consent( WP_REST_Request $request ) {
|
||||
$user_id = \get_current_user_id();
|
||||
$consent = \boolval( $request['consent'] );
|
||||
|
||||
try {
|
||||
$this->ai_generator_action->consent( $user_id, $consent );
|
||||
} catch ( Bad_Request_Exception | Forbidden_Exception | Internal_Server_Error_Exception | Not_Found_Exception | Payment_Required_Exception | Request_Timeout_Exception | Service_Unavailable_Exception | Too_Many_Requests_Exception | RuntimeException $e ) {
|
||||
return new WP_REST_Response( ( $consent ) ? 'Failed to store consent.' : 'Failed to revoke consent.', 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( ( $consent ) ? 'Consent successfully stored.' : 'Consent successfully revoked.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback that busts the subscription cache.
|
||||
*
|
||||
* @return WP_REST_Response The response of the callback action.
|
||||
*/
|
||||
public function bust_subscription_cache() {
|
||||
$this->ai_generator_action->bust_subscription_cache();
|
||||
|
||||
return new WP_REST_Response( 'Subscription cache successfully busted.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks:
|
||||
* - if the user is logged
|
||||
* - if the user can edit posts
|
||||
*
|
||||
* @return bool Whether the user is logged in, can edit posts and the feature is active.
|
||||
*/
|
||||
public function check_permissions() {
|
||||
$user = \wp_get_current_user();
|
||||
if ( $user === null || $user->ID < 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \user_can( $user, 'edit_posts' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Routes;
|
||||
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Shortlinker;
|
||||
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\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Workouts_Routes_Integration class
|
||||
*/
|
||||
class Workouts_Routes_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Allowed cornerstone steps.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const ALLOWED_CORNERSTONE_STEPS = [
|
||||
'chooseCornerstones',
|
||||
'checkLinks',
|
||||
'addLinks',
|
||||
'improved',
|
||||
'skipped',
|
||||
];
|
||||
|
||||
/**
|
||||
* Allowed orphaned steps.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const ALLOWED_ORPHANED_STEPS = [
|
||||
'improveRemove',
|
||||
'update',
|
||||
'addLinks',
|
||||
'removed',
|
||||
'noindexed',
|
||||
'improved',
|
||||
'skipped',
|
||||
];
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository The indexable repository.
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* The link suggestions action.
|
||||
*
|
||||
* @var Link_Suggestions_Action The action.
|
||||
*/
|
||||
private $link_suggestions_action;
|
||||
|
||||
/**
|
||||
* The admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Workouts_Integration constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexables repository.
|
||||
* @param Link_Suggestions_Action $link_suggestions_action The link suggestions action.
|
||||
* @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager.
|
||||
* @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,
|
||||
Link_Suggestions_Action $link_suggestions_action,
|
||||
WPSEO_Admin_Asset_Manager $admin_asset_manager,
|
||||
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->link_suggestions_action = $link_suggestions_action;
|
||||
$this->admin_asset_manager = $admin_asset_manager;
|
||||
$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_filter( 'Yoast\WP\SEO\workouts_route_args', [ $this, 'add_args_to_set_workouts_route' ] );
|
||||
\add_filter( 'Yoast\WP\SEO\workouts_route_save', [ $this, 'save_workouts_data' ], 10, 2 );
|
||||
\add_filter( 'Yoast\WP\SEO\workouts_options', [ $this, 'get_options' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds arguments to `set_workouts` route registration.
|
||||
*
|
||||
* @param array $args_array The existing array of arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_args_to_set_workouts_route( $args_array ) {
|
||||
$premium_args_array = [
|
||||
'cornerstone' => [
|
||||
'validate_callback' => [ $this, 'cornerstone_is_allowed' ],
|
||||
'required' => true,
|
||||
],
|
||||
'orphaned' => [
|
||||
'validate_callback' => [ $this, 'orphaned_is_allowed' ],
|
||||
'required' => true,
|
||||
],
|
||||
];
|
||||
|
||||
return \array_merge( $args_array, $premium_args_array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the cornerstone attribute.
|
||||
*
|
||||
* @param array $workout The cornerstone workout.
|
||||
* @return bool If the payload is valid or not.
|
||||
*/
|
||||
public function cornerstone_is_allowed( $workout ) {
|
||||
return $this->is_allowed( $workout, self::ALLOWED_CORNERSTONE_STEPS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the orphaned attribute.
|
||||
*
|
||||
* @param array $workout The orphaned workout.
|
||||
* @return bool If the payload is valid or not.
|
||||
*/
|
||||
public function orphaned_is_allowed( $workout ) {
|
||||
return $this->is_allowed( $workout, self::ALLOWED_ORPHANED_STEPS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a workout.
|
||||
*
|
||||
* @param array $workout The workout.
|
||||
* @param array $allowed_steps The allowed steps for this workout.
|
||||
* @return bool If the payload is valid or not.
|
||||
*/
|
||||
public function is_allowed( $workout, $allowed_steps ) {
|
||||
// Only 3 properties are allowed, the below validated finishedSteps property.
|
||||
if ( \count( $workout ) !== 3 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $workout['finishedSteps'] ) && \is_array( $workout['finishedSteps'] ) ) {
|
||||
foreach ( $workout['finishedSteps'] as $step ) {
|
||||
if ( ! \in_array( $step, $allowed_steps, true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Premium workouts data to the database.
|
||||
*
|
||||
* @param mixed|null $result The result of the previous save operations.
|
||||
* @param array $workouts_data The complete workouts data.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function save_workouts_data( $result, $workouts_data ) {
|
||||
$premium_workouts_data = [];
|
||||
$premium_workouts_data['cornerstone'] = $workouts_data['cornerstone'];
|
||||
$premium_workouts_data['orphaned'] = $workouts_data['orphaned'];
|
||||
|
||||
foreach ( $premium_workouts_data as $workout => $data ) {
|
||||
if ( isset( $data['indexablesByStep'] ) && \is_array( $data['indexablesByStep'] ) ) {
|
||||
foreach ( $data['indexablesByStep'] as $step => $indexables ) {
|
||||
if ( $step === 'removed' ) {
|
||||
continue;
|
||||
}
|
||||
$premium_workouts_data[ $workout ]['indexablesByStep'][ $step ] = \wp_list_pluck( $indexables, 'id' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->options_helper->set( 'workouts', $premium_workouts_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Premium workouts options from the database and adds it to the global array of workouts options.
|
||||
*
|
||||
* @param array $workouts_option The previous content of the workouts options.
|
||||
*
|
||||
* @return array The workouts options updated with the addition of the Premium workouts data.
|
||||
*/
|
||||
public function get_options( $workouts_option ) {
|
||||
$premium_option = $this->options_helper->get( 'workouts' );
|
||||
|
||||
if ( ! ( isset( $premium_option['orphaned']['indexablesByStep'] )
|
||||
&& \is_array( $premium_option['orphaned']['indexablesByStep'] )
|
||||
&& isset( $premium_option['cornerstone']['indexablesByStep'] )
|
||||
&& \is_array( $premium_option['cornerstone']['indexablesByStep'] ) )
|
||||
) {
|
||||
return \array_merge( $workouts_option, $premium_option );
|
||||
}
|
||||
|
||||
// Get all indexable ids from all workouts and all steps.
|
||||
$indexable_ids_in_workouts = [ 0 ];
|
||||
foreach ( [ 'orphaned', 'cornerstone' ] as $workout ) {
|
||||
foreach ( $premium_option[ $workout ]['indexablesByStep'] as $step => $indexables ) {
|
||||
if ( $step === 'removed' ) {
|
||||
continue;
|
||||
}
|
||||
foreach ( $indexables as $indexable_id ) {
|
||||
$indexable_ids_in_workouts[] = $indexable_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all indexables corresponding to the indexable ids.
|
||||
$indexables_in_workouts = $this->indexable_repository->find_by_ids( $indexable_ids_in_workouts );
|
||||
|
||||
// Extend the workouts option with the indexables data.
|
||||
foreach ( [ 'orphaned', 'cornerstone' ] as $workout ) {
|
||||
// Don't add indexables for steps that are not allowed.
|
||||
$premium_option[ $workout ]['finishedSteps'] = \array_values(
|
||||
\array_intersect(
|
||||
$premium_option[ $workout ]['finishedSteps'],
|
||||
[
|
||||
'orphaned' => self::ALLOWED_ORPHANED_STEPS,
|
||||
'cornerstone' => self::ALLOWED_CORNERSTONE_STEPS,
|
||||
][ $workout ]
|
||||
)
|
||||
);
|
||||
|
||||
// Don't add indexables that are not published or are no-indexed.
|
||||
foreach ( $premium_option[ $workout ]['indexablesByStep'] as $step => $indexables ) {
|
||||
if ( $step === 'removed' ) {
|
||||
continue;
|
||||
}
|
||||
$premium_option[ $workout ]['indexablesByStep'][ $step ] = \array_values(
|
||||
\array_filter(
|
||||
\array_map(
|
||||
static function( $indexable_id ) use ( $indexables_in_workouts ) {
|
||||
foreach ( $indexables_in_workouts as $updated_indexable ) {
|
||||
if ( \is_array( $indexable_id ) ) {
|
||||
$indexable_id = $indexable_id['id'];
|
||||
}
|
||||
if ( (int) $indexable_id === $updated_indexable->id ) {
|
||||
if ( $updated_indexable->post_status !== 'publish' && $updated_indexable->post_status !== null ) {
|
||||
return false;
|
||||
}
|
||||
if ( $updated_indexable->is_robots_noindex ) {
|
||||
return false;
|
||||
}
|
||||
return $updated_indexable;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
$indexables
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return \array_merge( $workouts_option, $premium_option );
|
||||
}
|
||||
}
|
||||
191
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/algolia.php
vendored
Normal file
191
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/algolia.php
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use WP_Term;
|
||||
use WP_User;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Algolia_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
use Yoast\WP\SEO\Surfaces\Values\Meta;
|
||||
|
||||
/**
|
||||
* BbPress integration.
|
||||
*/
|
||||
class Algolia implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The meta helper.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* Algolia constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Meta_Surface $meta The meta surface.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, Meta_Surface $meta ) {
|
||||
$this->options = $options;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Algolia_Enabled_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'algolia_searchable_post_shared_attributes', [ $this, 'add_attributes_post' ], 10, 2 );
|
||||
\add_filter( 'algolia_term_record', [ $this, 'add_attributes_term' ] );
|
||||
\add_filter( 'algolia_user_record', [ $this, 'add_attributes_user' ] );
|
||||
\add_filter( 'algolia_should_index_searchable_post', [ $this, 'blacklist_no_index_posts' ], 10, 2 );
|
||||
\add_filter( 'algolia_should_index_term', [ $this, 'blacklist_no_index_terms' ], 10, 2 );
|
||||
\add_filter( 'algolia_should_index_user', [ $this, 'blacklist_no_index_users' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the search result priority and the number of internal links to an article to Algolia's index.
|
||||
*
|
||||
* @param array $attributes The attributes Algolia should index.
|
||||
* @param WP_Post $post The post object that is being indexed.
|
||||
*
|
||||
* @return array The attributes Algolia should index.
|
||||
*/
|
||||
public function add_attributes_post( $attributes, $post ) {
|
||||
$meta = $this->meta->for_post( $post->ID );
|
||||
|
||||
return $this->add_attributes( $attributes, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the attributes for a term.
|
||||
*
|
||||
* @param array $attributes The recorded attributes.
|
||||
*
|
||||
* @return array The recorded attributes.
|
||||
*/
|
||||
public function add_attributes_term( $attributes ) {
|
||||
$meta = $this->meta->for_term( $attributes['objectID'] );
|
||||
|
||||
return $this->add_attributes( $attributes, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the attributes for a term.
|
||||
*
|
||||
* @param array $attributes The recorded attributes.
|
||||
*
|
||||
* @return array The recorded attributes.
|
||||
*/
|
||||
public function add_attributes_user( $attributes ) {
|
||||
$meta = $this->meta->for_author( $attributes['objectID'] );
|
||||
|
||||
return $this->add_attributes( $attributes, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the attributes for a searchable object.
|
||||
*
|
||||
* @param array $attributes Attributes to update.
|
||||
* @param Meta $meta Meta value object for the current object.
|
||||
*
|
||||
* @return array Attributes for the searchable object.
|
||||
*/
|
||||
private function add_attributes( array $attributes, Meta $meta ) {
|
||||
$attributes['yoast_seo_links'] = (int) $meta->indexable->incoming_link_count;
|
||||
$attributes['yoast_seo_metadesc'] = $meta->meta_description;
|
||||
|
||||
return $this->add_social_image( $attributes, $meta->open_graph_images );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the social image to an attributes array if we have one.
|
||||
*
|
||||
* @param array $attributes The array of search attributes for a record.
|
||||
* @param array $og_images The social images for the current item.
|
||||
*
|
||||
* @return array The array of search attributes for a record.
|
||||
*/
|
||||
private function add_social_image( $attributes, $og_images ) {
|
||||
if ( \is_array( $og_images ) && \count( $og_images ) > 0 ) {
|
||||
$attributes['images']['social'] = \reset( $og_images );
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a post should be indexed, taking the Yoast SEO no-index state into account.
|
||||
*
|
||||
* @param bool $should_index Whether Algolia should index the post or not.
|
||||
* @param WP_Post $post The post object.
|
||||
*
|
||||
* @return bool Whether Algolia should index the post or not.
|
||||
*/
|
||||
public function blacklist_no_index_posts( $should_index, $post ) {
|
||||
if ( $this->meta->for_post( $post->ID )->robots['index'] === 'noindex' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $should_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a term should be indexed, taking the Yoast SEO no-index state into account.
|
||||
*
|
||||
* @param bool $should_index Whether Algolia should index the term or not.
|
||||
* @param WP_Term $term The term object.
|
||||
*
|
||||
* @return bool Whether Algolia should index the term or not.
|
||||
*/
|
||||
public function blacklist_no_index_terms( $should_index, $term ) {
|
||||
if ( $this->meta->for_term( $term->term_id )->robots['index'] === 'noindex' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $should_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user should be indexed, taking the Yoast SEO no-index state into account.
|
||||
*
|
||||
* @param bool $should_index Whether Algolia should index the user or not.
|
||||
* @param WP_User $user The user object.
|
||||
*
|
||||
* @return bool Whether Algolia should index the user or not.
|
||||
*/
|
||||
public function blacklist_no_index_users( $should_index, $user ) {
|
||||
if ( $this->meta->for_author( $user->ID )->robots['index'] === 'noindex' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $should_index;
|
||||
}
|
||||
}
|
||||
135
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/edd.php
vendored
Normal file
135
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/edd.php
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WPSEO_Schema_Context;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\EDD_Conditional;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* EDD integration.
|
||||
*/
|
||||
class EDD implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The meta surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Front_End_Conditional::class, EDD_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* EDD constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Meta_Surface $meta The meta surface.
|
||||
*/
|
||||
public function __construct( Meta_Surface $meta ) {
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'edd_generate_download_structured_data', [ $this, 'filter_download_schema' ] );
|
||||
\add_filter( 'wpseo_schema_organization', [ $this, 'filter_organization_schema' ] );
|
||||
\add_filter( 'wpseo_schema_webpage', [ $this, 'filter_webpage_schema' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the Organization is classified as a Brand too.
|
||||
*
|
||||
* @param array $data The organization schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_organization_schema( $data ) {
|
||||
if ( \is_singular( 'download' ) ) {
|
||||
$data['@type'] = [ 'Organization', 'Brand' ];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the WebPage schema contains reference to the product.
|
||||
*
|
||||
* @param array $data The schema Webpage data.
|
||||
* @param WPSEO_Schema_Context $context Context object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_webpage_schema( $data, $context ) {
|
||||
if ( \is_singular( [ 'download' ] ) ) {
|
||||
$data['about'] = [ '@id' => $context->canonical . '#/schema/edd-product/' . \get_the_ID() ];
|
||||
$data['mainEntity'] = [ '@id' => $context->canonical . '#/schema/edd-product/' . \get_the_ID() ];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the structured data output for a download to tie into Yoast SEO's output.
|
||||
*
|
||||
* @param array $data Structured data for a download.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_download_schema( $data ) {
|
||||
$data['@id'] = $this->meta->for_current_page()->canonical . '#/schema/edd-product/' . \get_the_ID();
|
||||
$data['sku'] = (string) $data['sku'];
|
||||
$data['brand'] = $this->return_organization_node();
|
||||
$data['offers'] = $this->clean_up_offer( $data['offers'] );
|
||||
|
||||
if ( ! isset( $data['description'] ) ) {
|
||||
$data['description'] = $this->meta->for_current_page()->open_graph_description;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up EDD generated Offers.
|
||||
*
|
||||
* @param array $offer The schema array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function clean_up_offer( $offer ) {
|
||||
if ( \array_key_exists( 'priceValidUntil', $offer ) && $offer['priceValidUntil'] === null ) {
|
||||
unset( $offer['priceValidUntil'] );
|
||||
}
|
||||
$offer['seller'] = $this->return_organization_node();
|
||||
|
||||
return $offer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Schema node for the current site's Organization.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function return_organization_node() {
|
||||
return [
|
||||
'@type' => [ 'Organization', 'Brand' ],
|
||||
'@id' => $this->meta->for_home_page()->canonical . '#organization',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use WP_Post;
|
||||
use WPSEO_Admin_Asset_Yoast_Components_L10n;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use WPSEO_Capability_Utils;
|
||||
use WPSEO_Custom_Fields_Plugin;
|
||||
use WPSEO_Language_Utils;
|
||||
@@ -18,9 +18,11 @@ use WPSEO_Premium_Prominent_Words_Support;
|
||||
use WPSEO_Social_Previews;
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Admin\Prominent_Words\Indexing_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words\Indexing_Integration;
|
||||
use Yoast\WP\SEO\Premium\Integrations\Admin\Replacement_Variables_Integration;
|
||||
|
||||
/**
|
||||
* Elementor integration class for Yoast SEO Premium.
|
||||
@@ -34,6 +36,13 @@ class Elementor_Premium implements Integration_Interface {
|
||||
*/
|
||||
const SCRIPT_HANDLE = 'elementor-premium';
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
protected $current_page_helper;
|
||||
|
||||
/**
|
||||
* Represents the post.
|
||||
*
|
||||
@@ -68,10 +77,12 @@ class Elementor_Premium implements Integration_Interface {
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
*/
|
||||
public function __construct( Prominent_Words_Helper $prominent_words_helper ) {
|
||||
public function __construct( Prominent_Words_Helper $prominent_words_helper, Current_Page_Helper $current_page_helper ) {
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->post_watcher = new WPSEO_Post_Watcher();
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +123,9 @@ class Elementor_Premium implements Integration_Interface {
|
||||
$social_previews->enqueue_assets();
|
||||
$custom_fields = new WPSEO_Custom_Fields_Plugin();
|
||||
$custom_fields->enqueue();
|
||||
|
||||
$replacement_variables = new Replacement_Variables_Integration();
|
||||
$replacement_variables->enqueue_assets();
|
||||
}
|
||||
|
||||
// Below is mostly copied from `premium-metabox.php`.
|
||||
@@ -127,9 +141,6 @@ class Elementor_Premium implements Integration_Interface {
|
||||
\wp_enqueue_script( static::SCRIPT_HANDLE );
|
||||
\wp_enqueue_style( static::SCRIPT_HANDLE );
|
||||
|
||||
$localization = new WPSEO_Admin_Asset_Yoast_Components_L10n();
|
||||
$localization->localize_script( static::SCRIPT_HANDLE );
|
||||
|
||||
$premium_localization = new WPSEO_Premium_Asset_JS_L10n();
|
||||
$premium_localization->localize_script( static::SCRIPT_HANDLE );
|
||||
|
||||
@@ -142,17 +153,37 @@ class Elementor_Premium implements Integration_Interface {
|
||||
* @return void
|
||||
*/
|
||||
public function send_data_to_assets() {
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$assets_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
$data = [
|
||||
'restApi' => $this->get_rest_api_config(),
|
||||
'seoAnalysisEnabled' => $analysis_seo->is_enabled(),
|
||||
'licensedURL' => WPSEO_Utils::get_home_url(),
|
||||
'settingsPageUrl' => \admin_url( 'admin.php?page=wpseo_dashboard#top#features' ),
|
||||
'integrationsTabURL' => \admin_url( 'admin.php?page=wpseo_dashboard#top#integrations' ),
|
||||
'restApi' => $this->get_rest_api_config(),
|
||||
'seoAnalysisEnabled' => $analysis_seo->is_enabled(),
|
||||
'licensedURL' => WPSEO_Utils::get_home_url(),
|
||||
'settingsPageUrl' => \admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo-enable_link_suggestions' ),
|
||||
'integrationsTabURL' => \admin_url( 'admin.php?page=wpseo_integrations' ),
|
||||
'commonsScriptUrl' => \plugins_url(
|
||||
'assets/js/dist/commons-premium-' . $assets_manager->flatten_version( \WPSEO_PREMIUM_VERSION ) . \WPSEO_CSSJS_SUFFIX . '.js',
|
||||
\WPSEO_PREMIUM_FILE
|
||||
),
|
||||
'premiumAssessmentsScriptUrl' => \plugins_url(
|
||||
'assets/js/dist/register-premium-assessments-' . $assets_manager->flatten_version( \WPSEO_PREMIUM_VERSION ) . \WPSEO_CSSJS_SUFFIX . '.js',
|
||||
\WPSEO_PREMIUM_FILE
|
||||
),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
];
|
||||
if ( \defined( 'YOAST_SEO_TEXT_FORMALITY' ) && \YOAST_SEO_TEXT_FORMALITY === true ) {
|
||||
$data['textFormalityScriptUrl'] = \plugins_url(
|
||||
'assets/js/dist/register-text-formality-' . $assets_manager->flatten_version( \WPSEO_PREMIUM_VERSION ) . \WPSEO_CSSJS_SUFFIX . '.js',
|
||||
\WPSEO_PREMIUM_FILE
|
||||
);
|
||||
}
|
||||
$data = \array_merge( $data, $this->get_post_metabox_config() );
|
||||
|
||||
if ( \current_user_can( 'edit_others_posts' ) ) {
|
||||
$data['workoutsUrl'] = \admin_url( 'admin.php?page=wpseo_workouts' );
|
||||
}
|
||||
|
||||
// Use an extra level in the array to preserve booleans. WordPress sanitizes scalar values in the first level of the array.
|
||||
\wp_localize_script( static::SCRIPT_HANDLE, 'wpseoPremiumMetaboxData', [ 'data' => $data ] );
|
||||
}
|
||||
@@ -163,25 +194,23 @@ class Elementor_Premium implements Integration_Interface {
|
||||
* @return array The config.
|
||||
*/
|
||||
protected function get_post_metabox_config() {
|
||||
$insights_enabled = WPSEO_Options::get( 'enable_metabox_insights', false );
|
||||
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
if ( ! $prominent_words_support->is_post_type_supported( $this->get_metabox_post()->post_type ) ) {
|
||||
$insights_enabled = false;
|
||||
}
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
$is_prominent_words_available = $prominent_words_support->is_post_type_supported( $this->get_metabox_post()->post_type );
|
||||
|
||||
$site_locale = \get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
|
||||
return [
|
||||
'insightsEnabled' => ( $insights_enabled ) ? 'enabled' : 'disabled',
|
||||
'currentObjectId' => $this->get_metabox_post()->ID,
|
||||
'currentObjectType' => 'post',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $prominent_words_support->is_post_type_supported( $this->get_metabox_post()->post_type ),
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'currentObjectId' => $this->get_metabox_post()->ID,
|
||||
'currentObjectType' => 'post',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $is_prominent_words_available,
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'isProminentWordsAvailable' => $is_prominent_words_available,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -224,9 +253,9 @@ class Elementor_Premium implements Integration_Interface {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
$post = \filter_input( \INPUT_GET, 'post' );
|
||||
if ( ! empty( $post ) ) {
|
||||
$post_id = (int) WPSEO_Utils::validate_int( $post );
|
||||
$post_id = $this->current_page_helper->get_current_post_id();
|
||||
|
||||
if ( $post_id ) {
|
||||
|
||||
$this->post = \get_post( $post_id );
|
||||
|
||||
@@ -250,39 +279,13 @@ class Elementor_Premium implements Integration_Interface {
|
||||
protected function load_metabox() {
|
||||
// When the current page isn't a post related one.
|
||||
if ( WPSEO_Metabox::is_post_edit( $this->get_current_page() ) ) {
|
||||
return WPSEO_Post_Type::has_metabox_enabled( $this->get_current_post_type() );
|
||||
return WPSEO_Post_Type::has_metabox_enabled( $this->current_page_helper->get_current_post_type() );
|
||||
}
|
||||
|
||||
// Make sure ajax integrations are loaded.
|
||||
return \wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current post type.
|
||||
*
|
||||
* @codeCoverageIgnore It depends on external request input.
|
||||
*
|
||||
* @return string The post type.
|
||||
*/
|
||||
protected function get_current_post_type() {
|
||||
$post = \filter_input( \INPUT_GET, 'post', \FILTER_SANITIZE_STRING );
|
||||
|
||||
if ( $post ) {
|
||||
return \get_post_type( \get_post( $post ) );
|
||||
}
|
||||
|
||||
return \filter_input(
|
||||
\INPUT_GET,
|
||||
'post_type',
|
||||
\FILTER_SANITIZE_STRING,
|
||||
[
|
||||
'options' => [
|
||||
'default' => 'post',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the pagenow variable.
|
||||
*
|
||||
|
||||
175
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/mastodon.php
vendored
Normal file
175
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/mastodon.php
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Third_Party;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Social_Profiles_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Mastodon integration.
|
||||
*/
|
||||
class Mastodon implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Holds the social profiles helper.
|
||||
*
|
||||
* @var Social_Profiles_Helper
|
||||
*/
|
||||
protected $social_profiles_helper;
|
||||
|
||||
/**
|
||||
* Sets the helpers.
|
||||
*
|
||||
* @param Options_Helper $options_helper Options helper.
|
||||
* @param Social_Profiles_Helper $social_profiles_helper Social Profiles helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, Social_Profiles_Helper $social_profiles_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->social_profiles_helper = $social_profiles_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'add_social_link_tags' ], 10, 2 );
|
||||
\add_filter( 'wpseo_person_social_profile_fields', [ $this, 'add_mastodon_to_person_social_profile_fields' ], 11, 1 );
|
||||
\add_filter( 'wpseo_organization_social_profile_fields', [ $this, 'add_mastodon_to_organization_social_profile_fields' ], 11, 1 );
|
||||
\add_filter( 'wpseo_schema_person_social_profiles', [ $this, 'add_mastodon_to_person_schema' ], 10 );
|
||||
\add_filter( 'user_contactmethods', [ $this, 'add_mastodon_to_user_contactmethods' ], 10 );
|
||||
\add_filter( 'wpseo_mastodon_active', [ $this, 'check_mastodon_active' ], 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the social profiles presenter to the list of presenters to use.
|
||||
*
|
||||
* @param array $presenters The list of presenters.
|
||||
* @param string $page_type The page type for which the presenters have been collected.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_social_link_tags( $presenters, $page_type ) {
|
||||
// Bail out early if something's wrong with the presenters, let's not add any more confusion there.
|
||||
if ( ! \is_array( $presenters ) ) {
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) {
|
||||
$presenters = \array_merge( $presenters, [ 'Yoast\WP\SEO\Premium\Presenters\Mastodon_Link_Presenter' ] );
|
||||
}
|
||||
|
||||
return $presenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Mastodon to the list of social profiles.
|
||||
*
|
||||
* @param array $social_profile_fields The social profiles array.
|
||||
*
|
||||
* @return array The updated social profiles array.
|
||||
*/
|
||||
public function add_mastodon_to_person_social_profile_fields( $social_profile_fields ) {
|
||||
// Bail out early if something's wrong with the social profiles, let's not add any more confusion there.
|
||||
if ( ! \is_array( $social_profile_fields ) ) {
|
||||
return $social_profile_fields;
|
||||
}
|
||||
$social_profile_fields['mastodon'] = 'get_non_valid_url';
|
||||
|
||||
return $social_profile_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Mastodon to the list of social profiles.
|
||||
*
|
||||
* @param array $social_profile_fields The social profiles array.
|
||||
*
|
||||
* @return array The updated social profiles array.
|
||||
*/
|
||||
public function add_mastodon_to_organization_social_profile_fields( $social_profile_fields ) {
|
||||
// Bail out early if something's wrong with the social profiles, let's not add any more confusion there.
|
||||
if ( ! \is_array( $social_profile_fields ) ) {
|
||||
return $social_profile_fields;
|
||||
}
|
||||
$social_profile_fields['mastodon_url'] = 'get_non_valid_url';
|
||||
|
||||
return $social_profile_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Mastodon to the list of social profiles to add to a Person's Schema.
|
||||
*
|
||||
* @param array $social_profiles The social profiles array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_mastodon_to_person_schema( $social_profiles ) {
|
||||
// Bail out early if something's wrong with the social profiles, let's not add any more confusion there.
|
||||
if ( ! \is_array( $social_profiles ) ) {
|
||||
return $social_profiles;
|
||||
}
|
||||
$social_profiles[] = 'mastodon';
|
||||
|
||||
return $social_profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Mastodon to the list of contact methods for persons.
|
||||
*
|
||||
* @param array $contactmethods Currently set contactmethods.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_mastodon_to_user_contactmethods( $contactmethods ) {
|
||||
// Bail out early if something's wrong with the contact methods, let's not add any more confusion there.
|
||||
if ( ! \is_array( $contactmethods ) ) {
|
||||
return $contactmethods;
|
||||
}
|
||||
|
||||
$contactmethods['mastodon'] = \__( 'Mastodon profile URL', 'wordpress-seo-premium' );
|
||||
|
||||
return $contactmethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Mastodon field is filled in.
|
||||
*
|
||||
* @param bool $state The current state of the integration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_mastodon_active( $state ) {
|
||||
switch ( $this->options_helper->get( 'company_or_person', false ) ) {
|
||||
case 'company':
|
||||
$social_profiles = $this->social_profiles_helper->get_organization_social_profiles();
|
||||
if ( ! empty( $social_profiles['mastodon_url'] ) ) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'person':
|
||||
$company_or_person_id = $this->options_helper->get( 'company_or_person_user_id', 0 );
|
||||
$social_profiles = $this->social_profiles_helper->get_person_social_profiles( $company_or_person_id );
|
||||
if ( ! empty( $social_profiles['mastodon'] ) ) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use DateTime;
|
||||
use stdClass;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Date_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
@@ -33,6 +36,13 @@ class TranslationsPress implements Integration_Interface {
|
||||
*/
|
||||
protected $api_url;
|
||||
|
||||
/**
|
||||
* The array to cache our addition to the `site_transient_update_plugins` filter.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $cached_translations;
|
||||
|
||||
/**
|
||||
* The Date helper object.
|
||||
*
|
||||
@@ -83,6 +93,7 @@ class TranslationsPress implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Filters the translations transients to include the private plugin or theme.
|
||||
* Caches our own return value to prevent heavy overhead.
|
||||
*
|
||||
* @param bool|object $value The transient value.
|
||||
*
|
||||
@@ -90,18 +101,26 @@ class TranslationsPress implements Integration_Interface {
|
||||
*/
|
||||
public function site_transient_update_plugins( $value ) {
|
||||
if ( ! $value ) {
|
||||
$value = new \stdClass();
|
||||
$value = new stdClass();
|
||||
}
|
||||
|
||||
if ( ! isset( $value->translations ) ) {
|
||||
$value->translations = [];
|
||||
}
|
||||
|
||||
if ( \is_array( $this->cached_translations ) ) {
|
||||
$value->translations = \array_merge( $value->translations, $this->cached_translations );
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->cached_translations = [];
|
||||
|
||||
$translations = $this->get_translations();
|
||||
if ( empty( $translations[ $this->slug ]['translations'] ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// The following call is the reason we need to cache the results of this method.
|
||||
$installed_translations = \wp_get_installed_translations( 'plugins' );
|
||||
$available_languages = \get_available_languages();
|
||||
foreach ( $translations[ $this->slug ]['translations'] as $translation ) {
|
||||
@@ -110,18 +129,19 @@ class TranslationsPress implements Integration_Interface {
|
||||
}
|
||||
|
||||
if ( isset( $installed_translations[ $this->slug ][ $translation['language'] ] ) && $translation['updated'] ) {
|
||||
$local = new \DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] );
|
||||
$remote = new \DateTime( $translation['updated'] );
|
||||
$local = new DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] );
|
||||
$remote = new DateTime( $translation['updated'] );
|
||||
|
||||
if ( $local >= $remote ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$translation['type'] = 'plugin';
|
||||
$translation['slug'] = $this->slug;
|
||||
$translation['autoupdate'] = true;
|
||||
$value->translations[] = $translation;
|
||||
$translation['type'] = 'plugin';
|
||||
$translation['slug'] = $this->slug;
|
||||
$translation['autoupdate'] = true;
|
||||
$value->translations[] = $translation;
|
||||
$this->cached_translations[] = $translation;
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
82
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/wincher-keyphrases.php
vendored
Normal file
82
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/wincher-keyphrases.php
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Third_Party;
|
||||
|
||||
use WPSEO_Meta;
|
||||
use Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Enhances the Wincher keyphrases arrays.
|
||||
*/
|
||||
class Wincher_Keyphrases implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Wincher_Enabled_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_wincher_keyphrases_from_post', [ $this, 'add_additional_keyphrases_from_post' ], 10, 2 );
|
||||
\add_filter( 'wpseo_wincher_all_keyphrases', [ $this, 'add_all_additional_keyphrases' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the keyphrases collected from a post with the additional ones.
|
||||
*
|
||||
* @param array $keyphrases The keyphrases array.
|
||||
* @param int $post_id The ID of the post.
|
||||
*
|
||||
* @return array The enhanced array.
|
||||
*/
|
||||
public function add_additional_keyphrases_from_post( $keyphrases, $post_id ) {
|
||||
$additional_keywords = \json_decode( WPSEO_Meta::get_value( 'focuskeywords', $post_id ), true );
|
||||
return \array_merge( $keyphrases, $additional_keywords );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the keyphrases collected from all the posts with the additional ones.
|
||||
*
|
||||
* @param array $keyphrases The keyphrases array.
|
||||
*
|
||||
* @return array The enhanced array.
|
||||
*/
|
||||
public function add_all_additional_keyphrases( $keyphrases ) {
|
||||
global $wpdb;
|
||||
$meta_key = WPSEO_Meta::$meta_prefix . 'focuskeywords';
|
||||
|
||||
$query = "
|
||||
SELECT meta_value
|
||||
FROM $wpdb->postmeta
|
||||
JOIN $wpdb->posts ON {$wpdb->posts}.id = {$wpdb->postmeta}.post_id
|
||||
WHERE meta_key = '$meta_key' AND post_status != 'trash'
|
||||
";
|
||||
|
||||
// phpcs:ignore -- ignoring since it's complaining about not using prepare when it's perfectly safe here.
|
||||
$results = $wpdb->get_results( $query );
|
||||
|
||||
if ( $results ) {
|
||||
foreach ( $results as $row ) {
|
||||
$additional_keywords = \json_decode( $row->meta_value, true );
|
||||
if ( $additional_keywords !== null ) {
|
||||
$additional_keywords = \array_column( $additional_keywords, 'keyword' );
|
||||
$keyphrases = \array_merge( $keyphrases, $additional_keywords );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $keyphrases;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user