Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Avatars
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 6
812
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 avatar_data
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 wpcom_avatar_url
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
72
 default_url
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 is_signed_in
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 stored_url
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2/**
3 * Avatars for comments already written.
4 *
5 * @package automattic/jetpack-comments
6 */
7
8namespace Automattic\Jetpack\Comments;
9
10use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
11
12/**
13 * Avatars WordPress cannot derive from an email address.
14 */
15class Avatars {
16
17    /**
18     * Register the avatar filters.
19     *
20     * @return void
21     */
22    public static function init() {
23        add_filter( 'pre_get_avatar_data', array( __CLASS__, 'avatar_data' ), 10, 2 );
24        // WordPress.com replaces get_avatar() with its own, which never reaches pre_get_avatar_data.
25        add_filter( 'wpcom_get_avatar_url', array( __CLASS__, 'wpcom_avatar_url' ), 10, 6 );
26    }
27
28    /**
29     * Serve a stored avatar for comments that carry one.
30     *
31     * @param array $args        Avatar arguments.
32     * @param mixed $id_or_email What the avatar was requested for.
33     * @return array
34     */
35    public static function avatar_data( $args, $id_or_email ) {
36        if ( ! $id_or_email instanceof \WP_Comment || isset( $args['url'] ) ) {
37            return $args;
38        }
39
40        $url = self::stored_url( (int) $id_or_email->comment_ID, isset( $args['size'] ) ? (int) $args['size'] : 96 );
41
42        if ( null !== $url ) {
43            $args['url']          = $url;
44            $args['found_avatar'] = true;
45        } elseif ( self::is_signed_in( (int) $id_or_email->comment_ID ) ) {
46            // WordPress.com had no photo: the site default, not a Gravatar the commenter never chose.
47            $args['force_default'] = true;
48        }
49
50        return $args;
51    }
52
53    /**
54     * Serve a stored avatar on WordPress.com.
55     *
56     * @param array|false $url_class     Avatar URL and CSS class, or false.
57     * @param mixed       $id_or_email   What the avatar was requested for.
58     * @param int|string  $size          Avatar size.
59     * @param string      $default_value Default avatar. Unused.
60     * @param bool        $force_display Whether to show avatars when disabled. Unused.
61     * @param bool        $force_default Whether to force the default avatar.
62     * @return array|false
63     */
64    public static function wpcom_avatar_url( $url_class, $id_or_email, $size = 96, $default_value = '', $force_display = false, $force_default = false ) {
65        if ( $force_default || ! is_array( $url_class ) || ! is_object( $id_or_email ) || empty( $id_or_email->comment_ID ) ) {
66            return $url_class;
67        }
68
69        $url = self::stored_url( (int) $id_or_email->comment_ID, (int) $size );
70
71        if ( null !== $url ) {
72            $url_class[0] = $url;
73        } elseif ( self::is_signed_in( (int) $id_or_email->comment_ID ) ) {
74            $url_class[0] = self::default_url( (int) $size );
75            $url_class[1] = ( isset( $url_class[1] ) ? $url_class[1] . ' ' : '' ) . 'avatar-default';
76        }
77
78        return $url_class;
79    }
80
81    /**
82     * The site's default avatar, resolved the way the host resolves it.
83     *
84     * @param int $size Avatar size.
85     * @return string
86     */
87    public static function default_url( $size ) {
88        if ( function_exists( 'wpcom_get_avatar_url' ) ) {
89            // Re-enters wpcom_avatar_url() with no comment, so it returns early there.
90            $url_class = wpcom_get_avatar_url( '', $size, '', false, true );
91
92            // Built for HTML, so its query string is joined with &amp;, which Gravatar reads as a parameter named amp;d.
93            return is_array( $url_class ) ? html_entity_decode( (string) $url_class[0], ENT_QUOTES ) : '';
94        }
95
96        return (string) get_avatar_url(
97            '',
98            array(
99                'size'          => $size,
100                'force_default' => true,
101            )
102        );
103    }
104
105    /**
106     * Whether the comment was left through a popup sign-in.
107     *
108     * @param int $comment_id The comment ID.
109     * @return bool
110     */
111    private static function is_signed_in( $comment_id ) {
112        return '' !== (string) get_comment_meta( $comment_id, Checkpoint::META_ID, true );
113    }
114
115    /**
116     * The stored avatar for a comment, sized through the image CDN.
117     *
118     * @param int $comment_id The comment ID.
119     * @param int $size       Avatar size.
120     * @return string|null Null when the comment carries no servable avatar.
121     */
122    private static function stored_url( $comment_id, $size ) {
123        // WordPress.com asks twice per comment, through get_avatar_data() and again through wpcom_get_avatar_url.
124        static $resolved = array();
125
126        $key = get_current_blog_id() . ":$comment_id:$size";
127
128        if ( array_key_exists( $key, $resolved ) ) {
129            return $resolved[ $key ];
130        }
131
132        // Written only from an authenticated exchange with WordPress.com, so any https URL is served.
133        $stored = get_comment_meta( $comment_id, Checkpoint::META_AVATAR, true );
134
135        if ( ! is_string( $stored ) || $stored === '' || 'https' !== wp_parse_url( $stored, PHP_URL_SCHEME ) ) {
136            // Highlander and Verbum stored a Facebook avatar here, which the email cannot bring back.
137            // Written by the browser, so only that host is served. Legacy X avatars 404 and are skipped.
138            $stored = get_comment_meta( $comment_id, 'hc_avatar', true );
139            $host   = is_string( $stored ) ? wp_parse_url( $stored, PHP_URL_HOST ) : null;
140
141            if ( ! is_string( $host ) || ! preg_match( '/(^|\.)graph\.facebook\.com$/', $host ) ) {
142                $stored = null;
143            }
144        }
145
146        $resolved[ $key ] = null === $stored ? null : Image_CDN_Core::cdn_url( $stored, array( 'resize' => "$size,$size" ) );
147
148        return $resolved[ $key ];
149    }
150}