Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.00% covered (success)
98.00%
49 / 50
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Feedback_Author
98.00% covered (success)
98.00%
49 / 50
90.91% covered (success)
90.91%
10 / 11
21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 from_submission
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 get_computed_author_info
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 get_display_name
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 get_identity_background_color
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 get_avatar_url
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 get_name
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 get_email
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_first_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_last_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Feedback class.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8namespace Automattic\Jetpack\Forms\ContactForm;
9
10/**
11 * Class Feedback_Author
12 *
13 * Represents the author of a feedback entry, including their name, email, and URL.
14 */
15class Feedback_Author {
16
17    /**
18     * Background colors for the "initials" identity avatars, drawn from the
19     * Color Studio palette's 50 shades (https://color-studio.blog/).
20     *
21     * Only the 50-level shades are safe here: the white initials clear WCAG AA
22     * against them, and the warmer ones (orange, yellow, green, celadon) sit right
23     * on the 4.5:1 line. Swapping in a lighter tint would quietly drop below AA.
24     *
25     * Keep in sync with IDENTITY_BG_COLORS in
26     * projects/js-packages/components/components/gravatar/index.tsx, which colors
27     * the same authors in the Forms dashboard.
28     *
29     * Hex values without the leading `#`, as Gravatar's `bg_color` param expects.
30     *
31     * @var string[]
32     */
33    const IDENTITY_BG_COLORS = array(
34        '3858e9', // Blue 50.
35        '984a9c', // Purple 50.
36        'c9356e', // Pink 50.
37        'd63638', // Red 50.
38        'b26200', // Orange 50.
39        '9d6e00', // Yellow 50.
40        '008a20', // Green 50.
41        '008763', // Celadon 50.
42    );
43
44    /**
45     * The name of the author.
46     *
47     * @var string
48     */
49    private $name;
50
51    /**
52     * The email of the author.
53     *
54     * @var string
55     */
56    private $email;
57
58    /**
59     * The url of the author.
60     *
61     * @var string
62     */
63    private $url;
64
65    /**
66     * The first name of the author.
67     *
68     * @var string
69     */
70    private $first_name = '';
71
72    /**
73     * The last name of the author.
74     *
75     * @var string
76     */
77    private $last_name = '';
78
79    /**
80     * Constructor for Feedback_Author.
81     *
82     * @param string $name  The name of the author.
83     * @param string $email The email of the author.
84     * @param string $url   The URL of the author.
85     * @param string $first_name The first name of the author.
86     * @param string $last_name  The last name of the author.
87     */
88    public function __construct( $name = '', $email = '', $url = '', $first_name = '', $last_name = '' ) {
89        $this->name       = $name;
90        $this->email      = $email;
91        $this->url        = $url;
92        $this->first_name = $first_name;
93        $this->last_name  = $last_name;
94    }
95
96    /**
97     * Create a Feedback_Author instance from the submission data.
98     *
99     * @param array        $post_data The post data from the form submission.
100     * @param Contact_Form $form      The form object.
101     * @return Feedback_Author The Feedback_Author instance.
102     */
103    public static function from_submission( $post_data, $form ) {
104        $first = isset( $post_data['first-name'] ) ? sanitize_text_field( wp_unslash( $post_data['first-name'] ) ) : '';
105        $last  = isset( $post_data['last-name'] ) ? sanitize_text_field( wp_unslash( $post_data['last-name'] ) ) : '';
106        return new self(
107            self::get_computed_author_info( $post_data, 'name', 'pre_comment_author_name', $form ),
108            self::get_computed_author_info( $post_data, 'email', 'pre_comment_author_email', $form ),
109            self::get_computed_author_info( $post_data, 'url', 'pre_comment_author_url', $form ),
110            $first,
111            $last
112        );
113    }
114
115    /**
116     * Gets the computed author.
117     *
118     * @param array        $post_data The post data from the form submission.
119     * @param string       $type The type of author information to retrieve (e.g., 'name', 'email', 'url').
120     * @param string       $filter Optional filter to apply to the value.
121     * @param Contact_Form $form The form object.
122     *
123     * @return string Filter value for the author information.
124     */
125    private static function get_computed_author_info( $post_data, $type, $filter, $form ) {
126        $field_ids = $form->get_field_ids();
127        if ( isset( $field_ids[ $type ] ) ) {
128            $key   = $field_ids[ $type ];
129            $value = isset( $post_data[ $key ] ) ? sanitize_text_field( wp_unslash( $post_data[ $key ] ) ) : '';
130            if ( is_string( $value ) ) {
131                return Contact_Form_Plugin::strip_tags(
132                    stripslashes(
133                        /**
134                         *
135                         * Listed to help search find the filters.
136                         * apply_filters( ''pre_comment_author_name', $value )
137                         * apply_filters( ''pre_comment_author_email', $value )
138                         * apply_filters( ''pre_comment_author_url', $value )
139                        */
140                        apply_filters( $filter, addslashes( $value ) )
141                    )
142                );
143
144            }
145        }
146        return '';
147    }
148
149    /**
150     * Get the display name of the author.
151     *
152     * If the name is not set, it will return the email.
153     *
154     * @return string The display name of the author.
155     */
156    public function get_display_name(): string {
157        $name = $this->get_name();
158        return empty( $name ) ? $this->email : $name;
159    }
160
161    /**
162     * Pick a stable background color for an email's identity avatar, so the
163     * same address always renders on the same Color Studio color.
164     *
165     * Uses the first 8 hex chars of the normalized email's SHA-256, matching
166     * `getIdentityBackgroundColor()` in the shared Gravatar component
167     * (projects/js-packages/components/components/gravatar/index.tsx).
168     *
169     * @param string $email Email address the avatar is rendered for.
170     * @return string A hex color from IDENTITY_BG_COLORS.
171     */
172    private static function get_identity_background_color( string $email ): string {
173        $hash  = hash( 'sha256', strtolower( trim( $email ) ) );
174        $index = hexdec( substr( $hash, 0, 8 ) ) % count( self::IDENTITY_BG_COLORS );
175
176        return self::IDENTITY_BG_COLORS[ $index ];
177    }
178
179    /**
180     * Get the avatar URL of the author.
181     *
182     * Uses Gravatar's "initials" default so that users without a Gravatar
183     * get a colored circle with their initials — matching the dashboard.
184     *
185     * @see https://docs.gravatar.com/api/avatars/images/#default-image
186     *
187     * @return string The avatar URL of the author.
188     */
189    public function get_avatar_url(): string {
190        if ( empty( $this->email ) ) {
191            return '';
192        }
193
194        $hash = md5( strtolower( trim( $this->email ) ) );
195        $name = $this->get_name();
196
197        if ( empty( $name ) ) {
198            // Use the email prefix as a fallback for initials.
199            $name = strstr( $this->email, '@', true );
200        }
201
202        $bg_color = self::get_identity_background_color( $this->email );
203
204        return "https://gravatar.com/avatar/{$hash}?d=initials&s=96&bg_color={$bg_color}&name=" . rawurlencode( $name );
205    }
206
207    /**
208     * Get the name of the author.
209     *
210     * @return string The name of the author.
211     */
212    public function get_name() {
213        if ( $this->first_name && $this->last_name ) {
214            $raw = trim( $this->first_name . ' ' . $this->last_name );
215            return Contact_Form_Plugin::strip_tags(
216                stripslashes(
217                    /** This filter is already documented in core/wp-includes/comment-functions.php */
218                    apply_filters( 'pre_comment_author_name', addslashes( $raw ) )
219                )
220            );
221        }
222        // This name value is filtered upstream when class is instantiated.
223        return $this->name;
224    }
225
226    /**
227     * Get the email of the author.
228     *
229     * @return string The email of the author.
230     */
231    public function get_email() {
232        return $this->email;
233    }
234
235    /**
236     * Get the URL of the author.
237     *
238     * @return string The URL of the author.
239     */
240    public function get_url() {
241        return $this->url;
242    }
243
244    /**
245     * Get the first name of the author (if provided separately).
246     *
247     * @return string
248     */
249    public function get_first_name() {
250        return $this->first_name;
251    }
252
253    /**
254     * Get the last name of the author (if provided separately).
255     *
256     * @return string
257     */
258    public function get_last_name() {
259        return $this->last_name;
260    }
261}