PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 12.1
Jetpack – WP Security, Backup, Speed, & Growth v12.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 12.1, at modules/widgets/top-posts.php

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