PluginProbe
Parse.ly / 3.18.1
Parse.ly v3.18.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
wp-parsely / src / Models / class-smart-link.php

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

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