PluginProbe
Parse.ly / 3.14.1
Parse.ly v3.14.1
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 +57 -346 3.20.13.14.1 View file →
@@ -9,14 +9,10 @@
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 14 use Parsely\UI\Settings_Page;
18 -use Parsely\Utils\Utils;
19 15 use WP_Post;
20 16
21 17 /**
22 18 * Holds most of the logic for the plugin.
@@ -30,9 +26,8 @@
30 26 * api_secret: string,
31 27 * use_top_level_cats: bool,
32 28 * custom_taxonomy_section: string,
33 29 * cats_as_tags: bool,
34 - * content_helper: Parsely_Options_Content_Helper,
35 30 * track_authenticated_users: bool,
36 31 * lowercase_tags: bool,
37 32 * force_https_canonicals: bool,
38 33 * track_post_types: string[],
@@ -37,9 +32,9 @@
37 32 * force_https_canonicals: bool,
38 33 * track_post_types: string[],
39 34 * track_page_types: string[],
40 35 * track_post_types_as?: array<string, string>,
41 - * full_metadata_in_non_posts: bool,
36 + * full_metadata_in_non_posts: ?bool,
42 37 * disable_javascript: bool,
43 38 * disable_amp: bool,
44 39 * meta_type: string,
45 40 * logo: string,
@@ -47,28 +42,15 @@
47 42 * disable_autotrack: bool,
48 43 * plugin_version: string,
49 44 * }
50 45 *
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 46 * @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,
47 + * method: string,
48 + * timeout: float,
49 + * blocking: bool,
50 + * headers: array<string, string>,
51 + * body: string,
52 + * data_format: string,
71 53 * }
72 54 *
73 55 * @phpstan-import-type Metadata_Attributes from Metadata
74 56 */
@@ -75,38 +57,17 @@
75 57 class Parsely {
76 58 /**
77 59 * Declare our constants
78 60 */
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';
61 + public const VERSION = PARSELY_VERSION;
62 + public const MENU_SLUG = 'parsely'; // The page param passed to options-general.php.
63 + public const OPTIONS_KEY = 'parsely'; // The key used to store options in the WP database.
64 + public const CAPABILITY = 'manage_options'; // The capability required to administer settings.
65 + public const DASHBOARD_BASE_URL = 'https://dash.parsely.com';
66 + public const PUBLIC_API_BASE_URL = 'https://api.parsely.com/v2';
67 + public const PUBLIC_SUGGESTIONS_API_BASE_URL = 'https://content-suggestions-api.parsely.net/prod';
84 68
85 - private const PARSELY_CANONICAL_URL_META_KEY = '_parsely_canonical_url';
86 -
87 69 /**
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 70 * Declare some class properties
110 71 *
111 72 * @var Parsely_Options $option_defaults The defaults we need for the class.
112 73 */
@@ -116,27 +77,8 @@
116 77 'api_secret' => '',
117 78 'use_top_level_cats' => false,
118 79 'custom_taxonomy_section' => 'category',
119 80 '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 81 'track_authenticated_users' => false,
140 82 'lowercase_tags' => true,
141 83 'force_https_canonicals' => false,
142 84 'track_post_types' => array(),
@@ -239,34 +181,8 @@
239 181 $this->allow_parsely_remote_requests();
240 182 }
241 183
242 184 /**
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 185 * Registers action and filter hook callbacks, and immediately upgrades
270 186 * options if needed.
271 187 */
272 188 public function run(): void {
@@ -288,64 +204,12 @@
288 204 $options['plugin_version'] = self::VERSION;
289 205 update_option( self::OPTIONS_KEY, $options );
290 206 }
291 207
292 - // @phpstan-ignore return.void
293 - add_action( 'save_post', array( $this, 'call_update_metadata_endpoint' ) );
208 + add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) );
294 209 }
295 210
296 211 /**
297 - * Returns the Content API service.
298 - *
299 - * This method returns the Content API service, which is used to interact with the Parse.ly Content API.
300 - *
301 - * @since 3.17.0
302 - *
303 - * @return Content_API_Service
304 - */
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;
311 - }
312 -
313 - /**
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 212 * Gets the full URL of the JavaScript tracker file for the site. If an API
349 213 * key is not set, return an empty string.
350 214 *
351 215 * @since 3.2.0
@@ -404,8 +268,14 @@
404 268 * @return bool Should the post status be tracked for the provided post's post_type.
405 269 * By default,only 'publish' is allowed.
406 270 */
407 271 public static function post_has_trackable_status( $post ): bool {
272 + static $cache = array();
273 + $post_id = is_int( $post ) ? $post : $post->ID;
274 + if ( isset( $cache[ $post_id ] ) ) {
275 + return $cache[ $post_id ];
276 + }
277 +
408 278 /**
409 279 * Filters whether the post password check should be skipped when getting
410 280 * the post trackable status.
411 281 *
@@ -417,13 +287,26 @@
417 287 * @return bool
418 288 */
419 289 $skip_password_check = apply_filters( 'wp_parsely_skip_post_password_check', false, $post );
420 290 if ( ! $skip_password_check && post_password_required( $post ) ) {
291 + $cache[ $post_id ] = false;
421 292 return false;
422 293 }
423 294
424 - $statuses = self::get_trackable_statuses( $post );
425 - return in_array( get_post_status( $post ), $statuses, true );
295 + /**
296 + * Filters the statuses that are permitted to be tracked.
297 + *
298 + * By default, the only status tracked is 'publish'. Use this filter if
299 + * you have other published content that has a different (custom) status.
300 + *
301 + * @since 2.5.0
302 + *
303 + * @param string[] $trackable_statuses The list of post statuses that are allowed to be tracked.
304 + * @param int|WP_Post $post Which post object or ID is being checked.
305 + */
306 + $statuses = apply_filters( 'wp_parsely_trackable_statuses', array( 'publish' ), $post );
307 + $cache[ $post_id ] = in_array( get_post_status( $post ), $statuses, true );
308 + return $cache[ $post_id ];
426 309 }
427 310
428 311 /**
429 312 * Deprecated. Please use the `Metadata` class instead.
@@ -443,52 +326,23 @@
443 326 return $metadata->construct_metadata( $post );
444 327 }
445 328
446 329 /**
447 - * Calls Parse.ly's update metadata endpoint, sending the post's updated
448 - * metadata.
330 + * Updates the Parsely metadata endpoint with the new metadata of the post.
449 331 *
450 - * @param int $post_id The ID of the post to update.
451 - * @return bool True if the metadata endpoint was called, false otherwise.
332 + * @param int $post_id id of the post to update.
452 333 */
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;
334 + public function update_metadata_endpoint( int $post_id ): void {
335 + $parsely_options = $this->get_options();
336 + if ( $this->site_id_is_missing() || '' === $parsely_options['metadata_secret'] ) {
337 + return;
458 338 }
459 339
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 340 $post = get_post( $post_id );
481 341 if ( null === $post ) {
482 - return false;
342 + return;
483 343 }
484 344
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 345 $metadata = ( new Metadata( $this ) )->construct_metadata( $post );
492 346
493 347 $endpoint_metadata = array(
494 348 'canonical_url' => $metadata['url'] ?? '',
@@ -500,11 +354,10 @@
500 354 'authors' => $metadata['creator'] ?? '',
501 355 'tags' => $metadata['keywords'] ?? '',
502 356 );
503 357
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'];
358 + $parsely_api_endpoint = self::PUBLIC_API_BASE_URL . '/metadata/posts';
359 + $parsely_metadata_secret = $parsely_options['metadata_secret'];
507 360
508 361 $headers = array( 'Content-Type' => 'application/json' );
509 362 $body = wp_json_encode(
510 363 array(
@@ -516,11 +369,11 @@
516 369
517 370 /**
518 371 * POST request options.
519 372 *
520 - * @var WP_HTTP_Request_Args $request_options
373 + * @var WP_HTTP_Request_Args $options
521 374 */
522 - $request_options = array(
375 + $options = array(
523 376 'method' => 'POST',
524 377 'headers' => $headers,
525 378 'blocking' => false,
526 379 'body' => $body,
@@ -526,17 +379,14 @@
526 379 'body' => $body,
527 380 'data_format' => 'body',
528 381 );
529 382
530 - $response = wp_remote_post( $parsely_api_endpoint, $request_options );
383 + $response = wp_remote_post( $parsely_api_endpoint, $options );
531 384
532 - if ( is_wp_error( $response ) ) {
533 - return false;
385 + if ( ! is_wp_error( $response ) ) {
386 + $current_timestamp = time();
387 + update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp );
534 388 }
535 -
536 - update_post_meta( $post_id, 'parsely_metadata_last_updated', time() );
537 -
538 - return true;
539 389 }
540 390
541 391 /**
542 392 * Safely returns options for the plugin by assigning defaults contained in
@@ -554,42 +404,12 @@
554 404 * @var Parsely_Options|null
555 405 */
556 406 $options = get_option( self::OPTIONS_KEY, null );
557 407
558 - // Existing plugin installation without full metadata option.
559 - /* @phpstan-ignore isset.offset, booleanAnd.alwaysFalse */
560 408 if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) {
561 409 $this->set_default_full_metadata_in_non_posts();
562 410 }
563 411
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 412 if ( ! is_array( $options ) ) {
593 413 $this->set_default_track_as_values();
594 414 $this->set_default_full_metadata_in_non_posts();
595 415 $options = $this->option_defaults;
@@ -608,30 +428,8 @@
608 428 );
609 429 }
610 430
611 431 /**
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 432 * Sets the default values for the track_post_types and track_page_types
635 433 * options.
636 434 *
637 435 * @since 3.9.0
@@ -684,24 +482,8 @@
684 482 }
685 483 }
686 484
687 485 /**
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 486 * Gets the URL of the plugin's settings page.
705 487 *
706 488 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
707 489 * for context (Default null for current).
@@ -706,10 +488,10 @@
706 488 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
707 489 * for context (Default null for current).
708 490 * @return string
709 491 */
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 );
492 + public static function get_settings_url( int $_blog_id = null ): string {
493 + return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
712 494 }
713 495
714 496 /**
715 497 * Returns the URL of the Parse.ly dashboard for a specific page. If a page
@@ -994,79 +776,8 @@
994 776 }
995 777 }
996 778
997 779 /**
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 780 * Sanitizes the value of the passed managed option.
1070 781 *
1071 782 * @since 3.9.0
1072 783 * @access private
@@ -1150,18 +861,18 @@
1150 861 */
1151 862 private function allow_parsely_remote_requests(): void {
1152 863 $allowed_urls = array(
1153 864 self::DASHBOARD_BASE_URL,
1154 - Content_API_Service::get_base_url(),
1155 - Suggestions_API_Service::get_base_url(),
865 + self::PUBLIC_API_BASE_URL,
866 + self::PUBLIC_SUGGESTIONS_API_BASE_URL,
1156 867 );
1157 868
1158 869 add_filter(
1159 870 'http_request_host_is_external',
1160 - function ( bool $external, string $host, string $url ) use ( $allowed_urls ) {
871 + function ( $external, $host, $url ) use ( $allowed_urls ) {
1161 872 // Check if the URL matches any URLs on the allowed list.
1162 873 foreach ( $allowed_urls as $allowed_url ) {
1163 - if ( Utils::str_starts_with( $url, $allowed_url ) ) {
874 + if ( \Parsely\Utils\str_starts_with( $url, $allowed_url ) ) {
1164 875 return true;
1165 876 }
1166 877 }
1167 878 return $external;