PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 11.5
Jetpack – WP Security, Backup, Speed, & Growth v11.5
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / sal / class.json-api-post-base.php

class.json-api-post-base.php in Jetpack – WP Security, Backup, Speed, & Growth 11.5, at sal/class.json-api-post-base.php

1,056 lines 30.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * This class wraps a WP_Post and proxies any undefined attributes
4 * and methods to the wrapped class. We need to do this because at present
5 * the WP_Post class is marked as final (in 4.5 this will change, though it's
6 * not clear if there will be a mechanism to retrieve from the DB into the over-
7 * ridden class dynamically).
8 *
9 * @package automattic/jetpack
10 */
11
12 require_once __DIR__ . '/class.json-api-metadata.php';
13 require_once __DIR__ . '/class.json-api-date.php';
14 require_once ABSPATH . 'wp-admin/includes/post.php';
15 require_once ABSPATH . 'wp-includes/post.php';
16
17 /**
18 * Base class for SAL_Post.
19 */
20 abstract class SAL_Post {
21
22 /**
23 * A WP_Post instance.
24 *
25 * @var WP_Post
26 */
27 public $post;
28
29 /**
30 * The post request context (for example 'edit' or 'display')
31 *
32 * @var string
33 */
34 public $context;
35
36 /**
37 * A Jetpack_Site instance.
38 *
39 * @var Jetpack_Site
40 */
41 public $site;
42
43 /**
44 * Constructor function
45 *
46 * @param Jetpack_Site $site A Jetpack_Site instance.
47 * @param WP_Post $post A WP_Post instance.
48 * @param string $context The post request context (for example 'edit' or 'display').
49 */
50 public function __construct( $site, $post, $context ) {
51 $this->post = $post;
52 $this->context = $context;
53 $this->site = $site;
54 }
55
56 /**
57 * Setting this WP_Post instance's key value
58 *
59 * @param string $key The post key to set.
60 * @param string $value The value to set the post key to (for example filter, ID, post_status).
61 */
62 public function __set( $key, $value ) {
63 $this->post->{ $key } = $value;
64 }
65
66 /**
67 * Returning a WPCOM_JSON_API_Links instance if the post key is set to 'links', or the post key value.
68 *
69 * @param string $key The post key value.
70 *
71 * @return WPCOM_JSON_API_Links|string
72 */
73 public function __get( $key ) {
74 if ( 'links' === $key ) {
75 require_once __DIR__ . '/class.json-api-links.php';
76 return WPCOM_JSON_API_Links::getInstance();
77 }
78 return $this->post->{ $key };
79 }
80
81 /**
82 * A function to either call a given function, or return an error if it doesn't exist.
83 *
84 * @param string $name A function name to be called.
85 * @param mixed $arguments Arguments to be passed into the given function.
86 *
87 * @return mixed|bool
88 */
89 public function __call( $name, $arguments ) {
90 if ( is_callable( array( $this->post, $name ) ) ) {
91 return call_user_func_array( array( $this->post, $name ), $arguments );
92 } else {
93 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
94 trigger_error(
95 esc_html(
96 sprintf(
97 /* translators: %s is the method name that has been called */
98 __( 'Call to undefined method %s', 'jetpack' ),
99 $name
100 )
101 )
102 );
103 }
104 }
105
106 /**
107 * Checking to see if a given property is set.
108 *
109 * @param string $name Property to check if set.
110 *
111 * @return bool
112 */
113 public function __isset( $name ) {
114 return isset( $this->post->{ $name } );
115 }
116
117 /**
118 * Defining a base get_like_count() function to be extended in the Jetpack_Post class.
119 *
120 * This will define a default value for the like counts on a post, if this hasn't been defined yet.
121 *
122 * @see class.json-api-post-jetpack.php
123 */
124 abstract public function get_like_count();
125
126 /**
127 * Defining a base is_liked() function to be extended in the Jetpack_Post class.
128 *
129 * This will define a default value for whether or not the current user likes this post, if this hasn't been defined yet.
130 *
131 * @see class.json-api-post-jetpack.php
132 */
133 abstract public function is_liked();
134
135 /**
136 * Defining a base is_reblogged() function to be extended in the Jetpack_Post class.
137 *
138 * This will define a default value for whether or not the current user reblogged this post, if this hasn't been defined yet.
139 *
140 * @see class.json-api-post-jetpack.php
141 */
142 abstract public function is_reblogged();
143
144 /**
145 * Defining a base is_following() function to be extended in the Jetpack_Post class.
146 *
147 * This will define a default value for whether or not the current user is following this blog, if this hasn't been defined yet.
148 *
149 * @see class.json-api-post-jetpack.php
150 */
151 abstract public function is_following();
152
153 /**
154 * Defining a base get_global_id() function to be extended in the Jetpack_Post class.
155 *
156 * This will define the unique WordPress.com-wide representation of a post, if this hasn't been defined yet.
157 *
158 * @see class.json-api-post-jetpack.php
159 */
160 abstract public function get_global_id();
161
162 /**
163 * Defining a base get_geo() function to be extended in the Jetpack_Post class.
164 *
165 * This will define a default value for whether or not there is gelocation data for this post, if this hasn't been defined yet.
166 *
167 * @see class.json-api-post-jetpack.php
168 */
169 abstract public function get_geo();
170
171 /**
172 * Returns an int which helps define the menu order for the post.
173 *
174 * @return int
175 */
176 public function get_menu_order() {
177 return (int) $this->post->menu_order;
178 }
179
180 /**
181 * Returns a string which represents the post's GUID.
182 *
183 * @return string
184 */
185 public function get_guid() {
186 return (string) $this->post->guid;
187 }
188
189 /**
190 * Returns a string which represents the post type.
191 *
192 * @return string
193 */
194 public function get_type() {
195 return (string) $this->post->post_type;
196 }
197
198 /**
199 * Returns an object which holds the terms associated with that post object.
200 *
201 * @return object
202 */
203 public function get_terms() {
204 $taxonomies = get_object_taxonomies( $this->post, 'objects' );
205 $terms = array();
206 foreach ( $taxonomies as $taxonomy ) {
207 if ( ! $taxonomy->public && ! current_user_can( $taxonomy->cap->assign_terms ) ) {
208 continue;
209 }
210
211 $terms[ $taxonomy->name ] = array();
212
213 $taxonomy_terms = wp_get_object_terms( $this->post->ID, $taxonomy->name, array( 'fields' => 'all' ) );
214 foreach ( $taxonomy_terms as $term ) {
215 $formatted_term = $this->format_taxonomy( $term, $taxonomy->name, 'display' );
216 $terms[ $taxonomy->name ][ $term->name ] = $formatted_term;
217 }
218
219 $terms[ $taxonomy->name ] = (object) $terms[ $taxonomy->name ];
220 }
221
222 return (object) $terms;
223 }
224
225 /**
226 * Returns an object which holds the posts tag details
227 *
228 * @return object
229 */
230 public function get_tags() {
231 $tags = array();
232 $terms = wp_get_post_tags( $this->post->ID );
233 foreach ( $terms as $term ) {
234 if ( ! empty( $term->name ) ) {
235 $tags[ $term->name ] = $this->format_taxonomy( $term, 'post_tag', 'display' );
236 }
237 }
238 return (object) $tags;
239 }
240
241 /**
242 * Returns an object which holds the posts category details
243 *
244 * @return object
245 */
246 public function get_categories() {
247 $categories = array();
248 $terms = wp_get_object_terms( $this->post->ID, 'category', array( 'fields' => 'all' ) );
249 foreach ( $terms as $term ) {
250 if ( ! empty( $term->name ) ) {
251 $categories[ $term->name ] = $this->format_taxonomy( $term, 'category', 'display' );
252 }
253 }
254 return (object) $categories;
255 }
256
257 /**
258 * Returns an array of objects which hold the posts attachment information and numbers representing how many associated posts are found.
259 *
260 * @return array
261 */
262 public function get_attachments_and_count() {
263 $attachments = array();
264 $_attachments = new WP_Query(
265 array(
266 'post_parent' => $this->post->ID,
267 'post_status' => 'inherit',
268 'post_type' => 'attachment',
269 'posts_per_page' => '20',
270 )
271 );
272 foreach ( $_attachments->posts as $attachment ) {
273 $attachments[ $attachment->ID ] = $this->get_media_item_v1_1( $attachment->ID );
274 }
275 return array( (object) $attachments, (int) $_attachments->found_posts );
276 }
277
278 /**
279 * Returns an array with a posts metadata information.
280 *
281 * @return array
282 */
283 public function get_metadata() {
284 $metadata = array();
285 foreach ( (array) has_meta( $this->post->ID ) as $meta ) {
286 // Don't expose protected fields.
287 $meta_key = $meta['meta_key'];
288
289 $show = ! ( WPCOM_JSON_API_Metadata::is_internal_only( $meta_key ) )
290 &&
291 (
292 WPCOM_JSON_API_Metadata::is_public( $meta_key )
293 ||
294 current_user_can( 'edit_post_meta', $this->post->ID, $meta_key )
295 );
296
297 if ( Jetpack_SEO_Posts::DESCRIPTION_META_KEY === $meta_key && ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
298 $show = false;
299 }
300
301 if ( $show ) {
302 $metadata[] = array(
303 'id' => $meta['meta_id'],
304 'key' => $meta['meta_key'],
305 'value' => $this->safe_maybe_unserialize( $meta['meta_value'] ),
306 );
307 }
308 }
309
310 return $metadata;
311 }
312
313 /**
314 * Returns an object with a posts link meta details.
315 *
316 * @return object
317 */
318 public function get_meta() {
319 $meta = (object) array(
320 'links' => (object) array(
321 'self' => (string) $this->get_post_link(),
322 'help' => (string) $this->get_post_link( 'help' ),
323 'site' => (string) $this->get_site_link(),
324 'replies' => (string) $this->get_post_link( 'replies/' ),
325 'likes' => (string) $this->get_post_link( 'likes/' ),
326 ),
327 );
328
329 $amp_permalink = get_post_meta( $this->post->ID, '_jetpack_amp_permalink', true );
330
331 if ( ! empty( $amp_permalink ) ) {
332 $meta->links->amp = (string) $amp_permalink;
333 }
334
335 // add autosave link if a more recent autosave exists.
336 if ( 'edit' === $this->context ) {
337 $autosave = wp_get_post_autosave( $this->post->ID );
338 if ( $autosave && $autosave->post_modified > $this->post->post_modified ) {
339 $meta->links->autosave = (string) $this->get_post_link() . '/autosave';
340 }
341 }
342
343 return $meta;
344 }
345
346 /**
347 * Returns an array with the current user's publish, deletion and edit capabilities.
348 *
349 * @return array
350 */
351 public function get_current_user_capabilities() {
352 return array(
353 'publish_post' => current_user_can( 'publish_post', $this->post->ID ),
354 'delete_post' => current_user_can( 'delete_post', $this->post->ID ),
355 'edit_post' => current_user_can( 'edit_post', $this->post->ID ),
356 );
357 }
358
359 /**
360 * Returns an array with details of the posts revisions, or false if 'edit' isn't the current post request context.
361 *
362 * @return bool|array
363 */
364 public function get_revisions() {
365 if ( 'edit' !== $this->context ) {
366 return false;
367 }
368
369 $revisions = array();
370 $post_revisions = wp_get_post_revisions( $this->post->ID );
371
372 foreach ( $post_revisions as $_post ) {
373 $revisions[] = $_post->ID;
374 }
375
376 return $revisions;
377 }
378
379 /**
380 * Returns an object with extra post permalink suggestions.
381 *
382 * @return object
383 */
384 public function get_other_urls() {
385 $other_urls = array();
386
387 if ( 'publish' !== $this->post->post_status ) {
388 $other_urls = $this->get_permalink_suggestions( $this->post->post_title );
389 }
390
391 return (object) $other_urls;
392 }
393
394 /**
395 * Calls the WPCOM_JSON_API_Links get_site_link() function to generate a site link endpoint URL.
396 *
397 * @return string Endpoint URL including site information.
398 */
399 protected function get_site_link() {
400 return $this->links->get_site_link( $this->site->get_id() );
401 }
402
403 /**
404 * Calls the WPCOM_JSON_API_Links get_post_link() function to generate a posts endpoint URL.
405 *
406 * @param string $path Optional path to be appended to the URL.
407 * @return string Endpoint URL including post information.
408 */
409 protected function get_post_link( $path = null ) {
410 return $this->links->get_post_link( $this->site->get_id(), $this->post->ID, $path );
411 }
412
413 /**
414 * Returns an array of user and post specific social media post URLs.
415 *
416 * @return array
417 */
418 public function get_publicize_urls() {
419 $publicize_urls = array();
420 $publicize = get_post_meta( $this->post->ID, 'publicize_results', true );
421 if ( $publicize ) {
422 foreach ( $publicize as $service => $data ) {
423 switch ( $service ) {
424 case 'twitter':
425 foreach ( $data as $datum ) {
426 $publicize_urls[] = esc_url_raw( "https://twitter.com/{$datum['user_id']}/status/{$datum['post_id']}" );
427 }
428 break;
429 case 'fb':
430 foreach ( $data as $datum ) {
431 $publicize_urls[] = esc_url_raw( "https://www.facebook.com/permalink.php?story_fbid={$datum['post_id']}&id={$datum['user_id']}" );
432 }
433 break;
434 }
435 }
436 }
437 return (array) $publicize_urls;
438 }
439
440 /**
441 * Returns a string with the page's custom template metadata.
442 *
443 * @return string
444 */
445 public function get_page_template() {
446 return (string) get_post_meta( $this->post->ID, '_wp_page_template', true );
447 }
448
449 /**
450 * Returns a string representing the source URL of a post's featured image (or an empty string otherwise).
451 *
452 * Note - this is overridden in jetpack-shadow
453 *
454 * @return string
455 */
456 public function get_featured_image() {
457 $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $this->post->ID ), 'full' );
458 if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
459 return (string) $image_attributes[0];
460 } else {
461 return '';
462 }
463 }
464
465 /**
466 * Returns an object representing a post's featured image thumbnail image.
467 *
468 * @return object
469 */
470 public function get_post_thumbnail() {
471 $thumb = null;
472
473 $thumb_id = get_post_thumbnail_id( $this->post->ID );
474
475 if ( ! empty( $thumb_id ) ) {
476 $attachment = get_post( $thumb_id );
477 if ( ! empty( $attachment ) ) {
478 $featured_image_object = $this->get_attachment( $attachment );
479 }
480 if ( ! empty( $featured_image_object ) ) {
481 $thumb = (object) $featured_image_object;
482 }
483 }
484
485 return $thumb;
486 }
487
488 /**
489 * Returns the format slug for a post (for example 'link', 'image' - the default being 'standard').
490 *
491 * @return string
492 */
493 public function get_format() {
494 $format = (string) get_post_format( $this->post->ID );
495 if ( ! $format ) {
496 $format = 'standard';
497 }
498
499 return $format;
500 }
501
502 /**
503 * Returns an object with the post's attachment details.
504 *
505 * @param WP_POST $attachment The post's attachment details in the form of a WP_POST object.
506 *
507 * @return object
508 */
509 private function get_attachment( $attachment ) {
510 $metadata = wp_get_attachment_metadata( $attachment->ID );
511
512 $result = array(
513 'ID' => (int) $attachment->ID,
514 'URL' => (string) wp_get_attachment_url( $attachment->ID ),
515 'guid' => (string) $attachment->guid,
516 'mime_type' => (string) $attachment->post_mime_type,
517 'width' => (int) isset( $metadata['width'] ) ? $metadata['width'] : 0,
518 'height' => (int) isset( $metadata['height'] ) ? $metadata['height'] : 0,
519 );
520
521 if ( isset( $metadata['duration'] ) ) {
522 $result['duration'] = (int) $metadata['duration'];
523 }
524
525 /** This filter is documented in class.jetpack-sync.php */
526 return (object) apply_filters( 'get_attachment', $result );
527 }
528
529 /**
530 * Returns an ISO 8601 formatted datetime string representing the date of post creation.
531 *
532 * @return string
533 */
534 public function get_date() {
535 return (string) WPCOM_JSON_API_Date::format_date( $this->post->post_date_gmt, $this->post->post_date );
536 }
537
538 /**
539 * Returns an ISO 8601 formatted datetime string representing the date the post was last modified.
540 *
541 * @return string
542 */
543 public function get_modified_date() {
544 return (string) WPCOM_JSON_API_Date::format_date( $this->post->post_modified_gmt, $this->post->post_modified );
545 }
546
547 /**
548 * Returns the post's title.
549 *
550 * @return string
551 */
552 public function get_title() {
553 if ( 'display' === $this->context ) {
554 return (string) get_the_title( $this->post->ID );
555 } else {
556 return (string) htmlspecialchars_decode( $this->post->post_title, ENT_QUOTES );
557 }
558 }
559
560 /**
561 * Returns the permalink for the post (or the post parent if the post type is a revision).
562 *
563 * @return string
564 */
565 public function get_url() {
566 if ( 'revision' === $this->post->post_type ) {
567 return (string) esc_url_raw( get_permalink( $this->post->post_parent ) );
568 } else {
569 return (string) esc_url_raw( get_permalink( $this->post->ID ) );
570 }
571 }
572
573 /**
574 * Returns the shortlink for the post.
575 *
576 * @return string
577 */
578 public function get_shortlink() {
579 return (string) esc_url_raw( wp_get_shortlink( $this->post->ID ) );
580 }
581
582 /**
583 * Returns the post content, or a string saying 'This post is password protected' if that is the case.
584 *
585 * @return string
586 */
587 public function get_content() {
588 if ( 'display' === $this->context ) {
589 // @todo: move this WPCOM-specific hack
590 add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
591 $content = (string) $this->get_the_post_content_for_display();
592 remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
593 return $content;
594 } else {
595 return (string) $this->post->post_content;
596 }
597 }
598
599 /**
600 * Returns the post excerpt, or a string saying 'This post is password protected' if that is the case.
601 *
602 * @return string
603 */
604 public function get_excerpt() {
605 if ( 'display' === $this->context ) {
606 add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
607 ob_start();
608 the_excerpt();
609 $response = (string) ob_get_clean();
610 remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
611 } else {
612 $response = htmlspecialchars_decode( (string) $this->post->post_excerpt, ENT_QUOTES );
613 }
614 return $response;
615 }
616
617 /**
618 * Returns the current post status (publish, future, draft, pending, private).
619 *
620 * @return string
621 */
622 public function get_status() {
623 return (string) get_post_status( $this->post->ID );
624 }
625
626 /**
627 * Returns true if the post is a sticky post, false otherwise.
628 *
629 * @return bool
630 */
631 public function is_sticky() {
632 return (bool) is_sticky( $this->post->ID );
633 }
634
635 /**
636 * Returns the post's slug.
637 *
638 * @return string
639 */
640 public function get_slug() {
641 return (string) $this->post->post_name;
642 }
643
644 /**
645 * Returns the post's password, if password protected.
646 *
647 * @return string
648 */
649 public function get_password() {
650 $password = (string) $this->post->post_password;
651 if ( 'edit' === $this->context ) {
652 $password = htmlspecialchars_decode( (string) $password, ENT_QUOTES );
653 }
654 return $password;
655 }
656
657 /**
658 * Returns an object representing a post's parent, and false if it doesn't have one.
659 *
660 * @return object|bool
661 */
662 public function get_parent() {
663 if ( $this->post->post_parent ) {
664 $parent = get_post( $this->post->post_parent );
665 if ( 'display' === $this->context ) {
666 $parent_title = (string) get_the_title( $parent->ID );
667 } else {
668 $parent_title = (string) htmlspecialchars_decode( $this->post->post_title, ENT_QUOTES );
669 }
670 return (object) array(
671 'ID' => (int) $parent->ID,
672 'type' => (string) $parent->post_type,
673 'link' => (string) $this->links->get_post_link( $this->site->get_id(), $parent->ID ),
674 'title' => $parent_title,
675 );
676 } else {
677 return false;
678 }
679 }
680
681 /**
682 * Returns a string saying 'This post is password protected' (to be later used within the_password_form filter).
683 *
684 * @return string
685 */
686 public function the_password_form() {
687 return __( 'This post is password protected.', 'jetpack' );
688 }
689
690 /**
691 * Returns an array with information related to the comment and ping status of a post.
692 *
693 * @return array
694 */
695 public function get_discussion() {
696 return array(
697 'comments_open' => (bool) comments_open( $this->post->ID ),
698 'comment_status' => (string) $this->post->comment_status,
699 'pings_open' => (bool) pings_open( $this->post->ID ),
700 'ping_status' => (string) $this->post->ping_status,
701 'comment_count' => (int) $this->post->comment_count,
702 );
703 }
704
705 /**
706 * Returns true if likes are enabled - either for the post, or site-wide.
707 *
708 * @return bool
709 */
710 public function is_likes_enabled() {
711 /** This filter is documented in modules/likes.php */
712 $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
713 $post_likes_switched = get_post_meta( $this->post->ID, 'switch_like_status', true );
714
715 return $post_likes_switched || ( $sitewide_likes_enabled && '0' !== $post_likes_switched );
716 }
717
718 /**
719 * Returns true if sharing is enabled, false otherwise.
720 *
721 * @return bool
722 */
723 public function is_sharing_enabled() {
724 $show = true;
725 /** This filter is documented in modules/sharedaddy/sharing-service.php */
726 $show = apply_filters( 'sharing_show', $show, $this->post );
727
728 $switched_status = get_post_meta( $this->post->ID, 'sharing_disabled', false );
729
730 if ( ! empty( $switched_status ) ) {
731 $show = false;
732 }
733
734 return (bool) $show;
735 }
736
737 /**
738 * Returns the post content in the form of a string, ready for displaying.
739 *
740 * Note: No Blog ID parameter. No Post ID parameter. Depends on globals.
741 * Expects setup_postdata() to already have been run
742 *
743 * @return string
744 */
745 public function get_the_post_content_for_display() {
746 global $pages, $page;
747
748 $old_pages = $pages;
749 $old_page = $page;
750
751 $content = join( "\n\n", $pages );
752 $content = preg_replace( '/<!--more(.*?)?-->/', '', $content );
753 // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -- Assignment to globals is intentional
754 $pages = array( $content );
755 $page = 1;
756
757 ob_start();
758 the_content();
759 $return = ob_get_clean();
760
761 $pages = $old_pages;
762 $page = $old_page;
763 // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
764 return $return;
765 }
766
767 /**
768 * Returns an object containing the post author's information (eg. ID, display name, email if the user has post editing capabilities).
769 *
770 * @return object
771 */
772 public function get_author() {
773 if ( 0 == $this->post->post_author ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- numbers could be numeric strings.
774 return null;
775 }
776
777 $show_email = 'edit' === $this->context && current_user_can( 'edit_post', $this->post->ID );
778
779 $user = get_user_by( 'id', $this->post->post_author );
780
781 if ( ! $user || is_wp_error( $user ) ) {
782 trigger_error( 'Unknown user', E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
783
784 return null;
785 }
786
787 // @todo: factor this out
788 // phpcs:disable WordPress.NamingConventions.ValidVariableName
789 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
790 $active_blog = get_active_blog_for_user( $user->ID );
791 $site_id = $active_blog->blog_id;
792 $profile_URL = "https://en.gravatar.com/{$user->user_login}";
793 } else {
794 $profile_URL = 'https://en.gravatar.com/' . md5( strtolower( trim( $user->user_email ) ) );
795 $site_id = -1;
796 }
797
798 $author = array(
799 'ID' => (int) $user->ID,
800 'login' => (string) $user->user_login,
801 'email' => $show_email ? (string) $user->user_email : false,
802 'name' => (string) $user->display_name,
803 'first_name' => (string) $user->first_name,
804 'last_name' => (string) $user->last_name,
805 'nice_name' => (string) $user->user_nicename,
806 'URL' => (string) esc_url_raw( $user->user_url ),
807 'avatar_URL' => (string) esc_url_raw( $this->get_avatar_url( $user->user_email ) ),
808 'profile_URL' => (string) esc_url_raw( $profile_URL ),
809 );
810 // phpcs:enable WordPress.NamingConventions.ValidVariableName
811
812 if ( $site_id > -1 ) {
813 $author['site_ID'] = (int) $site_id;
814 }
815
816 return (object) $author;
817 }
818
819 /**
820 * Returns the avatar URL for a user, or an empty string if there isn't a valid avatar.
821 *
822 * @param string $email The user's email.
823 * @param int $avatar_size The size of the avatar in pixels.
824 *
825 * @return string
826 */
827 protected function get_avatar_url( $email, $avatar_size = 96 ) {
828 $avatar_url = wpcom_get_avatar_url( $email, $avatar_size, '', true );
829 if ( ! $avatar_url || is_wp_error( $avatar_url ) ) {
830 return '';
831 }
832
833 return esc_url_raw( htmlspecialchars_decode( $avatar_url[0] ) );
834 }
835
836 /**
837 * Return extra post permalink suggestions in an array including the 'permalink_URL' and the 'suggested_slug'.
838 *
839 * @param string $title The current post title.
840 *
841 * @return array
842 */
843 public function get_permalink_suggestions( $title ) {
844 $suggestions = array();
845 list( $suggestions['permalink_URL'], $suggestions['suggested_slug'] ) = get_sample_permalink( $this->post->ID, $title );
846 return $suggestions;
847 }
848
849 /**
850 * Returns an object with formatted taxonomy information such as slug and meta information.
851 *
852 * Otherwise, returns an error if the edit or display permissions aren't correct.
853 *
854 * @param WP_Term $taxonomy The current taxonomy.
855 * @param string $taxonomy_type The current taxonomy type, for example 'category'.
856 * @param string $context The current context, for example 'edit' or 'display'.
857 *
858 * @return object
859 */
860 private function format_taxonomy( $taxonomy, $taxonomy_type, $context ) {
861 // Permissions.
862 switch ( $context ) {
863 case 'edit':
864 $tax = get_taxonomy( $taxonomy_type );
865 if ( ! current_user_can( $tax->cap->edit_terms ) ) {
866 return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
867 }
868 break;
869 case 'display':
870 if ( -1 == get_option( 'blog_public' ) && ! current_user_can( 'read' ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
871 return new WP_Error( 'unauthorized', 'User cannot view taxonomy', 403 );
872 }
873 break;
874 default:
875 return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
876 }
877
878 $response = array();
879 $response['ID'] = (int) $taxonomy->term_id;
880 $response['name'] = (string) $taxonomy->name;
881 $response['slug'] = (string) $taxonomy->slug;
882 $response['description'] = (string) $taxonomy->description;
883 $response['post_count'] = (int) $taxonomy->count;
884
885 if ( is_taxonomy_hierarchical( $taxonomy_type ) ) {
886 $response['parent'] = (int) $taxonomy->parent;
887 }
888
889 $response['meta'] = (object) array(
890 'links' => (object) array(
891 'self' => (string) $this->links->get_taxonomy_link( $this->site->get_id(), $taxonomy->slug, $taxonomy_type ),
892 'help' => (string) $this->links->get_taxonomy_link( $this->site->get_id(), $taxonomy->slug, $taxonomy_type, 'help' ),
893 'site' => (string) $this->links->get_site_link( $this->site->get_id() ),
894 ),
895 );
896
897 return (object) $response;
898 }
899
900 /**
901 * Builds and returns the media item's details.
902 *
903 * @param int $media_id The media item ID.
904 * @todo: factor this out into site.
905 *
906 * @return object
907 */
908 private function get_media_item_v1_1( $media_id ) {
909 $media_item = get_post( $media_id );
910
911 if ( ! $media_item || is_wp_error( $media_item ) ) {
912 return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
913 }
914
915 $file = basename( wp_get_attachment_url( $media_item->ID ) );
916 $file_info = pathinfo( $file );
917 $ext = $file_info['extension'];
918
919 $response = array(
920 'ID' => $media_item->ID,
921 'URL' => wp_get_attachment_url( $media_item->ID ),
922 'guid' => $media_item->guid,
923 'date' => (string) WPCOM_JSON_API_Date::format_date( $media_item->post_date_gmt, $media_item->post_date ),
924 'post_ID' => $media_item->post_parent,
925 'author_ID' => (int) $media_item->post_author,
926 'file' => $file,
927 'mime_type' => $media_item->post_mime_type,
928 'extension' => $ext,
929 'title' => $media_item->post_title,
930 'caption' => $media_item->post_excerpt,
931 'description' => $media_item->post_content,
932 'alt' => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
933 'thumbnails' => array(),
934 );
935
936 if ( in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
937 $metadata = wp_get_attachment_metadata( $media_item->ID );
938 if ( isset( $metadata['height'], $metadata['width'] ) ) {
939 $response['height'] = $metadata['height'];
940 $response['width'] = $metadata['width'];
941 }
942
943 if ( isset( $metadata['sizes'] ) ) {
944 /**
945 * Filter the thumbnail sizes available for each attachment ID.
946 *
947 * @module json-api
948 *
949 * @since 3.9.0
950 *
951 * @param array $metadata['sizes'] Array of thumbnail sizes available for a given attachment ID.
952 * @param int $media_id The media item ID.
953 */
954 $sizes = apply_filters( 'rest_api_thumbnail_sizes', $metadata['sizes'], $media_id );
955 if ( is_array( $sizes ) ) {
956 foreach ( $sizes as $size => $size_details ) {
957 $response['thumbnails'][ $size ] = dirname( $response['URL'] ) . '/' . $size_details['file'];
958 }
959 }
960 }
961
962 if ( isset( $metadata['image_meta'] ) ) {
963 $response['exif'] = $metadata['image_meta'];
964 }
965 }
966
967 if ( in_array( $ext, array( 'mp3', 'm4a', 'wav', 'ogg' ), true ) ) {
968 $metadata = wp_get_attachment_metadata( $media_item->ID );
969 if ( isset( $metadata['length'] ) ) {
970 $response['length'] = $metadata['length'];
971 }
972
973 $response['exif'] = $metadata;
974 }
975
976 if ( in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ), true ) ) {
977 $metadata = wp_get_attachment_metadata( $media_item->ID );
978 if ( isset( $metadata['height'], $metadata['width'] ) ) {
979 $response['height'] = $metadata['height'];
980 $response['width'] = $metadata['width'];
981 }
982
983 if ( isset( $metadata['length'] ) ) {
984 $response['length'] = $metadata['length'];
985 }
986
987 // add VideoPress info.
988 if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
989 $info = video_get_info_by_blogpostid( $this->site->get_id(), $media_id );
990
991 // Thumbnails.
992 if ( function_exists( 'video_format_done' ) && function_exists( 'video_image_url_by_guid' ) ) {
993 $response['thumbnails'] = array(
994 'fmt_hd' => '',
995 'fmt_dvd' => '',
996 'fmt_std' => '',
997 );
998 foreach ( $response['thumbnails'] as $size => $thumbnail_url ) {
999 if ( video_format_done( $info, $size ) ) {
1000 $response['thumbnails'][ $size ] = video_image_url_by_guid( $info->guid, $size );
1001 } else {
1002 unset( $response['thumbnails'][ $size ] );
1003 }
1004 }
1005 }
1006
1007 $response['videopress_guid'] = $info->guid;
1008 $response['videopress_processing_done'] = true;
1009 if ( '0000-00-00 00:00:00' === $info->finish_date_gmt ) {
1010 $response['videopress_processing_done'] = false;
1011 }
1012 }
1013 }
1014
1015 $response['thumbnails'] = (object) $response['thumbnails'];
1016
1017 $response['meta'] = (object) array(
1018 'links' => (object) array(
1019 'self' => (string) $this->links->get_media_link( $this->site->get_id(), $media_id ),
1020 'help' => (string) $this->links->get_media_link( $this->site->get_id(), $media_id, 'help' ),
1021 'site' => (string) $this->links->get_site_link( $this->site->get_id() ),
1022 ),
1023 );
1024
1025 // add VideoPress link to the meta.
1026 if ( in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ), true ) ) {
1027 if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1028 $response['meta']->links->videopress = (string) $this->links->get_link( '/videos/%s', $response['videopress_guid'], '' );
1029 }
1030 }
1031
1032 if ( $media_item->post_parent > 0 ) {
1033 $response['meta']->links->parent = (string) $this->links->get_post_link( $this->site->get_id(), $media_item->post_parent );
1034 }
1035
1036 return (object) $response;
1037 }
1038
1039 /**
1040 * Temporary wrapper around maybe_unserialize() to catch exceptions thrown by unserialize().
1041 *
1042 * Can be removed after https://core.trac.wordpress.org/ticket/45895 lands in Core.
1043 *
1044 * @param string $original Serialized string.
1045 *
1046 * @return string Unserialized string or original string if an exception was raised.
1047 **/
1048 protected function safe_maybe_unserialize( $original ) {
1049 try {
1050 return maybe_unserialize( $original );
1051 } catch ( Exception $e ) {
1052 return $original;
1053 }
1054 }
1055 }
1056