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 +117 -487 3.16.23.8.4 View file →
@@ -10,9 +10,8 @@
10 10
11 11 namespace Parsely;
12 12
13 13 use Parsely\UI\Metadata_Renderer;
14 -use Parsely\UI\Settings_Page;
15 14 use WP_Post;
16 15
17 16 /**
18 17 * Holds most of the logic for the plugin.
@@ -26,9 +25,8 @@
26 25 * api_secret: string,
27 26 * use_top_level_cats: bool,
28 27 * custom_taxonomy_section: string,
29 28 * cats_as_tags: bool,
30 - * content_helper: Parsely_Options_Content_Helper,
31 29 * track_authenticated_users: bool,
32 30 * lowercase_tags: bool,
33 31 * force_https_canonicals: bool,
34 32 * track_post_types: string[],
@@ -33,39 +31,18 @@
33 31 * force_https_canonicals: bool,
34 32 * track_post_types: string[],
35 33 * track_page_types: string[],
36 34 * track_post_types_as?: array<string, string>,
37 - * full_metadata_in_non_posts: bool,
38 35 * disable_javascript: bool,
39 36 * disable_amp: bool,
40 37 * meta_type: string,
41 38 * logo: string,
42 39 * metadata_secret: string,
40 + * parsely_wipe_metadata_cache: bool,
43 41 * disable_autotrack: bool,
44 42 * plugin_version: string,
45 43 * }
46 44 *
47 - * @phpstan-type Parsely_Options_Content_Helper array{
48 - * ai_features_enabled: bool,
49 - * smart_linking: Parsely_Options_Content_Helper_Feature,
50 - * title_suggestions: Parsely_Options_Content_Helper_Feature,
51 - * excerpt_suggestions: Parsely_Options_Content_Helper_Feature,
52 - * }
53 - *
54 - * @phpstan-type Parsely_Options_Content_Helper_Feature array{
55 - * enabled: bool,
56 - * allowed_user_roles: string[],
57 - * }
58 - *
59 - * @phpstan-type WP_HTTP_Request_Args array{
60 - * method: string,
61 - * timeout: float,
62 - * blocking: bool,
63 - * headers: array<string, string>,
64 - * body: string,
65 - * data_format: string,
66 - * }
67 - *
68 45 * @phpstan-import-type Metadata_Attributes from Metadata
69 46 */
70 47 class Parsely {
71 48 /**
@@ -70,15 +47,14 @@
70 47 class Parsely {
71 48 /**
72 49 * Declare our constants
73 50 */
74 - public const VERSION = PARSELY_VERSION;
75 - public const MENU_SLUG = 'parsely'; // The page param passed to options-general.php.
76 - public const OPTIONS_KEY = 'parsely'; // The key used to store options in the WP database.
77 - public const CAPABILITY = 'manage_options'; // The capability required to administer settings.
78 - public const DASHBOARD_BASE_URL = 'https://dash.parsely.com';
79 - public const PUBLIC_API_BASE_URL = 'https://api.parsely.com/v2';
80 - public const PUBLIC_SUGGESTIONS_API_BASE_URL = 'https://content-suggestions-api.parsely.net/prod';
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';
81 57
82 58 /**
83 59 * Declare some class properties
84 60 *
@@ -84,51 +60,36 @@
84 60 *
85 61 * @var Parsely_Options $option_defaults The defaults we need for the class.
86 62 */
87 63 private $option_defaults = array(
88 - 'apikey' => '',
89 - 'content_id_prefix' => '',
90 - 'api_secret' => '',
91 - 'use_top_level_cats' => false,
92 - 'custom_taxonomy_section' => 'category',
93 - 'cats_as_tags' => false,
94 - 'content_helper' => array(
95 - 'ai_features_enabled' => true,
96 - 'smart_linking' => array(
97 - 'enabled' => true,
98 - 'allowed_user_roles' => array( 'administrator' ),
99 - ),
100 - 'title_suggestions' => array(
101 - 'enabled' => true,
102 - 'allowed_user_roles' => array( 'administrator' ),
103 - ),
104 - 'excerpt_suggestions' => array(
105 - 'enabled' => true,
106 - 'allowed_user_roles' => array( 'administrator' ),
107 - ),
108 - ),
109 - 'track_authenticated_users' => false,
110 - 'lowercase_tags' => true,
111 - 'force_https_canonicals' => false,
112 - 'track_post_types' => array(),
113 - 'track_page_types' => array(),
114 - 'full_metadata_in_non_posts' => true,
115 - 'disable_javascript' => false,
116 - 'disable_amp' => false,
117 - 'meta_type' => 'json_ld',
118 - 'logo' => '',
119 - 'metadata_secret' => '',
120 - 'disable_autotrack' => false,
121 - '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' => '',
122 83 );
123 84
124 85 /**
125 86 * Declare post types that Parse.ly will process as "posts".
126 87 *
88 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
89 + *
127 90 * @since 2.5.0
128 91 * @var string[]
129 - *
130 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
131 92 */
132 93 public const SUPPORTED_JSONLD_POST_TYPES = array(
133 94 'NewsArticle',
134 95 'Article',
@@ -137,26 +98,17 @@
137 98 'LiveBlogPosting',
138 99 'Report',
139 100 'Review',
140 101 'CreativeWork',
141 - 'OpinionNewsArticle',
142 - 'AnalysisNewsArticle',
143 - 'BackgroundNewsArticle',
144 - 'ReviewNewsArticle',
145 - 'ReportageNewsArticle',
146 - 'Recipe',
147 - 'AdvertiserContentArticle',
148 - 'MedicalWebPage',
149 - 'PodcastEpisode',
150 102 );
151 103
152 104 /**
153 105 * Declare post types that Parse.ly will process as "non-posts".
154 106 *
107 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
108 + *
155 109 * @since 2.5.0
156 110 * @var string[]
157 - *
158 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
159 111 */
160 112 public const SUPPORTED_JSONLD_NON_POST_TYPES = array(
161 113 'WebPage',
162 114 'Event',
@@ -173,41 +125,12 @@
173 125 */
174 126 private static $all_supported_types;
175 127
176 128 /**
177 - * Returns whether credentials are being managed at the platform level.
178 - *
179 - * This allows hosting providers to provide a more customized experience for
180 - * the plugin by handling credentials automatically.
181 - *
182 - * @since 3.9.0
183 - * @access private
184 - * @var bool
185 - */
186 - public $are_credentials_managed;
187 -
188 - /**
189 - * Holds the managed options and their values.
190 - *
191 - * This allows hosting providers to provide a more customized experience for
192 - * the plugin by handling options automatically.
193 - *
194 - * @since 3.9.0
195 - * @access private
196 - * @var array<empty>|array<string, bool|string|null>
197 - */
198 - public $managed_options = array();
199 -
200 - /**
201 129 * Constructor.
202 130 */
203 131 public function __construct() {
204 132 self::$all_supported_types = array_merge( self::SUPPORTED_JSONLD_POST_TYPES, self::SUPPORTED_JSONLD_NON_POST_TYPES );
205 -
206 - $this->are_credentials_managed = $this->are_credentials_managed();
207 - $this->set_managed_options();
208 -
209 - $this->allow_parsely_remote_requests();
210 133 }
211 134
212 135 /**
213 136 * Registers action and filter hook callbacks, and immediately upgrades
@@ -226,18 +149,35 @@
226 149 */
227 150 $callable = array( $this, $method );
228 151 call_user_func_array( $callable, array( $options ) );
229 152 }
230 -
231 153 // Update our version info.
232 154 $options['plugin_version'] = self::VERSION;
233 155 update_option( self::OPTIONS_KEY, $options );
234 156 }
235 157
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' ) );
236 161 add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) );
237 162 }
238 163
239 164 /**
165 + * Adds 10 minute cron interval.
166 + *
167 + * @param array<string, mixed> $schedules WP schedules array.
168 + *
169 + * @return array<string, mixed>
170 + */
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;
177 + }
178 +
179 + /**
240 180 * Gets the full URL of the JavaScript tracker file for the site. If an API
241 181 * key is not set, return an empty string.
242 182 *
243 183 * @since 3.2.0
@@ -311,9 +251,9 @@
311 251 *
312 252 * @param bool $skip True if the password check should be skipped.
313 253 * @param int|WP_Post $post Which post object or ID is being checked.
314 254 *
315 - * @return bool
255 + * @returns bool
316 256 */
317 257 $skip_password_check = apply_filters( 'wp_parsely_skip_post_password_check', false, $post );
318 258 if ( ! $skip_password_check && post_password_required( $post ) ) {
319 259 $cache[ $post_id ] = false;
@@ -345,8 +285,9 @@
345 285 * @see \Parsely\Metadata::construct_metadata
346 286 *
347 287 * @param array<string, mixed> $parsely_options parsely_options array.
348 288 * @param WP_Post $post object.
289 + *
349 290 * @return Metadata_Attributes
350 291 */
351 292 public function construct_parsely_metadata( array $parsely_options, WP_Post $post ) {
352 293 _deprecated_function( __FUNCTION__, '3.3', 'Metadata::construct_metadata()' );
@@ -384,11 +325,12 @@
384 325 );
385 326
386 327 $parsely_api_endpoint = self::PUBLIC_API_BASE_URL . '/metadata/posts';
387 328 $parsely_metadata_secret = $parsely_options['metadata_secret'];
388 -
389 - $headers = array( 'Content-Type' => 'application/json' );
390 - $body = wp_json_encode(
329 + $headers = array(
330 + 'Content-Type' => 'application/json',
331 + );
332 + $body = wp_json_encode(
391 333 array(
392 334 'secret' => $parsely_metadata_secret,
393 335 'apikey' => $this->get_site_id(),
394 336 'metadata' => $endpoint_metadata,
@@ -393,27 +335,67 @@
393 335 'apikey' => $this->get_site_id(),
394 336 'metadata' => $endpoint_metadata,
395 337 )
396 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 + );
397 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 +
398 372 /**
399 - * POST request options.
373 + * Variable.
400 374 *
401 - * @var WP_HTTP_Request_Args $options
375 + * @var int[]|false
402 376 */
403 - $options = array(
404 - 'method' => 'POST',
405 - 'headers' => $headers,
406 - 'blocking' => false,
407 - 'body' => $body,
408 - 'data_format' => 'body',
409 - );
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 + }
410 390
411 - $response = wp_remote_post( $parsely_api_endpoint, $options );
412 -
413 - if ( ! is_wp_error( $response ) ) {
414 - $current_timestamp = time();
415 - update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp );
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 );
416 398 }
417 399 }
418 400
419 401 /**
@@ -430,139 +412,23 @@
430 412 * Variable.
431 413 *
432 414 * @var Parsely_Options|null
433 415 */
434 - $options = get_option( self::OPTIONS_KEY, null );
416 + $options = get_option( self::OPTIONS_KEY, $this->option_defaults );
435 417
436 - // @phpstan-ignore isset.offset, booleanAnd.alwaysFalse
437 - if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) {
438 - // Existing plugin installation without full metadata option.
439 - $this->set_default_full_metadata_in_non_posts();
440 - }
441 -
442 - // @phpstan-ignore isset.offset, booleanAnd.alwaysFalse
443 - if ( is_array( $options ) && ! isset( $options['content_helper'] ) ) {
444 - // Existing plugin installation without Content Helper options.
445 - $this->set_default_content_helper_settings_values();
446 - }
447 -
448 - // New plugin installation that hasn't saved its options yet.
449 418 if ( ! is_array( $options ) ) {
450 - $this->set_default_track_as_values();
451 - $this->set_default_full_metadata_in_non_posts();
452 - $options = $this->option_defaults;
419 + return $this->option_defaults;
453 420 }
454 421
455 - /**
456 - * Final options including managed credentials and options.
457 - *
458 - * @var Parsely_Options
459 - */
460 - return array_merge(
461 - $this->option_defaults,
462 - $options,
463 - $this->get_managed_credentials(),
464 - $this->managed_options
465 - );
422 + return array_merge( $this->option_defaults, $options );
466 423 }
467 424
468 425 /**
469 - * Returns the value of a nested option.
470 - *
471 - * @since 3.16.0
472 - *
473 - * @param string $option The option to get.
474 - * @param Parsely_Options $options The options to get the value from.
475 - * @return mixed The value of the nested option.
476 - */
477 - public static function get_nested_option_value( $option, $options ) {
478 - $keys = explode( '[', str_replace( ']', '', $option ) );
479 - $value = $options;
480 -
481 - foreach ( $keys as $key ) {
482 - if ( isset( $value[ $key ] ) ) {
483 - $value = $value[ $key ];
484 - }
485 - }
486 -
487 - return $value;
488 - }
489 -
490 - /**
491 - * Sets the default values for the track_post_types and track_page_types
492 - * options.
493 - *
494 - * @since 3.9.0
495 - */
496 - public function set_default_track_as_values(): void {
497 - $this->option_defaults['track_page_types'] = array();
498 - $this->option_defaults['track_post_types'] = array();
499 -
500 - $post_types = get_post_types( array( 'public' => true ) );
501 -
502 - foreach ( $post_types as $post_type ) {
503 - if ( ! post_type_supports( $post_type, 'editor' ) ) {
504 - continue;
505 - }
506 -
507 - if ( is_post_type_hierarchical( $post_type ) ) {
508 - $this->option_defaults['track_page_types'][] = $post_type;
509 - } else {
510 - $this->option_defaults['track_post_types'][] = $post_type;
511 - }
512 - }
513 - }
514 -
515 - /**
516 - * Sets the default value for the full_metadata_in_non_posts option.
517 - *
518 - * @since 3.14.0
519 - */
520 - public function set_default_full_metadata_in_non_posts(): void {
521 - $this->option_defaults['full_metadata_in_non_posts'] = true;
522 -
523 - // Usage of any of these filters will result in the setting being set
524 - // to false.
525 - $filter_tags = array(
526 - 'wp_parsely_metadata',
527 - 'wp_parsely_post_tags',
528 - 'wp_parsely_permalink',
529 - 'wp_parsely_post_category',
530 - 'wp_parsely_pre_authors',
531 - 'wp_parsely_post_authors',
532 - 'wp_parsely_custom_taxonomies',
533 - 'wp_parsely_post_type',
534 - );
535 -
536 - foreach ( $filter_tags as $filter_tag ) {
537 - if ( has_filter( $filter_tag ) ) {
538 - $this->option_defaults['full_metadata_in_non_posts'] = false;
539 - break;
540 - }
541 - }
542 - }
543 -
544 - /**
545 - * Sets the default values for Content Helper options.
546 - *
547 - * Gives PCH access to all users having the edit_posts capability, to keep
548 - * consistent behavior with plugin versions prior to 3.16.0.
549 - *
550 - * @since 3.16.0
551 - */
552 - public function set_default_content_helper_settings_values(): void {
553 - $this->option_defaults['content_helper'] =
554 - Permissions::build_pch_permissions_settings_array(
555 - true,
556 - array_keys( Permissions::get_user_roles_with_edit_posts_cap() )
557 - );
558 - }
559 -
560 - /**
561 426 * Gets the URL of the plugin's settings page.
562 427 *
563 428 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
564 429 * for context (Default null for current).
430 + *
565 431 * @return string
566 432 */
567 433 public static function get_settings_url( int $_blog_id = null ): string {
568 434 return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
@@ -582,10 +448,9 @@
582 448 public static function get_dash_url( string $site_id, string $page_url = '' ): string {
583 449 $result = trailingslashit( self::DASHBOARD_BASE_URL . '/' . $site_id ) . 'find';
584 450
585 451 if ( '' !== $page_url ) {
586 - $page_url = self::get_url_with_itm_source( $page_url, null );
587 - $result .= '?url=' . rawurlencode( $page_url );
452 + $result .= '?url=' . rawurlencode( $page_url );
588 453 }
589 454
590 455 return $result;
591 456 }
@@ -590,28 +455,8 @@
590 455 return $result;
591 456 }
592 457
593 458 /**
594 - * Adds or replaces the itm_source parameter in the URL. Removes the
595 - * parameter if the passed value is null or an empty string.
596 - *
597 - * @since 3.9.0
598 - *
599 - * @param string $url The URL to modify.
600 - * @param string|null $itm_source The value of the itm_source parameter.
601 - * @return string The resulting URL.
602 - */
603 - public static function get_url_with_itm_source( string $url, $itm_source ): string {
604 - if ( null === $itm_source || '' === $itm_source ) {
605 - return remove_query_arg( 'itm_source', $url );
606 - }
607 -
608 - $itm_source = rawurlencode( $itm_source );
609 -
610 - return add_query_arg( 'itm_source', $itm_source, $url );
611 - }
612 -
613 - /**
614 459 * Checks to see if the current user is a member of the current blog.
615 460 *
616 461 * @return bool
617 462 */
@@ -631,9 +476,9 @@
631 476 * types, "index" is returned.
632 477 *
633 478 * @since 2.5.0
634 479 *
635 - * @see https://docs.parse.ly/metatags/#h-field-description
480 + * @see https://www.parse.ly/help/integration/metatags#field-description
636 481 *
637 482 * @param string $type JSON-LD type.
638 483 * @return string "post" or "index".
639 484 */
@@ -644,9 +489,9 @@
644 489 /**
645 490 * Determines if a Site ID is saved in the options.
646 491 *
647 492 * @since 2.6.0
648 - * @since 3.7.0 renamed from api_key_is_set.
493 + * @since 3.7.0 renamed from api_key_is_set
649 494 *
650 495 * @return bool True is Site ID is set, false if it is missing.
651 496 */
652 497 public function site_id_is_set(): bool {
@@ -658,9 +503,9 @@
658 503 /**
659 504 * Determines if a Site ID is not saved in the options.
660 505 *
661 506 * @since 2.6.0
662 - * @since 3.7.0 renamed from api_key_is_missing.
507 + * @since 3.7.0 renamed from api_key_is_missing
663 508 *
664 509 * @return bool True if Site ID is missing, false if it is set.
665 510 */
666 511 public function site_id_is_missing(): bool {
@@ -670,9 +515,9 @@
670 515 /**
671 516 * Gets the Site ID if set.
672 517 *
673 518 * @since 2.6.0
674 - * @since 3.7.0 renamed from get_site_id.
519 + * @since 3.7.0 renamed from get_site_id
675 520 *
676 521 * @return string Site ID if set, or empty string if not.
677 522 */
678 523 public function get_site_id(): string {
@@ -739,221 +584,6 @@
739 584 * @return Parsely_Options
740 585 */
741 586 public function get_default_options() {
742 587 return $this->option_defaults;
743 - }
744 -
745 - /**
746 - * Returns the credentials that are being managed at the platform level.
747 - *
748 - * @since 3.9.0
749 - * @access private
750 - *
751 - * @return Parsely_Options|array<empty> The managed credentials.
752 - */
753 - private function get_managed_credentials() {
754 - if ( true !== $this->are_credentials_managed ) {
755 - return array();
756 - }
757 -
758 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
759 -
760 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
761 - return array();
762 - }
763 -
764 - $result = array();
765 -
766 - if ( isset( $credentials['site_id'] ) ) {
767 - $result['apikey'] = $credentials['site_id'];
768 - }
769 -
770 - if ( isset( $credentials['api_secret'] ) ) {
771 - $result['api_secret'] = $credentials['api_secret'];
772 - }
773 -
774 - if ( isset( $credentials['metadata_secret'] ) ) {
775 - $result['metadata_secret'] = $credentials['metadata_secret'];
776 - }
777 -
778 - return $result;
779 - }
780 -
781 - /**
782 - * Returns whether credentials are being managed at the platform level.
783 - *
784 - * @since 3.9.0
785 - * @access private
786 - *
787 - * @return bool Whether credentials are being managed at the platform level.
788 - */
789 - private function are_credentials_managed(): bool {
790 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
791 -
792 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
793 - return false;
794 - }
795 -
796 - return $credentials['is_managed'] ?? false;
797 - }
798 -
799 - /**
800 - * Sets the values of managed options.
801 - *
802 - * This function won't accept managing credentials or certain plugin options
803 - * that are being managed through other means. For managing credentials,
804 - * please use the `wp_parsely_credentials` filter.
805 - *
806 - * @since 3.9.0
807 - * @access private
808 - */
809 - private function set_managed_options(): void {
810 - $managed_options = apply_filters( 'wp_parsely_managed_options', false );
811 -
812 - if ( ! is_array( $managed_options ) ) {
813 - return;
814 - }
815 -
816 - // Don't allow certain options to be set as managed.
817 - unset(
818 - $managed_options['apikey'],
819 - $managed_options['api_secret'],
820 - $managed_options['metadata_secret'],
821 - $managed_options['track_post_types'],
822 - $managed_options['track_page_types'],
823 - $managed_options['plugin_version']
824 - );
825 -
826 - if ( 0 === count( $managed_options ) ) {
827 - return;
828 - }
829 -
830 - /**
831 - * Current options.
832 - *
833 - * @var Parsely_Options $current_options
834 - */
835 - $current_options = get_option( self::OPTIONS_KEY, array() );
836 -
837 - // Set managed options values.
838 - foreach ( $managed_options as $key => $value ) {
839 - $is_option_valid = isset( $this->option_defaults[ $key ] );
840 -
841 - if ( $is_option_valid ) {
842 - if ( null === $value ) {
843 - // When null, the option gets its value from the database.
844 - $this->managed_options[ $key ] =
845 - $current_options[ $key ] ?? $this->option_defaults[ $key ];
846 - } else {
847 - $this->managed_options[ $key ] =
848 - $this->sanitize_managed_option( $key, $value );
849 - }
850 - }
851 - }
852 - }
853 -
854 - /**
855 - * Sanitizes the value of the passed managed option.
856 - *
857 - * @since 3.9.0
858 - * @access private
859 - *
860 - * @param string $option_id The option's ID.
861 - * @param bool|string $value The option's value.
862 - * @return bool|string The sanitized option value.
863 - */
864 - private function sanitize_managed_option( string $option_id, $value ) {
865 - $option_value_type = gettype( $this->option_defaults[ $option_id ] );
866 -
867 - if ( 'boolean' === $option_value_type && ! is_bool( $value ) ) {
868 - _doing_it_wrong(
869 - __FUNCTION__,
870 - esc_html(
871 - sprintf( /* translators: 1: Option ID */
872 - __( 'The value of the managed option `%1$s` must be of type `boolean`.', 'wp-parsely' ),
873 - $option_id
874 - )
875 - ),
876 - ''
877 - );
878 -
879 - return false;
880 - }
881 -
882 - if ( 'string' === $option_value_type ) {
883 - if ( ! is_string( $value ) ) {
884 - _doing_it_wrong(
885 - __FUNCTION__,
886 - esc_html(
887 - sprintf( /* translators: 1: Option ID */
888 - __( 'The value of the managed option `%1$s` must be of type `string`.', 'wp-parsely' ),
889 - $option_id
890 - )
891 - ),
892 - ''
893 - );
894 -
895 - $value = strval( $value );
896 - }
897 -
898 - // String options that are restricted to specific values.
899 - $restricted_value_options = array(
900 - 'custom_taxonomy_section' => Settings_Page::get_section_taxonomies(),
901 - 'meta_type' => array( 'json_ld', 'repeated_metas' ),
902 - );
903 -
904 - // Verify that the above values are respected.
905 - foreach ( $restricted_value_options as $option_key => $valid_values ) {
906 - if ( $option_id === $option_key ) {
907 - if ( ! in_array( $value, $valid_values, true ) ) {
908 - _doing_it_wrong(
909 - __FUNCTION__,
910 - esc_html(
911 - sprintf( /* translators: 1: Option value 2: Option ID */
912 - __( 'The value `%1$s` is not allowed for the managed option `%2$s`.', 'wp-parsely' ),
913 - $value,
914 - $option_id
915 - )
916 - ),
917 - ''
918 - );
919 -
920 - $value = $this->option_defaults[ $option_id ];
921 - }
922 - }
923 - }
924 - }
925 -
926 - return $value;
927 - }
928 -
929 - /**
930 - * Allows remote requests to Parse.ly.
931 - *
932 - * This is needed for environments, such as wp-now, that block remote requests.
933 - *
934 - * @since 3.13.0
935 - * @access private
936 - */
937 - private function allow_parsely_remote_requests(): void {
938 - $allowed_urls = array(
939 - self::DASHBOARD_BASE_URL,
940 - self::PUBLIC_API_BASE_URL,
941 - self::PUBLIC_SUGGESTIONS_API_BASE_URL,
942 - );
943 -
944 - add_filter(
945 - 'http_request_host_is_external',
946 - function ( $external, $host, $url ) use ( $allowed_urls ) {
947 - // Check if the URL matches any URLs on the allowed list.
948 - foreach ( $allowed_urls as $allowed_url ) {
949 - if ( \Parsely\Utils\str_starts_with( $url, $allowed_url ) ) {
950 - return true;
951 - }
952 - }
953 - return $external;
954 - },
955 - 10,
956 - 3
957 - );
958 588 }
959 589 }