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 +144 -728 3.20.13.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,40 +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 - * traffic_boost: Parsely_Options_Content_Helper_Feature,
57 - * }
58 - *
59 - * @phpstan-type Parsely_Options_Content_Helper_Feature array{
60 - * enabled: bool,
61 - * allowed_user_roles: string[],
62 - * }
63 - *
64 - * @phpstan-type WP_HTTP_Request_Args array{
65 - * method?: string,
66 - * timeout?: float,
67 - * blocking?: bool,
68 - * headers?: array<string, string>,
69 - * body?: string,
70 - * data_format?: string,
71 - * }
72 - *
73 45 * @phpstan-import-type Metadata_Attributes from Metadata
74 46 */
75 47 class Parsely {
76 48 /**
@@ -75,90 +47,49 @@
75 47 class Parsely {
76 48 /**
77 49 * Declare our constants
78 50 */
79 - public const VERSION = PARSELY_VERSION;
80 - public const MENU_SLUG = 'parsely-settings'; // The page param passed to admin.php.
81 - public const OPTIONS_KEY = 'parsely'; // The key used to store options in the WP database.
82 - public const CAPABILITY = 'manage_options'; // The capability required to administer settings.
83 - 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';
84 57
85 - private const PARSELY_CANONICAL_URL_META_KEY = '_parsely_canonical_url';
86 -
87 58 /**
88 - * The Content API service.
89 - *
90 - * @var ?Content_API_Service $content_api_service
91 - */
92 - private $content_api_service;
93 -
94 - /**
95 - * The Suggestions API service.
96 - *
97 - * @var ?Suggestions_API_Service $suggestions_api_service
98 - */
99 - private $suggestions_api_service;
100 -
101 - /**
102 - * The Parse.ly internal REST API controller.
103 - *
104 - * @var REST_API_Controller|null $rest_api_controller
105 - */
106 - private $rest_api_controller;
107 -
108 - /**
109 59 * Declare some class properties
110 60 *
111 61 * @var Parsely_Options $option_defaults The defaults we need for the class.
112 62 */
113 63 private $option_defaults = array(
114 - 'apikey' => '',
115 - 'content_id_prefix' => '',
116 - 'api_secret' => '',
117 - 'use_top_level_cats' => false,
118 - 'custom_taxonomy_section' => 'category',
119 - 'cats_as_tags' => false,
120 - 'content_helper' => array(
121 - 'ai_features_enabled' => true,
122 - 'smart_linking' => array(
123 - 'enabled' => true,
124 - 'allowed_user_roles' => array( 'administrator' ),
125 - ),
126 - 'title_suggestions' => array(
127 - 'enabled' => true,
128 - 'allowed_user_roles' => array( 'administrator' ),
129 - ),
130 - 'excerpt_suggestions' => array(
131 - 'enabled' => true,
132 - 'allowed_user_roles' => array( 'administrator' ),
133 - ),
134 - 'traffic_boost' => array(
135 - 'enabled' => true,
136 - 'allowed_user_roles' => array( 'administrator' ),
137 - ),
138 - ),
139 - 'track_authenticated_users' => false,
140 - 'lowercase_tags' => true,
141 - 'force_https_canonicals' => false,
142 - 'track_post_types' => array(),
143 - 'track_page_types' => array(),
144 - 'full_metadata_in_non_posts' => true,
145 - 'disable_javascript' => false,
146 - 'disable_amp' => false,
147 - 'meta_type' => 'json_ld',
148 - 'logo' => '',
149 - 'metadata_secret' => '',
150 - 'disable_autotrack' => false,
151 - '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' => '',
152 83 );
153 84
154 85 /**
155 86 * Declare post types that Parse.ly will process as "posts".
156 87 *
88 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
89 + *
157 90 * @since 2.5.0
158 91 * @var string[]
159 - *
160 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
161 92 */
162 93 public const SUPPORTED_JSONLD_POST_TYPES = array(
163 94 'NewsArticle',
164 95 'Article',
@@ -167,26 +98,17 @@
167 98 'LiveBlogPosting',
168 99 'Report',
169 100 'Review',
170 101 'CreativeWork',
171 - 'OpinionNewsArticle',
172 - 'AnalysisNewsArticle',
173 - 'BackgroundNewsArticle',
174 - 'ReviewNewsArticle',
175 - 'ReportageNewsArticle',
176 - 'Recipe',
177 - 'AdvertiserContentArticle',
178 - 'MedicalWebPage',
179 - 'PodcastEpisode',
180 102 );
181 103
182 104 /**
183 105 * Declare post types that Parse.ly will process as "non-posts".
184 106 *
107 + * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages
108 + *
185 109 * @since 2.5.0
186 110 * @var string[]
187 - *
188 - * @link https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages
189 111 */
190 112 public const SUPPORTED_JSONLD_NON_POST_TYPES = array(
191 113 'WebPage',
192 114 'Event',
@@ -203,70 +125,15 @@
203 125 */
204 126 private static $all_supported_types;
205 127
206 128 /**
207 - * Returns whether credentials are being managed at the platform level.
208 - *
209 - * This allows hosting providers to provide a more customized experience for
210 - * the plugin by handling credentials automatically.
211 - *
212 - * @since 3.9.0
213 - * @access private
214 - * @var bool
215 - */
216 - public $are_credentials_managed;
217 -
218 - /**
219 - * Holds the managed options and their values.
220 - *
221 - * This allows hosting providers to provide a more customized experience for
222 - * the plugin by handling options automatically.
223 - *
224 - * @since 3.9.0
225 - * @access private
226 - * @var array<empty>|array<string, bool|string|null>
227 - */
228 - public $managed_options = array();
229 -
230 - /**
231 129 * Constructor.
232 130 */
233 131 public function __construct() {
234 132 self::$all_supported_types = array_merge( self::SUPPORTED_JSONLD_POST_TYPES, self::SUPPORTED_JSONLD_NON_POST_TYPES );
235 -
236 - $this->are_credentials_managed = $this->are_credentials_managed();
237 - $this->set_managed_options();
238 -
239 - $this->allow_parsely_remote_requests();
240 133 }
241 134
242 135 /**
243 - * Gets the allowed post statuses for tracking.
244 - *
245 - * Uses the `wp_parsely_trackable_statuses` filter to determine which post statuses are allowed to be tracked.
246 - *
247 - * @since 3.17.0
248 - *
249 - * @param WP_Post|int|null $post The post object.
250 - * @return array<string> The allowed post statuses.
251 - */
252 - public static function get_trackable_statuses( $post = null ): array {
253 - /**
254 - * Filters the statuses that are permitted to be tracked.
255 - *
256 - * By default, the only status tracked is 'publish'. Use this filter if
257 - * you have other published content that has a different (custom) status.
258 - *
259 - * @since 2.5.0
260 - * @since 3.17.0 Filter extracted to a separate method.
261 - *
262 - * @param string[] $trackable_statuses The list of post statuses that are allowed to be tracked.
263 - * @param WP_Post|int|null $post Which post object or ID is being checked.
264 - */
265 - return apply_filters( 'wp_parsely_trackable_statuses', array( 'publish' ), $post );
266 - }
267 -
268 - /**
269 136 * Registers action and filter hook callbacks, and immediately upgrades
270 137 * options if needed.
271 138 */
272 139 public function run(): void {
@@ -282,70 +149,35 @@
282 149 */
283 150 $callable = array( $this, $method );
284 151 call_user_func_array( $callable, array( $options ) );
285 152 }
286 -
287 153 // Update our version info.
288 154 $options['plugin_version'] = self::VERSION;
289 155 update_option( self::OPTIONS_KEY, $options );
290 156 }
291 157
292 - // @phpstan-ignore return.void
293 - 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' ) );
294 162 }
295 163
296 164 /**
297 - * Returns the Content API service.
165 + * Adds 10 minute cron interval.
298 166 *
299 - * 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.
300 168 *
301 - * @since 3.17.0
302 - *
303 - * @return Content_API_Service
169 + * @return array<string, mixed>
304 170 */
305 - public function get_content_api(): Content_API_Service {
306 - if ( ! isset( $this->content_api_service ) ) {
307 - $this->content_api_service = new Content_API_Service( $this );
308 - }
309 -
310 - 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;
311 177 }
312 178
313 179 /**
314 - * Returns the Suggestions API service.
315 - *
316 - * This method returns the Suggestions API service, which is used to interact with the Parse.ly Suggestions API.
317 - *
318 - * @since 3.17.0
319 - *
320 - * @return Suggestions_API_Service
321 - */
322 - public function get_suggestions_api(): Suggestions_API_Service {
323 - if ( ! isset( $this->suggestions_api_service ) ) {
324 - $this->suggestions_api_service = new Suggestions_API_Service( $this );
325 - }
326 -
327 - return $this->suggestions_api_service;
328 - }
329 -
330 - /**
331 - * Gets the REST API controller.
332 - *
333 - * If the controller is not set, a new instance is created.
334 - *
335 - * @since 3.17.0
336 - *
337 - * @return REST_API_Controller
338 - */
339 - public function get_rest_api_controller(): REST_API_Controller {
340 - if ( ! isset( $this->rest_api_controller ) ) {
341 - $this->rest_api_controller = new REST_API_Controller( $this );
342 - }
343 -
344 - return $this->rest_api_controller;
345 - }
346 -
347 - /**
348 180 * Gets the full URL of the JavaScript tracker file for the site. If an API
349 181 * key is not set, return an empty string.
350 182 *
351 183 * @since 3.2.0
@@ -404,8 +236,14 @@
404 236 * @return bool Should the post status be tracked for the provided post's post_type.
405 237 * By default,only 'publish' is allowed.
406 238 */
407 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 +
408 246 /**
409 247 * Filters whether the post password check should be skipped when getting
410 248 * the post trackable status.
411 249 *
@@ -413,17 +251,30 @@
413 251 *
414 252 * @param bool $skip True if the password check should be skipped.
415 253 * @param int|WP_Post $post Which post object or ID is being checked.
416 254 *
417 - * @return bool
255 + * @returns bool
418 256 */
419 257 $skip_password_check = apply_filters( 'wp_parsely_skip_post_password_check', false, $post );
420 258 if ( ! $skip_password_check && post_password_required( $post ) ) {
259 + $cache[ $post_id ] = false;
421 260 return false;
422 261 }
423 262
424 - $statuses = self::get_trackable_statuses( $post );
425 - 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 ];
426 277 }
427 278
428 279 /**
429 280 * Deprecated. Please use the `Metadata` class instead.
@@ -434,8 +285,9 @@
434 285 * @see \Parsely\Metadata::construct_metadata
435 286 *
436 287 * @param array<string, mixed> $parsely_options parsely_options array.
437 288 * @param WP_Post $post object.
289 + *
438 290 * @return Metadata_Attributes
439 291 */
440 292 public function construct_parsely_metadata( array $parsely_options, WP_Post $post ) {
441 293 _deprecated_function( __FUNCTION__, '3.3', 'Metadata::construct_metadata()' );
@@ -443,52 +295,23 @@
443 295 return $metadata->construct_metadata( $post );
444 296 }
445 297
446 298 /**
447 - * Calls Parse.ly's update metadata endpoint, sending the post's updated
448 - * metadata.
299 + * Updates the Parsely metadata endpoint with the new metadata of the post.
449 300 *
450 - * @param int $post_id The ID of the post to update.
451 - * @return bool True if the metadata endpoint was called, false otherwise.
301 + * @param int $post_id id of the post to update.
452 302 */
453 - public function call_update_metadata_endpoint( int $post_id ): bool {
454 - $options = $this->get_options();
455 -
456 - if ( $this->site_id_is_missing() || '' === $options['metadata_secret'] ) {
457 - 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;
458 307 }
459 308
460 - $current_post_type = get_post_type( $post_id );
461 - if ( false === $current_post_type ) {
462 - return false;
463 - }
464 -
465 - $tracked_post_types = array_merge(
466 - $options['track_post_types'],
467 - $options['track_page_types']
468 - );
469 -
470 - // Check that the post's type is trackable.
471 - if ( ! in_array( $current_post_type, $tracked_post_types, true ) ) {
472 - return false;
473 - }
474 -
475 - // Check that the post's status is trackable.
476 - if ( ! self::post_has_trackable_status( $post_id ) ) {
477 - return false;
478 - }
479 -
480 309 $post = get_post( $post_id );
481 310 if ( null === $post ) {
482 - return false;
311 + return;
483 312 }
484 313
485 - // Don't call the endpoint when integration tests are running, but
486 - // signal that the above checks have passed.
487 - if ( defined( 'INTEGRATION_TESTS_RUNNING' ) ) {
488 - return true;
489 - }
490 -
491 314 $metadata = ( new Metadata( $this ) )->construct_metadata( $post );
492 315
493 316 $endpoint_metadata = array(
494 317 'canonical_url' => $metadata['url'] ?? '',
@@ -500,14 +323,14 @@
500 323 'authors' => $metadata['creator'] ?? '',
501 324 'tags' => $metadata['keywords'] ?? '',
502 325 );
503 326
504 - $parsely_api_base_url = Content_API_Service::get_base_url();
505 - $parsely_api_endpoint = $parsely_api_base_url . '/metadata/posts';
506 - $parsely_metadata_secret = $options['metadata_secret'];
507 -
508 - $headers = array( 'Content-Type' => 'application/json' );
509 - $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(
510 333 array(
511 334 'secret' => $parsely_metadata_secret,
512 335 'apikey' => $this->get_site_id(),
513 336 'metadata' => $endpoint_metadata,
@@ -512,31 +335,68 @@
512 335 'apikey' => $this->get_site_id(),
513 336 'metadata' => $endpoint_metadata,
514 337 )
515 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 + );
516 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 +
517 372 /**
518 - * POST request options.
373 + * Variable.
519 374 *
520 - * @var WP_HTTP_Request_Args $request_options
375 + * @var int[]|false
521 376 */
522 - $request_options = array(
523 - 'method' => 'POST',
524 - 'headers' => $headers,
525 - 'blocking' => false,
526 - 'body' => $body,
527 - 'data_format' => 'body',
528 - );
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 + }
529 390
530 - $response = wp_remote_post( $parsely_api_endpoint, $request_options );
531 -
532 - if ( is_wp_error( $response ) ) {
533 - 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 );
534 398 }
535 -
536 - update_post_meta( $post_id, 'parsely_metadata_last_updated', time() );
537 -
538 - return true;
539 399 }
540 400
541 401 /**
542 402 * Safely returns options for the plugin by assigning defaults contained in
@@ -552,164 +412,27 @@
552 412 * Variable.
553 413 *
554 414 * @var Parsely_Options|null
555 415 */
556 - $options = get_option( self::OPTIONS_KEY, null );
416 + $options = get_option( self::OPTIONS_KEY, $this->option_defaults );
557 417
558 - // Existing plugin installation without full metadata option.
559 - /* @phpstan-ignore isset.offset, booleanAnd.alwaysFalse */
560 - if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) {
561 - $this->set_default_full_metadata_in_non_posts();
562 - }
563 -
564 - // Existing plugin installation without Content Helper options.
565 - /* @phpstan-ignore isset.offset, booleanAnd.alwaysFalse */
566 - if ( is_array( $options ) && ! isset( $options['content_helper'] ) ) {
567 - $this->set_default_content_helper_settings_values();
568 - }
569 -
570 - // Existing plugin installation that's missing a Content Helper feature option.
571 - /* @phpstan-ignore isset.offset */
572 - if ( is_array( $options ) && isset( $options['content_helper'] ) ) {
573 - /** @var array<string,Parsely_Options_Content_Helper_Feature> $pch_options */
574 - $pch_options = $options['content_helper'];
575 -
576 - /** @var array<string,Parsely_Options_Content_Helper_Feature> $pch_options_defaults */
577 - $pch_options_defaults = $this->option_defaults['content_helper'];
578 -
579 - if ( count( $pch_options ) !== count( $pch_options_defaults ) ) {
580 - $new_keys = array_diff(
581 - array_keys( $pch_options_defaults ),
582 - array_keys( $pch_options )
583 - );
584 -
585 - foreach ( $new_keys as $key ) {
586 - $options['content_helper'][ $key ] = $pch_options_defaults[ $key ];
587 - }
588 - }
589 - }
590 -
591 - // New plugin installation that hasn't saved its options yet.
592 418 if ( ! is_array( $options ) ) {
593 - $this->set_default_track_as_values();
594 - $this->set_default_full_metadata_in_non_posts();
595 - $options = $this->option_defaults;
419 + return $this->option_defaults;
596 420 }
597 421
598 - /**
599 - * Final options including managed credentials and options.
600 - *
601 - * @var Parsely_Options
602 - */
603 - return array_merge(
604 - $this->option_defaults,
605 - $options,
606 - $this->get_managed_credentials(),
607 - $this->managed_options
608 - );
422 + return array_merge( $this->option_defaults, $options );
609 423 }
610 424
611 425 /**
612 - * Returns the value of a nested option.
613 - *
614 - * @since 3.16.0
615 - *
616 - * @param string $option The option to get.
617 - * @param Parsely_Options $options The options to get the value from.
618 - * @return mixed The value of the nested option.
619 - */
620 - public static function get_nested_option_value( $option, $options ) {
621 - $keys = explode( '[', str_replace( ']', '', $option ) );
622 - $value = $options;
623 -
624 - foreach ( $keys as $key ) {
625 - if ( isset( $value[ $key ] ) ) {
626 - $value = $value[ $key ];
627 - }
628 - }
629 -
630 - return $value;
631 - }
632 -
633 - /**
634 - * Sets the default values for the track_post_types and track_page_types
635 - * options.
636 - *
637 - * @since 3.9.0
638 - */
639 - public function set_default_track_as_values(): void {
640 - $this->option_defaults['track_page_types'] = array();
641 - $this->option_defaults['track_post_types'] = array();
642 -
643 - $post_types = get_post_types( array( 'public' => true ) );
644 -
645 - foreach ( $post_types as $post_type ) {
646 - if ( ! post_type_supports( $post_type, 'editor' ) ) {
647 - continue;
648 - }
649 -
650 - if ( is_post_type_hierarchical( $post_type ) ) {
651 - $this->option_defaults['track_page_types'][] = $post_type;
652 - } else {
653 - $this->option_defaults['track_post_types'][] = $post_type;
654 - }
655 - }
656 - }
657 -
658 - /**
659 - * Sets the default value for the full_metadata_in_non_posts option.
660 - *
661 - * @since 3.14.0
662 - */
663 - public function set_default_full_metadata_in_non_posts(): void {
664 - $this->option_defaults['full_metadata_in_non_posts'] = true;
665 -
666 - // Usage of any of these filters will result in the setting being set
667 - // to false.
668 - $filter_tags = array(
669 - 'wp_parsely_metadata',
670 - 'wp_parsely_post_tags',
671 - 'wp_parsely_permalink',
672 - 'wp_parsely_post_category',
673 - 'wp_parsely_pre_authors',
674 - 'wp_parsely_post_authors',
675 - 'wp_parsely_custom_taxonomies',
676 - 'wp_parsely_post_type',
677 - );
678 -
679 - foreach ( $filter_tags as $filter_tag ) {
680 - if ( has_filter( $filter_tag ) ) {
681 - $this->option_defaults['full_metadata_in_non_posts'] = false;
682 - break;
683 - }
684 - }
685 - }
686 -
687 - /**
688 - * Sets the default values for Content Helper options.
689 - *
690 - * Gives PCH access to all users having the edit_posts capability, to keep
691 - * consistent behavior with plugin versions prior to 3.16.0.
692 - *
693 - * @since 3.16.0
694 - */
695 - public function set_default_content_helper_settings_values(): void {
696 - $this->option_defaults['content_helper'] =
697 - Permissions::build_pch_permissions_settings_array(
698 - true,
699 - array_keys( Permissions::get_user_roles_with_edit_posts_cap() )
700 - );
701 - }
702 -
703 - /**
704 426 * Gets the URL of the plugin's settings page.
705 427 *
706 428 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
707 429 * for context (Default null for current).
430 + *
708 431 * @return string
709 432 */
710 - public static function get_settings_url( ?int $_blog_id = null ): string {
711 - return get_admin_url( $_blog_id, 'admin.php?page=' . self::MENU_SLUG );
433 + public static function get_settings_url( int $_blog_id = null ): string {
434 + return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
712 435 }
713 436
714 437 /**
715 438 * Returns the URL of the Parse.ly dashboard for a specific page. If a page
@@ -725,10 +448,9 @@
725 448 public static function get_dash_url( string $site_id, string $page_url = '' ): string {
726 449 $result = trailingslashit( self::DASHBOARD_BASE_URL . '/' . $site_id ) . 'find';
727 450
728 451 if ( '' !== $page_url ) {
729 - $page_url = self::get_url_with_itm_source( $page_url, null );
730 - $result .= '?url=' . rawurlencode( $page_url );
452 + $result .= '?url=' . rawurlencode( $page_url );
731 453 }
732 454
733 455 return $result;
734 456 }
@@ -733,28 +455,8 @@
733 455 return $result;
734 456 }
735 457
736 458 /**
737 - * Adds or replaces the itm_source parameter in the URL. Removes the
738 - * parameter if the passed value is null or an empty string.
739 - *
740 - * @since 3.9.0
741 - *
742 - * @param string $url The URL to modify.
743 - * @param string|null $itm_source The value of the itm_source parameter.
744 - * @return string The resulting URL.
745 - */
746 - public static function get_url_with_itm_source( string $url, $itm_source ): string {
747 - if ( null === $itm_source || '' === $itm_source ) {
748 - return remove_query_arg( 'itm_source', $url );
749 - }
750 -
751 - $itm_source = rawurlencode( $itm_source );
752 -
753 - return add_query_arg( 'itm_source', $itm_source, $url );
754 - }
755 -
756 - /**
757 459 * Checks to see if the current user is a member of the current blog.
758 460 *
759 461 * @return bool
760 462 */
@@ -774,9 +476,9 @@
774 476 * types, "index" is returned.
775 477 *
776 478 * @since 2.5.0
777 479 *
778 - * @see https://docs.parse.ly/metatags/#h-field-description
480 + * @see https://www.parse.ly/help/integration/metatags#field-description
779 481 *
780 482 * @param string $type JSON-LD type.
781 483 * @return string "post" or "index".
782 484 */
@@ -787,9 +489,9 @@
787 489 /**
788 490 * Determines if a Site ID is saved in the options.
789 491 *
790 492 * @since 2.6.0
791 - * @since 3.7.0 renamed from api_key_is_set.
493 + * @since 3.7.0 renamed from api_key_is_set
792 494 *
793 495 * @return bool True is Site ID is set, false if it is missing.
794 496 */
795 497 public function site_id_is_set(): bool {
@@ -801,9 +503,9 @@
801 503 /**
802 504 * Determines if a Site ID is not saved in the options.
803 505 *
804 506 * @since 2.6.0
805 - * @since 3.7.0 renamed from api_key_is_missing.
507 + * @since 3.7.0 renamed from api_key_is_missing
806 508 *
807 509 * @return bool True if Site ID is missing, false if it is set.
808 510 */
809 511 public function site_id_is_missing(): bool {
@@ -813,9 +515,9 @@
813 515 /**
814 516 * Gets the Site ID if set.
815 517 *
816 518 * @since 2.6.0
817 - * @since 3.7.0 renamed from get_site_id.
519 + * @since 3.7.0 renamed from get_site_id
818 520 *
819 521 * @return string Site ID if set, or empty string if not.
820 522 */
821 523 public function get_site_id(): string {
@@ -882,292 +584,6 @@
882 584 * @return Parsely_Options
883 585 */
884 586 public function get_default_options() {
885 587 return $this->option_defaults;
886 - }
887 -
888 - /**
889 - * Returns the credentials that are being managed at the platform level.
890 - *
891 - * @since 3.9.0
892 - * @access private
893 - *
894 - * @return Parsely_Options|array<empty> The managed credentials.
895 - */
896 - private function get_managed_credentials() {
897 - if ( true !== $this->are_credentials_managed ) {
898 - return array();
899 - }
900 -
901 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
902 -
903 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
904 - return array();
905 - }
906 -
907 - $result = array();
908 -
909 - if ( isset( $credentials['site_id'] ) ) {
910 - $result['apikey'] = $credentials['site_id'];
911 - }
912 -
913 - if ( isset( $credentials['api_secret'] ) ) {
914 - $result['api_secret'] = $credentials['api_secret'];
915 - }
916 -
917 - if ( isset( $credentials['metadata_secret'] ) ) {
918 - $result['metadata_secret'] = $credentials['metadata_secret'];
919 - }
920 -
921 - return $result;
922 - }
923 -
924 - /**
925 - * Returns whether credentials are being managed at the platform level.
926 - *
927 - * @since 3.9.0
928 - * @access private
929 - *
930 - * @return bool Whether credentials are being managed at the platform level.
931 - */
932 - private function are_credentials_managed(): bool {
933 - $credentials = apply_filters( 'wp_parsely_credentials', array() );
934 -
935 - if ( ! is_array( $credentials ) || 0 === count( $credentials ) ) {
936 - return false;
937 - }
938 -
939 - return $credentials['is_managed'] ?? false;
940 - }
941 -
942 - /**
943 - * Sets the values of managed options.
944 - *
945 - * This function won't accept managing credentials or certain plugin options
946 - * that are being managed through other means. For managing credentials,
947 - * please use the `wp_parsely_credentials` filter.
948 - *
949 - * @since 3.9.0
950 - * @access private
951 - */
952 - private function set_managed_options(): void {
953 - $managed_options = apply_filters( 'wp_parsely_managed_options', false );
954 -
955 - if ( ! is_array( $managed_options ) ) {
956 - return;
957 - }
958 -
959 - // Don't allow certain options to be set as managed.
960 - unset(
961 - $managed_options['apikey'],
962 - $managed_options['api_secret'],
963 - $managed_options['metadata_secret'],
964 - $managed_options['track_post_types'],
965 - $managed_options['track_page_types'],
966 - $managed_options['plugin_version']
967 - );
968 -
969 - if ( 0 === count( $managed_options ) ) {
970 - return;
971 - }
972 -
973 - /**
974 - * Current options.
975 - *
976 - * @var Parsely_Options $current_options
977 - */
978 - $current_options = get_option( self::OPTIONS_KEY, array() );
979 -
980 - // Set managed options values.
981 - foreach ( $managed_options as $key => $value ) {
982 - $is_option_valid = isset( $this->option_defaults[ $key ] );
983 -
984 - if ( $is_option_valid ) {
985 - if ( null === $value ) {
986 - // When null, the option gets its value from the database.
987 - $this->managed_options[ $key ] =
988 - $current_options[ $key ] ?? $this->option_defaults[ $key ];
989 - } else {
990 - $this->managed_options[ $key ] =
991 - $this->sanitize_managed_option( $key, $value );
992 - }
993 - }
994 - }
995 - }
996 -
997 - /**
998 - * Gets the Parse.ly canonical URL for a given post.
999 - *
1000 - * @since 3.19.0
1001 - *
1002 - * @param WP_Post|int $post The post ID or post object.
1003 - * @return string The Parse.ly canonical URL.
1004 - */
1005 - public static function get_canonical_url_from_post( $post ): string {
1006 - $post_id = is_int( $post ) ? $post : $post->ID;
1007 - $canonical_url = get_post_meta( $post_id, self::PARSELY_CANONICAL_URL_META_KEY, true );
1008 -
1009 - if ( null !== $canonical_url && is_string( $canonical_url ) && '' !== $canonical_url ) {
1010 - return $canonical_url;
1011 - }
1012 -
1013 - $permalink = get_permalink( $post );
1014 -
1015 - if ( false === $permalink ) {
1016 - return 'no permalink';
1017 - }
1018 -
1019 - return self::get_canonical_url( $permalink );
1020 - }
1021 -
1022 - /**
1023 - * Gets the canonical URL for a given URL.
1024 - *
1025 - * If the current domain is different from the Parse.ly site ID, this function
1026 - * will return the URL with the current domain.
1027 - *
1028 - * @since 3.19.0
1029 - *
1030 - * @param string $url The URL to get the canonical URL for.
1031 - * @return string The canonical URL.
1032 - */
1033 - public static function get_canonical_url( string $url ): string {
1034 - $parsely = \Parsely\get_parsely();
1035 - $site_id = $parsely->get_site_id();
1036 -
1037 - if ( wp_parse_url( $url, PHP_URL_HOST ) === $site_id ) {
1038 - return $url;
1039 - }
1040 -
1041 - $home_url = home_url();
1042 -
1043 - // Strip the protocol from the home URL.
1044 - $home_url = preg_replace( '/^https?:\/\//', '', $home_url );
1045 -
1046 - if ( null === $home_url ) {
1047 - return $url;
1048 - }
1049 -
1050 - // Replace the current domain with the Parse.ly site ID.
1051 - return str_replace( $home_url, $site_id, $url );
1052 - }
1053 -
1054 - /**
1055 - * Sets the Parse.ly canonical URL for a post.
1056 - *
1057 - * @since 3.19.0
1058 - *
1059 - * @param WP_Post|int $post The post object or post ID.
1060 - * @param string $url The canonical URL.
1061 - * @return bool True if the canonical URL was set, false otherwise.
1062 - */
1063 - public static function set_canonical_url( $post, string $url ): bool {
1064 - $post_id = is_int( $post ) ? $post : $post->ID;
1065 - return false !== update_post_meta( $post_id, self::PARSELY_CANONICAL_URL_META_KEY, $url );
1066 - }
1067 -
1068 - /**
1069 - * Sanitizes the value of the passed managed option.
1070 - *
1071 - * @since 3.9.0
1072 - * @access private
1073 - *
1074 - * @param string $option_id The option's ID.
1075 - * @param bool|string $value The option's value.
1076 - * @return bool|string The sanitized option value.
1077 - */
1078 - private function sanitize_managed_option( string $option_id, $value ) {
1079 - $option_value_type = gettype( $this->option_defaults[ $option_id ] );
1080 -
1081 - if ( 'boolean' === $option_value_type && ! is_bool( $value ) ) {
1082 - _doing_it_wrong(
1083 - __FUNCTION__,
1084 - esc_html(
1085 - sprintf( /* translators: 1: Option ID */
1086 - __( 'The value of the managed option `%1$s` must be of type `boolean`.', 'wp-parsely' ),
1087 - $option_id
1088 - )
1089 - ),
1090 - ''
1091 - );
1092 -
1093 - return false;
1094 - }
1095 -
1096 - if ( 'string' === $option_value_type ) {
1097 - if ( ! is_string( $value ) ) {
1098 - _doing_it_wrong(
1099 - __FUNCTION__,
1100 - esc_html(
1101 - sprintf( /* translators: 1: Option ID */
1102 - __( 'The value of the managed option `%1$s` must be of type `string`.', 'wp-parsely' ),
1103 - $option_id
1104 - )
1105 - ),
1106 - ''
1107 - );
1108 -
1109 - $value = strval( $value );
1110 - }
1111 -
1112 - // String options that are restricted to specific values.
1113 - $restricted_value_options = array(
1114 - 'custom_taxonomy_section' => Settings_Page::get_section_taxonomies(),
1115 - 'meta_type' => array( 'json_ld', 'repeated_metas' ),
1116 - );
1117 -
1118 - // Verify that the above values are respected.
1119 - foreach ( $restricted_value_options as $option_key => $valid_values ) {
1120 - if ( $option_id === $option_key ) {
1121 - if ( ! in_array( $value, $valid_values, true ) ) {
1122 - _doing_it_wrong(
1123 - __FUNCTION__,
1124 - esc_html(
1125 - sprintf( /* translators: 1: Option value 2: Option ID */
1126 - __( 'The value `%1$s` is not allowed for the managed option `%2$s`.', 'wp-parsely' ),
1127 - $value,
1128 - $option_id
1129 - )
1130 - ),
1131 - ''
1132 - );
1133 -
1134 - $value = $this->option_defaults[ $option_id ];
1135 - }
1136 - }
1137 - }
1138 - }
1139 -
1140 - return $value;
1141 - }
1142 -
1143 - /**
1144 - * Allows remote requests to Parse.ly.
1145 - *
1146 - * This is needed for environments, such as wp-now, that block remote requests.
1147 - *
1148 - * @since 3.13.0
1149 - * @access private
1150 - */
1151 - private function allow_parsely_remote_requests(): void {
1152 - $allowed_urls = array(
1153 - self::DASHBOARD_BASE_URL,
1154 - Content_API_Service::get_base_url(),
1155 - Suggestions_API_Service::get_base_url(),
1156 - );
1157 -
1158 - add_filter(
1159 - 'http_request_host_is_external',
1160 - function ( bool $external, string $host, string $url ) use ( $allowed_urls ) {
1161 - // Check if the URL matches any URLs on the allowed list.
1162 - foreach ( $allowed_urls as $allowed_url ) {
1163 - if ( Utils::str_starts_with( $url, $allowed_url ) ) {
1164 - return true;
1165 - }
1166 - }
1167 - return $external;
1168 - },
1169 - 10,
1170 - 3
1171 - );
1172 588 }
1173 589 }