PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 15.5.1
Jetpack – WP Security, Backup, Speed, & Growth v15.5.1
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / widgets / top-posts.php

top-posts.php in Jetpack – WP Security, Backup, Speed, & Growth 15.5.1, at modules/widgets/top-posts.php

882 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Top Posts widget.
4 *
5 * Currently, this widget depends on the Stats Module. To not load this file
6 * when the Stats Module is not active would potentially bypass Jetpack's
7 * fatal error detection on module activation, so we always load this file.
8 * Instead, we don't register the widget if the Stats Module isn't active.
9 *
10 * @package automattic/jetpack
11 */
12
13 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
14
15 use Automattic\Jetpack\Redirect;
16 use Automattic\Jetpack\Stats\WPCOM_Stats;
17 use Automattic\Jetpack\Status;
18 use Automattic\Jetpack\Status\Host;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit( 0 );
22 }
23
24 // Register the widget for use in Appearance -> Widgets
25 add_action( 'widgets_init', 'jetpack_top_posts_widget_init' );
26
27 /**
28 * Register the widget, if the Stats module is active.
29 */
30 function jetpack_top_posts_widget_init() {
31 // Currently, this widget depends on the Stats Module.
32 if (
33 ! ( defined( 'IS_WPCOM' ) && IS_WPCOM )
34 && (
35 ! Jetpack::is_module_active( 'stats' )
36 || ( new Status() )->is_offline_mode()
37 )
38 ) {
39 return;
40 }
41
42 register_widget( 'Jetpack_Top_Posts_Widget' );
43 }
44
45 /**
46 * Widget class.
47 */
48 class Jetpack_Top_Posts_Widget extends WP_Widget {
49 /**
50 * Widget unique identifier.
51 *
52 * @var string
53 */
54 public $alt_option_name = 'widget_stats_topposts';
55
56 /**
57 * Widget default title.
58 *
59 * @var string
60 */
61 public $default_title = '';
62
63 /**
64 * Constructor.
65 */
66 public function __construct() {
67 parent::__construct(
68 'top-posts',
69 /** This filter is documented in modules/widgets/facebook-likebox.php */
70 apply_filters( 'jetpack_widget_name', __( 'Top Posts &amp; Pages', 'jetpack' ) ),
71 array(
72 'description' => __( 'Shows your most viewed posts and pages.', 'jetpack' ),
73 'customize_selective_refresh' => true,
74 'show_instance_in_rest' => true,
75 )
76 );
77
78 $this->default_title = __( 'Top Posts &amp; Pages', 'jetpack' );
79
80 /**
81 * Add explanation about how the statistics are calculated.
82 *
83 * @module widgets
84 *
85 * @since 3.9.3
86 */
87 add_action( 'jetpack_widget_top_posts_after_fields', array( $this, 'stats_explanation' ) );
88 add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_widget_in_block_editor' ) );
89 }
90
91 /**
92 * Remove the "Top Posts and Pages" widget from the Legacy Widget block
93 *
94 * @param array $widget_types List of widgets that are currently removed from the Legacy Widget block.
95 * @return array $widget_types New list of widgets that will be removed.
96 */
97 public function hide_widget_in_block_editor( $widget_types ) {
98 // @TODO: Hide for Simple sites when the block API starts working.
99 if ( ! ( new Host() )->is_wpcom_simple() ) {
100 $widget_types[] = 'top-posts';
101 }
102 return $widget_types;
103 }
104
105 /**
106 * Enqueue stylesheet.
107 */
108 public function enqueue_style() {
109 wp_register_style( 'jetpack-top-posts-widget', plugins_url( 'top-posts/style.css', __FILE__ ), array(), '20141013' );
110 wp_enqueue_style( 'jetpack-top-posts-widget' );
111 }
112
113 /**
114 * Displays the form for this widget on the Widgets page of the WP Admin area.
115 *
116 * @param array $instance Instance configuration.
117 *
118 * @return string|void
119 */
120 public function form( $instance ) {
121 $instance = wp_parse_args( (array) $instance, static::defaults() );
122
123 if ( false === $instance['title'] ) {
124 $instance['title'] = $this->default_title;
125 }
126 $title = stripslashes( $instance['title'] );
127
128 $count = isset( $instance['count'] ) ? (int) $instance['count'] : 10;
129 if ( $count < 1 || 10 < $count ) {
130 $count = 10;
131 }
132
133 $allowed_post_types = array_values( get_post_types( array( 'public' => true ) ) );
134 $types = isset( $instance['types'] ) ? (array) $instance['types'] : array( 'post', 'page' );
135
136 // 'likes' are not available in Jetpack
137 $ordering = isset( $instance['ordering'] ) && 'likes' === $instance['ordering'] ? 'likes' : 'views';
138
139 if ( isset( $instance['display'] ) && in_array( $instance['display'], array( 'grid', 'list', 'text' ), true ) ) {
140 $display = $instance['display'];
141 } else {
142 $display = 'text';
143 }
144
145 ?>
146
147 <p>
148 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
149 <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 ); ?>" />
150 </p>
151
152 <p>
153 <label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Maximum number of posts to show (no more than 10):', 'jetpack' ); ?></label>
154 <input id="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>" type="number" value="<?php echo esc_attr( (string) $count ); ?>" min="1" max="10" />
155 </p>
156
157 <?php if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) : ?>
158 <p>
159 <label><?php esc_html_e( 'Order Top Posts &amp; Pages By:', 'jetpack' ); ?></label>
160 <ul>
161 <li><label><input id="<?php echo esc_attr( $this->get_field_id( 'ordering' ) ); ?>-likes" name="<?php echo esc_attr( $this->get_field_name( 'ordering' ) ); ?>" type="radio" value="likes" <?php checked( 'likes', $ordering ); ?> /> <?php esc_html_e( 'Likes', 'jetpack' ); ?></label></li>
162 <li><label><input id="<?php echo esc_attr( $this->get_field_id( 'ordering' ) ); ?>-views" name="<?php echo esc_attr( $this->get_field_name( 'ordering' ) ); ?>" type="radio" value="views" <?php checked( 'views', $ordering ); ?> /> <?php esc_html_e( 'Views', 'jetpack' ); ?></label></li>
163 </ul>
164 </p>
165 <?php endif; ?>
166
167 <p>
168 <label for="<?php echo esc_attr( $this->get_field_id( 'types' ) ); ?>"><?php esc_html_e( 'Types of pages to display:', 'jetpack' ); ?></label>
169 <ul>
170 <?php
171 foreach ( $allowed_post_types as $type ) {
172 // Get the Post Type name to display next to the checkbox.
173 $post_type_object = get_post_type_object( $type );
174 $label = $post_type_object->labels->name;
175
176 $checked = '';
177 if ( in_array( $type, $types, true ) ) {
178 $checked = 'checked="checked" ';
179 }
180 ?>
181
182 <li><label>
183 <input
184 value="<?php echo esc_attr( $type ); ?>"
185 name="<?php echo esc_attr( $this->get_field_name( 'types' ) ); ?>[]"
186 id="<?php echo esc_attr( $this->get_field_id( 'types' ) . '-' . $type ); ?>"
187 type="checkbox"
188 <?php echo $checked; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
189 >
190 <?php echo esc_html( $label ); ?>
191 </label></li>
192
193 <?php } // End foreach ?>
194 </ul>
195 </p>
196
197 <p>
198 <label><?php esc_html_e( 'Display as:', 'jetpack' ); ?></label>
199 <ul>
200 <li><label><input id="<?php echo esc_attr( $this->get_field_id( 'display' ) ); ?>-text" name="<?php echo esc_attr( $this->get_field_name( 'display' ) ); ?>" type="radio" value="text" <?php checked( 'text', $display ); ?> /> <?php esc_html_e( 'Text List', 'jetpack' ); ?></label></li>
201 <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( 'Image List', 'jetpack' ); ?></label></li>
202 <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( 'Image Grid', 'jetpack' ); ?></label></li>
203 </ul>
204 </p>
205 <?php
206
207 /**
208 * Fires after the fields are displayed in the Top Posts Widget settings in wp-admin.
209 *
210 * Allow adding extra content after the fields are displayed.
211 *
212 * @module widgets
213 *
214 * @since 3.9.3
215 *
216 * @param array $args {
217 * @param array $instance The widget instance.
218 * @param object $this The class object.
219 * }
220 */
221 do_action( 'jetpack_widget_top_posts_after_fields', array( $instance, $this ) );
222 }
223
224 /**
225 * Explains how the statics are calculated.
226 */
227 public function stats_explanation() {
228 echo '<p>';
229 esc_html_e( 'Top Posts &amp; Pages by views are calculated from 24-48 hours of stats. They take a while to change.', 'jetpack' );
230 echo '</p>';
231 }
232
233 /**
234 * Deals with the settings when they are saved by the admin.
235 *
236 * @param array $new_instance New configuration values.
237 * @param array $old_instance Old configuration values.
238 *
239 * @return array
240 */
241 public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
242 $instance = array();
243 $instance['title'] = wp_kses( $new_instance['title'], array() );
244 if ( $instance['title'] === $this->default_title ) {
245 $instance['title'] = false; // Store as false in case of language change.
246 }
247
248 $instance['count'] = (int) $new_instance['count'];
249 if ( $instance['count'] < 1 || 10 < $instance['count'] ) {
250 $instance['count'] = 10;
251 }
252
253 // 'likes' are not available in Jetpack
254 $instance['ordering'] = isset( $new_instance['ordering'] ) && 'likes' === $new_instance['ordering']
255 ? 'likes'
256 : 'views';
257
258 $allowed_post_types = array_values( get_post_types( array( 'public' => true ) ) );
259 $instance['types'] = $new_instance['types'];
260 foreach ( $new_instance['types'] as $key => $type ) {
261 if ( ! in_array( $type, $allowed_post_types, true ) ) {
262 unset( $new_instance['types'][ $key ] );
263 }
264 }
265
266 if ( isset( $new_instance['display'] ) && in_array( $new_instance['display'], array( 'grid', 'list', 'text' ), true ) ) {
267 $instance['display'] = $new_instance['display'];
268 } else {
269 $instance['display'] = 'text';
270 }
271
272 /**
273 * Filters Top Posts Widget settings before they're saved.
274 *
275 * @module widgets
276 *
277 * @since 3.9.3
278 *
279 * @param array $instance The santized widget instance. Only contains data processed by the current widget.
280 * @param array $new_instance The new widget instance before sanitization.
281 */
282 $instance = apply_filters( 'jetpack_top_posts_saving', $instance, $new_instance );
283
284 return $instance;
285 }
286
287 /**
288 * Outputs the HTML for this widget.
289 *
290 * @param array $args An array of standard parameters for widgets in this theme.
291 * @param array $instance An array of settings for this widget instance.
292 *
293 * @return void Echoes it's output
294 */
295 public function widget( $args, $instance ) {
296 /** This action is documented in modules/widgets/gravatar-profile.php */
297 do_action( 'jetpack_stats_extra', 'widget_view', 'top_posts' );
298
299 $instance = wp_parse_args( (array) $instance, static::defaults() );
300
301 $title = isset( $instance['title'] ) ? $instance['title'] : false;
302 if ( false === $title ) {
303 $title = $this->default_title;
304 }
305
306 /** This filter is documented in core/src/wp-includes/default-widgets.php */
307 $title = apply_filters( 'widget_title', $title );
308
309 // Enqueue front end assets.
310 $this->enqueue_style();
311
312 $count = isset( $instance['count'] ) ? (int) $instance['count'] : false;
313 if ( $count < 1 || 10 < $count ) {
314 $count = 10;
315 }
316 /**
317 * Control the number of displayed posts.
318 *
319 * @module widgets
320 *
321 * @since 3.3.0
322 *
323 * @param string $count Number of Posts displayed in the Top Posts widget. Default is 10.
324 */
325 $count = apply_filters( 'jetpack_top_posts_widget_count', $count );
326
327 $types = isset( $instance['types'] ) ? (array) $instance['types'] : array( 'post', 'page' );
328
329 // 'likes' are not available in Jetpack
330 $ordering = isset( $instance['ordering'] ) && 'likes' === $instance['ordering']
331 ? 'likes'
332 : 'views';
333
334 if (
335 isset( $instance['display'] )
336 && in_array( $instance['display'], array( 'grid', 'list', 'text' ), true )
337 ) {
338 $display = $instance['display'];
339 } else {
340 $display = 'text';
341 }
342
343 $get_image_options = array();
344 if ( 'text' !== $display ) {
345 $get_image_options = array(
346 'fallback_to_avatars' => true,
347 /** This filter is documented in modules/stats.php */
348 'gravatar_default' => apply_filters( 'jetpack_static_url', set_url_scheme( 'https://en.wordpress.com/i/logo/white-gray-80.png' ) ),
349 'avatar_size' => 40,
350 'width' => null,
351 'height' => null,
352 );
353 if ( 'grid' === $display ) {
354 $get_image_options['avatar_size'] = 200;
355 }
356 /**
357 * Top Posts Widget Image options.
358 *
359 * @module widgets
360 *
361 * @since 1.8.0
362 *
363 * @param array $get_image_options {
364 * Array of Image options.
365 * @type bool true Should we default to Gravatars when no image is found? Default is true.
366 * @type string $gravatar_default Default Image URL if no Gravatar is found.
367 * @type int $avatar_size Default Image size.
368 * @type mixed $width Image width, not set by default and $avatar_size is used instead.
369 * @type mixed $height Image height, not set by default and $avatar_size is used instead.
370 * }
371 */
372 $get_image_options = apply_filters( 'jetpack_top_posts_widget_image_options', $get_image_options );
373 }
374
375 if ( function_exists( 'wpl_get_blogs_most_liked_posts' ) && 'likes' === $ordering ) {
376 $posts = $this->get_by_likes( $count, $types );
377 } else {
378 $posts = $this->get_by_views( $count, $args, $types );
379 }
380
381 echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
382
383 if ( ! empty( $title ) ) {
384 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
385 }
386
387 /*
388 * If we have no posts, add some fallback posts
389 * and display a fallback message for admins.
390 */
391 if ( ! $posts ) {
392 if ( current_user_can( 'edit_theme_options' ) ) {
393 echo self::fallback_message(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
394 }
395
396 $posts = $this->get_fallback_posts( $count, $types );
397 }
398
399 /*
400 * Display our posts.
401 */
402
403 /**
404 * Filter the layout of the Top Posts Widget
405 *
406 * @module widgets
407 *
408 * @since 6.4.0
409 *
410 * @param string $layout layout of the Top Posts Widget (empty string).
411 * @param array $posts IDs of the posts to be displayed.
412 * @param array $display Display option from widget form.
413 */
414 $layout = apply_filters( 'jetpack_top_posts_widget_layout', '', $posts, $display );
415 if ( ! empty( $layout ) ) {
416 echo $layout; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
417 }
418
419 switch ( $display ) {
420 case 'list':
421 case 'grid':
422 // Keep the avatar_size as default dimensions for backward compatibility.
423 $width = (int) $get_image_options['avatar_size'];
424 $height = (int) $get_image_options['avatar_size'];
425
426 // Check if the user has changed the width.
427 if ( ! empty( $get_image_options['width'] ) ) {
428 $width = (int) $get_image_options['width'];
429 }
430
431 // Check if the user has changed the height.
432 if ( ! empty( $get_image_options['height'] ) ) {
433 $height = (int) $get_image_options['height'];
434 }
435
436 foreach ( $posts as &$post ) {
437 $image = Jetpack_PostImages::get_image(
438 $post['post_id'],
439 array(
440 'fallback_to_avatars' => (bool) $get_image_options['fallback_to_avatars'],
441 'width' => $width,
442 'height' => $height,
443 'avatar_size' => (int) $get_image_options['avatar_size'],
444 )
445 );
446
447 if ( $image ) {
448 $post['image'] = Jetpack_PostImages::fit_image_url(
449 $image['src'],
450 $width,
451 $height
452 );
453
454 $post['image_srcset'] = Jetpack_PostImages::generate_cropped_srcset(
455 $image,
456 $width,
457 $height
458 );
459
460 if ( empty( $post['image_srcset'] ) ) {
461 $post['image_srcset'] = "{$post['image']} 1x";
462 }
463 }
464 }
465 unset( $post );
466
467 if ( 'grid' === $display ) {
468 echo "<div class='widgets-grid-layout no-grav'>\n";
469 foreach ( $posts as $post ) {
470 echo '<div class="widget-grid-view-image">';
471
472 /**
473 * Fires before each Top Post result, inside <li>.
474 *
475 * @module widgets
476 *
477 * @since 3.2.0
478 *
479 * @param string $post['post_id'] Post ID.
480 */
481 do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
482
483 /**
484 * Filter the permalink of items in the Top Posts widget.
485 *
486 * @module widgets
487 *
488 * @since 4.4.0
489 *
490 * @param string $post['permalink'] Post permalink.
491 * @param array $post Post array.
492 */
493 $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
494
495 if ( $post['image'] ) {
496 printf(
497 '<a href="%1$s" title="%2$s" class="bump-view" data-bump-view="tp"%3$s><img loading="lazy" width="%4$d" height="%5$d" src="%6$s" srcset="%7$s" alt="%2$s" data-pin-nopin="true"/></a>',
498 esc_url( $filtered_permalink ),
499 esc_attr( wp_kses( $post['title'], array() ) ),
500 ( get_queried_object_id() === $post['post_id'] ? ' aria-current="page"' : '' ),
501 absint( $width ),
502 absint( $height ),
503 esc_url( $post['image'] ),
504 esc_attr( $post['image_srcset'] )
505 );
506 }
507
508 /**
509 * Fires after each Top Post result, inside <li>.
510 *
511 * @module widgets
512 *
513 * @since 3.2.0
514 *
515 * @param string $post['post_id'] Post ID.
516 */
517 do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
518
519 echo '</div>';
520 }
521 echo "</div>\n";
522 } else {
523 echo "<ul class='widgets-list-layout no-grav'>\n";
524 foreach ( $posts as $post ) {
525 echo '<li>';
526
527 /** This action is documented in modules/widgets/top-posts.php */
528 do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
529
530 /** This filter is documented in modules/widgets/top-posts.php */
531 $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
532
533 if ( $post['image'] ) {
534 printf(
535 '<a href="%1$s" title="%2$s" class="bump-view" data-bump-view="tp"%3$s><img loading="lazy" width="%4$d" height="%5$d" src="%6$s" srcset="%7$s" alt="%2$s" data-pin-nopin="true" class="widgets-list-layout-blavatar" /></a>',
536 esc_url( $filtered_permalink ),
537 esc_attr( wp_kses( $post['title'], array() ) ),
538 ( get_queried_object_id() === $post['post_id'] ? ' aria-current="page"' : '' ),
539 absint( $width ),
540 absint( $height ),
541 esc_url( $post['image'] ),
542 esc_attr( $post['image_srcset'] )
543 );
544 }
545
546 printf(
547 '<div class="widgets-list-layout-links">
548 <a href="%1$s" title="%2$s" class="bump-view" data-bump-view="tp"%3$s>%4$s</a>
549 </div>
550 ',
551 esc_url( $filtered_permalink ),
552 esc_attr( wp_kses( $post['title'], array() ) ),
553 ( get_queried_object_id() === $post['post_id'] ? ' aria-current="page"' : '' ),
554 esc_html( wp_kses( $post['title'], array() ) )
555 );
556
557 /** This action is documented in modules/widgets/top-posts.php */
558 do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
559
560 echo '</li>';
561 }
562 echo "</ul>\n";
563 }
564 break;
565 default:
566 echo '<ul>';
567
568 foreach ( $posts as $post ) {
569 echo '<li>';
570
571 /** This action is documented in modules/widgets/top-posts.php */
572 do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
573
574 /** This filter is documented in modules/widgets/top-posts.php */
575 $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
576
577 printf(
578 '<a href="%1$s" class="bump-view" data-bump-view="tp"%2$s>%3$s</a>',
579 esc_url( $filtered_permalink ),
580 ( get_queried_object_id() === $post['post_id'] ? ' aria-current="page"' : '' ),
581 esc_html( wp_kses( $post['title'], array() ) )
582 );
583
584 /** This action is documented in modules/widgets/top-posts.php */
585 do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
586
587 echo '</li>';
588 }
589
590 echo '</ul>';
591 break;
592 }
593
594 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
595 }
596
597 /**
598 * Display a message with recommendations when there are no recorded top posts.
599 *
600 * @return string $fallback_message
601 */
602 private static function fallback_message() {
603 $link = esc_url( Redirect::get_url( 'jetpack-support-getting-more-views-and-traffic' ) );
604 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
605 $link = 'https://en.support.wordpress.com/getting-more-site-traffic/';
606 }
607
608 $fallback_message = '<p>';
609 $fallback_message .= sprintf(
610 wp_kses(
611 /* Translators: Placeholder: link to the Jetpack support article. */
612 __( 'There are no popular posts to display. Instead, your visitors will see a list of your recent posts below. <a href="%s" target="_blank">Want more traffic?</a>', 'jetpack' ),
613 array(
614 'a' => array(
615 'href' => array(),
616 'target' => array(),
617 ),
618 )
619 ),
620 esc_url( $link )
621 );
622 $fallback_message .= '<p>';
623
624 return $fallback_message;
625 }
626
627 /**
628 * Widget default option values.
629 */
630 public static function defaults() {
631 return array(
632 'title' => esc_html__( 'Top Posts &amp; Pages', 'jetpack' ),
633 'count' => absint( 10 ),
634 'types' => array( 'post', 'page' ),
635 'ordering' => 'views',
636 'display' => 'text',
637 );
638 }
639
640 /**
641 * Get most liked posts
642 *
643 * ONLY TO BE USED IN WPCOM
644 *
645 * @since 8.4.0 Added $types param
646 *
647 * @param int $count The maximum number of posts to be returned.
648 * @param array $types The post types that should be returned. Optional. Defaults to 'post' and 'page'.
649 *
650 * @return array array of posts.
651 */
652 public function get_by_likes( $count, $types = array( 'post', 'page' ) ) {
653 $post_likes = wpl_get_blogs_most_liked_posts();
654 if ( ! $post_likes ) {
655 return array();
656 }
657
658 return $this->get_posts( array_keys( $post_likes ), $count, $types );
659 }
660
661 /**
662 * Get the top posts based on views
663 *
664 * @since 8.4.0 Added $types param
665 *
666 * @param int $count The maximum number of posts to be returned.
667 * @param array $args The widget arguments.
668 * @param array $types The post types that should be returned.
669 *
670 * @return array array of posts. Defaults to 'post' and 'page'.
671 */
672 public function get_by_views( $count, $args, $types = array( 'post', 'page' ) ) {
673 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
674 $post_views = wp_cache_get( "get_top_posts_$count", 'stats' );
675 if ( false === $post_views ) {
676 $stats_get_daily_history = stats_get_daily_history(
677 false,
678 get_current_blog_id(),
679 'postviews',
680 'post_id',
681 false,
682 2,
683 '',
684 $count * 2 + 10,
685 true
686 );
687 $post_views = array_shift( $stats_get_daily_history );
688 unset( $post_views[0] );
689 wp_cache_add( "get_top_posts_$count", $post_views, 'stats', 1200 );
690 }
691
692 return $this->get_posts( array_keys( $post_views ), $count, $types );
693 }
694
695 /**
696 * Filter the number of days used to calculate Top Posts for the Top Posts widget.
697 * We do not recommend accessing more than 10 days of results at one.
698 * When more than 10 days of results are accessed at once, results should be cached via the WordPress transients API.
699 * Querying for -1 days will give results for an infinite number of days.
700 *
701 * @module widgets
702 *
703 * @since 3.9.3
704 *
705 * @param int 2 Number of days. Default is 2.
706 * @param array $args The widget arguments.
707 */
708 $days = (int) apply_filters( 'jetpack_top_posts_days', 2, $args );
709
710 /** Handling situations where the number of days makes no sense - allows for unlimited days where $days = -1 */
711 if ( 0 === $days ) {
712 $days = 2;
713 }
714
715 $query_args = array(
716 'max' => 11,
717 'summarize' => 1,
718 'num' => $days,
719 );
720 $wpcom_stats = new WPCOM_Stats();
721 $post_view_posts = $wpcom_stats->convert_stats_array_to_object(
722 $wpcom_stats->get_top_posts( $query_args )
723 );
724
725 if ( ! isset( $post_view_posts->summary ) || empty( $post_view_posts->summary->postviews ) ) {
726 return array();
727 }
728
729 $post_view_ids = array_filter( wp_list_pluck( $post_view_posts->summary->postviews, 'id' ) );
730
731 if ( ! $post_view_ids ) {
732 return array();
733 }
734
735 return $this->get_posts( $post_view_ids, $count, $types );
736 }
737
738 /**
739 * Get some posts if no posts are found in the stats API
740 *
741 * @since 8.4.0 Added $count and $types
742 *
743 * @param int $count The maximum number of posts to be returned.
744 * @param array $types The post types that should be returned.
745 * @return array
746 */
747 public function get_fallback_posts( $count = 10, $types = array( 'post', 'page' ) ) {
748 $post_query = new WP_Query();
749
750 if ( ! is_array( $types ) || empty( $types ) ) {
751 $types = array( 'post', 'page' );
752 }
753
754 $posts = $post_query->query(
755 array(
756 'posts_per_page' => $count,
757 'post_status' => 'publish',
758 'post_type' => $types,
759 'no_found_rows' => true,
760 'fields' => 'ids',
761 )
762 );
763
764 if ( ! $posts ) {
765 return array();
766 }
767
768 return $this->get_posts( $posts, $count, $types );
769 }
770
771 /**
772 * Get posts from an array of IDs
773 *
774 * @since 8.4.0 Added $types parameters
775 *
776 * @param array $post_ids The post IDs.
777 * @param int $count The maximum number of posts to return.
778 * @param array $types The post types that should be returned. Optional. Defaults to 'post', 'page'.
779 * @return array
780 */
781 public function get_posts( $post_ids, $count, $types = array( 'post', 'page' ) ) {
782 $counter = 0;
783
784 if ( ! is_array( $types ) || empty( $types ) ) {
785 $types = array( 'post', 'page' );
786 }
787
788 $posts = array();
789 foreach ( (array) $post_ids as $post_id ) {
790 $post = get_post( $post_id );
791
792 if ( ! $post ) {
793 continue;
794 }
795
796 /**
797 * Attachment pages use the 'inherit' post status by default.
798 * To be able to remove attachment pages from private and password protect posts,
799 * we need to replace their post status by the parent post' status.
800 */
801 if ( 'inherit' === $post->post_status && 'attachment' === $post->post_type ) {
802 $post->post_status = get_post_status( $post_id );
803 }
804
805 // hide private and password protected posts.
806 if ( 'publish' !== $post->post_status || ! empty( $post->post_password ) ) {
807 continue;
808 }
809
810 // Filter by chosen Post Types.
811 if ( ! in_array( $post->post_type, $types, true ) ) {
812 continue;
813 }
814
815 // Both get HTML stripped etc on display.
816 if ( empty( $post->post_title ) ) {
817 $title_source = $post->post_content;
818 $title = wp_html_excerpt( $title_source, 50 );
819 $title .= '&hellip;';
820 } else {
821 $title = $post->post_title;
822 }
823
824 $permalink = get_permalink( $post->ID );
825
826 $post_type = $post->post_type;
827
828 $posts[] = compact( 'title', 'permalink', 'post_id', 'post_type' );
829 ++$counter;
830
831 if ( $counter == $count ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
832 break; // only need to load and show x number of likes.
833 }
834 }
835
836 /**
837 * Filter the Top Posts and Pages.
838 *
839 * @module widgets
840 *
841 * @since 3.0.0
842 *
843 * @param array $posts Array of the most popular posts.
844 * @param array $post_ids Array of Post IDs.
845 * @param string $count Number of Top Posts we want to display.
846 */
847 return apply_filters( 'jetpack_widget_get_top_posts', $posts, $post_ids, $count );
848 }
849 }
850
851 /**
852 * Create a shortcode to display the widget anywhere.
853 *
854 * @since 3.9.2
855 *
856 * @param array $instance Saved values from database.
857 */
858 function jetpack_do_top_posts_widget( $instance ) {
859 // Post Types can't be entered as an array in the shortcode parameters.
860 if ( isset( $instance['types'] ) && is_string( $instance['types'] ) ) {
861 $instance['types'] = explode( ',', $instance['types'] );
862 }
863
864 $instance = shortcode_atts(
865 Jetpack_Top_Posts_Widget::defaults(),
866 $instance,
867 'jetpack_top_posts_widget'
868 );
869
870 // Add a class to allow styling.
871 $args = array(
872 'before_widget' => sprintf( '<div class="%s">', 'jetpack_top_posts_widget' ),
873 );
874
875 ob_start();
876 the_widget( 'Jetpack_Top_Posts_Widget', $instance, $args );
877 $output = ob_get_clean();
878
879 return $output;
880 }
881 add_shortcode( 'jetpack_top_posts_widget', 'jetpack_do_top_posts_widget' );
882