plugin updates
This commit is contained in:
@@ -94,7 +94,9 @@ class AI_Generator_Action {
|
||||
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' ) {
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
|
||||
throw $this->handle_consent_revoked( $user->ID );
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
// Generate a verification code and store it in the database.
|
||||
@@ -204,7 +206,7 @@ class AI_Generator_Action {
|
||||
* @param string $platform The platform the post is intended for.
|
||||
* @param bool $retry_on_unauthorized Whether to retry when unauthorized (mechanism to retry once).
|
||||
*
|
||||
* @return array The suggestions.
|
||||
* @return string[] The suggestions.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
@@ -257,12 +259,86 @@ class AI_Generator_Action {
|
||||
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?).
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
|
||||
throw $this->handle_consent_revoked( $user->ID, $exception->getCode() );
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
return $this->ai_generator_helper->build_suggestions_array( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to generate improved copy through AI, that scores better on our content analysis' assessments.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
* @param string $assessment The assessment to improve.
|
||||
* @param string $prompt_content The excerpt taken from the post.
|
||||
* @param string $focus_keyphrase The focus keyphrase associated to the post.
|
||||
* @param string $synonyms Synonyms for the focus keyphrase.
|
||||
* @param string $language The language of the post.
|
||||
* @param bool $retry_on_unauthorized Whether to retry when unauthorized (mechanism to retry once).
|
||||
*
|
||||
* @return string The AI-generated content.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
public function fix_assessments(
|
||||
WP_User $user,
|
||||
string $assessment,
|
||||
string $prompt_content,
|
||||
string $focus_keyphrase,
|
||||
string $synonyms,
|
||||
string $language,
|
||||
bool $retry_on_unauthorized = true
|
||||
): string {
|
||||
$token = $this->get_or_request_access_token( $user );
|
||||
|
||||
// We are not sending the synonyms for now, as these are not used in the current prompts.
|
||||
$request_body = [
|
||||
'service' => 'openai',
|
||||
'user_id' => (string) $user->ID,
|
||||
'subject' => [
|
||||
'content' => $prompt_content,
|
||||
'focus_keyphrase' => $focus_keyphrase,
|
||||
'language' => $language,
|
||||
],
|
||||
];
|
||||
$request_headers = [
|
||||
'Authorization' => "Bearer $token",
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->ai_generator_helper->request( "/fix/assessments/seo-$assessment", $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->fix_assessments( $user, $assessment, $prompt_content, $focus_keyphrase, $synonyms, $language, false );
|
||||
} catch ( Forbidden_Exception $exception ) {
|
||||
// Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
|
||||
throw $this->handle_consent_revoked( $user->ID, $exception->getCode() );
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
return $this->ai_generator_helper->build_fixes_response( $prompt_content, $response );
|
||||
}
|
||||
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
@@ -335,7 +411,9 @@ class AI_Generator_Action {
|
||||
$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?).
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
|
||||
throw $this->handle_consent_revoked( $user->ID, $exception->getCode() );
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
$access_jwt = $this->ai_generator_helper->get_access_token( $user->ID );
|
||||
}
|
||||
@@ -385,6 +463,38 @@ class AI_Generator_Action {
|
||||
$this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_generator_refresh_jwt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to retrieve how much usage of the AI API has the current user had so far this month.
|
||||
*
|
||||
* @param WP_User $user The WP user.
|
||||
*
|
||||
* @return object<string, object<string>> The AI-generated content.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
* @throws Forbidden_Exception Forbidden_Exception.
|
||||
* @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
|
||||
* @throws Not_Found_Exception Not_Found_Exception.
|
||||
* @throws Payment_Required_Exception Payment_Required_Exception.
|
||||
* @throws Request_Timeout_Exception Request_Timeout_Exception.
|
||||
* @throws Service_Unavailable_Exception Service_Unavailable_Exception.
|
||||
* @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
|
||||
* @throws Unauthorized_Exception Unauthorized_Exception.
|
||||
* @throws RuntimeException Unable to retrieve the access token.
|
||||
*/
|
||||
public function get_usage(
|
||||
WP_User $user
|
||||
) {
|
||||
$token = $this->get_or_request_access_token( $user );
|
||||
$request_headers = [
|
||||
'Authorization' => "Bearer $token",
|
||||
];
|
||||
|
||||
$response = $this->ai_generator_helper->request( '/usage/' . \gmdate( 'Y-m' ), [], $request_headers, false );
|
||||
$json = \json_decode( $response->body );
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles consent revoked.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Exception;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_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;
|
||||
@@ -31,6 +32,13 @@ class Save_Action {
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* Contains helper function for prominent words.
|
||||
* For e.g. computing vector lengths and tf-idf scores.
|
||||
@@ -46,15 +54,20 @@ class Save_Action {
|
||||
* prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to read, update and delete
|
||||
* indexables from.
|
||||
*
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
$this->prominent_words_repository = $prominent_words_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
}
|
||||
|
||||
@@ -89,7 +102,7 @@ class Save_Action {
|
||||
public function link( $object_type, $object_id, $words ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
|
||||
if ( $indexable ) {
|
||||
if ( $indexable && $this->indexable_helper->should_index_indexable( $indexable ) ) {
|
||||
// Set the prominent words version number on the indexable.
|
||||
$indexable->prominent_words_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
|
||||
@@ -98,7 +111,7 @@ class Save_Action {
|
||||
* 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();
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
|
||||
// Find the prominent words that were already associated with this indexable.
|
||||
$old_words = $this->prominent_words_repository->find_by_indexable_id( $indexable->id );
|
||||
|
||||
@@ -23,7 +23,7 @@ class Addon_Installer {
|
||||
/**
|
||||
* The minimum Yoast SEO version required.
|
||||
*/
|
||||
public const MINIMUM_YOAST_SEO_VERSION = '22.9';
|
||||
public const MINIMUM_YOAST_SEO_VERSION = '23.7';
|
||||
|
||||
/**
|
||||
* The base directory for the installer.
|
||||
@@ -137,20 +137,29 @@ class Addon_Installer {
|
||||
}
|
||||
|
||||
echo (
|
||||
'<div class="error">'
|
||||
. '<p>'
|
||||
'<div class="error yoast-migrated-notice">'
|
||||
. '<h4 class="yoast-notice-migrated-header">'
|
||||
. \sprintf(
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required, %3$s: Yoast SEO Premium. */
|
||||
\esc_html__( '%1$s %2$s must be installed and activated in order to use %3$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION ),
|
||||
'Yoast SEO Premium'
|
||||
/* translators: %1$s: Yoast SEO */
|
||||
\esc_html__( 'Install latest %1$s', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO'
|
||||
)
|
||||
. '</p>'
|
||||
. '<p>'
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
|
||||
. $action
|
||||
. '</p>'
|
||||
. '</h4>'
|
||||
. '<div class="notice-yoast-content">'
|
||||
. '<p>'
|
||||
. \sprintf(
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required, %3$s: Yoast SEO Premium. */
|
||||
\esc_html__( '%1$s %2$s must be installed and activated in order to use %3$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION ),
|
||||
'Yoast SEO Premium'
|
||||
)
|
||||
. '</p>'
|
||||
. '<p>'
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
|
||||
. $action
|
||||
. '</p>'
|
||||
. '</div>'
|
||||
. '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application;
|
||||
|
||||
use DOMDocument;
|
||||
|
||||
/**
|
||||
* Class used to serialize the output dom to a string.
|
||||
*/
|
||||
class AI_Suggestions_Serializer {
|
||||
|
||||
/**
|
||||
* Serializes the output DOM to a string.
|
||||
*
|
||||
* @param DOMDocument $dom The output dom.
|
||||
*
|
||||
* @return string The serialized output dom.
|
||||
*/
|
||||
public function serialize( DOMDocument $dom ): string {
|
||||
$output_dom = new DOMDocument();
|
||||
$nodes = $dom->getElementsByTagName( 'body' )->item( 0 )->childNodes;
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
foreach ( $nodes as $node ) {
|
||||
if ( $node->nodeName !== 'meta' ) {
|
||||
$node = $output_dom->importNode( $node, true );
|
||||
$output_dom->appendChild( $node );
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return \html_entity_decode( \rtrim( $output_dom->saveHTML() ), \ENT_QUOTES, \get_bloginfo( 'charset' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
use Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain\Suggestion_Interface;
|
||||
use Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser;
|
||||
use Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor;
|
||||
/**
|
||||
* Class that implements the main flow of the AI suggestions unifier.
|
||||
*/
|
||||
class AI_Suggestions_Unifier {
|
||||
|
||||
public const PUNCTUATION_SPLIT_REGEX = '/(?<=[.!?])/i';
|
||||
|
||||
/**
|
||||
* The suggestion parser.
|
||||
*
|
||||
* @var DOM_Parser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* The suggestion processor.
|
||||
*
|
||||
* @var Node_Processor
|
||||
*/
|
||||
protected $node_processor;
|
||||
|
||||
/**
|
||||
* The sentence processor.
|
||||
*
|
||||
* @var Sentence_Processor
|
||||
*/
|
||||
protected $sentence_processor;
|
||||
|
||||
/**
|
||||
* The suggestion serializer.
|
||||
*
|
||||
* @var Suggestion_Processor
|
||||
*/
|
||||
private $suggestion_processor;
|
||||
|
||||
/**
|
||||
* The suggestion serializer.
|
||||
*
|
||||
* @var AI_Suggestions_Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* The class constructor.
|
||||
*
|
||||
* @param DOM_Parser $parser The DOM parser.
|
||||
* @param Node_Processor $node_processor The node processor.
|
||||
* @param Sentence_Processor $sentence_processor The sentence processor.
|
||||
* @param Suggestion_Processor $suggestion_processor The suggestion processor.
|
||||
* @param AI_Suggestions_Serializer $serializer The suggestion serializer.
|
||||
*/
|
||||
public function __construct( DOM_Parser $parser, Node_Processor $node_processor, Sentence_Processor $sentence_processor, Suggestion_Processor $suggestion_processor, AI_Suggestions_Serializer $serializer ) {
|
||||
$this->parser = $parser;
|
||||
$this->node_processor = $node_processor;
|
||||
$this->suggestion_processor = $suggestion_processor;
|
||||
$this->sentence_processor = $sentence_processor;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the suggestion
|
||||
*
|
||||
* @param Suggestion_Interface $suggestion The suggestion to process.
|
||||
*
|
||||
* @return string The processed suggestion
|
||||
*/
|
||||
public function unify_diffs( Suggestion_Interface $suggestion ): string {
|
||||
// Parse the suggestion into a DOMDocument object.
|
||||
$input_dom = $this->parser->parse( $suggestion->get_content(), \get_bloginfo( 'charset' ) );
|
||||
|
||||
/**
|
||||
* Substitutes the diff nodes with string placeholders.
|
||||
* This is done because of the next step where we ask for the text nodes: we need to have the diff tags in the text so that we can process them in the context of their parent node.
|
||||
*/
|
||||
$dom = $this->suggestion_processor->convert_diff_nodes_to_string_nodes( $input_dom );
|
||||
|
||||
/**
|
||||
* Fetch only the text nodes. This allows us to ignore all the other nodes which represents the post's structure.
|
||||
* By doing so we can process the nodes we're interested in disregarding whatever complex formatting the post has.
|
||||
*/
|
||||
$xpath = new DOMXPath( $dom );
|
||||
$nodes = $xpath->query( '//text()' );
|
||||
|
||||
// Process each text node.
|
||||
foreach ( \iterator_to_array( $nodes ) as $node ) {
|
||||
/**
|
||||
* We need to convert each applicable character to the corresponding HTML entity.
|
||||
* We do this because the WordPress editor expects so, and if we don't do this we get an error about the block not being correct.
|
||||
*/
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$text = \htmlentities( $node->nodeValue );
|
||||
if ( $text === '\n' ) {
|
||||
continue;
|
||||
}
|
||||
// This node doesn't contain any diff tags.
|
||||
if ( ( \strpos( $text, $this->sentence_processor::INS_PLACEHOLDER ) === false ) && \strpos( $text, $this->sentence_processor::DEL_PLACEHOLDER ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Naively split the text into sentences.
|
||||
$sentences = \array_filter( \preg_split( self::PUNCTUATION_SPLIT_REGEX, $text ) );
|
||||
/**
|
||||
* This step takes care of the cases where the last sentence in the array is a closing placeholder tag. This might happen if the text inside a diff tag ends with a punctuation mark.
|
||||
* If it is, we append the tag to the previous sentence.
|
||||
*/
|
||||
$this->sentence_processor->check_last_sentence( $sentences );
|
||||
/**
|
||||
* These two variables keep track of whether a dangling open ins or del tag has been encountered in the previous sentence.
|
||||
* This is important because if the current sentence doesn't contain any diff tags, we need to enclose it in the same diff tag as the previous sentence.
|
||||
*/
|
||||
$is_ins_tag_open = false;
|
||||
$is_del_tag_open = false;
|
||||
/**
|
||||
* This loop ensures that the diff tags are well-formed in each sentence.
|
||||
* It also checks if the sentence should be processed as a whole.
|
||||
*/
|
||||
foreach ( $sentences as &$sentence ) {
|
||||
$is_ins_tag_open = $this->sentence_processor->ensure_well_formedness_for_tag( $this->sentence_processor::INS_PLACEHOLDER, $sentence, $is_ins_tag_open );
|
||||
$is_del_tag_open = $this->sentence_processor->ensure_well_formedness_for_tag( $this->sentence_processor::DEL_PLACEHOLDER, $sentence, $is_del_tag_open );
|
||||
}
|
||||
|
||||
$processed_sentences = [];
|
||||
/**
|
||||
* Now we are sure that the diff tags are well-formed in each sentence, we can proceed to process them.
|
||||
*/
|
||||
foreach ( $sentences as $well_formed_sentence ) {
|
||||
// Check if the number of diff tags in the sentence is above the threshold.
|
||||
if ( $this->sentence_processor->should_switch_to_sentence_based( $well_formed_sentence ) ) {
|
||||
// First we create a new sentence of the form: [del-yst-tag]original sentence[/del-yst-tag].
|
||||
$processed_sentences[] = $this->sentence_processor::open( $this->sentence_processor::DEL_PLACEHOLDER )
|
||||
. $this->sentence_processor->dismiss_fixes( $well_formed_sentence )
|
||||
. $this->sentence_processor::close( $this->sentence_processor::DEL_PLACEHOLDER );
|
||||
// Then we create a new sentence of the form: [ins-yst-tag]corrected sentence[/ins-yst-tag].
|
||||
$processed_sentences[] = $this->sentence_processor::open( $this->sentence_processor::INS_PLACEHOLDER )
|
||||
. $this->sentence_processor->apply_fixes( $well_formed_sentence )
|
||||
. $this->sentence_processor::close( $this->sentence_processor::INS_PLACEHOLDER );
|
||||
}
|
||||
else {
|
||||
// Not enough diff tags to warrant sentence based processing.
|
||||
$processed_sentences[] = $well_formed_sentence;
|
||||
}
|
||||
}
|
||||
|
||||
// Join the processed sentences into a single string and convert the applicable characters to HTML entities.
|
||||
$processed_suggestion = \htmlentities( \implode( '', $processed_sentences ) );
|
||||
|
||||
// Replace the placeholders with actual diff tags.
|
||||
$processed_suggestion = $this->suggestion_processor->replace_placeholders_with_diff_tags( $processed_suggestion );
|
||||
|
||||
// Parse the new suggestion into a DOMDocument object to get the HTML nodes related to each part of the suggestion.
|
||||
$processed_content_dom = new DOMDocument();
|
||||
$processed_content_dom->loadHTML( $this->parser->add_charset( $processed_suggestion, \get_bloginfo( 'charset' ) ), ( \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD ) );
|
||||
|
||||
// The previous step added an extra <html> and <body> tag, so we need to keep only the nodes inside the body tag.
|
||||
$processed_content_nodes = $processed_content_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes;
|
||||
// Import the nodes into the original DOM representing the whole post.
|
||||
foreach ( $processed_content_nodes as $processed_content_node ) {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$imported_node = $dom->importNode( $processed_content_node, true );
|
||||
$node->parentNode->insertBefore( $imported_node, $node );
|
||||
}
|
||||
// Remove the original text node.
|
||||
$parent = $node->parentNode;
|
||||
$parent->removeChild( $node );
|
||||
}
|
||||
|
||||
// This is an additional unification step which searches for contiguous diff nodes and merges them into a single diff node.
|
||||
$this->suggestion_processor->unify_suggestion( $dom );
|
||||
|
||||
// This step searches for ins nodes whose content starts with a full stop, removes the full stop and moves it to the previous text node.
|
||||
$this->suggestion_processor->fix_leading_full_stop( $dom );
|
||||
|
||||
// Serialize the DOM back to a string.
|
||||
return $this->serializer->serialize( $dom );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application;
|
||||
|
||||
/**
|
||||
* Sentence_Processor class
|
||||
*/
|
||||
class Sentence_Processor {
|
||||
private const DIFF_THRESHOLD = 5;
|
||||
public const INS_PLACEHOLDER = 'ins-yst-tag';
|
||||
public const DEL_PLACEHOLDER = 'del-yst-tag';
|
||||
|
||||
/**
|
||||
* Open a tag
|
||||
*
|
||||
* @param string $tag The tag to open.
|
||||
*
|
||||
* @return string The opened tag
|
||||
*/
|
||||
public static function open( string $tag ): string {
|
||||
return "[$tag]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a tag
|
||||
*
|
||||
* @param string $tag The tag to close.
|
||||
*
|
||||
* @return string The closed tag
|
||||
*/
|
||||
public static function close( string $tag ): string {
|
||||
return "[/$tag]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the positions of a tag in a text
|
||||
*
|
||||
* @param string $tag The tag to search for.
|
||||
* @param string $text The text to search in.
|
||||
*
|
||||
* @return array<int> The positions of the tag in the text.
|
||||
*/
|
||||
public function get_tag_positions( string $tag, string $text ): array {
|
||||
$positions = [];
|
||||
$position = 0;
|
||||
|
||||
while ( ( $position = \strpos( $text, $tag, $position ) ) !== false ) {
|
||||
$positions[] = $position;
|
||||
$position = ( $position + \strlen( $tag ) );
|
||||
}
|
||||
|
||||
return $positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a tag is properly open and closed in a text
|
||||
*
|
||||
* @param string $tag The tag to check.
|
||||
* @param string $text The text containing the tag.
|
||||
* @param bool $is_tag_open Whether the ins tag is still open from previous sentence.
|
||||
*
|
||||
* @return bool Whether the tag is still open after the processing
|
||||
*/
|
||||
public function ensure_well_formedness_for_tag( string $tag, string &$text, bool $is_tag_open = false ): bool {
|
||||
$number_of_open_tags = \substr_count( $text, self::open( $tag ) );
|
||||
$number_of_close_tags = \substr_count( $text, self::close( $tag ) );
|
||||
|
||||
// If the sentence has no tag, we check if there's a dangling tag from the previous sentence.
|
||||
if ( $number_of_open_tags === 0 && $number_of_close_tags === 0 ) {
|
||||
// If there is a dangling tag, we enclose the sentence in a tag of the same kind.
|
||||
if ( $is_tag_open ) {
|
||||
$text = self::open( $tag ) . $text . self::close( $tag );
|
||||
}
|
||||
// If there is no dangling tag, no other action is needed.
|
||||
return $is_tag_open;
|
||||
}
|
||||
|
||||
// If the number of open tags is greater than the number of close tags, we close the tag at the end of the sentence.
|
||||
if ( $number_of_open_tags > $number_of_close_tags ) {
|
||||
$text = $text . self::close( $tag );
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the number of open tags is less than the number of close tags, we open the tag at the beginning of the sentence.
|
||||
if ( $number_of_open_tags < $number_of_close_tags ) {
|
||||
$text = self::open( $tag ) . $text;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve all the positions of the open and close tags.
|
||||
$open_tag_positions = $this->get_tag_positions( self::open( $tag ), $text );
|
||||
$close_tag_positions = $this->get_tag_positions( self::close( $tag ), $text );
|
||||
|
||||
// If the last open tag is after the last close tag, we close the tag at the end of the sentence and we communicate to the next sentence that the tag is still open.
|
||||
if ( \end( $open_tag_positions ) > \end( $close_tag_positions ) ) {
|
||||
$text = self::open( $tag ) . $text . self::close( $tag );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the suggestion preview should be switched to sentence based
|
||||
*
|
||||
* @param string $sentence The sentence to check.
|
||||
*
|
||||
* @return bool Whether the suggestion should be switched to sentence based
|
||||
*/
|
||||
public function should_switch_to_sentence_based( string $sentence ): bool {
|
||||
if ( ( \count( $this->get_tag_positions( self::open( self::INS_PLACEHOLDER ), $sentence ) ) +
|
||||
\count( $this->get_tag_positions( self::open( self::DEL_PLACEHOLDER ), $sentence ) ) ) < self::DIFF_THRESHOLD ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the last sentence in the array is a closing diff tag
|
||||
*
|
||||
* @param array<string> $sentences The sentences to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_last_sentence( array &$sentences ): void {
|
||||
$last_sentence = \end( $sentences );
|
||||
if ( \trim( $last_sentence ) === self::close( self::INS_PLACEHOLDER ) || \trim( $last_sentence ) === self::close( self::DEL_PLACEHOLDER ) ) {
|
||||
// If the last sentence is a closing diff tag, we remove it from the array...
|
||||
\array_pop( $sentences );
|
||||
// ...move to the end of the array...
|
||||
\end( $sentences );
|
||||
// ...get a pointer to the last element of the array...
|
||||
$last_sentence_in_array = &$sentences[ \key( $sentences ) ];
|
||||
// ... and append the closing diff tag to that last element in the array
|
||||
$last_sentence_in_array .= $last_sentence;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply fixes to a sentence
|
||||
*
|
||||
* @param string $sentence The sentence to apply the fixes to.
|
||||
*
|
||||
* @return string The sentence with the fixes applied
|
||||
*/
|
||||
public function apply_fixes( string $sentence ): string {
|
||||
$pattern = \sprintf( '/\[%s\].*?\[\/%s\]/', self::DEL_PLACEHOLDER, self::DEL_PLACEHOLDER );
|
||||
// We first remove the del placeholders and anything in between.
|
||||
$remove_applied = \preg_replace( $pattern, '', $sentence );
|
||||
// Then we remove the ins placeholders and keep the content in between.
|
||||
return \str_replace( [ self::open( self::INS_PLACEHOLDER ), self::close( self::INS_PLACEHOLDER ) ], '', $remove_applied );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss fixes from a sentence
|
||||
*
|
||||
* @param string $sentence The sentence to dismiss the fixes from.
|
||||
*
|
||||
* @return string The sentence with the fixes dismissed
|
||||
*/
|
||||
public function dismiss_fixes( string $sentence ): string {
|
||||
$pattern = \sprintf( '/\[%s\].*?\[\/%s\]/', self::INS_PLACEHOLDER, self::INS_PLACEHOLDER );
|
||||
// We first remove the del placeholders and keep the content in between.
|
||||
$remove_dismissed = \str_replace( [ self::open( self::DEL_PLACEHOLDER ), self::close( self::DEL_PLACEHOLDER ) ], '', $sentence );
|
||||
// Then we remove the ins placeholders and anything in between.
|
||||
return \preg_replace( $pattern, '', $remove_dismissed );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use DOMNodeList;
|
||||
use DOMXPath;
|
||||
use Text_Diff;
|
||||
use WPSEO_HTML_Diff_Renderer;
|
||||
use Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser;
|
||||
use Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor;
|
||||
|
||||
/**
|
||||
* Class implementing the processing elements used on the AI suggestions.
|
||||
*/
|
||||
class Suggestion_Processor {
|
||||
// Class name for the diff elements.
|
||||
public const YST_DIFF_CLASS = 'yst-diff';
|
||||
|
||||
/**
|
||||
* The DOM Parser.
|
||||
*
|
||||
* @var DOM_Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* The suggestion processor.
|
||||
*
|
||||
* @var Node_Processor
|
||||
*/
|
||||
protected $node_processor;
|
||||
|
||||
/**
|
||||
* The suggestion serializer.
|
||||
*
|
||||
* @var AI_Suggestions_Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DOM_Parser $parser The DOM parser.
|
||||
* @param Node_Processor $node_processor The node processor.
|
||||
* @param AI_Suggestions_Serializer $serializer The suggestion serializer.
|
||||
*/
|
||||
public function __construct(
|
||||
DOM_Parser $parser,
|
||||
Node_Processor $node_processor,
|
||||
AI_Suggestions_Serializer $serializer
|
||||
) {
|
||||
$this->parser = $parser;
|
||||
$this->node_processor = $node_processor;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the AI response and returns the suggestion
|
||||
*
|
||||
* @param string $ai_response The AI response to parse.
|
||||
*
|
||||
* @return string The suggestion from the AI response.
|
||||
*/
|
||||
public function get_suggestion_from_ai_response( string $ai_response ): string {
|
||||
$json = \json_decode( $ai_response );
|
||||
if ( $json === null || ! isset( $json->choices ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$raw_fixes = $json->choices[0]->text;
|
||||
// Remove any new lines (potentially surrounded by spaces) from the response.
|
||||
$raw_fixes = \preg_replace( '/\s*[\n\r]+\s*/', '', $raw_fixes );
|
||||
// Remove any newline characters from the response.
|
||||
$raw_fixes = \str_replace( "\\n", '', $raw_fixes );
|
||||
|
||||
return $raw_fixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the diff between the original text and the fixed text.
|
||||
* Differences are marked with `<ins>` and `<del>` tags.
|
||||
*
|
||||
* @param string $original The original text.
|
||||
* @param string $raw_fixes The suggested fixes.
|
||||
* @return string The difference between the two strings.
|
||||
*/
|
||||
public function calculate_diff( string $original, string $raw_fixes ): string {
|
||||
if ( ! \class_exists( 'Text_Diff' ) ) {
|
||||
require_once \ABSPATH . '/wp-includes/wp-diff.php';
|
||||
}
|
||||
|
||||
$left_lines = \explode( "\n", $original );
|
||||
$right_lines = \explode( "\n", $raw_fixes );
|
||||
|
||||
$text_diff = new Text_Diff( $left_lines, $right_lines );
|
||||
$renderer = new WPSEO_HTML_Diff_Renderer();
|
||||
return $renderer->render( $text_diff );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTML tags from the suggestion.
|
||||
*
|
||||
* @param string $diff The suggestion to remove the HTML tags from.
|
||||
*
|
||||
* @return string The suggestion without the HTML tags.
|
||||
*/
|
||||
public function remove_html_from_suggestion( string $diff ): string {
|
||||
// Sometimes, the AI inadvertently suggests adding HTML tags or new blocks (through comments). We remove these here.
|
||||
// Note that the HTML tags enter the diff escaped (so < and > instead of < and >).
|
||||
// And yes, we are using a regex to parse HTML. We are aware of the risks.
|
||||
$html_tags = \sprintf( '/(<ins class="%s">)([^<]*?)<.*?>(.*?)(<\/ins>)/', self::YST_DIFF_CLASS );
|
||||
$replacement = static function ( $matches ) {
|
||||
// If there is no text before and after the tags, we remove the tags completely.
|
||||
if ( $matches[2] === '' && $matches[3] === '' ) {
|
||||
return '';
|
||||
}
|
||||
// Otherwise, we keep the text before and after the tags.
|
||||
else {
|
||||
return $matches[1] . $matches[2] . $matches[3] . $matches[4];
|
||||
}
|
||||
};
|
||||
// Keep replacing until there are no more tags left.
|
||||
while ( \preg_match( $html_tags, $diff ) ) {
|
||||
$diff = \preg_replace_callback( $html_tags, $replacement, $diff );
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retains any replacements of non-breaking spaces in suggestions.
|
||||
*
|
||||
* @param string $diff The diff to keep its non-breaking spaces.
|
||||
*
|
||||
* @return string The suggestion with non-breaking spaces intact.
|
||||
*/
|
||||
public function keep_nbsp_in_suggestions( string $diff ): string {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Not our properties.
|
||||
$dom = $this->parser->parse( $diff, \get_bloginfo( 'charset' ) );
|
||||
|
||||
// XPath query to select all our <del> tags.
|
||||
$xpath = new DOMXPath( $dom );
|
||||
$del_nodes_query = \sprintf( '//del[@class="%s"]', self::YST_DIFF_CLASS );
|
||||
$del_nodes = $xpath->query( $del_nodes_query );
|
||||
|
||||
$flattened_encoded_changes = false;
|
||||
|
||||
// Iterate through all <del> tags that contain .
|
||||
foreach ( $del_nodes as $del_node ) {
|
||||
if ( \strpos( $del_node->textContent, ' ' ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the next sibling <ins> tag.
|
||||
$next_ins_node = $del_node->nextSibling;
|
||||
while ( $next_ins_node && $next_ins_node->nodeName !== 'ins' ) {
|
||||
$next_ins_node = $next_ins_node->nextSibling;
|
||||
}
|
||||
|
||||
// If there is no later <ins> tag, we'll do no more post-processing and just show the rest of the <del> tags as is - unlikely, since <del>s usually are followed by <ins>s.
|
||||
if ( ! $next_ins_node ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If the <ins> tag is the same with the <del> tag, apart from the transformation, flatten the change as if there was no suggestion.
|
||||
if ( $next_ins_node->textContent === \str_replace( ' ', ' ', $del_node->textContent ) ) {
|
||||
// Replace the <del> tag with the text content of the <del> tag.
|
||||
$del_node->parentNode->replaceChild( $dom->createTextNode( $del_node->textContent ), $del_node );
|
||||
|
||||
// Remove the <ins> tag.
|
||||
$this->node_processor->remove( $next_ins_node );
|
||||
|
||||
$flattened_encoded_changes = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the <del> tag is just a and it's being suggested to be replaced by a block of text starting with a space, flatten the change and just move the into the suggested text.
|
||||
if ( $del_node->textContent === ' ' && ( \substr( $next_ins_node->textContent, 0, 1 ) === ' ' ) ) {
|
||||
// Replace the starting space with in the <ins> tag.
|
||||
$ins_content = \ltrim( $next_ins_node->textContent, ' ' );
|
||||
$ins_content = ' ' . $ins_content;
|
||||
$next_ins_node->textContent = $ins_content;
|
||||
|
||||
// Remove the <del> tag.
|
||||
$this->node_processor->remove( $del_node );
|
||||
|
||||
$flattened_encoded_changes = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// phpcs:enable
|
||||
|
||||
if ( $flattened_encoded_changes ) {
|
||||
$diff = $this->serializer->serialize( $dom );
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Yoast diff nodes from the DOM
|
||||
*
|
||||
* @param DOMDocument $dom The DOM to get the diff nodes from.
|
||||
* @param string|null $node_type The type of node to get. If null the method will get both ins and del nodes.
|
||||
*
|
||||
* @return DOMNodeList The diff nodes
|
||||
*/
|
||||
public function get_diff_nodes( DOMDocument $dom, ?string $node_type = null ): DOMNodeList {
|
||||
$xpath = new DOMXPath( $dom );
|
||||
// If the node type is null, we get both ins and del nodes; if it's not, we get the specified node type.
|
||||
$local_name_query = \is_null( $node_type ) ? '//*[local-name()="ins" or local-name()="del"]' : \sprintf( "//*[local-name()='%s']", $node_type );
|
||||
$diff_nodes_query = \sprintf( "%s[contains(concat(' ', normalize-space(@class), ' '), '%s')]", $local_name_query, self::YST_DIFF_CLASS );
|
||||
return $xpath->query( $diff_nodes_query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is a Yoast diff node
|
||||
*
|
||||
* @param DOMNode $node The node to check.
|
||||
*
|
||||
* @return bool Whether the node is a Yoast diff node.
|
||||
*/
|
||||
public function is_yoast_diff_node( DOMNode $node ): bool {
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
if ( $node->nodeName !== 'ins' && $node->nodeName !== 'del' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $node->hasAttribute( 'class' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$class = $node->getAttribute( 'class' );
|
||||
return \strpos( $class, self::YST_DIFF_CLASS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert diff nodes to string
|
||||
*
|
||||
* @param DOMDocument $dom The DOM to convert.
|
||||
*
|
||||
* @return DOMDocument The converted DOM
|
||||
*/
|
||||
public function convert_diff_nodes_to_string_nodes( DOMDocument $dom ): DOMDocument {
|
||||
$diff_nodes = $this->get_diff_nodes( $dom );
|
||||
|
||||
foreach ( $diff_nodes as $node ) {
|
||||
// Build the text node content based on the diff node nodeName and nodeValue attributes.
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$text = \sprintf( '[%s-yst-tag]%s[/%s-yst-tag]', $node->nodeName, $node->nodeValue, $node->nodeName );
|
||||
$text_node = $dom->createTextNode( $text );
|
||||
$parent = $node->parentNode;
|
||||
// If the node has no parent, we insert the new text node before the diff node and remove the diff node.
|
||||
if ( \is_null( $parent ) ) {
|
||||
$dom->insertBefore( $node, $text_node );
|
||||
$dom->removeChild( $node );
|
||||
}
|
||||
// If the node has a parent, we replace the diff node with the new text node.
|
||||
else {
|
||||
$parent->replaceChild( $text_node, $node );
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return $this->parser->parse( \rtrim( $dom->saveHTML(), "\n" ), \get_bloginfo( 'charset' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders with diff tags
|
||||
*
|
||||
* @param string $suggestion The suggestion to process.
|
||||
*
|
||||
* @return string The suggestion with the tags replaced
|
||||
*/
|
||||
public function replace_placeholders_with_diff_tags( string $suggestion ): string {
|
||||
$suggestion = \str_replace( '[ins-yst-tag]', \sprintf( '<ins class="%s">', self::YST_DIFF_CLASS ), $suggestion );
|
||||
$suggestion = \str_replace( '[/ins-yst-tag]', '</ins>', $suggestion );
|
||||
$suggestion = \str_replace( '[del-yst-tag]', \sprintf( '<del class="%s">', self::YST_DIFF_CLASS ), $suggestion );
|
||||
$suggestion = \str_replace( '[/del-yst-tag]', '</del>', $suggestion );
|
||||
|
||||
return $suggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional unification step to join contiguous diff nodes with the same tag.
|
||||
*
|
||||
* @param DOMDocument $dom The DOM to unify.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unify_suggestion( DOMDocument $dom ): void {
|
||||
$diff_nodes = $this->get_diff_nodes( $dom );
|
||||
|
||||
foreach ( $diff_nodes as $diff_node ) {
|
||||
// If this diff node has no next sibling, we continue to the next diff node.
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$next_sibling = $diff_node->nextSibling;
|
||||
if ( \is_null( $next_sibling ) ) {
|
||||
continue;
|
||||
}
|
||||
// If the next sibling is the same kind of the current node, we proceed to join them.
|
||||
if ( $next_sibling->nodeName === $diff_node->nodeName ) {
|
||||
// we encode the HTML entities in the diff node value, create a text node out of it and prepend it to the next sibling's content.
|
||||
$encoded_diff_node_value = \htmlentities( $diff_node->nodeValue, \ENT_QUOTES, \get_bloginfo( 'charset' ) );
|
||||
$text_diff_node = $dom->createTextNode( $encoded_diff_node_value );
|
||||
$next_sibling->insertBefore( $text_diff_node, $next_sibling->firstChild );
|
||||
// We remove the diff node.
|
||||
( $diff_node->parentNode )->removeChild( $diff_node );
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of the cases when an ins node starts with a full stop.
|
||||
*
|
||||
* @param DOMDocument $dom The DOM to fix the leading full stop in.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fix_leading_full_stop( DOMDocument $dom ): void {
|
||||
$ins_nodes = $this->get_diff_nodes( $dom, 'ins' );
|
||||
|
||||
foreach ( $ins_nodes as $ins_node ) {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
// Check if the diff node starts with a dot and a space.
|
||||
if ( \strpos( $ins_node->nodeValue, '. ' ) === 0 ) {
|
||||
// A next sibling needs to exist for the next check.
|
||||
$next_sibling = $ins_node->nextSibling;
|
||||
|
||||
if ( \is_null( $next_sibling ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The previous sibling should exist because we need to append the full stop to it.
|
||||
$previous_sibling = $ins_node->previousSibling;
|
||||
|
||||
if ( \is_null( $previous_sibling ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The next sibling should start with a full stop.
|
||||
if ( \strpos( $next_sibling->nodeValue, '.' ) !== 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the next sibling is a single full stop character, we remove the node, remove the full stop
|
||||
// at the beginning of the ins node and append the full stop to the previous sibling.
|
||||
if ( \strlen( $next_sibling->nodeValue ) === 1 ) {
|
||||
( $next_sibling->parentNode )->removeChild( $next_sibling );
|
||||
$ins_node->nodeValue = \substr( $ins_node->nodeValue, 2 ) . '.';
|
||||
$previous_sibling->nodeValue = $previous_sibling->nodeValue . '. ';
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the next sibling is a full stop followed by characters, we remove the full stop and space from it,
|
||||
// add it at the beginning of the ins node and append the full stop to the previous sibling.
|
||||
if ( \strpos( $next_sibling->nodeValue, '. ' ) === 0 ) {
|
||||
$next_sibling->nodeValue = \substr( $next_sibling->nodeValue, 1 );
|
||||
$ins_node->nodeValue = \substr( $ins_node->nodeValue, 2 ) . '.';
|
||||
$previous_sibling->nodeValue = $previous_sibling->nodeValue . '. ';
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain;
|
||||
|
||||
/**
|
||||
* Interface representing a suggestion domain object.
|
||||
*/
|
||||
interface Suggestion_Interface {
|
||||
|
||||
/**
|
||||
* Gets the suggestion content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content(): string;
|
||||
|
||||
/**
|
||||
* Sets the suggestion string.
|
||||
*
|
||||
* @param string $content The suggestion content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_content( string $content ): void;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain;
|
||||
|
||||
/**
|
||||
* Class implementing the Suggestion_Interface.
|
||||
*/
|
||||
class Suggestion implements Suggestion_Interface {
|
||||
|
||||
/**
|
||||
* The suggestion.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Gets the suggestion content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content(): string {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggestion string.
|
||||
*
|
||||
* @param string $content The suggestion content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_content( string $content ): void {
|
||||
$this->content = $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Graceful deprecation of the Ai_Generate_Titles_And_Descriptions_Introduction class.
|
||||
*
|
||||
* {@internal As this file is just (temporarily) put in place to warn extending
|
||||
* plugins about the class name changes, it is exempt from select CS standards.}
|
||||
*
|
||||
* @deprecated 23.2
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
* @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound
|
||||
* @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated
|
||||
* @phpcs:disable Yoast.Commenting.FileComment.Unnecessary
|
||||
* @phpcs:disable Yoast.Files.FileName.InvalidClassFileName
|
||||
*/
|
||||
namespace Yoast\WP\SEO\Premium\Introductions\Application;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Introductions\Application\User_Allowed_Trait;
|
||||
|
||||
/**
|
||||
* Represents the introduction for the AI generate titles and introduction upsell.
|
||||
*
|
||||
* @deprecated 23.2 Use {@see \Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction} instead.
|
||||
*/
|
||||
class Ai_Generate_Titles_And_Descriptions_Introduction extends Ai_Fix_Assessments_Introduction {
|
||||
|
||||
use User_Allowed_Trait;
|
||||
|
||||
public const ID = 'ai-generate-titles-and-descriptions';
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Holds the user helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
private $user_helper;
|
||||
|
||||
/**
|
||||
* Constructs the introduction.
|
||||
*
|
||||
* @deprecated 23.2
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param User_Helper $user_helper The user helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper, User_Helper $user_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 23.2' );
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID.
|
||||
*
|
||||
* @deprecated 23.2
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 23.2' );
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name.
|
||||
*
|
||||
* @deprecated 21.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 21.6', 'Please use get_id() instead' );
|
||||
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested pagination priority. Lower means earlier.
|
||||
*
|
||||
* @deprecated 23.2
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_priority() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 23.2' );
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this introduction should show.
|
||||
*
|
||||
* @deprecated 23.2
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_show() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO 23.2' );
|
||||
// Outdated feature introduction.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\DOM_Manager\Application;
|
||||
|
||||
use DOMDocument;
|
||||
|
||||
/**
|
||||
* Class used to parse a string into a DOMDocument object.
|
||||
*/
|
||||
class DOM_Parser {
|
||||
|
||||
/**
|
||||
* Parses a string into a DOMDocument object
|
||||
*
|
||||
* @param string $html_string The string to be parsed.
|
||||
* @param string|null $charset The charset of the string.
|
||||
*
|
||||
* @return DOMDocument
|
||||
*/
|
||||
public function parse( string $html_string, ?string $charset = null ): DOMDocument {
|
||||
$dom = new DOMDocument();
|
||||
\libxml_use_internal_errors( true );
|
||||
$dom->loadHTML( $this->add_charset( $html_string, $charset ), ( \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD ) );
|
||||
\libxml_clear_errors();
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add charset to the node html
|
||||
*
|
||||
* @param string $html_string The node html to add charset to.
|
||||
* @param string|null $charset The charset to add to the node html.
|
||||
*
|
||||
* @return string The node html with charset
|
||||
*/
|
||||
public function add_charset( string $html_string, ?string $charset ): string {
|
||||
if ( \is_null( $charset ) ) {
|
||||
return $html_string;
|
||||
}
|
||||
return \sprintf( '<html><head><meta content="text/html; charset=%s" http-equiv="Content-Type"></head><body>%s</body></html>', $charset, $html_string );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\DOM_Manager\Application;
|
||||
|
||||
use DOMElement;
|
||||
|
||||
/**
|
||||
* Class implementing the processing elements used to unify the AI suggestions.
|
||||
*/
|
||||
class Node_Processor {
|
||||
|
||||
/**
|
||||
* Unwrap the input node
|
||||
*
|
||||
* @param DOMElement $node The node to unwrap.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unwrap( DOMElement $node ): void {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$parent = $node->parentNode;
|
||||
while ( $node->hasChildNodes() ) {
|
||||
$child = $node->removeChild( $node->firstChild );
|
||||
$parent->insertBefore( $child, $node );
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$parent->removeChild( $node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the input node
|
||||
*
|
||||
* @param DOMElement $node The node to remove.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove( DOMElement $node ): void {
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$parent = $node->parentNode;
|
||||
$parent->removeChild( $node );
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<?php return array('draft-js-emoji-picker.min.js' => array('dependencies' => array('lodash', 'react', 'wp-polyfill', 'yoast-seo-draft-js-package', 'yoast-seo-prop-types-package'), 'version' => 'e941559b9bed6a91096b'));
|
||||
<?php return array('draft-js-emoji-picker.min.js' => array('dependencies' => array('lodash', 'react', 'wp-polyfill', 'yoast-seo-draft-js-package', 'yoast-seo-prop-types-package'), 'version' => 'b38d454ededf6f6b306d'));
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?php return array('social-metadata-previews-2290.min.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-prop-types-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '1c09414490cdc1f8c9ba'));
|
||||
<?php return array('social-metadata-previews-2370.min.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-prop-types-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '1c09414490cdc1f8c9ba'));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,6 +25,7 @@ class Cached_Container extends Container
|
||||
{
|
||||
$this->services = [];
|
||||
$this->normalizedIds = [
|
||||
'autowired.yoast\\wp\\seo\\introductions\\infrastructure\\introductions_seen_repository' => 'autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository',
|
||||
'wpseo_addon_manager' => 'WPSEO_Addon_Manager',
|
||||
'wpseo_admin_asset_manager' => 'WPSEO_Admin_Asset_Manager',
|
||||
'wpseo_premium_prominent_words_support' => 'WPSEO_Premium_Prominent_Words_Support',
|
||||
@@ -60,7 +61,6 @@ class Cached_Container extends Container
|
||||
'yoast\\wp\\seo\\helpers\\meta_helper' => 'Yoast\\WP\\SEO\\Helpers\\Meta_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\options_helper' => 'Yoast\\WP\\SEO\\Helpers\\Options_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\post_type_helper' => 'Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\request_helper' => 'Yoast\\WP\\SEO\\Helpers\\Request_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\robots_helper' => 'Yoast\\WP\\SEO\\Helpers\\Robots_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\score_icon_helper' => 'Yoast\\WP\\SEO\\Helpers\\Score_Icon_Helper',
|
||||
'yoast\\wp\\seo\\helpers\\social_profiles_helper' => 'Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper',
|
||||
@@ -77,6 +77,11 @@ class Cached_Container extends Container
|
||||
'yoast\\wp\\seo\\premium\\actions\\prominent_words\\complete_action' => 'Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Complete_Action',
|
||||
'yoast\\wp\\seo\\premium\\actions\\prominent_words\\content_action' => 'Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Content_Action',
|
||||
'yoast\\wp\\seo\\premium\\actions\\prominent_words\\save_action' => 'Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Save_Action',
|
||||
'yoast\\wp\\seo\\premium\\ai_suggestions_postprocessor\\application\\ai_suggestions_serializer' => 'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer',
|
||||
'yoast\\wp\\seo\\premium\\ai_suggestions_postprocessor\\application\\ai_suggestions_unifier' => 'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Unifier',
|
||||
'yoast\\wp\\seo\\premium\\ai_suggestions_postprocessor\\application\\sentence_processor' => 'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor',
|
||||
'yoast\\wp\\seo\\premium\\ai_suggestions_postprocessor\\application\\suggestion_processor' => 'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor',
|
||||
'yoast\\wp\\seo\\premium\\ai_suggestions_postprocessor\\domain\\suggestion' => 'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Domain\\Suggestion',
|
||||
'yoast\\wp\\seo\\premium\\conditionals\\ai_editor_conditional' => 'Yoast\\WP\\SEO\\Premium\\Conditionals\\Ai_Editor_Conditional',
|
||||
'yoast\\wp\\seo\\premium\\conditionals\\algolia_enabled_conditional' => 'Yoast\\WP\\SEO\\Premium\\Conditionals\\Algolia_Enabled_Conditional',
|
||||
'yoast\\wp\\seo\\premium\\conditionals\\cornerstone_enabled_conditional' => 'Yoast\\WP\\SEO\\Premium\\Conditionals\\Cornerstone_Enabled_Conditional',
|
||||
@@ -87,6 +92,8 @@ class Cached_Container extends Container
|
||||
'yoast\\wp\\seo\\premium\\config\\badge_group_names' => 'Yoast\\WP\\SEO\\Premium\\Config\\Badge_Group_Names',
|
||||
'yoast\\wp\\seo\\premium\\config\\migrations\\addindexonindexableidandstem' => 'Yoast\\WP\\SEO\\Premium\\Config\\Migrations\\AddIndexOnIndexableIdAndStem',
|
||||
'yoast\\wp\\seo\\premium\\database\\migration_runner_premium' => 'Yoast\\WP\\SEO\\Premium\\Database\\Migration_Runner_Premium',
|
||||
'yoast\\wp\\seo\\premium\\dom_manager\\application\\dom_parser' => 'Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser',
|
||||
'yoast\\wp\\seo\\premium\\dom_manager\\application\\node_processor' => 'Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor',
|
||||
'yoast\\wp\\seo\\premium\\helpers\\ai_generator_helper' => 'Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper',
|
||||
'yoast\\wp\\seo\\premium\\helpers\\current_page_helper' => 'Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper',
|
||||
'yoast\\wp\\seo\\premium\\helpers\\prominent_words_helper' => 'Yoast\\WP\\SEO\\Premium\\Helpers\\Prominent_Words_Helper',
|
||||
@@ -98,6 +105,7 @@ class Cached_Container extends Container
|
||||
'yoast\\wp\\seo\\premium\\initializers\\woocommerce' => 'Yoast\\WP\\SEO\\Premium\\Initializers\\Woocommerce',
|
||||
'yoast\\wp\\seo\\premium\\initializers\\wp_cli_initializer' => 'Yoast\\WP\\SEO\\Premium\\Initializers\\Wp_Cli_Initializer',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\admin\\ai_consent_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Consent_Integration',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\admin\\ai_fix_assessments_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Fix_Assessments_Integration',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\admin\\ai_generator_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Generator_Integration',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\admin\\cornerstone_column_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Column_Integration',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\admin\\cornerstone_taxonomy_column_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Taxonomy_Column_Integration',
|
||||
@@ -142,7 +150,7 @@ class Cached_Container extends Container
|
||||
'yoast\\wp\\seo\\premium\\integrations\\user_profile_integration' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\User_Profile_Integration',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\watchers\\prominent_words_watcher' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Watchers\\Prominent_Words_Watcher',
|
||||
'yoast\\wp\\seo\\premium\\integrations\\watchers\\stale_cornerstone_content_watcher' => 'Yoast\\WP\\SEO\\Premium\\Integrations\\Watchers\\Stale_Cornerstone_Content_Watcher',
|
||||
'yoast\\wp\\seo\\premium\\introductions\\application\\ai_generate_titles_and_descriptions_introduction' => 'Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction',
|
||||
'yoast\\wp\\seo\\premium\\introductions\\application\\ai_fix_assessments_introduction' => 'Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Fix_Assessments_Introduction',
|
||||
'yoast\\wp\\seo\\premium\\main' => 'Yoast\\WP\\SEO\\Premium\\Main',
|
||||
'yoast\\wp\\seo\\premium\\repositories\\prominent_words_repository' => 'Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository',
|
||||
'yoast\\wp\\seo\\premium\\routes\\link_suggestions_route' => 'Yoast\\WP\\SEO\\Premium\\Routes\\Link_Suggestions_Route',
|
||||
@@ -198,7 +206,6 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Helpers\\Meta_Helper' => 'getMetaHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Options_Helper' => 'getOptionsHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper' => 'getPostTypeHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Request_Helper' => 'getRequestHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Robots_Helper' => 'getRobotsHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Score_Icon_Helper' => 'getScoreIconHelperService',
|
||||
'Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper' => 'getSocialProfilesHelperService',
|
||||
@@ -210,6 +217,11 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository' => 'getWistiaEmbedPermissionRepositoryService',
|
||||
'Yoast\\WP\\SEO\\Loader' => 'getLoaderService',
|
||||
'Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer' => 'getMetaTagsContextMemoizerService',
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer' => 'getAISuggestionsSerializerService',
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Unifier' => 'getAISuggestionsUnifierService',
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor' => 'getSentenceProcessorService',
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor' => 'getSuggestionProcessorService',
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Domain\\Suggestion' => 'getSuggestionService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Actions\\AI_Generator_Action' => 'getAIGeneratorActionService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Actions\\Link_Suggestions_Action' => 'getLinkSuggestionsActionService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Complete_Action' => 'getCompleteActionService',
|
||||
@@ -224,6 +236,8 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Premium\\Conditionals\\Yoast_Admin_Or_Introductions_Route_Conditional' => 'getYoastAdminOrIntroductionsRouteConditionalService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Config\\Badge_Group_Names' => 'getBadgeGroupNamesService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Config\\Migrations\\AddIndexOnIndexableIdAndStem' => 'getAddIndexOnIndexableIdAndStemService',
|
||||
'Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser' => 'getDOMParserService',
|
||||
'Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor' => 'getNodeProcessorService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Database\\Migration_Runner_Premium' => 'getMigrationRunnerPremiumService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper' => 'getAIGeneratorHelperService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper' => 'getCurrentPageHelper2Service',
|
||||
@@ -236,6 +250,7 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Premium\\Initializers\\Woocommerce' => 'getWoocommerceService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Initializers\\Wp_Cli_Initializer' => 'getWpCliInitializerService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Consent_Integration' => 'getAiConsentIntegrationService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Fix_Assessments_Integration' => 'getAiFixAssessmentsIntegrationService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Generator_Integration' => 'getAiGeneratorIntegrationService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Column_Integration' => 'getCornerstoneColumnIntegrationService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Taxonomy_Column_Integration' => 'getCornerstoneTaxonomyColumnIntegrationService',
|
||||
@@ -280,7 +295,7 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\User_Profile_Integration' => 'getUserProfileIntegration2Service',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Watchers\\Prominent_Words_Watcher' => 'getProminentWordsWatcherService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Integrations\\Watchers\\Stale_Cornerstone_Content_Watcher' => 'getStaleCornerstoneContentWatcherService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction' => 'getAiGenerateTitlesAndDescriptionsIntroductionService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Fix_Assessments_Introduction' => 'getAiFixAssessmentsIntroductionService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Main' => 'getMainService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository' => 'getProminentWordsRepositoryService',
|
||||
'Yoast\\WP\\SEO\\Premium\\Routes\\Link_Suggestions_Route' => 'getLinkSuggestionsRouteService',
|
||||
@@ -298,10 +313,12 @@ class Cached_Container extends Container
|
||||
'Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface' => 'getOpenGraphHelpersSurfaceService',
|
||||
'Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface' => 'getSchemaHelpersSurfaceService',
|
||||
'Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface' => 'getTwitterHelpersSurfaceService',
|
||||
'autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository' => 'getIntroductionsSeenRepositoryService',
|
||||
'wpdb' => 'getWpdbService',
|
||||
];
|
||||
$this->privates = [
|
||||
'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
'autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository' => true,
|
||||
];
|
||||
$this->aliases = [
|
||||
'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => 'service_container',
|
||||
@@ -314,6 +331,7 @@ class Cached_Container extends Container
|
||||
'Psr\\Container\\ContainerInterface' => true,
|
||||
'YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
'Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Domain\\Suggestion_Interface' => true,
|
||||
'autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository' => true,
|
||||
];
|
||||
}
|
||||
@@ -685,16 +703,6 @@ class Cached_Container extends Container
|
||||
return $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] = \Yoast\WP\Lib\Dependency_Injection\Container_Registry::get('yoast-seo', 'Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Helpers\Request_Helper' shared service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Helpers\Request_Helper
|
||||
*/
|
||||
protected function getRequestHelperService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Helpers\\Request_Helper'] = \Yoast\WP\Lib\Dependency_Injection\Container_Registry::get('yoast-seo', 'Yoast\\WP\\SEO\\Helpers\\Request_Helper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Helpers\Robots_Helper' shared service.
|
||||
*
|
||||
@@ -804,6 +812,7 @@ class Cached_Container extends Container
|
||||
$instance->register_initializer('Yoast\\WP\\SEO\\Premium\\Initializers\\Woocommerce');
|
||||
$instance->register_initializer('Yoast\\WP\\SEO\\Premium\\Initializers\\Wp_Cli_Initializer');
|
||||
$instance->register_integration('Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Consent_Integration');
|
||||
$instance->register_integration('Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Fix_Assessments_Integration');
|
||||
$instance->register_integration('Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Generator_Integration');
|
||||
$instance->register_integration('Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Column_Integration');
|
||||
$instance->register_integration('Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Cornerstone_Taxonomy_Column_Integration');
|
||||
@@ -868,6 +877,56 @@ class Cached_Container extends Container
|
||||
return $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] = \Yoast\WP\Lib\Dependency_Injection\Container_Registry::get('yoast-seo', 'Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Serializer' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Serializer
|
||||
*/
|
||||
protected function getAISuggestionsSerializerService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Serializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Unifier' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Unifier
|
||||
*/
|
||||
protected function getAISuggestionsUnifierService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Unifier'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Unifier(${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser']) ? $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser'] : ($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor']) ? $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor'] : ($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor'] : ($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Sentence_Processor())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor'] : $this->getSuggestionProcessorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer'] : ($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Serializer())) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Sentence_Processor' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Sentence_Processor
|
||||
*/
|
||||
protected function getSentenceProcessorService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Sentence_Processor'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Sentence_Processor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Suggestion_Processor' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Suggestion_Processor
|
||||
*/
|
||||
protected function getSuggestionProcessorService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Suggestion_Processor(${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser']) ? $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser'] : ($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor']) ? $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor'] : ($this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer'] : ($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Serializer'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Serializer())) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain\Suggestion' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain\Suggestion
|
||||
*/
|
||||
protected function getSuggestionService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Domain\\Suggestion'] = new \Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain\Suggestion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Actions\AI_Generator_Action' shared autowired service.
|
||||
*
|
||||
@@ -915,7 +974,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getSaveActionService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Save_Action'] = new \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository'] : ($this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository'] = new \Yoast\WP\SEO\Premium\Repositories\Prominent_Words_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Prominent_Words_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Prominent_Words_Helper'] : $this->getProminentWordsHelperService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Prominent_Words\\Save_Action'] = new \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository'] : ($this->services['Yoast\\WP\\SEO\\Premium\\Repositories\\Prominent_Words_Repository'] = new \Yoast\WP\SEO\Premium\Repositories\Prominent_Words_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Prominent_Words_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Prominent_Words_Helper'] : $this->getProminentWordsHelperService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1008,6 +1067,26 @@ class Cached_Container extends Container
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Config\\Migrations\\AddIndexOnIndexableIdAndStem'] = new \Yoast\WP\SEO\Premium\Config\Migrations\AddIndexOnIndexableIdAndStem(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : $this->getAdapterService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser
|
||||
*/
|
||||
protected function getDOMParserService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\DOM_Parser'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\DOM_Parser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor
|
||||
*/
|
||||
protected function getNodeProcessorService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\DOM_Manager\\Application\\Node_Processor'] = new \Yoast\WP\SEO\Premium\DOM_Manager\Application\Node_Processor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Database\Migration_Runner_Premium' shared autowired service.
|
||||
*
|
||||
@@ -1025,7 +1104,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getAIGeneratorHelperService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper'] = new \Yoast\WP\SEO\Premium\Helpers\AI_Generator_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : $this->getDateHelperService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper'] = new \Yoast\WP\SEO\Premium\Helpers\AI_Generator_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Unifier']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\AI_Suggestions_Unifier'] : $this->getAISuggestionsUnifierService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor']) ? $this->services['Yoast\\WP\\SEO\\Premium\\AI_Suggestions_Postprocessor\\Application\\Suggestion_Processor'] : $this->getSuggestionProcessorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : $this->getDateHelperService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1075,7 +1154,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getIntroductionsInitializerService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Initializers\\Introductions_Initializer'] = new \Yoast\WP\SEO\Premium\Initializers\Introductions_Initializer(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction'] : $this->getAiGenerateTitlesAndDescriptionsIntroductionService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Initializers\\Introductions_Initializer'] = new \Yoast\WP\SEO\Premium\Initializers\Introductions_Initializer(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Fix_Assessments_Introduction']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Fix_Assessments_Introduction'] : $this->getAiFixAssessmentsIntroductionService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1128,6 +1207,16 @@ class Cached_Container extends Container
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Consent_Integration'] = new \Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Consent_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository'] : $this->getWistiaEmbedPermissionRepositoryService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Fix_Assessments_Integration' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Fix_Assessments_Integration
|
||||
*/
|
||||
protected function getAiFixAssessmentsIntegrationService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Fix_Assessments_Integration'] = new \Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Fix_Assessments_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper'] : $this->getAIGeneratorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'}, ${($_ = isset($this->services['autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository']) ? $this->services['autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository'] : $this->getIntroductionsSeenRepositoryService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Generator_Integration' shared autowired service.
|
||||
*
|
||||
@@ -1135,9 +1224,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getAiGeneratorIntegrationService()
|
||||
{
|
||||
$a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'};
|
||||
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Generator_Integration'] = new \Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Generator_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper'] : $this->getAIGeneratorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper'] : ($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper'] = new \Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, $a, new \Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository($a));
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Admin\\Ai_Generator_Integration'] = new \Yoast\WP\SEO\Premium\Integrations\Admin\Ai_Generator_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\AI_Generator_Helper'] : $this->getAIGeneratorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper'] : ($this->services['Yoast\\WP\\SEO\\Premium\\Helpers\\Current_Page_Helper'] = new \Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'}, ${($_ = isset($this->services['autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository']) ? $this->services['autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository'] : $this->getIntroductionsSeenRepositoryService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1377,7 +1464,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getIndexNowPingService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Index_Now_Ping'] = new \Yoast\WP\SEO\Premium\Integrations\Index_Now_Ping(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Request_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Request_Helper'] : $this->getRequestHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Integrations\\Index_Now_Ping'] = new \Yoast\WP\SEO\Premium\Integrations\Index_Now_Ping(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1571,13 +1658,13 @@ class Cached_Container extends Container
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction' shared autowired service.
|
||||
* Gets the public 'Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Premium\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction
|
||||
* @return \Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction
|
||||
*/
|
||||
protected function getAiGenerateTitlesAndDescriptionsIntroductionService()
|
||||
protected function getAiFixAssessmentsIntroductionService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction'] = new \Yoast\WP\SEO\Premium\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Introductions\\Application\\Ai_Fix_Assessments_Introduction'] = new \Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : $this->getOptionsHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1627,7 +1714,7 @@ class Cached_Container extends Container
|
||||
*/
|
||||
protected function getWorkoutsRouteService()
|
||||
{
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Routes\\Workouts_Route'] = new \Yoast\WP\SEO\Premium\Routes\Workouts_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Link_Suggestions_Action']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Link_Suggestions_Action'] : $this->getLinkSuggestionsActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] : $this->getIndexableTermBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'});
|
||||
return $this->services['Yoast\\WP\\SEO\\Premium\\Routes\\Workouts_Route'] = new \Yoast\WP\SEO\Premium\Routes\Workouts_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Link_Suggestions_Action']) ? $this->services['Yoast\\WP\\SEO\\Premium\\Actions\\Link_Suggestions_Action'] : $this->getLinkSuggestionsActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] : $this->getIndexableTermBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1759,4 +1846,14 @@ class Cached_Container extends Container
|
||||
{
|
||||
return $this->services['wpdb'] = \Yoast\WP\Lib\Dependency_Injection\Container_Registry::get('yoast-seo', 'wpdb');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the private 'autowired.Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository' shared autowired service.
|
||||
*
|
||||
* @return \Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository
|
||||
*/
|
||||
protected function getIntroductionsSeenRepositoryService()
|
||||
{
|
||||
return $this->services['autowired.Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository'] = new \Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : $this->getUserHelperService()) && false ?: '_'});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Helpers\Date_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
use Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\AI_Suggestions_Unifier;
|
||||
use Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Application\Suggestion_Processor;
|
||||
use Yoast\WP\SEO\Premium\AI_Suggestions_Postprocessor\Domain\Suggestion;
|
||||
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;
|
||||
@@ -36,6 +39,20 @@ class AI_Generator_Helper {
|
||||
*/
|
||||
protected $base_url = 'https://ai.yoa.st/api/v1';
|
||||
|
||||
/**
|
||||
* The AI suggestion helper.
|
||||
*
|
||||
* @var AI_Suggestions_Unifier
|
||||
*/
|
||||
private $ai_suggestions_unifier;
|
||||
|
||||
/**
|
||||
* The suggestion processor.
|
||||
*
|
||||
* @var Suggestion_Processor
|
||||
*/
|
||||
private $suggestion_processor;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
@@ -62,14 +79,18 @@ class AI_Generator_Helper {
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param User_Helper $user_helper The User helper.
|
||||
* @param Date_Helper $date_helper The date helper.
|
||||
* @param AI_Suggestions_Unifier $ai_suggestions_unifier The AI suggestion unifier.
|
||||
* @param Suggestion_Processor $suggestion_processor The suggestion processor.
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param User_Helper $user_helper The User helper.
|
||||
* @param Date_Helper $date_helper The date helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, User_Helper $user_helper, Date_Helper $date_helper ) {
|
||||
$this->options_helper = $options;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->date_helper = $date_helper;
|
||||
public function __construct( AI_Suggestions_Unifier $ai_suggestions_unifier, Suggestion_Processor $suggestion_processor, Options_Helper $options, User_Helper $user_helper, Date_Helper $date_helper ) {
|
||||
$this->ai_suggestions_unifier = $ai_suggestions_unifier;
|
||||
$this->suggestion_processor = $suggestion_processor;
|
||||
$this->options_helper = $options;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->date_helper = $date_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +188,7 @@ class AI_Generator_Helper {
|
||||
*
|
||||
* @param int $timeout The default timeout in seconds.
|
||||
*/
|
||||
return \apply_filters( 'Yoast\WP\SEO\ai_suggestions_timeout', 30 );
|
||||
return \apply_filters( 'Yoast\WP\SEO\ai_suggestions_timeout', 60 );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,9 +212,10 @@ class AI_Generator_Helper {
|
||||
/**
|
||||
* Performs the request using WordPress internals.
|
||||
*
|
||||
* @param string $action_path The path to the desired action.
|
||||
* @param array $request_body The request body.
|
||||
* @param string[] $request_headers The request headers.
|
||||
* @param string $action_path The path to the desired action.
|
||||
* @param array<string> $request_body The request body.
|
||||
* @param array<string> $request_headers The request headers.
|
||||
* @param bool $is_post Whether it's a POST request.
|
||||
*
|
||||
* @return object The response object.
|
||||
*
|
||||
@@ -208,17 +230,20 @@ class AI_Generator_Helper {
|
||||
* @throws Unauthorized_Exception When the response code is 401.
|
||||
* @throws WP_Request_Exception When the wp_remote_post() returns an error.
|
||||
*/
|
||||
public function request( $action_path, $request_body = [], $request_headers = [] ) {
|
||||
public function request( $action_path, $request_body = [], $request_headers = [], $is_post = true ) {
|
||||
// Our API expects JSON.
|
||||
// The request times out after 30 seconds.
|
||||
$request_headers = \array_merge( $request_headers, [ 'Content-Type' => 'application/json' ] );
|
||||
$request_arguments = [
|
||||
'timeout' => $this->get_request_timeout(),
|
||||
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Reason: We don't want the debug/pretty possibility.
|
||||
'body' => \wp_json_encode( $request_body ),
|
||||
'headers' => $request_headers,
|
||||
];
|
||||
|
||||
if ( $is_post ) {
|
||||
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Reason: We don't want the debug/pretty possibility.
|
||||
$request_arguments['body'] = \wp_json_encode( $request_body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\ai_api_url' - Replaces the default URL for the AI API with a custom one.
|
||||
*
|
||||
@@ -230,7 +255,7 @@ class AI_Generator_Helper {
|
||||
* @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 );
|
||||
$response = ( $is_post ) ? \wp_remote_post( $api_url . $action_path, $request_arguments ) : \wp_remote_get( $api_url . $action_path, $request_arguments );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
|
||||
@@ -285,10 +310,43 @@ class AI_Generator_Helper {
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a response for the AI assessment fixes route by comparing the response to the input.
|
||||
* The differences are marked with `<ins>` and `<del>` tags.
|
||||
*
|
||||
* @param string $original The original text.
|
||||
* @param object $response The response from the API.
|
||||
*
|
||||
* @return string The HTML containing the suggested content.
|
||||
*
|
||||
* @throws Bad_Request_Exception Bad_Request_Exception.
|
||||
*/
|
||||
public function build_fixes_response( string $original, object $response ): string {
|
||||
$raw_fixes = $this->suggestion_processor->get_suggestion_from_ai_response( $response->body );
|
||||
if ( $raw_fixes === '' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We output the diff as an HTML string and will parse this string on the JavaScript side.
|
||||
$diff = $this->suggestion_processor->calculate_diff( $original, $raw_fixes );
|
||||
|
||||
$diff = $this->suggestion_processor->remove_html_from_suggestion( $diff );
|
||||
|
||||
$diff = $this->suggestion_processor->keep_nbsp_in_suggestions( $diff );
|
||||
|
||||
// If we end up with no suggestions, we have to show an error to the user.
|
||||
if ( \strpos( $diff, 'yst-diff' ) === false ) {
|
||||
throw new Bad_Request_Exception();
|
||||
}
|
||||
$suggestion = new Suggestion();
|
||||
$suggestion->set_content( $diff );
|
||||
return $this->ai_suggestions_unifier->unify_diffs( $suggestion );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response from the API.
|
||||
*
|
||||
* @param array|WP_Error $response The response from the API.
|
||||
* @param array<string>|WP_Error $response The response from the API.
|
||||
*
|
||||
* @return (string|int)[] The response code and message.
|
||||
*/
|
||||
@@ -302,7 +360,7 @@ class AI_Generator_Helper {
|
||||
$json_body = \json_decode( \wp_remote_retrieve_body( $response ) );
|
||||
if ( $json_body !== null ) {
|
||||
$response_message = ( $json_body->message ?? $response_message );
|
||||
$error_code = ( $json_body->error_code ?? $this->map_message_to_code( $json_body->message ) );
|
||||
$error_code = ( $json_body->error_code ?? $this->map_message_to_code( $response_message ) );
|
||||
if ( $response_code === 402 ) {
|
||||
$missing_licenses = isset( $json_body->missing_licenses ) ? (array) $json_body->missing_licenses : [];
|
||||
}
|
||||
|
||||
@@ -113,8 +113,7 @@ class Ai_Consent_Integration implements Integration_Interface {
|
||||
'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 ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( $user_id ),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_Screen;
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Admin_Asset_Manager;
|
||||
use Yoast\WP\SEO\Conditionals\Admin\Post_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\Introductions_Seen_Repository;
|
||||
use Yoast\WP\SEO\Premium\Helpers\AI_Generator_Helper;
|
||||
use Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction;
|
||||
|
||||
/**
|
||||
* Ai_Fix_Assessments_Integration class.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Ai_Fix_Assessments_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 AI generator helper.
|
||||
*
|
||||
* @var AI_Generator_Helper
|
||||
*/
|
||||
private $ai_generator_helper;
|
||||
|
||||
/**
|
||||
* Represents the options manager.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Represents the user helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
private $user_helper;
|
||||
|
||||
/**
|
||||
* Represents the introductions seen repository.
|
||||
*
|
||||
* @var Introductions_Seen_Repository
|
||||
*/
|
||||
private $introductions_seen_repository;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals(): array {
|
||||
return [ Post_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 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 Introductions_Seen_Repository $introductions_seen_repository The introductions seen repository.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Admin_Asset_Manager $asset_manager,
|
||||
WPSEO_Addon_Manager $addon_manager,
|
||||
AI_Generator_Helper $ai_generator_helper,
|
||||
Options_Helper $options_helper,
|
||||
User_Helper $user_helper,
|
||||
Introductions_Seen_Repository $introductions_seen_repository
|
||||
) {
|
||||
$this->asset_manager = $asset_manager;
|
||||
$this->addon_manager = $addon_manager;
|
||||
$this->ai_generator_helper = $ai_generator_helper;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->introductions_seen_repository = $introductions_seen_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! $this->options_helper->get( 'enable_ai_generator', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
\add_action( 'admin_head', [ $this, 'render_react_container' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the React container.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_react_container(): void {
|
||||
if ( ! WP_Screen::get()->is_block_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div id="yoast-seo-premium-ai-fix-assessments"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subscription status for Yoast SEO Premium and Yoast WooCommerce SEO.
|
||||
*
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function get_product_subscriptions() {
|
||||
return [
|
||||
'premiumSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ),
|
||||
'wooCommerceSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! WP_Screen::get()->is_block_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = $this->user_helper->get_current_user_id();
|
||||
|
||||
\wp_enqueue_script( 'wp-seo-premium-ai-fix-assessments' );
|
||||
\wp_localize_script(
|
||||
'wp-seo-premium-fix-assessments',
|
||||
'wpseoPremiumAiFixAssessments',
|
||||
[
|
||||
'adminUrl' => \admin_url( 'admin.php' ),
|
||||
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
|
||||
'productSubscriptions' => $this->get_product_subscriptions(),
|
||||
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, Ai_Fix_Assessments_Introduction::ID ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
'requestTimeout' => $this->ai_generator_helper->get_request_timeout(),
|
||||
]
|
||||
);
|
||||
$this->asset_manager->enqueue_style( 'premium-ai-fix-assessments' );
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Ai_Editor_Conditional;
|
||||
use Yoast\WP\SEO\Premium\Helpers\AI_Generator_Helper;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Premium\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction;
|
||||
use Yoast\WP\SEO\Premium\Introductions\Application\Ai_Fix_Assessments_Introduction;
|
||||
|
||||
/**
|
||||
* Ai_Generator_Integration class.
|
||||
@@ -150,7 +150,7 @@ class Ai_Generator_Integration implements Integration_Interface {
|
||||
'adminUrl' => \admin_url( 'admin.php' ),
|
||||
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
|
||||
'productSubscriptions' => $this->get_product_subscriptions(),
|
||||
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, Ai_Generate_Titles_And_Descriptions_Introduction::ID ),
|
||||
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, Ai_Fix_Assessments_Introduction::ID ),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
'postType' => $this->get_post_type(),
|
||||
'contentType' => $this->get_content_type(),
|
||||
|
||||
@@ -138,9 +138,8 @@ class Update_Premium_Notification implements Integration_Interface {
|
||||
'action': 'dismiss_update_premium_notification',
|
||||
};
|
||||
|
||||
jQuery.post( ajaxurl, data, function( response ) {
|
||||
jQuery( '#yoast-update-premium-notification' ).hide();
|
||||
});
|
||||
jQuery( '#yoast-update-premium-notification' ).hide();
|
||||
jQuery.post( ajaxurl, data, function( response ) {});
|
||||
}
|
||||
|
||||
jQuery( document ).ready( function() {
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Blocks\Dynamic_Block;
|
||||
use Yoast\WP\SEO\Integrations\Blocks\Dynamic_Block_V3;
|
||||
|
||||
/**
|
||||
* Estimated_Reading_Time_Block class.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Estimated_Reading_Time_Block extends Dynamic_Block {
|
||||
class Estimated_Reading_Time_Block extends Dynamic_Block_V3 {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
@@ -33,47 +33,17 @@ class Estimated_Reading_Time_Block extends Dynamic_Block {
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* Registers the block.
|
||||
*
|
||||
* @return void
|
||||
* The constructor.
|
||||
*/
|
||||
public function register_block() {
|
||||
\register_block_type(
|
||||
'yoast-seo/' . $this->block_name,
|
||||
[
|
||||
'editor_script' => $this->script,
|
||||
'render_callback' => [ $this, 'present' ],
|
||||
'attributes' => [
|
||||
'className' => [
|
||||
'default' => '',
|
||||
'type' => 'string',
|
||||
],
|
||||
'estimatedReadingTime' => [
|
||||
'type' => 'number',
|
||||
'default' => 0,
|
||||
],
|
||||
'descriptiveText' => [
|
||||
'type' => 'string',
|
||||
'default' => \__( 'Estimated reading time:', 'wordpress-seo-premium' ) . ' ',
|
||||
],
|
||||
'showDescriptiveText' => [
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
],
|
||||
'showIcon' => [
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
public function __construct() {
|
||||
$this->base_path = \WPSEO_PREMIUM_PATH . 'assets/blocks/dynamic-blocks/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
* @param string $content The content.
|
||||
* @param array<string, bool|string|int|array> $attributes The block attributes.
|
||||
* @param string $content The content.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ class Related_Links_Block implements Integration_Interface {
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\register_block_type( 'yoast-seo/related-links', [ 'editor_script' => 'wp-seo-premium-blocks' ] );
|
||||
$base_path = \WPSEO_PREMIUM_PATH . 'assets/blocks/dynamic-blocks/';
|
||||
\register_block_type( $base_path . 'related-links-block/block.json', [ 'editor_script_handles' => [ 'wp-seo-premium-blocks' ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -24,13 +23,6 @@ class Index_Now_Ping implements Integration_Interface {
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The request helper.
|
||||
*
|
||||
* @var Request_Helper
|
||||
*/
|
||||
private $request_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
@@ -49,16 +41,13 @@ class Index_Now_Ping implements Integration_Interface {
|
||||
* 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;
|
||||
|
||||
/**
|
||||
@@ -111,7 +100,7 @@ class Index_Now_Ping implements Integration_Interface {
|
||||
}
|
||||
|
||||
// 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() ) {
|
||||
if ( $new_status === 'publish' && \wp_is_serving_rest_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,21 @@ class AI_Generator_Route implements Route_Interface {
|
||||
public const GET_SUGGESTIONS_ROUTE = self::ROUTE_PREFIX . '/get_suggestions';
|
||||
|
||||
/**
|
||||
* The get_suggestions route constant.
|
||||
* The fix_assessments route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FIX_ASSESSMENTS_ROUTE = self::ROUTE_PREFIX . '/fix_assessments';
|
||||
|
||||
/**
|
||||
* The get_usage route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const GET_USAGE_ROUTE = self::ROUTE_PREFIX . '/get_usage';
|
||||
|
||||
/**
|
||||
* The consent route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@@ -167,6 +181,8 @@ class AI_Generator_Route implements Route_Interface {
|
||||
'meta-description',
|
||||
'product-seo-title',
|
||||
'product-meta-description',
|
||||
'product-taxonomy-seo-title',
|
||||
'product-taxonomy-meta-description',
|
||||
'taxonomy-seo-title',
|
||||
'taxonomy-meta-description',
|
||||
],
|
||||
@@ -203,6 +219,59 @@ class AI_Generator_Route implements Route_Interface {
|
||||
]
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::FIX_ASSESSMENTS_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'assessment' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'enum' => [
|
||||
'keyphrase-introduction',
|
||||
'keyphrase-density',
|
||||
'keyphrase-distribution',
|
||||
'keyphrase-subheadings',
|
||||
],
|
||||
'description' => 'The assessment.',
|
||||
],
|
||||
'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.',
|
||||
],
|
||||
'synonyms' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'description' => 'The synonyms for the focus keyphrase.',
|
||||
],
|
||||
'language' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The language the post is written in.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'fix_assessments' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::GET_USAGE_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'get_usage' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::BUST_SUBSCRIPTION_CACHE_ROUTE,
|
||||
@@ -238,7 +307,7 @@ class AI_Generator_Route implements Route_Interface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to get ai-generated suggestions.
|
||||
* Runs the callback to get AI-generated suggestions.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
@@ -267,6 +336,36 @@ class AI_Generator_Route implements Route_Interface {
|
||||
return new WP_REST_Response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to improve assessment results through AI.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the assess action.
|
||||
*/
|
||||
public function fix_assessments( WP_REST_Request $request ) {
|
||||
try {
|
||||
$user = \wp_get_current_user();
|
||||
$data = $this->ai_generator_action->fix_assessments( $user, $request['assessment'], $request['prompt_content'], $request['focus_keyphrase'], $request['synonyms'], $request['language'] );
|
||||
} catch ( Remote_Request_Exception $e ) {
|
||||
$message = [
|
||||
'message' => $e->getMessage(),
|
||||
'errorIdentifier' => $e->get_error_identifier(),
|
||||
];
|
||||
if ( $e instanceof Payment_Required_Exception ) {
|
||||
$message['missingLicenses'] = $e->get_missing_licenses();
|
||||
}
|
||||
return new WP_REST_Response(
|
||||
$message,
|
||||
$e->getCode()
|
||||
);
|
||||
} catch ( RuntimeException $e ) {
|
||||
return new WP_REST_Response( 'Failed to retrieve text improvements.', 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback to store the consent given by the user to use AI-based services.
|
||||
*
|
||||
@@ -287,6 +386,26 @@ class AI_Generator_Route implements Route_Interface {
|
||||
return new WP_REST_Response( ( $consent ) ? 'Consent successfully stored.' : 'Consent successfully revoked.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback that gets the monthly usage of the user.
|
||||
*
|
||||
* @return WP_REST_Response The response of the callback action.
|
||||
*/
|
||||
public function get_usage() {
|
||||
$user = \wp_get_current_user();
|
||||
|
||||
try {
|
||||
$data = $this->ai_generator_action->get_usage( $user );
|
||||
} catch ( Remote_Request_Exception $e ) {
|
||||
return new WP_REST_Response(
|
||||
'Failed to get usage: ' . $e->getMessage(),
|
||||
$e->getCode()
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the callback that busts the subscription cache.
|
||||
*
|
||||
|
||||
@@ -8,15 +8,15 @@ use Yoast\WP\SEO\Introductions\Application\User_Allowed_Trait;
|
||||
use Yoast\WP\SEO\Introductions\Domain\Introduction_Interface;
|
||||
|
||||
/**
|
||||
* Represents the introduction for the AI generate titles and introduction upsell.
|
||||
* Represents the introduction for the AI fix assessments feature.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Ai_Generate_Titles_And_Descriptions_Introduction implements Introduction_Interface {
|
||||
class Ai_Fix_Assessments_Introduction implements Introduction_Interface {
|
||||
|
||||
use User_Allowed_Trait;
|
||||
|
||||
public const ID = 'ai-generate-titles-and-descriptions';
|
||||
public const ID = 'ai-fix-assessments';
|
||||
|
||||
/**
|
||||
* Holds the options helper.
|
||||
@@ -46,19 +46,16 @@ class Ai_Generate_Titles_And_Descriptions_Introduction implements Introduction_I
|
||||
/**
|
||||
* Returns the ID.
|
||||
*
|
||||
* @return string
|
||||
* @return string The ID.
|
||||
*/
|
||||
public function get_id() {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name.
|
||||
* Returns the name of the introdyction.
|
||||
*
|
||||
* @deprecated 21.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
* @return string The name.
|
||||
*/
|
||||
public function get_name() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 21.6', 'Please use get_id() instead' );
|
||||
@@ -69,7 +66,7 @@ class Ai_Generate_Titles_And_Descriptions_Introduction implements Introduction_I
|
||||
/**
|
||||
* Returns the requested pagination priority. Lower means earlier.
|
||||
*
|
||||
* @return int
|
||||
* @return int The priority.
|
||||
*/
|
||||
public function get_priority() {
|
||||
return 10;
|
||||
@@ -78,25 +75,15 @@ class Ai_Generate_Titles_And_Descriptions_Introduction implements Introduction_I
|
||||
/**
|
||||
* Returns whether this introduction should show.
|
||||
*
|
||||
* @return bool
|
||||
* @return bool Whether this introduction should show.
|
||||
*/
|
||||
public function should_show() {
|
||||
// Feature was already enabled, no need to introduce it again.
|
||||
if ( $this->options_helper->get( 'ai_enabled_pre_default', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the current user ID, if no user is logged in we bail as this is needed for the next checks.
|
||||
$current_user_id = $this->user_helper->get_current_user_id();
|
||||
if ( $current_user_id === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consent was already given, no need to ask again.
|
||||
if ( $this->user_helper->get_meta( $current_user_id, '_yoast_wpseo_ai_consent', true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_user_allowed( [ 'edit_posts' ] ) ) {
|
||||
return false;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use WPSEO_Redirect_Manager;
|
||||
use WPSEO_Taxonomy_Meta;
|
||||
use Yoast\WP\SEO\Builders\Indexable_Term_Builder;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
@@ -95,6 +96,13 @@ class Workouts_Route implements Route_Interface {
|
||||
*/
|
||||
private $indexable_term_builder;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
@@ -108,17 +116,20 @@ class Workouts_Route implements Route_Interface {
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
* @param Link_Suggestions_Action $link_suggestions_action The link suggestions action.
|
||||
* @param Indexable_Term_Builder $indexable_term_builder The indexable term builder.
|
||||
* @param Indexable_Helper $indexable_helper The indexable 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,
|
||||
Indexable_Term_Builder $indexable_term_builder,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Post_Type_Helper $post_type_helper
|
||||
) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->link_suggestions_action = $link_suggestions_action;
|
||||
$this->indexable_term_builder = $indexable_term_builder;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
}
|
||||
|
||||
@@ -262,19 +273,6 @@ class Workouts_Route implements Route_Interface {
|
||||
if ( $request['object_type'] === 'post' ) {
|
||||
WPSEO_Meta::set_value( 'meta-robots-noindex', 1, $request['object_id'] );
|
||||
}
|
||||
elseif ( $request['object_type'] === 'term' ) {
|
||||
WPSEO_Taxonomy_Meta::set_value( $request['object_id'], $request['object_sub_type'], 'noindex', 'noindex' );
|
||||
// Rebuild the indexable as WPSEO_Taxonomy_Meta does not trigger any actions on which term indexables are rebuild.
|
||||
$indexable = $this->indexable_term_builder->build( $request['object_id'], $this->indexable_repository->find_by_id_and_type( $request['object_id'], $request['object_type'] ) );
|
||||
if ( \is_a( $indexable, Indexable::class ) ) {
|
||||
$indexable->save();
|
||||
}
|
||||
else {
|
||||
return new WP_REST_Response(
|
||||
[ 'json' => false ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[ 'json' => true ]
|
||||
@@ -298,7 +296,7 @@ class Workouts_Route implements Route_Interface {
|
||||
// Rebuild the indexable as WPSEO_Taxonomy_Meta does not trigger any actions on which term indexables are rebuild.
|
||||
$indexable = $this->indexable_term_builder->build( $request['object_id'], $this->indexable_repository->find_by_id_and_type( $request['object_id'], $request['object_type'] ) );
|
||||
if ( \is_a( $indexable, Indexable::class ) ) {
|
||||
$indexable->save();
|
||||
$this->indexable_helper->save_indexable( $indexable );
|
||||
}
|
||||
else {
|
||||
return new WP_REST_Response(
|
||||
|
||||
Reference in New Issue
Block a user