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 +55 -245 3.18.03.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,27 +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 - * }
57 - *
58 - * @phpstan-type Parsely_Options_Content_Helper_Feature array{
59 - * enabled: bool,
60 - * allowed_user_roles: string[],
61 - * }
62 - *
63 46 * @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,
47 + * method: string,
48 + * timeout: float,
49 + * blocking: bool,
50 + * headers: array<string, string>,
51 + * body: string,
52 + * data_format: string,
70 53 * }
71 54 *
72 55 * @phpstan-import-type Metadata_Attributes from Metadata
73 56 */
@@ -74,36 +57,17 @@
74 57 class Parsely {
75 58 /**
76 59 * Declare our constants
77 60 */
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';
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';
83 68
84 69 /**
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 70 * Declare some class properties
107 71 *
108 72 * @var Parsely_Options $option_defaults The defaults we need for the class.
109 73 */
@@ -113,23 +77,8 @@
113 77 'api_secret' => '',
114 78 'use_top_level_cats' => false,
115 79 'custom_taxonomy_section' => 'category',
116 80 '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 81 'track_authenticated_users' => false,
133 82 'lowercase_tags' => true,
134 83 'force_https_canonicals' => false,
135 84 'track_post_types' => array(),
@@ -232,34 +181,8 @@
232 181 $this->allow_parsely_remote_requests();
233 182 }
234 183
235 184 /**
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 185 * Registers action and filter hook callbacks, and immediately upgrades
263 186 * options if needed.
264 187 */
265 188 public function run(): void {
@@ -281,64 +204,12 @@
281 204 $options['plugin_version'] = self::VERSION;
282 205 update_option( self::OPTIONS_KEY, $options );
283 206 }
284 207
285 - // @phpstan-ignore return.void
286 - add_action( 'save_post', array( $this, 'call_update_metadata_endpoint' ) );
208 + add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) );
287 209 }
288 210
289 211 /**
290 - * Returns the Content API service.
291 - *
292 - * This method returns the Content API service, which is used to interact with the Parse.ly Content API.
293 - *
294 - * @since 3.17.0
295 - *
296 - * @return Content_API_Service
297 - */
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;
304 - }
305 -
306 - /**
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 212 * Gets the full URL of the JavaScript tracker file for the site. If an API
342 213 * key is not set, return an empty string.
343 214 *
344 215 * @since 3.2.0
@@ -397,8 +268,14 @@
397 268 * @return bool Should the post status be tracked for the provided post's post_type.
398 269 * By default,only 'publish' is allowed.
399 270 */
400 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 +
401 278 /**
402 279 * Filters whether the post password check should be skipped when getting
403 280 * the post trackable status.
404 281 *
@@ -410,13 +287,26 @@
410 287 * @return bool
411 288 */
412 289 $skip_password_check = apply_filters( 'wp_parsely_skip_post_password_check', false, $post );
413 290 if ( ! $skip_password_check && post_password_required( $post ) ) {
291 + $cache[ $post_id ] = false;
414 292 return false;
415 293 }
416 294
417 - $statuses = self::get_trackable_statuses( $post );
418 - 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 ];
419 309 }
420 310
421 311 /**
422 312 * Deprecated. Please use the `Metadata` class instead.
@@ -436,52 +326,23 @@
436 326 return $metadata->construct_metadata( $post );
437 327 }
438 328
439 329 /**
440 - * Calls Parse.ly's update metadata endpoint, sending the post's updated
441 - * metadata.
330 + * Updates the Parsely metadata endpoint with the new metadata of the post.
442 331 *
443 - * @param int $post_id The ID of the post to update.
444 - * @return bool True if the metadata endpoint was called, false otherwise.
332 + * @param int $post_id id of the post to update.
445 333 */
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;
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;
451 338 }
452 339
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 340 $post = get_post( $post_id );
474 341 if ( null === $post ) {
475 - return false;
342 + return;
476 343 }
477 344
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 345 $metadata = ( new Metadata( $this ) )->construct_metadata( $post );
485 346
486 347 $endpoint_metadata = array(
487 348 'canonical_url' => $metadata['url'] ?? '',
@@ -493,11 +354,10 @@
493 354 'authors' => $metadata['creator'] ?? '',
494 355 'tags' => $metadata['keywords'] ?? '',
495 356 );
496 357
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'];
358 + $parsely_api_endpoint = self::PUBLIC_API_BASE_URL . '/metadata/posts';
359 + $parsely_metadata_secret = $parsely_options['metadata_secret'];
500 360
501 361 $headers = array( 'Content-Type' => 'application/json' );
502 362 $body = wp_json_encode(
503 363 array(
@@ -509,11 +369,11 @@
509 369
510 370 /**
511 371 * POST request options.
512 372 *
513 - * @var WP_HTTP_Request_Args $request_options
373 + * @var WP_HTTP_Request_Args $options
514 374 */
515 - $request_options = array(
375 + $options = array(
516 376 'method' => 'POST',
517 377 'headers' => $headers,
518 378 'blocking' => false,
519 379 'body' => $body,
@@ -519,17 +379,14 @@
519 379 'body' => $body,
520 380 'data_format' => 'body',
521 381 );
522 382
523 - $response = wp_remote_post( $parsely_api_endpoint, $request_options );
383 + $response = wp_remote_post( $parsely_api_endpoint, $options );
524 384
525 - if ( is_wp_error( $response ) ) {
526 - return false;
385 + if ( ! is_wp_error( $response ) ) {
386 + $current_timestamp = time();
387 + update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp );
527 388 }
528 -
529 - update_post_meta( $post_id, 'parsely_metadata_last_updated', time() );
530 -
531 - return true;
532 389 }
533 390
534 391 /**
535 392 * Safely returns options for the plugin by assigning defaults contained in
@@ -547,21 +404,12 @@
547 404 * @var Parsely_Options|null
548 405 */
549 406 $options = get_option( self::OPTIONS_KEY, null );
550 407
551 - // @phpstan-ignore isset.offset, booleanAnd.alwaysFalse
552 408 if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) {
553 - // Existing plugin installation without full metadata option.
554 409 $this->set_default_full_metadata_in_non_posts();
555 410 }
556 411
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 412 if ( ! is_array( $options ) ) {
565 413 $this->set_default_track_as_values();
566 414 $this->set_default_full_metadata_in_non_posts();
567 415 $options = $this->option_defaults;
@@ -580,30 +428,8 @@
580 428 );
581 429 }
582 430
583 431 /**
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 432 * Sets the default values for the track_post_types and track_page_types
607 433 * options.
608 434 *
609 435 * @since 3.9.0
@@ -656,24 +482,8 @@
656 482 }
657 483 }
658 484
659 485 /**
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 486 * Gets the URL of the plugin's settings page.
677 487 *
678 488 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
679 489 * for context (Default null for current).
@@ -678,9 +488,9 @@
678 488 * @param int|null $_blog_id The Blog ID for the multisite subsite to use
679 489 * for context (Default null for current).
680 490 * @return string
681 491 */
682 - public static function get_settings_url( ?int $_blog_id = null ): string {
492 + public static function get_settings_url( int $_blog_id = null ): string {
683 493 return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
684 494 }
685 495
686 496 /**
@@ -1051,10 +861,10 @@
1051 861 */
1052 862 private function allow_parsely_remote_requests(): void {
1053 863 $allowed_urls = array(
1054 864 self::DASHBOARD_BASE_URL,
1055 - Content_API_Service::get_base_url(),
1056 - Suggestions_API_Service::get_base_url(),
865 + self::PUBLIC_API_BASE_URL,
866 + self::PUBLIC_SUGGESTIONS_API_BASE_URL,
1057 867 );
1058 868
1059 869 add_filter(
1060 870 'http_request_host_is_external',
@@ -1060,9 +870,9 @@
1060 870 'http_request_host_is_external',
1061 871 function ( $external, $host, $url ) use ( $allowed_urls ) {
1062 872 // Check if the URL matches any URLs on the allowed list.
1063 873 foreach ( $allowed_urls as $allowed_url ) {
1064 - if ( Utils::str_starts_with( $url, $allowed_url ) ) {
874 + if ( \Parsely\Utils\str_starts_with( $url, $allowed_url ) ) {
1065 875 return true;
1066 876 }
1067 877 }
1068 878 return $external;