Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 361
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_JSON_API_Post_Endpoint
0.00% covered (danger)
0.00%
0 / 359
0.00% covered (danger)
0.00%
0 / 9
18632
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 the_password_form
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_post_by
0.00% covered (danger)
0.00%
0 / 296
0.00% covered (danger)
0.00%
0 / 1
13110
 get_the_post_content_for_display
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 get_blog_post
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 find_featured_media
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_attachment
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 get_current_user_capabilities
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_post_id_by_name
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3if ( ! defined( 'ABSPATH' ) ) {
4    exit( 0 );
5}
6
7/**
8 * Post Endpoint class.
9 */
10abstract class WPCOM_JSON_API_Post_Endpoint extends WPCOM_JSON_API_Endpoint {
11    /**
12     * Post object format.
13     *
14     * @var array
15     */
16    public $post_object_format = array(
17        // explicitly document and cast all output
18        'ID'               => '(int) The post ID.',
19        'site_ID'          => '(int) The site ID.',
20        'author'           => '(object>author) The author of the post.',
21        'date'             => "(ISO 8601 datetime) The post's creation time.",
22        'modified'         => "(ISO 8601 datetime) The post's most recent update time.",
23        'title'            => '(HTML) <code>context</code> dependent.',
24        'URL'              => '(URL) The full permalink URL to the post.',
25        'short_URL'        => '(URL) The wp.me short URL.',
26        'content'          => '(HTML) <code>context</code> dependent.',
27        'excerpt'          => '(HTML) <code>context</code> dependent.',
28        'slug'             => '(string) The name (slug) for the post, used in URLs.',
29        'guid'             => '(string) The GUID for the post.',
30        'status'           => array(
31            'publish'    => 'The post is published.',
32            'draft'      => 'The post is saved as a draft.',
33            'pending'    => 'The post is pending editorial approval.',
34            'private'    => 'The post is published privately',
35            'future'     => 'The post is scheduled for future publishing.',
36            'trash'      => 'The post is in the trash.',
37            'auto-draft' => 'The post is a placeholder for a new post.',
38        ),
39        'sticky'           => '(bool) Is the post sticky?',
40        'password'         => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
41        'parent'           => "(object>post_reference|false) A reference to the post's parent, if it has one.",
42        'type'             => "(string) The post's post_type. Post types besides post, page and revision need to be whitelisted using the <code>rest_api_allowed_post_types</code> filter.",
43        'comments_open'    => '(bool) Is the post open for comments?',
44        'pings_open'       => '(bool) Is the post open for pingbacks, trackbacks?',
45        'likes_enabled'    => '(bool) Is the post open to likes?',
46        'sharing_enabled'  => '(bool) Should sharing buttons show on this post?',
47        'comment_count'    => '(int) The number of comments for this post.',
48        'like_count'       => '(int) The number of likes for this post.',
49        'i_like'           => '(bool) Does the current user like this post?',
50        'is_reblogged'     => '(bool) Did the current user reblog this post?',
51        'is_following'     => '(bool) Is the current user following this blog?',
52        'global_ID'        => '(string) A unique WordPress.com-wide representation of a post.',
53        'featured_image'   => '(URL) The URL to the featured image for this post if it has one.',
54        'post_thumbnail'   => '(object>attachment) The attachment object for the featured image if it has one.',
55        'format'           => array(), // see constructor
56        'geo'              => '(object>geo|false)',
57        'menu_order'       => '(int) (Pages Only) The order pages should appear in.',
58        'publicize_URLs'   => '(array:URL) Array of Facebook URLs published by this post.',
59        'tags'             => '(object:tag) Hash of tags (keyed by tag name) applied to the post.',
60        'categories'       => '(object:category) Hash of categories (keyed by category name) applied to the post.',
61        'attachments'      => '(object:attachment) Hash of post attachments (keyed by attachment ID).',
62        'metadata'         => '(array) Array of post metadata keys and values. All unprotected meta keys are available by default for read requests. Both unprotected and protected meta keys are available for authenticated requests with access. Protected meta keys can be made available with the <code>rest_api_allowed_public_metadata</code> filter.',
63        'meta'             => '(object) API result meta data',
64        'current_user_can' => '(object) List of permissions. Note, deprecated in favor of `capabilities`',
65        'capabilities'     => '(object) List of post-specific permissions for the user; publish_post, edit_post, delete_post',
66    );
67
68    /**
69     * Constructor function.
70     *
71     * @param string|array|object $args — Args.
72     */
73    public function __construct( $args ) {
74        if ( is_array( $this->post_object_format ) && isset( $this->post_object_format['format'] ) ) {
75            $this->post_object_format['format'] = get_post_format_strings();
76        }
77        if ( ! $this->response_format ) {
78            $this->response_format =& $this->post_object_format;
79        }
80        parent::__construct( $args );
81    }
82
83    /**
84     * Filter to replace the password form with a simple message that the post is protected.
85     *
86     * @return string
87     */
88    public function the_password_form() {
89        return __( 'This post is password protected.', 'jetpack' );
90    }
91
92    /**
93     * Get a post by a specified field and value
94     *
95     * @param string $field - the field.
96     * @param string $field_value - the field value.
97     * @param string $context Post use context (e.g. 'display').
98     * @return array|bool|WP_Error Post
99     **/
100    public function get_post_by( $field, $field_value, $context = 'display' ) {
101        global $blog_id;
102
103        /** This filter is documented in class.json-api-endpoints.php */
104        $is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
105
106        if ( defined( 'GEO_LOCATION__CLASS' ) && class_exists( GEO_LOCATION__CLASS ) ) {
107            $geo = call_user_func( array( GEO_LOCATION__CLASS, 'init' ) );
108        } else {
109            $geo = false;
110        }
111
112        if ( 'display' === $context ) {
113            $args = $this->query_args();
114            if ( isset( $args['content_width'] ) && $args['content_width'] ) {
115                $GLOBALS['content_width'] = (int) $args['content_width'];
116            }
117        }
118
119        switch ( $field ) {
120            case 'name':
121                $post_id = $this->get_post_id_by_name( $field_value );
122                if ( is_wp_error( $post_id ) ) {
123                    return $post_id;
124                }
125                break;
126            default:
127                $post_id = (int) $field_value;
128                break;
129        }
130
131        $post = get_post( $post_id, OBJECT, $context );
132
133        if ( ! $post || is_wp_error( $post ) ) {
134            return new WP_Error( 'unknown_post', 'Unknown post', 404 );
135        }
136
137        if ( ! $this->is_post_type_allowed( $post->post_type ) && ( ! function_exists( 'is_post_freshly_pressed' ) || ! is_post_freshly_pressed( $post->ID ) ) ) {
138            return new WP_Error( 'unknown_post', 'Unknown post', 404 );
139        }
140
141        // Permissions
142        $capabilities = $this->get_current_user_capabilities( $post );
143
144        switch ( $context ) {
145            case 'edit':
146                if ( ! $capabilities['edit_post'] ) {
147                    return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
148                }
149                break;
150            case 'display':
151                break;
152            default:
153                return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
154        }
155
156        $can_view = $this->user_can_view_post( $post->ID );
157        if ( ! $can_view || is_wp_error( $can_view ) ) {
158            return $can_view;
159        }
160
161        $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
162
163        if ( 'display' === $context ) {
164            setup_postdata( $post );
165        }
166
167        $response = array();
168
169        $fields = null;
170        if ( 'display' === $context && ! empty( $this->api->query['fields'] ) ) {
171            $fields = array_fill_keys( array_map( 'trim', explode( ',', $this->api->query['fields'] ) ), true );
172        }
173
174        foreach ( array_keys( $this->post_object_format ) as $key ) {
175            if ( $fields !== null && ! isset( $fields[ $key ] ) ) {
176                continue;
177            }
178            switch ( $key ) {
179                case 'ID':
180                    // explicitly cast all output
181                    $response[ $key ] = (int) $post->ID;
182                    break;
183                case 'site_ID':
184                    $response[ $key ] = (int) $this->api->get_blog_id_for_output();
185                    break;
186                case 'author':
187                    $response[ $key ] = (object) $this->get_author( $post, 'edit' === $context && $capabilities['edit_post'] );
188                    break;
189                case 'date':
190                    $response[ $key ] = (string) $this->format_date( $post->post_date_gmt, $post->post_date );
191                    break;
192                case 'modified':
193                    $response[ $key ] = (string) $this->format_date( $post->post_modified_gmt, $post->post_modified );
194                    break;
195                case 'title':
196                    if ( 'display' === $context ) {
197                        $response[ $key ] = (string) get_the_title( $post->ID );
198                    } else {
199                        $response[ $key ] = htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
200                    }
201                    break;
202                case 'URL':
203                    if ( 'revision' === $post->post_type ) {
204                        $response[ $key ] = (string) esc_url_raw( get_permalink( $post->post_parent ) );
205                    } else {
206                        $response[ $key ] = (string) esc_url_raw( get_permalink( $post->ID ) );
207                    }
208                    break;
209                case 'short_URL':
210                    $response[ $key ] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) );
211                    break;
212                case 'content':
213                    if ( 'display' === $context ) {
214                        add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
215                        $response[ $key ] = (string) $this->get_the_post_content_for_display();
216                        remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
217                    } else {
218                        $response[ $key ] = (string) $post->post_content;
219                    }
220                    break;
221                case 'excerpt':
222                    if ( 'display' === $context ) {
223                        add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
224                        ob_start();
225                        the_excerpt();
226                        $response[ $key ] = (string) ob_get_clean();
227                        remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
228                    } else {
229                        $response[ $key ] = htmlspecialchars_decode( (string) $post->post_excerpt, ENT_QUOTES );
230                    }
231                    break;
232                case 'status':
233                    $response[ $key ] = (string) get_post_status( $post->ID );
234                    break;
235                case 'sticky':
236                    $response[ $key ] = (bool) is_sticky( $post->ID );
237                    break;
238                case 'slug':
239                    $response[ $key ] = (string) $post->post_name;
240                    break;
241                case 'guid':
242                    $response[ $key ] = (string) $post->guid;
243                    break;
244                case 'password':
245                    $response[ $key ] = (string) $post->post_password;
246                    if ( 'edit' === $context ) {
247                        $response[ $key ] = htmlspecialchars_decode( $response[ $key ], ENT_QUOTES );
248                    }
249                    break;
250                /** (object|false) */
251                case 'parent':
252                    if ( $post->post_parent ) {
253                        $parent = get_post( $post->post_parent );
254                        if ( 'display' === $context ) {
255                            $parent_title = (string) get_the_title( $parent->ID );
256                        } else {
257                            $parent_title = htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
258                        }
259                        $response[ $key ] = (object) array(
260                            'ID'    => (int) $parent->ID,
261                            'type'  => (string) $parent->post_type,
262                            'link'  => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $parent->ID ),
263                            'title' => $parent_title,
264                        );
265                    } else {
266                        $response[ $key ] = false;
267                    }
268                    break;
269                case 'type':
270                    $response[ $key ] = (string) $post->post_type;
271                    break;
272                case 'comments_open':
273                    $response[ $key ] = (bool) comments_open( $post->ID );
274                    break;
275                case 'pings_open':
276                    $response[ $key ] = (bool) pings_open( $post->ID );
277                    break;
278                case 'likes_enabled':
279                    /** This filter is documented in modules/likes.php */
280                    $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
281                    $post_likes_switched    = get_post_meta( $post->ID, 'switch_like_status', true );
282                    $post_likes_enabled     = $post_likes_switched || ( $sitewide_likes_enabled && $post_likes_switched !== '0' );
283                    $response[ $key ]       = $post_likes_enabled;
284                    break;
285                case 'sharing_enabled':
286                    $show = true;
287                    /** This filter is documented in modules/sharedaddy/sharing-service.php */
288                    $show = apply_filters( 'sharing_show', $show, $post );
289
290                    $switched_status = get_post_meta( $post->ID, 'sharing_disabled', false );
291
292                    if ( ! empty( $switched_status ) ) {
293                        $show = false;
294                    }
295                    $response[ $key ] = (bool) $show;
296                    break;
297                case 'comment_count':
298                    $response[ $key ] = (int) $post->comment_count;
299                    break;
300                case 'like_count':
301                    $response[ $key ] = (int) $this->api->post_like_count( $blog_id, $post->ID );
302                    break;
303                case 'i_like':
304                    $response[ $key ] = (bool) $this->api->is_liked( $blog_id, $post->ID );
305                    break;
306                case 'is_reblogged':
307                    $response[ $key ] = (bool) $this->api->is_reblogged( $blog_id, $post->ID );
308                    break;
309                case 'is_following':
310                    $response[ $key ] = (bool) $this->api->is_following( $blog_id );
311                    break;
312                case 'global_ID':
313                    $response[ $key ] = (string) $this->api->add_global_ID( $blog_id, $post->ID );
314                    break;
315                case 'featured_image':
316                    if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
317                        $response[ $key ] = get_post_meta( $post->ID, '_jetpack_featured_image', true );
318                    } else {
319                        $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
320                        if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
321                            $response[ $key ] = (string) $image_attributes[0];
322                        } else {
323                            $response[ $key ] = '';
324                        }
325                    }
326                    break;
327                case 'post_thumbnail':
328                    $response[ $key ] = null;
329
330                    $thumb_id = get_post_thumbnail_id( $post->ID );
331                    if ( ! empty( $thumb_id ) ) {
332                        $attachment = get_post( $thumb_id );
333                        if ( ! empty( $attachment ) ) {
334                            $featured_image_object = $this->get_attachment( $attachment );
335                        }
336
337                        if ( ! empty( $featured_image_object ) ) {
338                            $response[ $key ] = (object) $featured_image_object;
339                        }
340                    }
341                    break;
342                case 'format':
343                    $response[ $key ] = (string) get_post_format( $post->ID );
344                    if ( ! $response[ $key ] ) {
345                        $response[ $key ] = 'standard';
346                    }
347                    break;
348                /** (object|false) */
349                case 'geo':
350                    if ( ! $geo ) {
351                        $response[ $key ] = false;
352                    } else {
353                        $geo_data         = $geo->get_geo( 'post', $post->ID );
354                        $response[ $key ] = false;
355                        if ( $geo_data ) {
356                            $geo_data = array_intersect_key(
357                                $geo_data,
358                                array(
359                                    'latitude'  => true,
360                                    'longitude' => true,
361                                    'address'   => true,
362                                    'public'    => true,
363                                )
364                            );
365                            if ( $geo_data ) {
366                                $response[ $key ] = (object) array(
367                                    'latitude'  => isset( $geo_data['latitude'] ) ? (float) $geo_data['latitude'] : 0,
368                                    'longitude' => isset( $geo_data['longitude'] ) ? (float) $geo_data['longitude'] : 0,
369                                    'address'   => isset( $geo_data['address'] ) ? (string) $geo_data['address'] : '',
370                                );
371                            } else {
372                                $response[ $key ] = false;
373                            }
374                            // Private
375                            if ( ! isset( $geo_data['public'] ) || ! $geo_data['public'] ) {
376                                if ( 'edit' !== $context || ! $capabilities['edit_post'] ) {
377                                    // user can't access
378                                    $response[ $key ] = false;
379                                }
380                            }
381                        }
382                    }
383                    break;
384                case 'menu_order':
385                    $response[ $key ] = (int) $post->menu_order;
386                    break;
387                case 'publicize_URLs':
388                    $publicize_urls = array();
389                    $publicize      = get_post_meta( $post->ID, 'publicize_results', true );
390                    if ( $publicize ) {
391                        foreach ( $publicize as $service => $data ) {
392                            switch ( $service ) {
393                                // @todo Explore removing once Twitter has been removed from Publicize.
394                                case 'twitter':
395                                    foreach ( $data as $datum ) {
396                                        $publicize_urls[] = esc_url_raw( "https://twitter.com/{$datum['user_id']}/status/{$datum['post_id']}" );
397                                    }
398                                    break;
399                                case 'fb':
400                                    foreach ( $data as $datum ) {
401                                        $publicize_urls[] = esc_url_raw( "https://www.facebook.com/permalink.php?story_fbid={$datum['post_id']}&id={$datum['user_id']}" );
402                                    }
403                                    break;
404                            }
405                        }
406                    }
407                    $response[ $key ] = $publicize_urls;
408                    break;
409                case 'tags':
410                    $response[ $key ] = array();
411                    $terms            = wp_get_post_tags( $post->ID );
412                    foreach ( $terms as $term ) {
413                        if ( ! empty( $term->name ) ) {
414                            $response[ $key ][ $term->name ] = $this->format_taxonomy( $term, 'post_tag', 'display' );
415                        }
416                    }
417                    $response[ $key ] = (object) $response[ $key ];
418                    break;
419                case 'categories':
420                    $response[ $key ] = array();
421                    $terms            = wp_get_object_terms( $post->ID, 'category', array( 'fields' => 'all' ) );
422                    foreach ( $terms as $term ) {
423                        if ( ! empty( $term->name ) ) {
424                            $response[ $key ][ $term->name ] = $this->format_taxonomy( $term, 'category', 'display' );
425                        }
426                    }
427                    $response[ $key ] = (object) $response[ $key ];
428                    break;
429                case 'attachments':
430                    $response[ $key ] = array();
431                    $_attachments     = get_posts(
432                        array(
433                            'post_parent'    => $post->ID,
434                            'post_status'    => 'inherit',
435                            'post_type'      => 'attachment',
436                            'posts_per_page' => 100,
437                        )
438                    );
439                    foreach ( $_attachments as $attachment ) {
440                        $response[ $key ][ $attachment->ID ] = $this->get_attachment( $attachment );
441                    }
442                    $response[ $key ] = (object) $response[ $key ];
443                    break;
444                /** (array|false) */
445                case 'metadata':
446                    $metadata = array();
447                    foreach ( (array) has_meta( $post_id ) as $meta ) {
448                        // Don't expose protected fields.
449                        $show = false;
450                        if ( WPCOM_JSON_API_Metadata::is_public( $meta['meta_key'] ) ) {
451                            $show = true;
452                        }
453                        if ( current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) {
454                            $show = true;
455                        }
456
457                        if (
458                            in_array( $meta['meta_key'], Jetpack_SEO_Posts::POST_META_KEYS_ARRAY, true ) &&
459                            ! Jetpack_SEO_Utils::is_enabled_jetpack_seo()
460                        ) {
461                            $show = false;
462                        }
463
464                        if ( ! $show ) {
465                            continue;
466                        }
467
468                        $metadata[] = array(
469                            'id'    => $meta['meta_id'],
470                            'key'   => $meta['meta_key'],
471                            'value' => maybe_unserialize( $meta['meta_value'] ),
472                        );
473                    }
474
475                    if ( ! empty( $metadata ) ) {
476                        $response[ $key ] = $metadata;
477                    } else {
478                        $response[ $key ] = false;
479                    }
480                    break;
481                case 'meta':
482                    $response[ $key ] = (object) array(
483                        'links' => (object) array(
484                            'self'    => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
485                            'help'    => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'help' ),
486                            'site'    => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
487                            'replies' => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'replies/' ),
488                            'likes'   => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'likes/' ),
489                        ),
490                    );
491                    break;
492                case 'current_user_can':
493                    $response[ $key ] = $capabilities;
494                    break;
495                case 'capabilities':
496                    $response[ $key ] = $capabilities;
497                    break;
498
499            }
500        }
501
502        unset( $GLOBALS['post'] );
503        return $response;
504    }
505
506    /**
507     *
508     * Get the post content for display.
509     *
510     * No Blog ID parameter.  No Post ID parameter.  Depends on globals.
511     * Expects setup_postdata() to already have been run.
512     *
513     * @return string|false
514     */
515    public function get_the_post_content_for_display() {
516        global $pages, $page;
517
518        $old_pages = $pages;
519        $old_page  = $page;
520
521        $content = implode( "\n\n", $pages );
522        $content = preg_replace( '/<!--more(.*?)?-->/', '', $content );
523        $pages   = array( $content ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
524        $page    = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
525
526        ob_start();
527        the_content();
528        $return = ob_get_clean();
529
530        $pages = $old_pages; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
531        $page  = $old_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
532
533        return $return;
534    }
535
536    /**
537     * Gets the blog post.
538     *
539     * @param int    $blog_id - the blog ID.
540     * @param int    $post_id - the post ID.
541     * @param string $context - the context.
542     * @return array|bool|WP_Error Post
543     */
544    public function get_blog_post( $blog_id, $post_id, $context = 'display' ) {
545        $blog_id = $this->api->get_blog_id( $blog_id );
546        if ( ! $blog_id || is_wp_error( $blog_id ) ) {
547            return $blog_id;
548        }
549        switch_to_blog( $blog_id );
550        $post = $this->get_post_by( 'ID', $post_id, $context );
551        restore_current_blog();
552        return $post;
553    }
554
555    /**
556     * Supporting featured media in post endpoints. Currently on for wpcom blogs
557     * since it's calling WPCOM_JSON_API_Read_Endpoint methods which presently
558     * rely on wpcom specific functionality.
559     *
560     * @param  WP_Post $post - the WP Post object.
561     * @return object list of featured media
562     */
563    public static function find_featured_media( &$post ) {
564
565        if ( class_exists( 'WPCOM_JSON_API_Read_Endpoint' ) ) {
566            return WPCOM_JSON_API_Read_Endpoint::find_featured_worthy_media( (array) $post );
567        } else {
568            return (object) array();
569        }
570    }
571
572    /**
573     * Returns attachment object.
574     *
575     * @param object $attachment attachment row.
576     *
577     * @return object
578     */
579    public function get_attachment( $attachment ) {
580        $metadata = wp_get_attachment_metadata( $attachment->ID );
581
582        $result = array(
583            'ID'        => (int) $attachment->ID,
584            'URL'       => (string) wp_get_attachment_url( $attachment->ID ),
585            'guid'      => (string) $attachment->guid,
586            'mime_type' => (string) $attachment->post_mime_type,
587            'width'     => (int) isset( $metadata['width'] ) ? $metadata['width'] : 0,
588            'height'    => (int) isset( $metadata['height'] ) ? $metadata['height'] : 0,
589        );
590
591        if ( isset( $metadata['duration'] ) ) {
592            $result['duration'] = (int) $metadata['duration'];
593        }
594
595        return (object) apply_filters( 'get_attachment', $result );
596    }
597
598    /**
599     * Get post-specific user capabilities
600     *
601     * @param WP_Post $post - the WP_Post object.
602     *
603     * @return array - array of post-level permissions; 'publish_post', 'delete_post', 'edit_post'
604     */
605    public function get_current_user_capabilities( $post ) {
606        return array(
607            'publish_post' => current_user_can( 'publish_post', $post->ID ),
608            'delete_post'  => current_user_can( 'delete_post', $post->ID ),
609            'edit_post'    => current_user_can( 'edit_post', $post->ID ),
610        );
611    }
612
613    /**
614     * Get post ID by name
615     *
616     * Attempts to match name on post title and page path
617     *
618     * @param string $name - the name of the post.
619     *
620     * @return int|object Post ID on success, WP_Error object on failure
621     **/
622    protected function get_post_id_by_name( $name ) {
623        $name = sanitize_title( $name );
624
625        if ( ! $name ) {
626            return new WP_Error( 'invalid_post', 'Invalid post', 400 );
627        }
628
629        $posts = get_posts(
630            array(
631                'name'        => $name,
632                'numberposts' => 1,
633                'post_type'   => $this->_get_whitelisted_post_types(),
634            )
635        );
636
637        if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
638            $page = get_page_by_path( $name );
639
640            if ( ! $page ) {
641                return new WP_Error( 'unknown_post', 'Unknown post', 404 );
642            }
643
644            $post_id = $page->ID;
645        } else {
646            $post_id = (int) $posts[0]->ID;
647        }
648
649        return $post_id;
650    }
651}