Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 169
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
jetpack_posts_i_like_widget_init
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Jetpack_Posts_I_Like_Widget
0.00% covered (danger)
0.00%
0 / 167
0.00% covered (danger)
0.00%
0 / 8
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 enqueue_style
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_script
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 form
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
56
 update
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 widget
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 1
306
 get_liked_posts
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 get_local_user_from_wpcom_user
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
1<?php // phpcs:ignore Squiz.Commenting.FileComment.Missing
2
3/**
4 * Posts I Like Widget
5 */
6class Jetpack_Posts_I_Like_Widget extends WP_Widget {
7    /**
8     * Widget settings.
9     *
10     * @var array $defaults
11     */
12    public $defaults = array();
13
14    /**
15     * Registers the widget with WordPress.
16     */
17    public function __construct() {
18        parent::__construct(
19            'jetpack_posts_i_like', // Base ID
20            __( 'Posts I Like', 'wpcomsh' ), // Name
21            array(
22                'description' => __( 'A list of the posts I most recently liked', 'wpcomsh' ),
23            )
24        );
25
26        $this->defaults = array(
27            'title'   => __( 'Posts I Like', 'wpcomsh' ),
28            'liker'   => 0,
29            'number'  => 5,
30            'display' => 'list',
31        );
32
33        if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) ) {
34            add_action( 'wp_print_styles', array( $this, 'enqueue_style' ) );
35            add_action( 'wp_print_scripts', array( $this, 'enqueue_script' ) );
36        }
37    }
38
39    /**
40     * Enqueue style.
41     */
42    public function enqueue_style() {
43        wp_enqueue_style( 'widget-grid-and-list' );
44    }
45
46    /**
47     * Enqueue script.
48     */
49    public function enqueue_script() {
50        wp_enqueue_script( 'widget-bump-view' );
51    }
52
53    /**
54     * Back-end widget form.
55     *
56     * @see WP_Widget::form()
57     *
58     * @param array $instance Previously saved values from database.
59     * @return never
60     */
61    public function form( $instance ) {
62        // outputs the options form on admin
63        $instance = array_merge( $this->defaults, $instance );
64
65        $title  = $instance['title'];
66        $number = (int) $instance['number'];
67
68        if ( $number < 1 ) {
69            $number = 1;
70        } elseif ( $number > 15 ) {
71            $number = 15;
72        }
73
74        $liker = $instance['liker'];
75        // If the liker is a wpcom user, convert it into a local user.
76        if ( empty( $instance['local_liker'] ) ) {
77            $liker = self::get_local_user_from_wpcom_user( $liker );
78        }
79        if ( 0 === $liker ) {
80            $liker = get_current_user_id();
81        }
82
83        $display = ( 'grid' === $instance['display'] ) ? 'grid' : 'list';
84
85        ?>
86        <p>
87            <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wpcomsh' ); ?></label>
88            <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
89        </p>
90
91        <p>
92            <label for="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>"><?php esc_html_e( 'Number of posts to show (1 to 15):', 'wpcomsh' ); ?></label>
93            <input id="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'number' ) ); ?>" type="number" value="<?php echo esc_attr( (string) $number ); ?>" min="1" max="15" />
94        </p>
95
96        <p>
97            <label><?php esc_html_e( 'Display as:', 'wpcomsh' ); ?></label>
98                <ul>
99                    <li><label><input id="<?php echo esc_attr( $this->get_field_id( 'display' ) ); ?>-list" name="<?php echo esc_attr( $this->get_field_name( 'display' ) ); ?>" type="radio" value="list" <?php checked( 'list', $display ); ?> /> <?php esc_html_e( 'List', 'wpcomsh' ); ?></label></li>
100                    <li><label><input id="<?php echo esc_attr( $this->get_field_id( 'display' ) ); ?>-grid" name="<?php echo esc_attr( $this->get_field_name( 'display' ) ); ?>" type="radio" value="grid" <?php checked( 'grid', $display ); ?> /> <?php esc_html_e( 'Grid', 'wpcomsh' ); ?></label></li>
101                </ul>
102        </p>
103        <?php
104
105        $liker_dropdown = wp_dropdown_users(
106            array(
107                'selected'                => $liker,
108                'name'                    => $this->get_field_name( 'liker' ),
109                'id'                      => $this->get_field_id( 'liker' ),
110                'echo'                    => false,
111                'hide_if_only_one_author' => true,
112            )
113        );
114
115        if ( $liker_dropdown ) :
116            ?>
117            <p>
118                <label for="<?php echo esc_attr( $this->get_field_id( 'liker' ) ); ?>"><?php esc_html_e( "Author's likes to display:", 'wpcomsh' ); ?></label>
119                <?php echo $liker_dropdown; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- as this is HTML code from core's wp_dropdown_users() */ ?>
120            </p>
121        <?php else : ?>
122            <input type="hidden"
123                id="<?php echo esc_attr( $this->get_field_id( 'liker' ) ); ?>"
124                name="<?php echo esc_attr( $this->get_field_name( 'liker' ) ); ?>"
125                value="<?php echo (int) $liker; ?>"
126            />
127            <?php
128        endif;
129    }
130
131    /**
132     * Sanitize widget form values as they are saved.
133     *
134     * @see WP_Widget::update()
135     *
136     * @param array $new_instance Values just sent to be saved.
137     * @param array $old_instance Previously saved values from database.
138     *
139     * @return array Updated safe values to be saved.
140     */
141    public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
142        // processes widget options to be saved
143
144        $instance          = array();
145        $instance['title'] = wp_kses( $new_instance['title'], array() );
146        if ( $instance['title'] === $this->defaults['title'] ) {
147            $instance['title'] = false; // Store as false in case of language change
148        }
149
150        $instance['number'] = (int) $new_instance['number'];
151        if ( $instance['number'] < 1 ) {
152            $instance['number'] = 1;
153        } elseif ( $instance['number'] > 15 ) {
154            $instance['number'] = 15;
155        }
156
157        $instance['display'] = isset( $new_instance['display'] ) && 'grid' === $new_instance['display'] ? 'grid' : 'list';
158
159        $instance['liker'] = (int) $new_instance['liker'];
160        if ( ! is_user_member_of_blog( $instance['liker'] ) ) {
161            $instance['liker'] = 0;
162        }
163
164        $instance['local_liker'] = true;
165
166        return $instance;
167    }
168
169    /**
170     * Front-end display of widget.
171     *
172     * @see WP_Widget::widget()
173     *
174     * @param array $args     Widget arguments.
175     * @param array $instance Saved values from database.
176     */
177    public function widget( $args, $instance ) {
178        // Set default values to avoid displaying undeinfed index notices
179        $instance = array_merge( $this->defaults, $instance );
180        $title    = apply_filters( 'widget_title', $instance['title'] );
181
182        /**
183         * Use the Like's API to load all of a user's ($instance['liker']) likes
184         * and put them all in a posts_i_like array so we can easily output it based on grid or list
185         */
186        $liker             = $instance['liker'] ?? null;
187        $number_to_display = $instance['number'];
188
189        $get_image_options = array(
190            'from_html'           => true,
191            'fallback_to_avatars' => true,
192            'gravatar_default'    => 'https://s0.wp.com/i/logo/white-gray-80.png',
193        );
194
195        if ( 'grid' === $instance['display'] ) {
196            // for grid display, we need an even number so it looks ok
197            $number_to_display               += $number_to_display % 2;
198            $get_image_options['avatar_size'] = 200;
199        } else {
200            $get_image_options['avatar_size'] = 40;
201        }
202
203        // If the liker is a wpcom user, convert it into a local user.
204        if ( $liker && empty( $instance['local_liker'] ) ) {
205            $liker = self::get_local_user_from_wpcom_user( $liker );
206        }
207
208        $posts_i_like = array();
209        if ( $liker && is_user_member_of_blog( $liker ) ) {
210            $force_update = is_customize_preview();
211            $posts        = $this->get_liked_posts( $liker, $number_to_display, $force_update );
212
213            foreach ( $posts as $post ) {
214                $posts_i_like[] = (object) $post;
215            }
216
217            do_action( 'jetpack_stats_extra', 'widget_view', 'posts_i_like' );
218        }
219
220        $current_user_controls_widget = ( is_user_logged_in() && get_current_user_id() === $liker ) || current_user_can( 'edit_theme_options' );
221        if ( ! $posts_i_like ) {
222            // Bail if There are no likes and the current user can do nothing about it.
223            if ( ! $current_user_controls_widget ) {
224                return;
225            }
226        }
227
228        echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
229
230        if ( ! empty( $title ) ) {
231            echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
232        }
233
234        if ( $posts_i_like ) {
235            if ( 'grid' === $instance['display'] ) {
236                $output = '';
237
238                echo "<div class='widgets-grid-layout no-grav'>";
239
240                foreach ( $posts_i_like as $post ) {
241                    $hover_text = sprintf(
242                        /* translators: %1$s is the post title, %1$s is the blog name. */
243                        _x( '%1$s on %2$s', '1: Post Title, 2: Blog Name', 'wpcomsh' ),
244                        wp_kses( $post->post_title, array() ),
245                        wp_kses( $post->blog_name, array() )
246                    );
247
248                    $output .= "<div class='widget-grid-view-image'>";
249                    $output .= "<a href='" . esc_url( $post->post_permalink ) . "' title='" . esc_attr( $hover_text ) . "' class='bump-view' data-bump-view='pil'>";
250                    $output .= "<img src='" . esc_url( $post->post_image ) . "'/>";
251                    $output .= '</a>';
252                    $output .= '</div>';
253                }
254
255                echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- as this is intentially complex HTML and the vars have been escaped already.
256
257                echo '</div>';
258
259            } else {
260                echo "<ul class='widgets-list-layout no-grav'>";
261
262                foreach ( $posts_i_like as $post ) {
263                    echo '<li>';
264                    echo "<img src='" . esc_url( $post->post_image ) . "' class='widgets-list-layout-blavatar' />";
265                    echo "<div class='widgets-list-layout-links'><a href='" . esc_url( $post->post_permalink ) . "' class='bump-view' data-bump-view='pil'>" . esc_html( $post->post_title ) . '</a> ';
266                    echo '<span>' . esc_html__( 'on', 'wpcomsh' );
267                    echo "&nbsp;<a href='" . esc_url( $post->blog_url ) . "' class='bump-view' data-bump-view='pil'>" . esc_html( $post->blog_name ) . '</a>';
268                    echo '</span></div>';
269                    echo '</li>';
270                }
271
272                echo '</ul>';
273            }
274        } elseif ( $current_user_controls_widget ) {
275            echo '<p>' . sprintf(
276                wp_kses(
277                    // translators: %s is a URL to the widgets settings page.
278                    __( 'You have not recently liked any posts. Once you do, this <a href="%s">Posts I Like</a> widget will display them.', 'wpcomsh' ),
279                    array(
280                        'a' => array( 'href' => array() ),
281                    )
282                ),
283                esc_url( admin_url( 'widgets.php' ) )
284            ) . '</p>';
285        }
286
287        echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
288    }
289
290    /**
291     * Gets posts that are marked as liked.
292     *
293     * @param int  $user_id User ID.
294     * @param int  $post_count Count of posts to retrieve.
295     * @param bool $force_update Whether or not to use cached results if available.
296     */
297    public function get_liked_posts( $user_id, $post_count = 5, $force_update = false ) {
298        $transient_key = implode( '|', array( 'wpcomsh-post-i-like-widget', $user_id, $post_count ) );
299
300        if ( ! $force_update ) {
301            $posts = get_transient( $transient_key );
302
303            if ( false !== $posts ) {
304                return $posts;
305            }
306        }
307
308        $version = 2;
309        $path    = 'liked-posts';
310
311        $args = array(
312            'url'     => sprintf( '%s/wpcom/v%s/%s?count=%s', JETPACK__WPCOM_JSON_API_BASE, $version, $path, $post_count ),
313            'method'  => 'GET',
314            'user_id' => $user_id,
315            'blog_id' => (int) Jetpack_Options::get_option( 'id' ),
316        );
317
318        $response = Automattic\Jetpack\Connection\Client::remote_request( $args );
319
320        if ( is_wp_error( $response ) || 200 !== $response['response']['code'] || empty( $response['body'] ) ) {
321            return array();
322        }
323
324        $posts = json_decode( $response['body'], true );
325        set_transient( $transient_key, $posts, 20 * MINUTE_IN_SECONDS );
326
327        return $posts;
328    }
329
330    /**
331     * Get local user id from wpcom user id.
332     *
333     * @param int $user_id wpcom user id.
334     * @return int local user id that connected to the passed wpcom user id. Returns 0 if no result is found.
335     */
336    public static function get_local_user_from_wpcom_user( $user_id ) {
337        global $wpdb;
338
339        return (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
340            $wpdb->prepare(
341                "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key=%s AND meta_value=%s",
342                'wpcom_user_id',
343                $user_id
344            )
345        );
346    }
347}
348
349/**
350 * Register the widget for use in Appearance -> Widgets
351 */
352function jetpack_posts_i_like_widget_init() { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed
353    register_widget( 'Jetpack_Posts_I_Like_Widget' );
354}
355add_action( 'widgets_init', 'jetpack_posts_i_like_widget_init' );