Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.43% covered (warning)
71.43%
30 / 42
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Post_Thumbnail
71.43% covered (warning)
71.43%
30 / 42
0.00% covered (danger)
0.00%
0 / 2
10.89
0.00% covered (danger)
0.00%
0 / 1
 get_post_thumbnail
58.33% covered (warning)
58.33%
14 / 24
0.00% covered (danger)
0.00%
0 / 1
5.16
 get_first_image_id_from_post_content
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
5.03
1<?php
2/**
3 * This file contains the Post_Thumbnail class used for finding a suitable thumbnail for a post.
4 *
5 * @package automattic/jetpack-post-list
6 */
7
8namespace Automattic\Jetpack\Post_List;
9
10/**
11 * The Post_Thumbnail class contains methods to find and return a suitable thumbnail for a post.
12 */
13class Post_Thumbnail {
14    /**
15     * Returns the featured image thumbnail or if no featured image is set, return the first image in the post. If
16     * neither exists returns the image array with null values.
17     *
18     * @param object $post The current post.
19     * @return array|null The thumbnail image id and URLs
20     */
21    public static function get_post_thumbnail( $post ) {
22        $image_id    = null;
23        $image_url   = null;
24        $image_alt   = null;
25        $image_thumb = false;
26
27        $post_id = $post->ID;
28
29        // If a featured image exists for the post, use that thumbnail.
30        if ( has_post_thumbnail( $post_id ) ) {
31            $image_id    = get_post_thumbnail_id( $post_id );
32            $image_url   = get_the_post_thumbnail_url( $post_id );
33            $image_thumb = get_the_post_thumbnail_url( $post_id, array( 50, 50 ) );
34            $image_alt   = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
35        } else {
36            // If a featured image does not exist look for the first "media library" hosted image on the post.
37            $attachment_id = self::get_first_image_id_from_post_content( $post->post_content );
38
39            if ( null !== $attachment_id ) {
40                $image_id    = $attachment_id;
41                $image_url   = wp_get_attachment_image_url( $attachment_id, 'full-size' );
42                $image_thumb = wp_get_attachment_image_url( $attachment_id, array( 50, 50 ) );
43                $image_alt   = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
44            }
45        }
46
47        // If no thumbnail is found return null.
48        if ( false === $image_thumb ) {
49            return null;
50        }
51
52        // Escape values just in case.
53        return array(
54            'id'    => esc_attr( $image_id ),
55            'url'   => esc_url( $image_url ),
56            'thumb' => esc_url( $image_thumb ),
57            'alt'   => esc_attr( $image_alt ),
58        );
59    }
60
61    /**
62     * Looks for the first image in the post content containing a class value of 'wp-image-{$attachment_id}' and
63     * returns the $attachment_id from that class value.
64     *
65     * @param string $post_content The current post's HTML content.
66     * @return int The image attachment id.
67     */
68    public static function get_first_image_id_from_post_content( $post_content ) {
69        // If $post_content does not contain a value of substance, return null right away and avoid trying to parse it.
70        if ( empty( $post_content ) ) {
71            return null;
72        }
73
74        $attachment_id = null;
75        $dom           = new \DOMDocument();
76
77        // libxml_use_internal_errors(true) silences PHP warnings and errors from malformed HTML in loadHTML().
78        // you can consult libxml_get_last_error() or libxml_get_errors() to check for errors if needed.
79        libxml_use_internal_errors( true );
80        $dom->loadHTML( $post_content );
81
82        // Media library images have a class attribute value containing 'wp-image-{$attachment_id}'.
83        // Use DomXPath to parse the post content and get the first img tag containing 'wp-image-' as a class value.
84        $class_name = 'wp-image-';
85        $dom_x_path = new \DomXPath( $dom );
86        $nodes      = $dom_x_path->query( "//img[contains(@class, '$class_name')]/@class" );
87
88        if ( $nodes->length > 0 ) {
89            // Get the class attribute value of the 1st image node (aka index 0).
90            $class_value = $nodes[0]->value;
91
92            // Ignore all class attribute values except 'wp-image{$attachment_id}'.
93            // Regex english translation: Look for a word \b, that does not start or end with a hyphen (?!-), that
94            // starts with 'wp-image-', and ends with a number of any length \d+.
95            $class_name_found = preg_match( '/\b(?!-)wp-image-\d+(?!-)\b/', $class_value, $class_value );
96
97            if ( $class_name_found ) {
98                // Get the $attachment_id from the end of the class name value.
99                $attachment_id = str_replace( $class_name, '', $class_value[0] );
100
101                // If the ID we found is numeric, cast it as an int. Else, make it null.
102                if ( is_numeric( $attachment_id ) ) {
103                    $attachment_id = (int) $attachment_id;
104                } else {
105                    $attachment_id = null;
106                }
107            }
108        }
109
110        return $attachment_id;
111    }
112}