PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.1
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / ai-generator / user-interface / ai-generator-integration.php

ai-generator-integration.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.1, at src/ai-generator/user-interface/ai-generator-integration.php

304 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\AI_Generator\User_Interface;
4
5 use WPSEO_Addon_Manager;
6 use WPSEO_Admin_Asset_Manager;
7 use Yoast\WP\SEO\AI\Consent\Application\Consent_Endpoints_Repository;
8 use Yoast\WP\SEO\AI\Free_Sparks\Application\Free_Sparks_Endpoints_Repository;
9 use Yoast\WP\SEO\AI\Generator\Application\Generator_Endpoints_Repository;
10 use Yoast\WP\SEO\AI_HTTP_Request\Infrastructure\API_Client;
11 use Yoast\WP\SEO\Conditionals\AI_Conditional;
12 use Yoast\WP\SEO\Conditionals\AI_Editor_Conditional;
13 use Yoast\WP\SEO\Conditionals\MyYoast_Connection_Conditional;
14 use Yoast\WP\SEO\Conditionals\Old_Premium_AI_Conditional;
15 use Yoast\WP\SEO\Helpers\Current_Page_Helper;
16 use Yoast\WP\SEO\Helpers\Options_Helper;
17 use Yoast\WP\SEO\Helpers\Short_Link_Helper;
18 use Yoast\WP\SEO\Helpers\User_Helper;
19 use Yoast\WP\SEO\Integrations\Admin\Integrations_Page;
20 use Yoast\WP\SEO\Integrations\Integration_Interface;
21 use Yoast\WP\SEO\Introductions\Application\Ai_Fix_Assessments_Upsell;
22 use Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository;
23 use Yoast\WP\SEO\MyYoast_Client\User_Interface\Connection_Permission;
24 use Yoast\WP\SEO\MyYoast_Client\User_Interface\Status_Presenter;
25
26 /**
27 * Ai_Generator_Integration class.
28 */
29 class Ai_Generator_Integration implements Integration_Interface {
30
31 /**
32 * Represents the admin asset manager.
33 *
34 * @var WPSEO_Admin_Asset_Manager
35 */
36 private $asset_manager;
37
38 /**
39 * Represents the add-on manager.
40 *
41 * @var WPSEO_Addon_Manager
42 */
43 private $addon_manager;
44
45 /**
46 * Holds the API client instance.
47 *
48 * @var API_Client
49 */
50 private $api_client;
51
52 /**
53 * Represents the current page helper.
54 *
55 * @var Current_Page_Helper
56 */
57 private $current_page_helper;
58
59 /**
60 * Represents the options manager.
61 *
62 * @var Options_Helper
63 */
64 private $options_helper;
65
66 /**
67 * Represents the user helper.
68 *
69 * @var User_Helper
70 */
71 private $user_helper;
72
73 /**
74 * Represents the introductions seen repository.
75 *
76 * @var Introductions_Seen_Repository
77 */
78 private $introductions_seen_repository;
79
80 /**
81 * Represents the endpoints repository.
82 *
83 * @var Generator_Endpoints_Repository
84 */
85 private $generator_endpoints_repository;
86
87 /**
88 * Represents the consent endpoints repository.
89 *
90 * @var Consent_Endpoints_Repository
91 */
92 private $consent_endpoints_repository;
93
94 /**
95 * Represents the free sparks endpoints repository.
96 *
97 * @var Free_Sparks_Endpoints_Repository
98 */
99 private $free_sparks_endpoints_repository;
100
101 /**
102 * The MyYoast connection feature-flag conditional.
103 *
104 * @var MyYoast_Connection_Conditional
105 */
106 private $myyoast_connection_conditional;
107
108 /**
109 * The MyYoast connection status presenter.
110 *
111 * @var Status_Presenter
112 */
113 private $status_presenter;
114
115 /**
116 * The short-link helper.
117 *
118 * @var Short_Link_Helper
119 */
120 private $short_link_helper;
121
122 /**
123 * The MyYoast connection-management permission check.
124 *
125 * @var Connection_Permission
126 */
127 private $connection_permission;
128
129 /**
130 * Returns the conditionals based in which this loadable should be active.
131 *
132 * @return array<string>
133 */
134 public static function get_conditionals() {
135 return [ AI_Conditional::class, AI_Editor_Conditional::class, Old_Premium_AI_Conditional::class ];
136 }
137
138 /**
139 * Constructs the class.
140 *
141 * @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
142 * @param WPSEO_Addon_Manager $addon_manager The addon manager.
143 * @param API_Client $api_client The API client.
144 * @param Current_Page_Helper $current_page_helper The current page helper.
145 * @param Options_Helper $options_helper The options helper.
146 * @param User_Helper $user_helper The user helper.
147 * @param Introductions_Seen_Repository $introductions_seen_repository The introductions seen repository.
148 * @param Generator_Endpoints_Repository $generator_endpoints_repository The Generator endpoints repository.
149 * @param Consent_Endpoints_Repository $consent_endpoints_repository The Consent endpoints repository.
150 * @param Free_Sparks_Endpoints_Repository $free_sparks_endpoints_repository The Free Sparks endpoints repository.
151 * @param MyYoast_Connection_Conditional $myyoast_connection_conditional The MyYoast connection feature-flag conditional.
152 * @param Status_Presenter $status_presenter The MyYoast connection status presenter.
153 * @param Short_Link_Helper $short_link_helper The short-link helper.
154 * @param Connection_Permission $connection_permission The MyYoast connection-management permission check.
155 */
156 public function __construct(
157 WPSEO_Admin_Asset_Manager $asset_manager,
158 WPSEO_Addon_Manager $addon_manager,
159 API_Client $api_client,
160 Current_Page_Helper $current_page_helper,
161 Options_Helper $options_helper,
162 User_Helper $user_helper,
163 Introductions_Seen_Repository $introductions_seen_repository,
164 Generator_Endpoints_Repository $generator_endpoints_repository,
165 Consent_Endpoints_Repository $consent_endpoints_repository,
166 Free_Sparks_Endpoints_Repository $free_sparks_endpoints_repository,
167 MyYoast_Connection_Conditional $myyoast_connection_conditional,
168 Status_Presenter $status_presenter,
169 Short_Link_Helper $short_link_helper,
170 Connection_Permission $connection_permission
171 ) {
172 $this->asset_manager = $asset_manager;
173 $this->addon_manager = $addon_manager;
174 $this->api_client = $api_client;
175 $this->current_page_helper = $current_page_helper;
176 $this->options_helper = $options_helper;
177 $this->user_helper = $user_helper;
178 $this->introductions_seen_repository = $introductions_seen_repository;
179 $this->generator_endpoints_repository = $generator_endpoints_repository;
180 $this->consent_endpoints_repository = $consent_endpoints_repository;
181 $this->free_sparks_endpoints_repository = $free_sparks_endpoints_repository;
182 $this->myyoast_connection_conditional = $myyoast_connection_conditional;
183 $this->status_presenter = $status_presenter;
184 $this->short_link_helper = $short_link_helper;
185 $this->connection_permission = $connection_permission;
186 }
187
188 /**
189 * Initializes the integration.
190 *
191 * This is the place to register hooks and filters.
192 *
193 * @return void
194 */
195 public function register_hooks() {
196 \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
197 // Enqueue after Elementor_Premium integration, which re-registers the assets.
198 \add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 );
199 }
200
201 /**
202 * Gets the subscription status for Yoast SEO Premium and Yoast WooCommerce SEO.
203 *
204 * @return array<string, bool>
205 */
206 public function get_product_subscriptions() {
207 return [
208 'premiumSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ),
209 'wooCommerceSubscription' => $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ),
210 ];
211 }
212
213 /**
214 * Returns the data that should be passed to the script.
215 *
216 * @return array<string|array<string>>
217 */
218 public function get_script_data() {
219 $user_id = $this->user_helper->get_current_user_id();
220
221 $endpoints = $this->generator_endpoints_repository->get_all_endpoints()
222 ->merge_with(
223 $this->consent_endpoints_repository->get_all_endpoints(),
224 )->merge_with(
225 $this->free_sparks_endpoints_repository->get_all_endpoints(),
226 )->to_paths_array();
227
228 return [
229 'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
230 'productSubscriptions' => $this->get_product_subscriptions(),
231 'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, AI_Fix_Assessments_Upsell::ID ),
232 'requestTimeout' => $this->api_client->get_request_timeout(),
233 'isFreeSparks' => $this->options_helper->get( 'ai_free_sparks_started_on', null ) !== null,
234 'endpoints' => $endpoints,
235 'myyoastConnection' => $this->get_myyoast_connection_data(),
236 ];
237 }
238
239 /**
240 * Builds the read-only MyYoast connection payload used to pick the
241 * "Yoast AI cannot reach your site" notification variant in the editor.
242 *
243 * Returns `null` when the feature flag is disabled, so the editor treats the
244 * connection as unavailable and shows the informational-only variant. The
245 * payload is deliberately minimal — no store, actions, or tokens reach the
246 * editor; the connect call-to-action is just a nonce-protected link that
247 * auto-starts the flow on the Integrations page in a new tab.
248 *
249 * @return array{isProvisioned: bool, canConnect: bool, connectUrl: string|null, learnMoreUrl: string}|null
250 */
251 public function get_myyoast_connection_data() {
252 if ( ! $this->myyoast_connection_conditional->is_met() ) {
253 return null;
254 }
255
256 $status = $this->status_presenter->present();
257 $can_connect = $this->connection_permission->can_manage();
258
259 return [
260 'isProvisioned' => \is_bool( $status['is_provisioned'] ) && $status['is_provisioned'],
261 'canConnect' => $can_connect,
262 // Only users who can manage options can start the flow; for everyone
263 // else the link is omitted and the editor shows the "ask your admin" variant.
264 'connectUrl' => ( $can_connect ) ? $this->get_connect_url() : null,
265 'learnMoreUrl' => $this->short_link_helper->get( 'https://yoa.st/ai-myyoast-connection' ),
266 ];
267 }
268
269 /**
270 * Builds the nonce-protected Integrations-page URL that auto-starts the
271 * MyYoast connection flow when opened.
272 *
273 * The nonce keeps the auto-start trigger from being cross-site forgeable; the
274 * Integrations page verifies it before kicking off the flow.
275 *
276 * Built with `add_query_arg()` + `wp_create_nonce()` rather than `wp_nonce_url()`:
277 * the latter HTML-encodes the `&` separators for markup output, but this URL is
278 * localized and assigned to a React `href`, where it isn't decoded — the browser
279 * would then send `amp;start-myyoast-connection` as the query-arg name and the
280 * flow would never start. This form keeps the separators as plain `&`.
281 *
282 * @return string The connect URL.
283 */
284 private function get_connect_url() {
285 return \add_query_arg(
286 '_wpnonce',
287 \wp_create_nonce( 'wpseo-start-myyoast-connection' ),
288 \self_admin_url( 'admin.php?page=' . Integrations_Page::PAGE . '&start-myyoast-connection=1' ),
289 );
290 }
291
292 /**
293 * Enqueues the required assets.
294 *
295 * @return void
296 */
297 public function enqueue_assets() {
298
299 $this->asset_manager->enqueue_script( 'ai-generator' );
300 $this->asset_manager->localize_script( 'ai-generator', 'wpseoAiGenerator', $this->get_script_data() );
301 $this->asset_manager->enqueue_style( 'ai-generator' );
302 }
303 }
304