PluginProbe
Parse.ly / 3.24.3
Parse.ly v3.24.3
3.24.3 3.24.2 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 All 107 releases
← All changes | src/rest-api/content-helper/class-endpoint-smart-linking.php +156 -75 3.18.1 → 3.24.3 View file →
@@ -1,8 +1,8 @@
1 1 <?php
2 2 /**
3 3 * Endpoint: Smart Linking
4 - * Parse.ly Content Helper `/smart-linking` API endpoint class
4 + * Parse.ly Content Intelligence `/smart-linking` API endpoint class
5 5 *
6 6 * @package Parsely
7 7 * @since 3.17.0
8 8 */
@@ -11,8 +11,9 @@
11 11
12 12 namespace Parsely\REST_API\Content_Helper;
13 13
14 14 use Parsely\Models\Smart_Link;
15 +use Parsely\Models\Smart_Link_Status;
15 16 use Parsely\REST_API\Base_Endpoint;
16 17 use Parsely\REST_API\Use_Post_ID_Parameter_Trait;
17 18 use Parsely\Services\Suggestions_API\Suggestions_API_Service;
18 19 use Parsely\Utils\Utils;
@@ -45,9 +46,9 @@
45 46 * Initializes the class.
46 47 *
47 48 * @since 3.17.0
48 49 *
49 - * @param Content_Helper_Controller $controller The content helper controller.
50 + * @param Content_Helper_Controller $controller The Content Intelligence controller.
50 51 */
51 52 public function __construct( Content_Helper_Controller $controller ) {
52 53 parent::__construct( $controller );
53 54 $this->suggestions_api = $controller->get_parsely()->get_suggestions_api();
@@ -57,9 +58,9 @@
57 58 * Returns the name of the endpoint.
58 59 *
59 60 * @since 3.17.0
60 61 *
61 - * @return string The endpoint name.
62 + * @return string The endpoint's name.
62 63 */
63 64 public static function get_endpoint_name(): string {
64 65 return 'smart-linking';
65 66 }
@@ -68,9 +69,9 @@
68 69 * Returns the name of the feature associated with the current endpoint.
69 70 *
70 71 * @since 3.17.0
71 72 *
72 - * @return string The feature name.
73 + * @return string The feature's name.
73 74 */
74 75 public function get_pch_feature_name(): string {
75 76 return 'smart_linking';
76 77 }
@@ -92,19 +93,20 @@
92 93 array(
93 94 'text' => array(
94 95 'required' => true,
95 96 'type' => 'string',
96 - 'description' => __( 'The text to generate smart links for.', 'wp-parsely' ),
97 + 'description' => __( 'The text to generate Smart Links for.', 'wp-parsely' ),
97 98 ),
98 99 'max_links' => array(
99 100 'type' => 'integer',
100 - 'description' => __( 'The maximum number of smart links to generate.', 'wp-parsely' ),
101 + 'description' => __( 'The maximum number of Smart Links to generate.', 'wp-parsely' ),
101 102 'default' => 10,
102 103 ),
103 104 'url_exclusion_list' => array(
104 105 'type' => 'array',
105 - 'description' => __( 'The list of URLs to exclude from the smart links.', 'wp-parsely' ),
106 - 'validate_callback' => array( $this, 'validate_url_exclusion_list' ),
106 + 'description' => __( 'The list of URLs to exclude from the Smart Links.', 'wp-parsely' ),
107 + 'validate_callback' => array( Validations\Validate_Url_Exclusion_List::class, 'validate' ),
108 + 'sanitize_callback' => array( Validations\Validate_Url_Exclusion_List::class, 'sanitize' ),
107 109 'default' => array(),
108 110 ),
109 111 )
110 112 );
@@ -130,14 +132,14 @@
130 132 array(
131 133 'link' => array(
132 134 'required' => true,
133 135 'type' => 'object',
134 - 'description' => __( 'The smart link data to add.', 'wp-parsely' ),
136 + 'description' => __( 'The Smart Link data to add.', 'wp-parsely' ),
135 137 'validate_callback' => array( $this, 'validate_smart_link_params' ),
136 138 ),
137 139 'update' => array(
138 140 'type' => 'boolean',
139 - 'description' => __( 'Whether to update the existing smart link.', 'wp-parsely' ),
141 + 'description' => __( 'Whether to update the existing Smart Link.', 'wp-parsely' ),
140 142 'default' => false,
141 143 ),
142 144 )
143 145 );
@@ -153,14 +155,14 @@
153 155 array(
154 156 'links' => array(
155 157 'required' => true,
156 158 'type' => 'array',
157 - 'description' => __( 'The multiple smart links data to add.', 'wp-parsely' ),
159 + 'description' => __( 'The multiple Smart Links data to add.', 'wp-parsely' ),
158 160 'validate_callback' => array( $this, 'validate_multiple_smart_links' ),
159 161 ),
160 162 'update' => array(
161 163 'type' => 'boolean',
162 - 'description' => __( 'Whether to update the existing smart links.', 'wp-parsely' ),
164 + 'description' => __( 'Whether to update the existing Smart Links.', 'wp-parsely' ),
163 165 'default' => false,
164 166 ),
165 167 )
166 168 );
@@ -176,9 +178,9 @@
176 178 array(
177 179 'links' => array(
178 180 'required' => true,
179 181 'type' => 'array',
180 - 'description' => __( 'The smart links data to set.', 'wp-parsely' ),
182 + 'description' => __( 'The Smart Links data to set.', 'wp-parsely' ),
181 183 'validate_callback' => array( $this, 'validate_multiple_smart_links' ),
182 184 ),
183 185 )
184 186 );
@@ -194,8 +196,10 @@
194 196 array(
195 197 'urls' => array(
196 198 'required' => true,
197 199 'type' => 'array',
200 + 'items' => array( 'type' => 'string' ),
201 + 'maxItems' => 500,
198 202 'description' => __( 'The URLs to get meta information for.', 'wp-parsely' ),
199 203 ),
200 204 )
201 205 );
@@ -235,11 +239,11 @@
235 239
236 240 $response = $this->suggestions_api->get_smart_links(
237 241 $post_content,
238 242 array(
239 - 'max_items' => $max_links,
240 - ),
241 - $url_exclusion_list
243 + 'max_items' => $max_links,
244 + 'url_exclusion_list' => $url_exclusion_list,
245 + )
242 246 );
243 247
244 248 if ( is_wp_error( $response ) ) {
245 249 return $response;
@@ -246,8 +250,11 @@
246 250 }
247 251
248 252 $smart_links = array_map(
249 253 function ( Smart_Link $link ) {
254 + // Set the context to Smart Linking.
255 + $link->set_context( $this->get_pch_feature_name() );
256 +
250 257 return $link->to_array();
251 258 },
252 259 $response
253 260 );
@@ -260,26 +267,30 @@
260 267 *
261 268 * Gets the smart links for a post.
262 269 *
263 270 * @since 3.16.0
271 + * @since 3.24.2 Inbound links of non-visible source posts are omitted.
264 272 *
265 273 * @param WP_REST_Request $request The request object.
266 - * @return WP_REST_Response The response object.
274 + * @return WP_REST_Response|WP_Error The response object.
267 275 */
268 - public function get_smart_links( WP_REST_Request $request ): WP_REST_Response {
269 - /**
270 - * The post object.
271 - *
272 - * @var WP_Post $post
273 - */
274 - $post = $request->get_param( 'post' );
276 + public function get_smart_links( WP_REST_Request $request ) {
277 + $post = $this->get_request_post( $request );
275 278
276 - $outbound_links = Smart_Link::get_outbound_smart_links( $post->ID );
277 - $inbound_links = Smart_Link::get_inbound_smart_links( $post->ID );
279 + if ( ! $post instanceof WP_Post ) {
280 + return new WP_Error(
281 + 'invalid_post',
282 + __( 'Invalid post.', 'wp-parsely' ),
283 + array( 'status' => 404 )
284 + );
285 + }
278 286
287 + $outbound_links = Smart_Link::get_outbound_smart_links( $post->ID, Smart_Link_Status::APPLIED );
288 + $inbound_links = Smart_Link::get_inbound_smart_links( $post->ID, Smart_Link_Status::APPLIED );
289 +
279 290 $response = array(
280 291 'outbound' => $this->serialize_smart_links( $outbound_links ),
281 - 'inbound' => $this->serialize_smart_links( $inbound_links ),
292 + 'inbound' => $this->serialize_visible_inbound_links( $inbound_links ),
282 293 );
283 294
284 295 return new WP_REST_Response( array( 'data' => $response ), 200 );
285 296 }
@@ -308,9 +319,9 @@
308 319 return new WP_REST_Response(
309 320 array(
310 321 'error' => array(
311 322 'name' => 'smart_link_exists',
312 - 'message' => __( 'Smart link already exists.', 'wp-parsely' ),
323 + 'message' => __( 'Smart Link already exists.', 'wp-parsely' ),
313 324 ),
314 325 ),
315 326 409 // HTTP Conflict.
316 327 );
@@ -315,8 +326,16 @@
315 326 409 // HTTP Conflict.
316 327 );
317 328 }
318 329
330 + // Mark as applied.
331 + $smart_link->set_status( Smart_Link_Status::APPLIED );
332 +
333 + // If the context is not set, set it to Smart Linking.
334 + if ( null === $smart_link->get_context() ) {
335 + $smart_link->set_context( $this->get_pch_feature_name() );
336 + }
337 +
319 338 // The smart link properties are set in the validate callback.
320 339 $saved = $smart_link->save();
321 340 if ( ! $saved ) {
322 341 return new WP_REST_Response(
@@ -322,9 +341,9 @@
322 341 return new WP_REST_Response(
323 342 array(
324 343 'error' => array(
325 344 'name' => 'add_smart_link_failed',
326 - 'message' => __( 'Failed to add the smart link.', 'wp-parsely' ),
345 + 'message' => __( 'Failed to add the Smart Link.', 'wp-parsely' ),
327 346 ),
328 347 ),
329 348 500
330 349 );
@@ -329,8 +348,11 @@
329 348 500
330 349 );
331 350 }
332 351
352 + // Clear the cache for the smart link.
353 + $smart_link->flush_all_cache();
354 +
333 355 return new WP_REST_Response(
334 356 array(
335 357 'data' => json_decode( $smart_link->serialize() ),
336 358 ),
@@ -368,8 +390,11 @@
368 390 }
369 391
370 392 $updated_link = $smart_link->exists() && $should_update;
371 393
394 + // Mark as applied.
395 + $smart_link->set_status( Smart_Link_Status::APPLIED );
396 +
372 397 // The smart link properties are set in the validate callback.
373 398 $saved = $smart_link->save();
374 399
375 400 if ( ! $saved ) {
@@ -381,8 +406,11 @@
381 406 $updated_links[] = $smart_link;
382 407 } else {
383 408 $added_links[] = $smart_link;
384 409 }
410 +
411 + // Clear the cache for the smart link.
412 + $smart_link->flush_all_cache();
385 413 }
386 414
387 415 // If no link was added, return an error response.
388 416 if ( count( $added_links ) === 0 && count( $updated_links ) === 0 ) {
@@ -389,9 +417,9 @@
389 417 return new WP_REST_Response(
390 418 array(
391 419 'error' => array(
392 420 'name' => 'add_smart_link_failed',
393 - 'message' => __( 'Failed to add all the smart links.', 'wp-parsely' ),
421 + 'message' => __( 'Failed to add all the Smart Links.', 'wp-parsely' ),
394 422 ),
395 423 ),
396 424 500
397 425 );
@@ -418,18 +446,21 @@
418 446 *
419 447 * @since 3.16.0
420 448 *
421 449 * @param WP_REST_Request $request The request object.
422 - * @return WP_REST_Response The response object.
450 + * @return WP_REST_Response|WP_Error The response object.
423 451 */
424 - public function set_smart_links( WP_REST_Request $request ): WP_REST_Response {
425 - /**
426 - * The post object.
427 - *
428 - * @var WP_Post $post
429 - */
430 - $post = $request->get_param( 'post' );
452 + public function set_smart_links( WP_REST_Request $request ) {
453 + $post = $this->get_request_post( $request );
431 454
455 + if ( ! $post instanceof WP_Post ) {
456 + return new WP_Error(
457 + 'invalid_post',
458 + __( 'Invalid post.', 'wp-parsely' ),
459 + array( 'status' => 404 )
460 + );
461 + }
462 +
432 463 /**
433 464 * Array of Smart Link models provided in the request.
434 465 *
435 466 * @var Smart_Link[] $smart_links
@@ -436,9 +467,9 @@
436 467 */
437 468 $smart_links = $request->get_param( 'smart_links' );
438 469
439 470 // Get the current stored smart links.
440 - $existing_links = Smart_Link::get_outbound_smart_links( $post->ID );
471 + $existing_links = Smart_Link::get_outbound_smart_links( $post->ID, Smart_Link_Status::APPLIED );
441 472 $removed_links = array();
442 473
443 474 foreach ( $existing_links as $existing_link ) {
444 475 $found = false;
@@ -451,8 +482,11 @@
451 482
452 483 if ( ! $found ) {
453 484 $removed_links[] = $existing_link;
454 485 $existing_link->delete();
486 +
487 + // Clear the cache for the smart link.
488 + $existing_link->flush_all_cache();
455 489 }
456 490 }
457 491
458 492 $saved_links = array();
@@ -458,8 +492,11 @@
458 492 $saved_links = array();
459 493 $failed_links = array();
460 494
461 495 foreach ( $smart_links as $smart_link ) {
496 + // Mark as applied.
497 + $smart_link->set_status( Smart_Link_Status::APPLIED );
498 +
462 499 // The smart link properties are set in the validate callback.
463 500 $saved = $smart_link->save();
464 501
465 502 if ( ! $saved ) {
@@ -467,8 +504,11 @@
467 504 continue;
468 505 }
469 506
470 507 $saved_links[] = $smart_link;
508 +
509 + // Clear the cache for the smart link.
510 + $smart_link->flush_all_cache();
471 511 }
472 512
473 513 $response = array(
474 514 'saved' => $this->serialize_smart_links( $saved_links ),
@@ -501,9 +541,10 @@
501 541
502 542 if ( $post_id > 0 ) {
503 543 $post = get_post( $post_id );
504 544
505 - if ( null !== $post ) {
545 + // URLs resolve by slug, irrespective of the post's status.
546 + if ( null !== $post && current_user_can( 'read_post', $post_id ) ) {
506 547 $post_type_obj = get_post_type_object( $post->post_type );
507 548 $post_type_name = $post_type_obj instanceof \WP_Post_Type ? $post_type_obj->labels->singular_name : '';
508 549
509 550 $posts_meta[] = array(
@@ -522,49 +563,25 @@
522 563 return new WP_REST_Response( array( 'data' => $posts_meta ), 200 );
523 564 }
524 565
525 566 /**
526 - * Validates the URL exclusion list parameter.
527 - *
528 - * The callback sets the URL exclusion list in the request object if the parameter is valid.
529 - *
530 - * @since 3.17.0
531 - * @access private
532 - *
533 - * @param mixed $param The parameter value.
534 - * @param WP_REST_Request $request The request object.
535 - * @return true|WP_Error Whether the parameter is valid.
536 - */
537 - public function validate_url_exclusion_list( $param, WP_REST_Request $request ) {
538 - if ( ! is_array( $param ) ) {
539 - return new WP_Error( 'invalid_url_exclusion_list', __( 'The URL exclusion list must be an array.', 'wp-parsely' ) );
540 - }
541 -
542 - $valid_urls = array_filter(
543 - $param,
544 - function ( $url ) {
545 - return is_string( $url ) && false !== filter_var( $url, FILTER_VALIDATE_URL );
546 - }
547 - );
548 -
549 - $request->set_param( 'url_exclusion_list', $valid_urls );
550 -
551 - return true;
552 - }
553 -
554 - /**
555 567 * Validates the smart link parameters.
556 568 *
557 569 * The callback sets the smart link object in the request object if the parameters are valid.
558 570 *
559 571 * @since 3.16.0
572 + * @since 3.24.2 Rejects non-arrays.
560 573 * @access private
561 574 *
562 - * @param array<mixed> $params The parameters.
575 + * @param mixed $params The raw parameter value, as validation runs before type checks.
563 576 * @param WP_REST_Request $request The request object.
564 577 * @return bool Whether the parameters are valid.
565 578 */
566 - public function validate_smart_link_params( array $params, WP_REST_Request $request ): bool {
579 + public function validate_smart_link_params( $params, WP_REST_Request $request ): bool {
580 + if ( ! is_array( $params ) ) {
581 + return false;
582 + }
583 +
567 584 $required_params = array( 'uid', 'href', 'title', 'text', 'offset' );
568 585
569 586 foreach ( $required_params as $param ) {
570 587 if ( ! isset( $params[ $param ] ) ) {
@@ -585,13 +602,25 @@
585 602 if ( ! is_string( $params['uid'] ) ) {
586 603 return false;
587 604 }
588 605
606 + if ( ! is_array( $params['href'] ) ) {
607 + return false;
608 + }
609 +
610 + if ( ! is_string( $params['href']['raw'] ) ) {
611 + return false;
612 + }
613 +
614 + if ( ! Smart_Link::is_valid_href( $params['href']['raw'] ) ) {
615 + return false;
616 + }
617 +
589 618 // Try to get the smart link from the UID.
590 619 $smart_link = Smart_Link::get_smart_link( $params['uid'], intval( $post_id ) );
591 620 if ( $smart_link->exists() ) {
592 621 // Update the smart link with the new data.
593 - $smart_link->set_href( $params['href'] );
622 + $smart_link->set_href( $params['href']['raw'] );
594 623 $smart_link->title = $params['title'];
595 624 $smart_link->text = $params['text'];
596 625 $smart_link->offset = $params['offset'];
597 626 } else {
@@ -603,8 +632,13 @@
603 632 $smart_link = Smart_Link::deserialize( $encoded_data );
604 633 $smart_link->set_source_post_id( intval( $post_id ) );
605 634 }
606 635
636 + // If the context is not set, set it to Smart Linking.
637 + if ( null === $smart_link->get_context() ) {
638 + $smart_link->set_context( $this->get_pch_feature_name() );
639 + }
640 +
607 641 // Set the smart link attribute in the request.
608 642 $request->set_param( 'smart_link', $smart_link );
609 643
610 644 return true;
@@ -615,15 +649,20 @@
615 649 *
616 650 * The callback sets the smart links object in the request object if the parameters are valid.
617 651 *
618 652 * @since 3.16.0
653 + * @since 3.24.2 Rejects non-arrays.
619 654 * @access private
620 655 *
621 - * @param array<array<mixed>> $param The parameter value.
622 - * @param WP_REST_Request $request The request object.
656 + * @param mixed $param The raw parameter value, as validation runs before type checks.
657 + * @param WP_REST_Request $request The request object.
623 658 * @return bool Whether the parameter is valid.
624 659 */
625 - public function validate_multiple_smart_links( array $param, WP_REST_Request $request ): bool {
660 + public function validate_multiple_smart_links( $param, WP_REST_Request $request ): bool {
661 + if ( ! is_array( $param ) ) {
662 + return false;
663 + }
664 +
626 665 $smart_links = array();
627 666
628 667 foreach ( $param as $link ) {
629 668 if ( $this->validate_smart_link_params( $link, $request ) ) {
@@ -636,8 +675,50 @@
636 675 $request->set_param( 'smart_link', null );
637 676 $request->set_param( 'smart_links', $smart_links );
638 677
639 678 return true;
679 + }
680 +
681 + /**
682 + * Serializes inbound Smart Links, dropping the ones whose source post the
683 + * current user cannot see.
684 + *
685 + * @since 3.24.2
686 + *
687 + * @param Smart_Link[] $links The inbound Smart Links to serialize.
688 + * @return array<mixed> The serialized Smart Links.
689 + */
690 + private function serialize_visible_inbound_links( array $links ): array {
691 + $serialized = array();
692 +
693 + foreach ( $links as $link ) {
694 + $source_post_id = $link->source_post_id;
695 + $can_edit = current_user_can( 'edit_post', $source_post_id );
696 +
697 + if ( ! $can_edit && ! is_post_publicly_viewable( $source_post_id ) ) {
698 + continue;
699 + }
700 +
701 + $data = json_decode( $link->serialize(), true );
702 +
703 + if ( ! is_array( $data ) ) {
704 + continue;
705 + }
706 +
707 + // WordPress withholds a protected post's content; the anchor text
708 + // and the paragraph are both drawn from it.
709 + if ( ! $can_edit && post_password_required( $source_post_id ) ) {
710 + $data['text'] = '';
711 +
712 + if ( isset( $data['post_data'] ) && is_array( $data['post_data'] ) ) {
713 + $data['post_data']['paragraph'] = '';
714 + }
715 + }
716 +
717 + $serialized[] = $data;
718 + }
719 +
720 + return $serialized;
640 721 }
641 722
642 723 /**
643 724 * Serializes an array of Smart Links.