PluginProbe
Parse.ly / 3.16.2
Parse.ly v3.16.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 3.20.2 3.20.3 All 105 releases
wp-parsely / src / Models / class-smart-link.php

class-smart-link.php in Parse.ly 3.16.2, at src/Models/class-smart-link.php

667 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Smart Link model: Represents a smart link suggestion returned by the Smart
4 * Linking API
5 *
6 * @package Parsely
7 * @since 3.16.0
8 */
9
10 declare(strict_types=1);
11
12 namespace Parsely\Models;
13
14 use InvalidArgumentException;
15
16 /**
17 * Smart Link class.
18 *
19 * Represents a smart link suggestion returned by the Smart Linking API.
20 *
21 * @since 3.16.0
22 */
23 class Smart_Link extends Base_Model {
24 /**
25 * The internal ID of the smart link custom post type object.
26 *
27 * @since 3.16.0
28 * @var int The ID of the smart link.
29 */
30 private $smart_link_id = 0;
31
32 /**
33 * The post ID of the suggested link (link source).
34 *
35 * @since 3.16.0
36 * @var int The post ID of the suggested link, 0 if not set.
37 */
38 public $source_post_id = 0;
39
40 /**
41 * The post ID of the link destination.
42 *
43 * @since 3.16.0
44 * @var int The post ID of the link destination, 0 if not set.
45 */
46 public $destination_post_id = 0;
47
48 /**
49 * The post type of the suggested link.
50 *
51 * @since 3.16.0
52 * @var string The post type of the suggested link.
53 */
54 public $destination_post_type = 'external';
55
56 /**
57 * The URL of the suggested link.
58 *
59 * @since 3.16.0
60 * @var string The URL of the suggested link.
61 */
62 protected $href;
63
64 /**
65 * The title of the suggested link.
66 *
67 * @since 3.16.0
68 * @var string The title of the suggested link.
69 */
70 public $title;
71
72 /**
73 * The text of the suggested link.
74 *
75 * @since 3.16.0
76 * @var string The text of the suggested link.
77 */
78 public $text;
79
80 /**
81 * The offset/position for the suggested link.
82 *
83 * @since 3.16.0
84 * @var int The offset/position for the suggested link.
85 */
86 public $offset;
87
88 /**
89 * The unique ID of the suggested link.
90 *
91 * @since 3.16.0
92 * @var string The unique ID of the suggested link.
93 */
94 public $uid;
95
96 /**
97 * Whether the link has been applied.
98 *
99 * @since 3.16.0
100 * @var bool Whether the link has been applied.
101 */
102 public $applied = false;
103
104 /**
105 * Whether the smart link exists on the database.
106 *
107 * @since 3.16.0
108 * @var bool Whether the link exists.
109 */
110 private $exists = false;
111
112 /**
113 * Smart Link constructor.
114 *
115 * @since 3.16.0
116 *
117 * @param string $href The URL of the suggested link.
118 * @param string $title The title of the suggested link.
119 * @param string $text The text of the suggested link.
120 * @param int $offset The offset/position for the suggested link.
121 * @param int $post_id The post ID of the suggested link.
122 */
123 public function __construct(
124 string $href,
125 string $title,
126 string $text,
127 int $offset,
128 int $post_id = 0
129 ) {
130 $this->set_href( $href );
131
132 // Set the title to be the destination post title if the destination post ID is set.
133 if ( 0 !== $this->destination_post_id ) {
134 $this->title = get_the_title( $this->destination_post_id );
135 } else {
136 $this->title = $title;
137 }
138
139 $this->text = $text;
140 $this->offset = $offset;
141 $this->source_post_id = $post_id;
142
143 parent::__construct();
144 }
145
146 /**
147 * Gets the smart link post object by UID.
148 *
149 * @since 3.16.0
150 *
151 * @param string $uid The UID of the smart link.
152 * @return int The ID of the smart link post object.
153 */
154 private function get_smart_link_object_by_uid( string $uid ): int {
155 $cached = wp_cache_get( $uid . $this->source_post_id, 'wp_parsely_smart_link_id' );
156 if ( is_int( $cached ) && 0 !== $cached ) {
157 return $cached;
158 }
159
160 $smart_links = new \WP_Query(
161 array(
162 'post_type' => 'parsely_smart_link',
163 'fields' => 'ids', // Only get the post IDs to improve performance.
164 'posts_per_page' => 1,
165 'title' => $uid,
166 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
167 'tax_query' => array(
168 array(
169 'taxonomy' => 'smart_link_source',
170 'include_children' => false, // Performance optimization.
171 'field' => 'name',
172 'terms' => (string) $this->source_post_id,
173 ),
174 ),
175 )
176 );
177
178 if ( $smart_links->have_posts() && is_int( $smart_links->posts[0] ) ) {
179 wp_cache_set(
180 $uid . $this->source_post_id,
181 $smart_links->posts[0],
182 'wp_parsely_smart_link_id'
183 );
184 return $smart_links->posts[0];
185 }
186
187 return 0;
188 }
189
190 /**
191 * Loads the smart link post object.
192 *
193 * @since 3.16.0
194 *
195 * @return bool True if the smart link was loaded successfully, false otherwise.
196 */
197 private function load(): bool {
198 if ( 0 === $this->smart_link_id ) {
199 // Try to get the smart link id from the UID.
200 $this->smart_link_id = $this->get_smart_link_object_by_uid( $this->uid );
201 if ( 0 === $this->smart_link_id ) {
202 $this->exists = false;
203 return false;
204 }
205 }
206
207 $smart_link = get_post( $this->smart_link_id );
208
209 if ( null === $smart_link ) {
210 $this->exists = false;
211 return false;
212 }
213
214 $this->exists = true;
215 $this->applied = true;
216
217 $this->uid = $smart_link->post_title;
218
219 // Load the Smart Link properties from the post meta.
220 $this->title = $this->get_string_meta( '_smart_link_title' );
221 $this->href = $this->get_string_meta( '_smart_link_href' );
222 $this->text = $this->get_string_meta( '_smart_link_text' );
223 $this->offset = $this->get_int_meta( '_smart_link_offset' );
224
225 // Load the source post ID.
226 $source_terms = wp_get_post_terms( $this->smart_link_id, 'smart_link_source' );
227 if ( ! is_wp_error( $source_terms ) && count( $source_terms ) > 0 ) {
228 $source_term = $source_terms[0];
229 $this->source_post_id = (int) $source_term->name;
230 }
231
232 // Load the destination post ID.
233 $destination_terms = wp_get_post_terms( $this->smart_link_id, 'smart_link_destination' );
234 if ( ! is_wp_error( $destination_terms ) && count( $destination_terms ) > 0 ) {
235 $destination_term = $destination_terms[0];
236 if ( 'external' !== $destination_term->slug ) {
237 $this->destination_post_id = (int) $destination_term->name;
238 }
239 }
240
241 // If the destination post ID is not set, try to get it from the URL.
242 if ( 0 === $this->destination_post_id ) {
243 $this->destination_post_id = $this->get_post_id_by_url( $this->href );
244 }
245
246 // Get the post type of the destination post.
247 $post_type = get_post_type( $this->destination_post_id );
248 if ( false !== $post_type ) {
249 $post_type_object = get_post_type_object( $post_type );
250 if ( null !== $post_type_object ) {
251 $this->destination_post_type = $post_type_object->labels->singular_name;
252 }
253 } else {
254 $this->destination_post_type = 'external';
255 }
256
257 return true;
258 }
259
260 /**
261 * Saves the smart link to the post meta.
262 *
263 * @since 3.16.0
264 *
265 * @return bool True if the smart link was saved successfully, false otherwise.
266 */
267 public function save(): bool {
268 if ( 0 === $this->source_post_id ) {
269 return false;
270 }
271
272 if ( ! $this->exists() ) {
273 // Create the post object.
274 $post_id = wp_insert_post(
275 array(
276 'post_type' => 'parsely_smart_link',
277 'post_title' => $this->uid,
278 'post_status' => 'publish',
279 )
280 );
281
282 if ( 0 === $post_id ) {
283 return false;
284 }
285
286 $this->smart_link_id = $post_id;
287 wp_cache_set( $this->uid . $this->source_post_id, $post_id, 'wp_parsely_smart_link_id' );
288 }
289
290 // Update UID.
291 wp_update_post(
292 array(
293 'ID' => $this->smart_link_id,
294 'post_title' => $this->uid,
295 )
296 );
297
298 // Update the smart link meta.
299 $meta = array(
300 '_smart_link_title' => $this->title,
301 '_smart_link_href' => $this->href,
302 '_smart_link_text' => $this->text,
303 '_smart_link_offset' => $this->offset,
304 );
305 foreach ( $meta as $key => $value ) {
306 update_post_meta( $this->smart_link_id, $key, $value );
307 }
308
309 // Add the source term.
310 wp_set_post_terms( $this->smart_link_id, (string) $this->source_post_id, 'smart_link_source' );
311
312 // Add the destination term.
313 if ( 0 !== $this->destination_post_id ) {
314 wp_set_post_terms( $this->smart_link_id, (string) $this->destination_post_id, 'smart_link_destination' );
315 } else {
316 wp_set_post_terms( $this->smart_link_id, 'external', 'smart_link_destination' );
317 }
318
319 $this->applied = true;
320 $this->exists = true;
321
322 return true;
323 }
324
325 /**
326 * Removes the smart link from the database.
327 *
328 * @since 3.16.0
329 *
330 * @return bool True if the smart link was removed successfully, false otherwise.
331 */
332 public function delete(): bool {
333 if ( 0 === $this->smart_link_id ) {
334 return false;
335 }
336
337 // Delete the post object.
338 $deleted = wp_delete_post( $this->smart_link_id, true );
339
340 if ( false !== $deleted && null !== $deleted && is_a( $deleted, 'WP_Post' ) ) {
341 $this->smart_link_id = 0;
342 $this->exists = false;
343 wp_cache_delete( $this->uid . $this->source_post_id, 'wp_parsely_smart_link_id' );
344 return true;
345 }
346
347 return false;
348 }
349
350 /**
351 * Checks if the smart link is saved in the database.
352 *
353 * @since 3.16.0
354 *
355 * @return bool True if the smart link exists, false otherwise.
356 */
357 public function exists(): bool {
358 if ( $this->exists ) {
359 return true;
360 }
361
362 // Try to find a smart link with the same UID.
363 $smart_link_id = $this->get_smart_link_object_by_uid( $this->uid );
364
365 if ( 0 !== $smart_link_id ) {
366 $this->exists = true;
367 $this->smart_link_id = $smart_link_id;
368 return true;
369 }
370
371 $this->exists = false;
372 $this->smart_link_id = 0;
373 return false;
374 }
375
376 /**
377 * Gets a string meta value from the smart link post.
378 *
379 * @since 3.16.0
380 *
381 * @param string $meta_key The meta key to get the value for.
382 * @param string $default_value The default value to return if the meta value is not a string.
383 * @return string The meta value.
384 */
385 private function get_string_meta( string $meta_key, string $default_value = '' ): string {
386 $meta_value = get_post_meta( $this->smart_link_id, $meta_key, true );
387 return is_string( $meta_value ) ? $meta_value : $default_value;
388 }
389
390 /**
391 * Gets an integer meta value from the smart link post.
392 *
393 * @since 3.16.0
394 *
395 * @param string $meta_key The meta key to get the value for.
396 * @param int $default_value The default value to return if the meta value is not an integer.
397 * @return int The meta value.
398 */
399 private function get_int_meta( string $meta_key, int $default_value = 0 ): int {
400 $meta_value = get_post_meta( $this->smart_link_id, $meta_key, true );
401 return is_int( $meta_value ) ? $meta_value : $default_value;
402 }
403
404 /**
405 * Gets the post ID by URL.
406 *
407 * @since 3.16.0
408 *
409 * @param string $url The URL to get the post ID for.
410 * @return int The post ID of the URL, 0 if not found.
411 */
412 private function get_post_id_by_url( string $url ): int {
413 $cache = wp_cache_get( $url, 'wp_parsely_smart_link_url_to_postid' );
414 if ( is_integer( $cache ) ) {
415 return $cache;
416 }
417
418 if ( function_exists( 'wpcom_vip_url_to_postid' ) ) {
419 $post_id = wpcom_vip_url_to_postid( $url );
420 } else {
421 // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.url_to_postid_url_to_postid
422 $post_id = url_to_postid( $url );
423 wp_cache_set( $url, $post_id, 'wp_parsely_smart_link_url_to_postid' );
424 }
425
426 return $post_id;
427 }
428
429 /**
430 * Sets the source post ID.
431 *
432 * @since 3.16.0
433 *
434 * @param int $source_post_id The source post ID.
435 */
436 public function set_source_post_id( int $source_post_id ): void {
437 $this->source_post_id = $source_post_id;
438 }
439
440 /**
441 * Sets the UID of the smart link.
442 *
443 * @since 3.16.0
444 *
445 * @param string $uid The UID of the smart link.
446 */
447 public function set_uid( string $uid ): void {
448 $this->uid = $uid;
449 }
450
451 /**
452 * Sets the href of the smart link.
453 *
454 * @since 3.16.0
455 *
456 * @param string $href The href of the smart link.
457 */
458 public function set_href( string $href ): void {
459 $this->href = $href;
460 $this->destination_post_id = $this->get_post_id_by_url( $href );
461
462 if ( 0 !== $this->destination_post_id ) {
463 $post_type = get_post_type( $this->destination_post_id );
464 $this->destination_post_type = false !== $post_type ? $post_type : 'external';
465 }
466 }
467
468 /**
469 * Generates a unique ID for the suggested link.
470 *
471 * It takes the href, title, text, and offset properties and concatenates
472 * them to create a unique ID. This ID is hashed to ensure it is unique.
473 *
474 * @since 3.16.0
475 *
476 * @return string The unique ID.
477 */
478 protected function generate_uid(): string {
479 return md5( $this->href . $this->title . $this->text . $this->offset );
480 }
481
482 /**
483 * Serializes the model to a JSON string.
484 *
485 * @since 3.16.0
486 *
487 * @return array<mixed> The serialized model.
488 */
489 public function to_array(): array {
490 return array(
491 'smart_link_id' => $this->smart_link_id,
492 'uid' => $this->uid,
493 'href' => $this->href,
494 'title' => $this->title,
495 'text' => $this->text,
496 'offset' => $this->offset,
497 'applied' => $this->applied,
498 'source' => array(
499 'post_type' => get_post_type( $this->source_post_id ),
500 'post_id' => $this->source_post_id,
501 ),
502 'destination' => array(
503 'post_type' => $this->destination_post_type,
504 'post_id' => $this->destination_post_id,
505 ),
506 );
507 }
508
509 /**
510 * Deserializes a JSON string to a model.
511 *
512 * @since 3.16.0
513 *
514 * @throws InvalidArgumentException If the JSON data is invalid.
515 *
516 * @param string $json The JSON string to deserialize.
517 * @return Base_Model The deserialized model.
518 */
519 public static function deserialize( string $json ): Base_Model {
520 $data = json_decode( $json, true );
521
522 // Validate the JSON data.
523 if ( ! is_array( $data ) ) {
524 throw new InvalidArgumentException( 'Invalid JSON data' );
525 }
526
527 // If the UID has been provided, set it on the model.
528 $smart_link = new Smart_Link( $data['href'], $data['title'], $data['text'], $data['offset'] );
529
530 if ( isset( $data['uid'] ) ) {
531 $smart_link->set_uid( $data['uid'] );
532
533 if ( $smart_link->exists() ) {
534 $smart_link->load();
535 // Update the fields.
536 $smart_link->set_href( $data['href'] );
537 $smart_link->title = $data['title'];
538 $smart_link->text = $data['text'];
539 $smart_link->offset = $data['offset'];
540 }
541 }
542
543 return $smart_link;
544 }
545
546 /**
547 * Gets a smart link by UID.
548 *
549 * @since 3.16.0
550 *
551 * @param string $uid The UID of the smart link.
552 * @param int $post_id The post ID of the smart link.
553 * @return Smart_Link The smart link object.
554 */
555 public static function get_smart_link( string $uid, int $post_id ): Smart_Link {
556 $smart_link = new Smart_Link( '', '', '', 0 );
557 $smart_link->uid = $uid;
558 $smart_link->source_post_id = $post_id;
559 $smart_link->load();
560 return $smart_link;
561 }
562
563 /**
564 * Gets a smart link by post object ID.
565 *
566 * @since 3.16.0
567 *
568 * @param int $smart_link_id The ID of the smart link.
569 * @return Smart_Link The smart link object.
570 */
571 private static function get_smart_link_by_id( int $smart_link_id ): Smart_Link {
572 $smart_link = new Smart_Link( '', '', '', 0 );
573 $smart_link->smart_link_id = $smart_link_id;
574 $smart_link->load();
575 return $smart_link;
576 }
577
578 /**
579 * Gets the outbound smart links in a post.
580 *
581 * Outbound smart links are smart links that link to other posts.
582 *
583 * @since 3.16.0
584 *
585 * @param int $post_id The post ID to get the smart links for.
586 * @return array<Smart_Link> The smart links in the post.
587 */
588 public static function get_outbound_smart_links( int $post_id ): array {
589 $smart_links = new \WP_Query(
590 array(
591 'post_type' => 'parsely_smart_link',
592 'posts_per_page' => -1,
593 'fields' => 'ids', // Only get the post IDs to improve performance.
594 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
595 'tax_query' => array(
596 array(
597 'taxonomy' => 'smart_link_source',
598 'include_children' => false, // Performance optimization.
599 'field' => 'name',
600 'terms' => (string) $post_id,
601 ),
602 ),
603 )
604 );
605
606 $links = array();
607 foreach ( $smart_links->posts as $smart_link_id ) {
608 if ( ! is_int( $smart_link_id ) ) {
609 continue;
610 }
611 $smart_link = self::get_smart_link_by_id( $smart_link_id );
612 $links[] = $smart_link;
613 }
614
615 return $links;
616 }
617
618 /**
619 * Gets the inbound smart links in a post.
620 *
621 * Inbound smart links are links on other posts that link to the post.
622 *
623 * @since 3.16.0
624 *
625 * @param int $post_id The post ID to get the smart links for.
626 * @return array<Inbound_Smart_Link> The smart links in the post.
627 */
628 public static function get_inbound_smart_links( int $post_id ): array {
629 $smart_links = new \WP_Query(
630 array(
631 'post_type' => 'parsely_smart_link',
632 'posts_per_page' => -1,
633 'fields' => 'ids', // Only get the post IDs to improve performance.
634 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
635 'tax_query' => array(
636 array(
637 'taxonomy' => 'smart_link_destination',
638 'include_children' => false, // Performance optimization.
639 'field' => 'name',
640 'terms' => (string) $post_id,
641 ),
642 ),
643 )
644 );
645
646 $links = array();
647 foreach ( $smart_links->posts as $smart_link_id ) {
648 if ( ! is_int( $smart_link_id ) ) {
649 continue;
650 }
651 $smart_link = self::get_smart_link_by_id( $smart_link_id );
652 $smart_link = Inbound_Smart_Link::from_smart_link( $smart_link );
653
654 // Check if this inbound smart link is still linked to a post.
655 // If not, do not add it to the array, and instead remove it.
656 if ( ! $smart_link->is_linked() ) {
657 $smart_link->delete();
658 continue;
659 }
660
661 $links[] = $smart_link;
662 }
663
664 return $links;
665 }
666 }
667