PluginProbe
Parse.ly / 3.8.4
Parse.ly v3.8.4
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
← All changes | src/class-parsely.php +143 -628 3.18.03.8.4 View file →
@@ -9,14 +9,9 @@
9 9 declare(strict_types=1);
10 10
11 11 namespace Parsely;
12 12
13 -use Parsely\REST_API\REST_API_Controller;
14 -use Parsely\Services\Content_API\Content_API_Service;
15 -use Parsely\Services\Suggestions_API\Suggestions_API_Service;
16 13 use Parsely\UI\Metadata_Renderer;
17 -use Parsely\UI\Settings_Page;
18 -use Parsely\Utils\Utils;
19 14 use WP_Post;
20 15
21 16 /**
22 17 * Holds most of the logic for the plugin.
@@ -30,9 +25,8 @@
30 25 * api_secret: string,
31 26 * use_top_level_cats: bool,
32 27 * custom_taxonomy_section: string,
33 28 * cats_as_tags: bool,
34 - * content_helper: Parsely_Options_Content_Helper,
35 29 * track_authenticated_users: bool,
36 30 * lowercase_tags: bool,
37 31 * force_https_canonicals: bool,
38 32 * track_post_types: string[],
@@ -37,39 +31,18 @@
37 31 * force_https_canonicals: bool,
38 32 * track_post_types: string[],
39 33 * track_page_types: string[],
40 34 * track_post_types_as?: array<string, string>,
41 - * full_metadata_in_non_posts: bool,
42 35 * disable_javascript: bool,
43 36 * disable_amp: bool,
44 37 * meta_type: string,
45 38 * logo: string,
46 39 * metadata_secret: string,
40 + * parsely_wipe_metadata_cache: bool,
47 41 * disable_autotrack: bool,
48 42 * plugin_version: string,
49 43 * }
50 44 *
51 - * @phpstan-type Parsely_Options_Content_Helper array{
52 - * ai_features_enabled: bool,
53 - * smart_linking: Parsely_Options_Content_Helper_Feature,
54 - * title_suggestions: Parsely_Options_Content_Helper_Feature,
55 - * excerpt_suggestions: Parsely_Options_Content_Helper_Feature,
56 - * }
57 - *
58 - * @phpstan-type Parsely_Options_Content_Helper_Feature array{
59 - * enabled: bool,
60 - * allowed_user_roles: string[],
61 - * }
62 - *
63 - * @phpstan-type WP_HTTP_Request_Args array{
64 - * method?: string,
65 - * timeout?: float,
66 - * blocking?: bool,
67 - * headers?: array<string, string>,
68 - * body?: string,
69 - * data_format?: string,
70 - * }
71 - *
72 45 * @phpstan-import-type Metadata_Attributes from Metadata
73 46 */
74 47 class Parsely {
75 48 /**
@@ -74,84 +47,49 @@
74 47 class Parsely {
75 48 /**
76 49 * Declare our constants
77 50 */
78 - public const VERSION = PARSELY_VERSION;
79 - public const MENU_SLUG = 'parsely'; // The page param passed to options-general.php.
80 - public const OPTIONS_KEY = 'parsely'; // The key used to store options in the WP database.
81 - public const CAPABILITY = 'manage_options'; // The capability required to administer settings.
82 - public const DASHBOARD_BASE_URL = 'https://dash.parsely.com';
51 + public const VERSION = PARSELY_VERSION;
52 + public const MENU_SLUG = 'parsely'; // Defines the page param passed to options-general.php.
53 + public const OPTIONS_KEY = 'parsely'; // Defines the key used to store options in the WP database.
54 + public const CAPABILITY = 'manage_options'; // The capability required for the user to administer settings.
55 + public const DASHBOARD_BASE_URL = 'https://dash.parsely.com';
56 + public const PUBLIC_API_BASE_URL = 'https://api.parsely.com/v2';
83 57
84 58 /**
85 - * The Content API service.
86 - *
87 - * @var ?Content_API_Service $content_api_service
88 - */
89 - private $content_api_service;
90 -
91 - /**
92 - * The Suggestions API service.
93 - *
94 - * @var ?Suggestions_API_Service $suggestions_api_service
95 - */
96 - private $suggestions_api_service;
97 -
98 - /**
99 - * The Parse.ly internal REST API controller.
100 - *
101 - * @var REST_API_Controller|null $rest_api_controller
102 - */
103 - private $rest_api_controller;
104 -
105 - /**
106 59 * Declare some class properties
107 60 *
108 61 * @var Parsely_Options $option_defaults The defaults we need for the class.
109 62 */
110 63 private $option_defaults = array(
111 - 'apikey' => '',
112 - 'content_id_prefix' => '',
113 - 'api_secret' => '',
114 - 'use_top_level_cats' => false,
115 - 'custom_taxonomy_section' => 'category',
116 - 'cats_as_tags' => false,
117 - 'content_helper' => array(
118 - 'ai_features_enabled' => true,
119 - 'smart_linking' => array(
120 - 'enabled' => true,
121 - 'allowed_user_roles' => array( 'administrator' ),
122 - ),
123 - 'title_suggestions' => array(
124 - 'enabled' => true,
125 - 'allowed_user_roles' => array( 'administrator' ),
126 - ),
127 - 'excerpt_suggestions' => array(
128 - 'enabled' => true,
129 - 'allowed_user_roles' => array( 'administrator' ),
130 - ),
131 - ),
132 - 'track_authenticated_users' => false,
133 - 'lowercase_tags' => true,
134 - 'force_https_canonicals' => false,
135 - 'track_post_types' => array(),
136 - 'track_page_types' => array(),
137 - 'full_metadata_in_non_posts' => true,
138 - 'disable_javascript' => false,
139 - 'disable_amp' => false,
140 - 'meta_type' => 'json_ld',
141 - 'logo' => '',
142 - 'metadata_secret' => '',
143 - 'disable_autotrack' => false,
144 - 'plugin_version' => self::VERSION,
64 + 'apikey' => '',
65 + 'content_id_prefix' => '',
66 + 'api_secret' => '',
67 + 'use_top_level_cats' => false,
68 + 'custom_taxonomy_section' => 'category',
69 + 'cats_as_tags' => false,
70 + 'track_authenticated_users' => false,
71 + 'lowercase_tags' => true,
72 + 'force_https_canonicals' => false,
73 + 'track_post_types' => array( 'post' ),
74 + 'track_page_types' => array( 'page' ),
75 + 'disable_javascript' => false,
76 + 'disable_amp' => false,
77 + 'meta_type' => 'json_ld',
78 + 'logo' => '',
79 + 'metadata_secret' => '',
80 + 'parsely_wipe_metadata_cache' => false,
81 + 'disable_autotrack' => false,
82 + 'plugin_version' => '',
145 83 );
146 84
147 85 /**
148 86 * Declare post types that Parse.ly will process as "posts".
149 87 *
88 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
89 + *
150 90 * @since 2.5.0
151 91 * @var string[]
152 - *
153 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
154 92 */
155 93 public const SUPPORTED_JSONLD_POST_TYPES = array(
156 94 'NewsArticle',
157 95 'Article',
@@ -160,26 +98,17 @@
160 98 'LiveBlogPosting',
161 99 'Report',
162 100 'Review',
163 101 'CreativeWork',
164 - 'OpinionNewsArticle',
165 - 'AnalysisNewsArticle',
166 - 'BackgroundNewsArticle',
167 - 'ReviewNewsArticle',
168 - 'ReportageNewsArticle',
169 - 'Recipe',
170 - 'AdvertiserContentArticle',
171 - 'MedicalWebPage',
172 - 'PodcastEpisode',
173 102 );
174 103
175 104 /**
176 105 * Declare post types that Parse.ly will process as "non-posts".
177 106 *
107 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
108 + *
178 109 * @since 2.5.0
179 110 * @var string[]
180 - *
181 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
182 111 */
183 112 public const SUPPORTED_JSONLD_NON_POST_TYPES = array(
184 113 'WebPage',
185 114 'Event',
@@ -196,70 +125,15 @@
196 125 */
197 126 private static $all_supported_types;
198 127
199 128 /**
200 - * Returns whether credentials are being managed at the platform level.
201 - *
202 - * This allows hosting providers to provide a more customized experience for
203 - * the plugin by handling credentials automatically.
204 - *
205 - * @since 3.9.0
206 - * @access private
207 - * @var bool
208 - */
209 - public $are_credentials_managed;
210 -
211 - /**
212 - * Holds the managed options and their values.
213 - *
214 - * This allows hosting providers to provide a more customized experience for
215 - * the plugin by handling options automatically.
216 - *
217 - * @since 3.9.0
218 - * @access private
219 - * @var array<empty>|array<string, bool|string|null>
220 - */
221 - public $managed_options = array();
222 -
223 - /**
224 129 * Constructor.
225 130 */
226 131 public function __construct() {
227 132 self::$all_supported_types = array_merge( self::SUPPORTED_JSONLD_POST_TYPES, self::SUPPORTED_JSONLD_NON_POST_TYPES );
228 -
229 - $this->are_credentials_managed = $this->are_credentials_managed();
230 - $this->set_managed_options();
231 -
232 - $this->allow_parsely_remote_requests();
233 133 }
234 134
235 135 /**
236 - * Gets the allowed post statuses for tracking.
237 - *
238 - * Uses the `wp_parsely_trackable_statuses` filter to determine which post statuses are allowed to be tracked.
239 - *
240 - * @since 3.17.0
241 - *
242 - * @param WP_Post|int|null $post The post object.
243 - * @return array<string> The allowed post statuses.
244 - */
245 - public static function get_trackable_statuses( $post = null ): array {
246 - /**
247 - * Filters the statuses that are permitted to be tracked.
248 - *
249 - * By default, the only status tracked is 'publish'. Use this filter if
250 - * you have other published content that has a different (custom) status.
251 - *
252 - * @since 2.5.0
253 - * @since 3.17.0 Filter extracted to a separate method.
254 - *
255 - * @param string[] $trackable_statuses The list of post statuses that are allowed to be tracked.
256 - * @param WP_Post|int|null $post Which post object or ID is being checked.
257 - */
258 - return apply_filters( 'wp_parsely_trackable_statuses', array( 'publish' ), $post );
259 - }
260 -
261 - /**
262 136 * Registers action and filter hook callbacks, and immediately upgrades
263 137 * options if needed.
264 138 */
265 139 public function run(): void {
@@ -275,70 +149,35 @@
275 149 */
276 150 $callable = array( $this, $method );
277 151 call_user_func_array( $callable, array( $options ) );
278 152 }
279 -
280 153 // Update our version info.
281 154 $options['plugin_version'] = self::VERSION;
282 155 update_option( self::OPTIONS_KEY, $options );
283 156 }
284 157
285 - // @phpstan-ignore return.void
286 - add_action( 'save_post', array( $this, 'call_update_metadata_endpoint' ) );
158 + // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval
159 + add_filter( 'cron_schedules', array( $this, 'wpparsely_add_cron_interval' ) );
160 + add_action( 'parsely_bulk_metas_update', array( $this, 'bulk_update_posts' ) );
161 + add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) );
287 162 }
288 163
289 164 /**
290 - * Returns the Content API service.
165 + * Adds 10 minute cron interval.
291 166 *
292 - * This method returns the Content API service, which is used to interact with the Parse.ly Content API.
167 + * @param array<string, mixed> $schedules WP schedules array.
293 168 *
294 - * @since 3.17.0
295 - *
296 - * @return Content_API_Service
169 + * @return array<string, mixed>
297 170 */
298 - public function get_content_api(): Content_API_Service {
299 - if ( ! isset( $this->content_api_service ) ) {
300 - $this->content_api_service = new Content_API_Service( $this );
301 - }
302 -
303 - return $this->content_api_service;
171 + public function wpparsely_add_cron_interval( array $schedules ): array {
172 + $schedules['everytenminutes'] = array(
173 + 'interval' => 600, // time in seconds.
174 + 'display' => __( 'Every 10 Minutes', 'wp-parsely' ),
175 + );
176 + return $schedules;
304 177 }
305 178
306 179 /**
307 - * Returns the Suggestions API service.
308 - *
309 - * This method returns the Suggestions API service, which is used to interact with the Parse.ly Suggestions API.
310 - *
311 - * @since 3.17.0
312 - *
313 - * @return Suggestions_API_Service
314 - */
315 - public function get_suggestions_api(): Suggestions_API_Service {
316 - if ( ! isset( $this->suggestions_api_service ) ) {
317 - $this->suggestions_api_service = new Suggestions_API_Service( $this );
318 - }
319 -
320 - return $this->suggestions_api_service;
321 - }
322 -
323 - /**
324 - * Gets the REST API controller.
325 - *
326 - * If the controller is not set, a new instance is created.
327 - *
328 - * @since 3.17.0
329 - *
330 - * @return REST_API_Controller
331 - */
332 - public function get_rest_api_controller(): REST_API_Controller {
333 - if ( ! isset( $this->rest_api_controller ) ) {
334 - $this->rest_api_controller = new REST_API_Controller( $this );
335 - }
336 -
337 - return $this->rest_api_controller;
338 - }
339 -
340 - /**
341 180 * Gets the full URL of the JavaScript tracker file for the site. If an API
342 181 * key is not set, return an empty string.
343 182 *
344 183 * @since 3.2.0
@@ -397,8 +236,14 @@
397 236 * @return bool Should the post status be tracked for the provided post's post_type.
398 237 * By default,only 'publish' is allowed.
399 238 */
400 239 public static function post_has_trackable_status( $post ): bool {
240 + static $cache = array();
241 + $post_id = is_int( $post ) ? $post : $post->ID;
242 + if ( isset( $cache[ $post_id ] ) ) {
243 + return $cache[ $post_id ];
244 + }
245 +
401 246 /**
402 247 * Filters whether the post password check should be skipped when getting
403 248 * the post trackable status.
404 249 *
@@ -406,17 +251,30 @@
406 251 *
407 252 * @param bool $skip True if the password check should be skipped.
408 253 * @param int|WP_Post $post Which post object or ID is being checked.
409 254 *
410 - * @return bool
255 + * @returns bool
411 256 */
412 257 $skip_password_check = apply_filters( 'wp_parsely_skip_post_password_check', false, $post );
413 258 if ( ! $skip_password_check && post_password_required( $post ) ) {
259 + $cache[ $post_id ] = false;
414 260 return false;
415 261 }
416 262
417 - $statuses = self::get_trackable_statuses( $post );
418 - return in_array( get_post_status( $post ), $statuses, true );
263 + /**
264 + * Filters the statuses that are permitted to be tracked.
265 + *
266 + * By default, the only status tracked is 'publish'. Use this filter if
267 + * you have other published content that has a different (custom) status.
268 + *
269 + * @since 2.5.0
270 + *
271 + * @param string[] $trackable_statuses The list of post statuses that are allowed to be tracked.
272 + * @param int|WP_Post $post Which post object or ID is being checked.
273 + */
274 + $statuses = apply_filters( 'wp_parsely_trackable_statuses', array( 'publish' ), $post );
275 + $cache[ $post_id ] = in_array( get_post_status( $post ), $statuses, true );
276 + return $cache[ $post_id ];
419 277 }
420 278
421 279 /**
422 280 * Deprecated. Please use the `Metadata` class instead.
@@ -427,8 +285,9 @@
427 285 * @see \Parsely\Metadata::construct_metadata
428 286 *
429 287 * @param array<string, mixed> $parsely_options parsely_options array.
430 288 * @param WP_Post $post object.
289 + *
431 290 * @return Metadata_Attributes
432 291 */
433 292 public function construct_parsely_metadata( array $parsely_options, WP_Post $post ) {
434 293 _deprecated_function( __FUNCTION__, '3.3', 'Metadata::construct_metadata()' );
@@ -436,52 +295,23 @@
436 295 return $metadata->construct_metadata( $post );
437 296 }
438 297
439 298 /**
440 - * Calls Parse.ly's update metadata endpoint, sending the post's updated
441 - * metadata.
299 + * Updates the Parsely metadata endpoint with the new metadata of the post.
442 300 *
443 - * @param int $post_id The ID of the post to update.
444 - * @return bool True if the metadata endpoint was called, false otherwise.
301 + * @param int $post_id id of the post to update.
445 302 */
446 - public function call_update_metadata_endpoint( int $post_id ): bool {
447 - $options = $this->get_options();
448 -
449 - if ( $this->site_id_is_missing() || '' === $options['metadata_secret'] ) {
450 - return false;
303 + public function update_metadata_endpoint( int $post_id ): void {
304 + $parsely_options = $this->get_options();
305 + if ( $this->site_id_is_missing() || '' === $parsely_options['metadata_secret'] ) {
306 + return;
451 307 }
452 308
453 - $current_post_type = get_post_type( $post_id );
454 - if ( false === $current_post_type ) {
455 - return false;
456 - }
457 -
458 - $tracked_post_types = array_merge(
459 - $options['track_post_types'],
460 - $options['track_page_types']
461 - );
462 -
463 - // Check that the post's type is trackable.
464 - if ( ! in_array( $current_post_type, $tracked_post_types, true ) ) {
465 - return false;
466 - }
467 -
468 - // Check that the post's status is trackable.
469 - if ( ! self::post_has_trackable_status( $post_id ) ) {
470 - return false;
471 - }
472 -
473 309 $post = get_post( $post_id );
474 310 if ( null === $post ) {
475 - return false;
311 + return;
476 312 }
477 313
478 - // Don't call the endpoint when integration tests are running, but
479 - // signal that the above checks have passed.
480 - if ( defined( 'INTEGRATION_TESTS_RUNNING' ) ) {
481 - return true;
482 - }
483 -
484 314 $metadata = ( new Metadata( $this ) )->construct_metadata( $post );
485 315
486 316 $endpoint_metadata = array(
487 317 'canonical_url' => $metadata['url'] ?? '',
@@ -493,14 +323,14 @@
493 323 'authors' => $metadata['creator'] ?? '',
494 324 'tags' => $metadata['keywords'] ?? '',
495 325 );
496 326
497 - $parsely_api_base_url = Content_API_Service::get_base_url();
498 - $parsely_api_endpoint = $parsely_api_base_url . '/metadata/posts';
499 - $parsely_metadata_secret = $options['metadata_secret'];
500 -
501 - $headers = array( 'Content-Type' => 'application/json' );
502 - $body = wp_json_encode(
327 + $parsely_api_endpoint = self::PUBLIC_API_BASE_URL . '/metadata/posts';
328 + $parsely_metadata_secret = $parsely_options['metadata_secret'];
329 + $headers = array(
330 + 'Content-Type' => 'application/json',
331 + );
332 + $body = wp_json_encode(
503 333 array(
504 334 'secret' => $parsely_metadata_secret,
505 335 'apikey' => $this->get_site_id(),
506 336 'metadata' => $endpoint_metadata,
@@ -505,31 +335,68 @@
505 335 'apikey' => $this->get_site_id(),
506 336 'metadata' => $endpoint_metadata,
507 337 )
508 338 );
339 + $response = wp_remote_post(
340 + $parsely_api_endpoint,
341 + array(
342 + 'method' => 'POST',
343 + 'headers' => $headers,
344 + 'blocking' => false,
345 + 'body' => $body,
346 + 'data_format' => 'body',
347 + )
348 + );
509 349
350 + if ( ! is_wp_error( $response ) ) {
351 + $current_timestamp = time();
352 + update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp );
353 + }
354 + }
355 +
356 + /**
357 + * Updates posts with Parsely metadata API in bulk.
358 + */
359 + public function bulk_update_posts(): void {
360 + global $wpdb;
361 + $allowed_types = $this->get_all_track_types();
362 + $allowed_types_string = implode(
363 + ', ',
364 + array_map(
365 + function( $v ) {
366 + return "'" . esc_sql( $v ) . "'";
367 + },
368 + $allowed_types
369 + )
370 + );
371 +
510 372 /**
511 - * POST request options.
373 + * Variable.
512 374 *
513 - * @var WP_HTTP_Request_Args $request_options
375 + * @var int[]|false
514 376 */
515 - $request_options = array(
516 - 'method' => 'POST',
517 - 'headers' => $headers,
518 - 'blocking' => false,
519 - 'body' => $body,
520 - 'data_format' => 'body',
521 - );
377 + $ids = wp_cache_get( 'parsely_post_ids_need_meta_updating' );
378 + if ( false === $ids ) {
379 + $ids = array();
380 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
381 + $results = $wpdb->get_results(
382 + $wpdb->prepare( "SELECT DISTINCT(id) FROM {$wpdb->posts} WHERE post_type IN (\" . %s . \") AND id NOT IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = 'parsely_metadata_last_updated');", $allowed_types_string ),
383 + ARRAY_N
384 + );
385 + foreach ( $results as $result ) {
386 + $ids[] = $result[0];
387 + }
388 + wp_cache_set( 'parsely_post_ids_need_meta_updating', $ids, '', 86400 );
389 + }
522 390
523 - $response = wp_remote_post( $parsely_api_endpoint, $request_options );
524 -
525 - if ( is_wp_error( $response ) ) {
526 - return false;
391 + for ( $i = 0; $i < 100; $i++ ) {
392 + $post_id = array_pop( $ids );
393 + if ( null === $post_id ) {
394 + wp_clear_scheduled_hook( 'parsely_bulk_metas_update' );
395 + break;
396 + }
397 + $this->update_metadata_endpoint( $post_id );
527 398 }
528 -
529 - update_post_meta( $post_id, 'parsely_metadata_last_updated', time() );
530 -
531 - return true;
532 399 }
533 400
534 401 /**
535 402 * Safely returns options for the plugin by assigning defaults contained in
@@ -545,142 +412,26 @@
545 412 * Variable.
546 413 *
547 414 * @var Parsely_Options|null
548 415 */
549 - $options = get_option( self::OPTIONS_KEY, null );
416 + $options = get_option( self::OPTIONS_KEY, $this->option_defaults );
550 417
551 - // @phpstan-ignore isset.offset, booleanAnd.alwaysFalse
552 - if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) {
553 - // Existing plugin installation without full metadata option.
554 - $this->set_default_full_metadata_in_non_posts();
555 - }
556 -
557 - // @phpstan-ignore isset.offset, booleanAnd.alwaysFalse
558 - if ( is_array( $options ) && ! isset( $options['content_helper'] ) ) {
559 - // Existing plugin installation without Content Helper options.
560 - $this->set_default_content_helper_settings_values();
561 - }
562 -
563 - // New plugin installation that hasn't saved its options yet.
564 418 if ( ! is_array( $options ) ) {
565 - $this->set_default_track_as_values();
566 - $this->set_default_full_metadata_in_non_posts();
567 - $options = $this->option_defaults;
419 + return $this->option_defaults;
568 420 }
569 421
570 - /**
571 - * Final options including managed credentials and options.
572 - *
573 - * @var Parsely_Options
574 - */
575 - return array_merge(
576 - $this->option_defaults,
577 - $options,
578 - $this->get_managed_credentials(),
579 - $this->managed_options
580 - );
422 + return array_merge( $this->option_defaults, $options );
581 423 }
582 424
583 425 /**
584 - * Returns the value of a nested option.
585 - *
586 - * @since 3.16.0
587 - *
588 - * @param string $option The option to get.
589 - * @param Parsely_Options $options The options to get the value from.
590 - * @return mixed The value of the nested option.
591 - */
592 - public static function get_nested_option_value( $option, $options ) {
593 - $keys = explode( '[', str_replace( ']', '', $option ) );
594 - $value = $options;
595 -
596 - foreach ( $keys as $key ) {
597 - if ( isset( $value[ $key ] ) ) {
598 - $value = $value[ $key ];
599 - }
600 - }
601 -
602 - return $value;
603 - }
604 -
605 - /**
606 - * Sets the default values for the track_post_types and track_page_types
607 - * options.
608 - *
609 - * @since 3.9.0
610 - */
611 - public function set_default_track_as_values(): void {
612 - $this->option_defaults['track_page_types'] = array();
613 - $this->option_defaults['track_post_types'] = array();
614 -
615 - $post_types = get_post_types( array( 'public' => true ) );
616 -
617 - foreach ( $post_types as $post_type ) {
618 - if ( ! post_type_supports( $post_type, 'editor' ) ) {
619 - continue;
620 - }
621 -
622 - if ( is_post_type_hierarchical( $post_type ) ) {
623 - $this->option_defaults['track_page_types'][] = $post_type;
624 - } else {
625 - $this->option_defaults['track_post_types'][] = $post_type;
626 - }
627 - }
628 - }
629 -
630 - /**
631 - * Sets the default value for the full_metadata_in_non_posts option.
632 - *
633 - * @since 3.14.0
634 - */
635 - public function set_default_full_metadata_in_non_posts(): void {
636 - $this->option_defaults['full_metadata_in_non_posts'] = true;
637 -
638 - // Usage of any of these filters will result in the setting being set
639 - // to false.
640 - $filter_tags = array(
641 - 'wp_parsely_metadata',
642 - 'wp_parsely_post_tags',
643 - 'wp_parsely_permalink',
644 - 'wp_parsely_post_category',
645 - 'wp_parsely_pre_authors',
646 - 'wp_parsely_post_authors',
647 - 'wp_parsely_custom_taxonomies',
648 - 'wp_parsely_post_type',
649 - );
650 -
651 - foreach ( $filter_tags as $filter_tag ) {
652 - if ( has_filter( $filter_tag ) ) {
653 - $this->option_defaults['full_metadata_in_non_posts'] = false;
654 - break;
655 - }
656 - }
657 - }
658 -
659 - /**
660 - * Sets the default values for Content Helper options.
661 - *
662 - * Gives PCH access to all users having the edit_posts capability, to keep
663 - * consistent behavior with plugin versions prior to 3.16.0.
664 - *
665 - * @since 3.16.0
666 - */
667 - public function set_default_content_helper_settings_values(): void {
668 - $this->option_defaults['content_helper'] =
669 - Permissions::build_pch_permissions_settings_array(
670 - true,
671 - array_keys( Permissions::get_user_roles_with_edit_posts_cap() )
672 - );
673 - }
674 -
675 - /**
676 426 * Gets the URL of the plugin's settings page.
677 427 *
678 428 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
679 429 * for context (Default null for current).
430 + *
680 431 * @return string
681 432 */
682 - public static function get_settings_url( ?int $_blog_id = null ): string {
433 + public static function get_settings_url( int $_blog_id = null ): string {
683 434 return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
684 435 }
685 436
686 437 /**
@@ -697,10 +448,9 @@
697 448 public static function get_dash_url( string $site_id, string $page_url = '' ): string {
698 449 $result = trailingslashit( self::DASHBOARD_BASE_URL . '/' . $site_id ) . 'find';
699 450
700 451 if ( '' !== $page_url ) {
701 - $page_url = self::get_url_with_itm_source( $page_url, null );
702 - $result .= '?url=' . rawurlencode( $page_url );
452 + $result .= '?url=' . rawurlencode( $page_url );
703 453 }
704 454
705 455 return $result;
706 456 }
@@ -705,28 +455,8 @@
705 455 return $result;
706 456 }
707 457
708 458 /**
709 - * Adds or replaces the itm_source parameter in the URL. Removes the
710 - * parameter if the passed value is null or an empty string.
711 - *
712 - * @since 3.9.0
713 - *
714 - * @param string $url The URL to modify.
715 - * @param string|null $itm_source The value of the itm_source parameter.
716 - * @return string The resulting URL.
717 - */
718 - public static function get_url_with_itm_source( string $url, $itm_source ): string {
719 - if ( null === $itm_source || '' === $itm_source ) {
720 - return remove_query_arg( 'itm_source', $url );
721 - }
722 -
723 - $itm_source = rawurlencode( $itm_source );
724 -
725 - return add_query_arg( 'itm_source', $itm_source, $url );
726 - }
727 -
728 - /**
729 459 * Checks to see if the current user is a member of the current blog.
730 460 *
731 461 * @return bool
732 462 */
@@ -746,9 +476,9 @@
746 476 * types, "index" is returned.
747 477 *
748 478 * @since 2.5.0
749 479 *
750 - * @see https://docs.parse.ly/metatags/#h-field-description
480 + * @see https://www.parse.ly/help/integration/metatags#field-description
751 481 *
752 482 * @param string $type JSON-LD type.
753 483 * @return string "post" or "index".
754 484 */
@@ -759,9 +489,9 @@
759 489 /**
760 490 * Determines if a Site ID is saved in the options.
761 491 *
762 492 * @since 2.6.0
763 - * @since 3.7.0 renamed from api_key_is_set.
493 + * @since 3.7.0 renamed from api_key_is_set
764 494 *
765 495 * @return bool True is Site ID is set, false if it is missing.
766 496 */
767 497 public function site_id_is_set(): bool {
@@ -773,9 +503,9 @@
773 503 /**
774 504 * Determines if a Site ID is not saved in the options.
775 505 *
776 506 * @since 2.6.0
777 - * @since 3.7.0 renamed from api_key_is_missing.
507 + * @since 3.7.0 renamed from api_key_is_missing
778 508 *
779 509 * @return bool True if Site ID is missing, false if it is set.
780 510 */
781 511 public function site_id_is_missing(): bool {
@@ -785,9 +515,9 @@
785 515 /**
786 516 * Gets the Site ID if set.
787 517 *
788 518 * @since 2.6.0
789 - * @since 3.7.0 renamed from get_site_id.
519 + * @since 3.7.0 renamed from get_site_id
790 520 *
791 521 * @return string Site ID if set, or empty string if not.
792 522 */
793 523 public function get_site_id(): string {
@@ -854,221 +584,6 @@
854 584 * @return Parsely_Options
855 585 */
856 586 public function get_default_options() {
857 587 return $this->option_defaults;
858 - }
859 -
860 - /**
861 - * Returns the credentials that are being managed at the platform level.
862 - *
863 - * @since 3.9.0
864 - * @access private
865 - *
866 - * @return Parsely_Options|array<empty> The managed credentials.
867 - */
868 - private function get_managed_credentials() {
869 - if ( true !== $this->are_credentials_managed ) {
870 - return array();
871 - }
872 -
873 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
874 -
875 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
876 - return array();
877 - }
878 -
879 - $result = array();
880 -
881 - if ( isset( $credentials['site_id'] ) ) {
882 - $result['apikey'] = $credentials['site_id'];
883 - }
884 -
885 - if ( isset( $credentials['api_secret'] ) ) {
886 - $result['api_secret'] = $credentials['api_secret'];
887 - }
888 -
889 - if ( isset( $credentials['metadata_secret'] ) ) {
890 - $result['metadata_secret'] = $credentials['metadata_secret'];
891 - }
892 -
893 - return $result;
894 - }
895 -
896 - /**
897 - * Returns whether credentials are being managed at the platform level.
898 - *
899 - * @since 3.9.0
900 - * @access private
901 - *
902 - * @return bool Whether credentials are being managed at the platform level.
903 - */
904 - private function are_credentials_managed(): bool {
905 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
906 -
907 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
908 - return false;
909 - }
910 -
911 - return $credentials['is_managed'] ?? false;
912 - }
913 -
914 - /**
915 - * Sets the values of managed options.
916 - *
917 - * This function won't accept managing credentials or certain plugin options
918 - * that are being managed through other means. For managing credentials,
919 - * please use the `wp_parsely_credentials` filter.
920 - *
921 - * @since 3.9.0
922 - * @access private
923 - */
924 - private function set_managed_options(): void {
925 - $managed_options = apply_filters( 'wp_parsely_managed_options', false );
926 -
927 - if ( ! is_array( $managed_options ) ) {
928 - return;
929 - }
930 -
931 - // Don't allow certain options to be set as managed.
932 - unset(
933 - $managed_options['apikey'],
934 - $managed_options['api_secret'],
935 - $managed_options['metadata_secret'],
936 - $managed_options['track_post_types'],
937 - $managed_options['track_page_types'],
938 - $managed_options['plugin_version']
939 - );
940 -
941 - if ( 0 === count( $managed_options ) ) {
942 - return;
943 - }
944 -
945 - /**
946 - * Current options.
947 - *
948 - * @var Parsely_Options $current_options
949 - */
950 - $current_options = get_option( self::OPTIONS_KEY, array() );
951 -
952 - // Set managed options values.
953 - foreach ( $managed_options as $key => $value ) {
954 - $is_option_valid = isset( $this->option_defaults[ $key ] );
955 -
956 - if ( $is_option_valid ) {
957 - if ( null === $value ) {
958 - // When null, the option gets its value from the database.
959 - $this->managed_options[ $key ] =
960 - $current_options[ $key ] ?? $this->option_defaults[ $key ];
961 - } else {
962 - $this->managed_options[ $key ] =
963 - $this->sanitize_managed_option( $key, $value );
964 - }
965 - }
966 - }
967 - }
968 -
969 - /**
970 - * Sanitizes the value of the passed managed option.
971 - *
972 - * @since 3.9.0
973 - * @access private
974 - *
975 - * @param string $option_id The option's ID.
976 - * @param bool|string $value The option's value.
977 - * @return bool|string The sanitized option value.
978 - */
979 - private function sanitize_managed_option( string $option_id, $value ) {
980 - $option_value_type = gettype( $this->option_defaults[ $option_id ] );
981 -
982 - if ( 'boolean' === $option_value_type && ! is_bool( $value ) ) {
983 - _doing_it_wrong(
984 - __FUNCTION__,
985 - esc_html(
986 - sprintf( /* translators: 1: Option ID */
987 - __( 'The value of the managed option `%1$s` must be of type `boolean`.', 'wp-parsely' ),
988 - $option_id
989 - )
990 - ),
991 - ''
992 - );
993 -
994 - return false;
995 - }
996 -
997 - if ( 'string' === $option_value_type ) {
998 - if ( ! is_string( $value ) ) {
999 - _doing_it_wrong(
1000 - __FUNCTION__,
1001 - esc_html(
1002 - sprintf( /* translators: 1: Option ID */
1003 - __( 'The value of the managed option `%1$s` must be of type `string`.', 'wp-parsely' ),
1004 - $option_id
1005 - )
1006 - ),
1007 - ''
1008 - );
1009 -
1010 - $value = strval( $value );
1011 - }
1012 -
1013 - // String options that are restricted to specific values.
1014 - $restricted_value_options = array(
1015 - 'custom_taxonomy_section' => Settings_Page::get_section_taxonomies(),
1016 - 'meta_type' => array( 'json_ld', 'repeated_metas' ),
1017 - );
1018 -
1019 - // Verify that the above values are respected.
1020 - foreach ( $restricted_value_options as $option_key => $valid_values ) {
1021 - if ( $option_id === $option_key ) {
1022 - if ( ! in_array( $value, $valid_values, true ) ) {
1023 - _doing_it_wrong(
1024 - __FUNCTION__,
1025 - esc_html(
1026 - sprintf( /* translators: 1: Option value 2: Option ID */
1027 - __( 'The value `%1$s` is not allowed for the managed option `%2$s`.', 'wp-parsely' ),
1028 - $value,
1029 - $option_id
1030 - )
1031 - ),
1032 - ''
1033 - );
1034 -
1035 - $value = $this->option_defaults[ $option_id ];
1036 - }
1037 - }
1038 - }
1039 - }
1040 -
1041 - return $value;
1042 - }
1043 -
1044 - /**
1045 - * Allows remote requests to Parse.ly.
1046 - *
1047 - * This is needed for environments, such as wp-now, that block remote requests.
1048 - *
1049 - * @since 3.13.0
1050 - * @access private
1051 - */
1052 - private function allow_parsely_remote_requests(): void {
1053 - $allowed_urls = array(
1054 - self::DASHBOARD_BASE_URL,
1055 - Content_API_Service::get_base_url(),
1056 - Suggestions_API_Service::get_base_url(),
1057 - );
1058 -
1059 - add_filter(
1060 - 'http_request_host_is_external',
1061 - function ( $external, $host, $url ) use ( $allowed_urls ) {
1062 - // Check if the URL matches any URLs on the allowed list.
1063 - foreach ( $allowed_urls as $allowed_url ) {
1064 - if ( Utils::str_starts_with( $url, $allowed_url ) ) {
1065 - return true;
1066 - }
1067 - }
1068 - return $external;
1069 - },
1070 - 10,
1071 - 3
1072 - );
1073 588 }
1074 589 }