PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 7.3.7
WP Popular Posts v7.3.7
4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 5.0.0 5.0.1 5.0.2 5.1.0 5.2.0 5.2.1 5.2.2 5.2.3 5.2.4 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.2.0 6.2.1 6.3.0 6.3.1 6.3.2 6.3.3 6.3.4 6.4.0 6.4.1 6.4.2 7.0.0 7.0.1 7.1.0 7.2.0 7.3.0 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.3.7 7.3.8 7.4.0 trunk 2.3.7 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.2 4.0.3 4.0.5 4.0.6
wordpress-popular-posts / src / Output.php
wordpress-popular-posts / src Last commit date
Activation 9 months ago Admin 5 months ago Block 9 months ago Compatibility 9 months ago Container 9 months ago Front 1 year ago Rest 8 months ago Shortcode 1 year ago Traits 4 years ago Widget 9 months ago Bootstrap.php 5 years ago Cache.php 9 months ago Helper.php 9 months ago I18N.php 5 years ago Image.php 8 months ago Output.php 5 months ago Query.php 9 months ago Settings.php 5 months ago Themer.php 9 months ago Translate.php 3 years ago Upgrader.php 8 months ago WordPressPopularPosts.php 9 months ago deprecated.php 4 years ago template-tags.php 9 months ago
Output.php
1148 lines
1 <?php
2 /**
3 * This class formats the HTML output of every popular posts listing.
4 *
5 *
6 * @package WordPressPopularPosts
7 * @author Hector Cabrera <me@cabrerahector.com>
8 */
9
10 namespace WordPressPopularPosts;
11
12 class Output {
13
14 /**
15 * Popular posts data.
16 *
17 * @since 4.0.0
18 * @var string
19 */
20 private $data;
21
22 /**
23 * HTML output.
24 *
25 * @since 4.0.0
26 * @var string
27 */
28 private $output;
29
30 /**
31 * Widget / shortcode settings.
32 *
33 * @since 4.0.0
34 * @var array
35 */
36 private $public_options = [];
37
38 /**
39 * Administrative settings.
40 *
41 * @since 2.3.3
42 * @var array
43 */
44 private $admin_options = [];
45
46 /**
47 * Default excerpt 'more' string.
48 *
49 * @since 4.2.1
50 * @var string
51 */
52 private $more;
53
54 /**
55 * Image object
56 *
57 * @since 4.0.2
58 * @var WordPressPopularPosts\Image
59 */
60 private $thumbnail;
61
62 /**
63 * Translate object.
64 *
65 * @var \WordPressPopularPosts\Translate $translate
66 * @access private
67 */
68 private $translate;
69
70 /**
71 * Themer object.
72 *
73 * @var \WordPressPopularPosts\Themer $themer
74 * @access private
75 */
76 private $themer;
77
78 /**
79 * WordPress Date format.
80 *
81 * @var string
82 * @access private
83 */
84 private $wp_date_format;
85
86 /**
87 * Constructor.
88 *
89 * @since 4.0.0
90 * @param array $public_options
91 * @param array $admin_options
92 * @param WordPressPopularPosts\Image $thumbnail
93 * @param WordPressPopularPosts\Translate $translate
94 * @param WordPressPopularPosts\Themer $themer
95 */
96 public function __construct(array $public_options, array $admin_options, Image $thumbnail, Translate $translate, Themer $themer)
97 {
98 $this->public_options = $public_options;
99 $this->admin_options = $admin_options;
100 $this->thumbnail = $thumbnail;
101 $this->translate = $translate;
102 $this->themer = $themer;
103
104 $this->more = '...';
105
106 $this->wp_date_format = get_option('date_format');
107
108 if ( ! $this->wp_date_format ) {
109 $this->wp_date_format = 'F j, Y';
110 }
111 }
112
113 /**
114 * Sets data.
115 *
116 * @since 5.0.0
117 * @param array
118 */
119 public function set_data(array $data = [])
120 {
121 $this->data = $data;
122 }
123
124 /**
125 * Sets public options.
126 *
127 * @since 5.0.0
128 * @param array
129 */
130 public function set_public_options(array $public_options = [])
131 {
132 $this->public_options = Helper::merge_array_r(
133 Settings::get('widget_options'),
134 $public_options
135 );
136 }
137
138 /**
139 * Output the HTML.
140 *
141 * @since 4.0.0
142 */
143 public function output()
144 {
145 echo $this->get_output(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- At this point everything has been escaped / sanitized already
146 }
147
148 /**
149 * Return the HTML.
150 *
151 * @since 4.0.0
152 * @return string
153 */
154 public function get_output()
155 {
156 $this->output = $this->get_clean_output();
157 $this->output = ( WP_DEBUG ? "\n" . '<!-- WP Popular Posts v' . WPP_VERSION . ( $this->admin_options['tools']['cache']['active'] ? ' - cached' : '' ) . ' -->' . "\n" : '' ) . $this->output;
158 return $this->output;
159 }
160
161 /**
162 * Returns a "clean" version of the HTML output.
163 *
164 * @since 7.3.7
165 * @return string
166 */
167 private function get_clean_output()
168 {
169 if ( $this->output ) {
170 // Attempt to close open tags
171 $this->output = force_balance_tags($this->output);
172
173 /**
174 * @ToDo
175 *
176 * Look into \Dom\HTMLDocument (PHP 8.4 apparently) to see
177 * if it's a good alternative to the code below.
178 */
179
180 if ( extension_loaded('mbstring') && function_exists('mb_encode_numericentity') ) {
181 $clean_html = '';
182 $html = trim($this->output);
183
184 // Process special characters
185 $html = htmlspecialchars_decode(mb_encode_numericentity(htmlentities($html, ENT_QUOTES, 'UTF-8'), [0x80, 0x10FFFF, 0, ~0], 'UTF-8'));
186
187 // Remove empty tags
188 $html = '<!DOCTYPE html><html><head><meta charset="UTF-8" /></head><body>' . $html . '</body></html>';
189
190 $dom = new \DOMDocument();
191 $dom->loadHTML($html, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
192 $xpath = new \DOMXPath($dom);
193
194 // Void/empty tags that should never be removed
195 $voids = ['br', 'div', 'hr', 'img', 'source', 'span'];
196 $voidPred = implode(' or ', array_map(function($n) { return "name() = '" . $n . "'"; }, $voids));
197
198 // Build XPath predicate
199 $query = "//*[not(normalize-space()) and not(*) and not({$voidPred})]";
200
201 // Iterate until no more removable nodes found (because removing can create new empty tags)
202 do {
203 $removed = false;
204 $nodes = $xpath->query($query);
205
206 if ( $nodes === null || $nodes->length === 0 ) {
207 break;
208 }
209
210 // Collect nodes first to avoid live-node issues
211 $toRemove = [];
212
213 foreach( $nodes as $node ) {
214 $toRemove[] = $node;
215 }
216
217 foreach( $toRemove as $node ) {
218 if ($node->parentNode) {
219 $node->parentNode->removeChild($node);
220 $removed = true;
221 }
222 }
223 } while( $removed );
224
225 $body = $dom->getElementsByTagName('body')->item(0);
226
227 if ( isset($body->childNodes) ) {
228 foreach( $body->childNodes as $node ) {
229 $clean_html .= $dom->saveHTML($node);
230 }
231 }
232
233 $this->output = trim($clean_html);
234 } else {
235 if ( defined('WP_DEBUG') && WP_DEBUG ) {
236 trigger_error('WP Popular Posts - looks like PHP\'s mbstring extension isn\'t enabled on this site. Please enable it for the plugin to be able to properly format your popular post list.', E_USER_WARNING);
237 }
238 }
239
240 // Remove excess line jumps
241 $this->output = preg_replace('/\R+/', "\n", $this->output);
242
243 // Sanitize HTML
244 $this->output = Helper::sanitize_html($this->output, $this->public_options);
245 }
246
247 return $this->output;
248 }
249
250 /**
251 * Build the HTML output.
252 *
253 * @since 4.0.0
254 */
255 public function build_output()
256 {
257 // Got some posts, format 'em!
258 if ( ! empty($this->data) ) {
259
260 $this->output = '';
261
262 // Allow WP themers / coders access to raw data
263 // so they can build their own output
264 if ( has_filter('wpp_custom_html') ) {
265 $this->output .= apply_filters('wpp_custom_html', $this->data, $this->public_options);
266 return;
267 }
268
269 if (
270 isset($this->public_options['theme']['name'])
271 && $this->public_options['theme']['name']
272 ) {
273 $this->output .= '<div class="popular-posts-sr">';
274
275 if ( @file_exists(get_stylesheet_directory() . '/wordpress-popular-posts/themes/' . $this->public_options['theme']['name'] . '/style.css') ) {
276 $theme_stylesheet = get_stylesheet_directory() . '/wordpress-popular-posts/themes/' . $this->public_options['theme']['name'] . '/style.css';
277 } else {
278 $theme_stylesheet = $this->themer->get_theme($this->public_options['theme']['name'])['path'] . '/style.css';
279 }
280
281 $theme_css_rules = wp_strip_all_tags(file_get_contents($theme_stylesheet), true); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- We're loading a local file
282 $additional_styles = '';
283
284 if ( has_filter('wpp_additional_theme_styles') ) {
285 $additional_styles = wp_strip_all_tags(apply_filters('wpp_additional_theme_styles', '', $this->public_options['theme']['name']), true);
286
287 if ( $additional_styles ) {
288 $additional_styles = ' /* additional rules */ ' . $additional_styles;
289 }
290 }
291
292 $this->output .= '<style>' . $theme_css_rules . $additional_styles . '</style>';
293 }
294
295 // Opening HTML wrapper
296 $this->output .= $this->build_opening_wrapper();
297
298 // Format each post
299 $position = 0;
300
301 foreach( $this->data as $post_object ) {
302 $position++;
303 $this->output .= $this->render_post($post_object, $position);
304 }
305
306 // Closing HTML wrapper
307 $this->output .= $this->build_closing_wrapper();
308
309 if (
310 isset($this->public_options['theme']['name'])
311 && $this->public_options['theme']['name']
312 ) {
313 $this->output .= '</div>';
314 }
315
316 }
317 // Got nothing to show, give 'em the old "Sorry. No data so far." message!
318 else {
319 $this->output = apply_filters('wpp_no_data', '<p class="wpp-no-data">' . __('Sorry. No data so far.', 'wordpress-popular-posts') . '</p>');
320 }
321 }
322
323 /**
324 * Builds the opening HTML wrapper for the list.
325 *
326 * @since 7.3.3
327 * @return string
328 */
329 public function build_opening_wrapper()
330 {
331 // Custom opening HTML wrapper
332 if (
333 isset($this->public_options['markup']['custom_html'])
334 && $this->public_options['markup']['custom_html']
335 && isset($this->public_options['markup']['wpp-start'])
336 && isset($this->public_options['markup']['wpp-end'])
337 ){
338 return "\n" . htmlspecialchars_decode($this->public_options['markup']['wpp-start'], ENT_QUOTES) . "\n";
339 }
340
341 // Default opening HTML wrapper
342 $classes = 'wpp-list';
343
344 if ( $this->public_options['thumbnail']['active'] ) {
345 $classes .= ' wpp-list-with-thumbnails';
346 }
347
348 return "\n<ul class=\"{$classes}\">\n";
349 }
350
351 /**
352 * Builds the closing HTML wrapper for the list.
353 *
354 * @since 7.3.3
355 * @return string
356 */
357 public function build_closing_wrapper()
358 {
359 // Custom closing HTML wrapper
360 if (
361 isset($this->public_options['markup']['custom_html'])
362 && $this->public_options['markup']['custom_html']
363 && isset($this->public_options['markup']['wpp-start'])
364 && isset($this->public_options['markup']['wpp-end'])
365 ){
366 return "\n" . htmlspecialchars_decode($this->public_options['markup']['wpp-end'], ENT_QUOTES) . "\n";
367 }
368
369 // Default closing HTML wrapper
370 return '</ul>' . "\n";
371 }
372
373 /**
374 * Build the HTML markup for a single post.
375 *
376 * @since 4.0.0
377 * @access private
378 * @param object $post_object
379 * @param integer $position
380 * @return string
381 */
382 private function render_post(\stdClass $post_object, int $position = 1)
383 {
384 $is_single = $this->is_single();
385 $post = '';
386 $post_id = $post_object->id;
387 $trid = $this->translate->get_object_id(
388 $post_object->id,
389 get_post_type($post_object->id)
390 );
391
392 if ( $trid && $post_id != $trid ) {
393 $post_id = $trid;
394 }
395
396 $is_current_post = ( $is_single && ($is_single == $post_id || $is_single == $post_object->id) ) ? true : false;
397
398 // Permalink
399 $permalink = esc_url($this->get_permalink($post_object, $post_id));
400
401 // Post title (and title attribute)
402 $post_title_attr = esc_attr(wp_strip_all_tags($this->get_title($post_object, $post_id)));
403 $post_title = $this->get_title($post_object, $post_id);
404
405 if ( $this->public_options['shorten_title']['active'] ) {
406 $length = ( filter_var($this->public_options['shorten_title']['length'], FILTER_VALIDATE_INT) && $this->public_options['shorten_title']['length'] > 0 )
407 ? $this->public_options['shorten_title']['length']
408 : 25;
409
410 $more = $this->public_options['shorten_title']['words'] ? ' ' . $this->more : $this->more;
411 $more = apply_filters('wpp_title_more', $more);
412 $post_title = Helper::truncate($post_title, $length, $this->public_options['shorten_title']['words'], $more);
413 }
414
415 // Thumbnail
416 $post_thumbnail = $this->get_thumbnail($post_id);
417
418 // Post excerpt
419 $post_excerpt = $this->get_excerpt($post_object, $post_id);
420
421 // Post rating
422 $post_rating = $this->get_rating($post_object);
423
424 /**
425 * Post meta
426 */
427
428 // Post date
429 $post_date = $this->get_date($post_object);
430
431 // Post taxonomies
432 $post_taxonomies = $this->get_taxonomies($post_id);
433
434 // Post author
435 $post_author = $this->get_author($post_object, $post_id);
436
437 // Post views count
438 $post_views = $this->get_pageviews($post_object);
439
440 // Post comments count
441 $post_comments = $this->get_comments($post_object);
442
443 // Post meta
444 $meta_arr = $this->get_metadata(
445 $post_object,
446 $post_id,
447 $post_date,
448 $post_taxonomies,
449 $post_author,
450 $post_views,
451 $post_comments
452 );
453
454 if (
455 is_array($meta_arr)
456 && ! empty($meta_arr)
457 && 'views' == $this->public_options['order_by']
458 ) {
459 $keys = ['views', 'comments', 'author', 'date', 'taxonomy'];
460 $new_meta_arr = [];
461
462 foreach($keys as $key) {
463 if ( isset($meta_arr[$key])) {
464 $new_meta_arr[$key] = $meta_arr[$key];
465 }
466 }
467
468 if ( ! empty($new_meta_arr) ) {
469 $meta_arr = $new_meta_arr;
470 }
471 }
472
473 $post_meta_separator = esc_html(apply_filters('wpp_post_meta_separator', ' | '));
474 $post_meta = join($post_meta_separator, $meta_arr);
475
476 $prettify_numbers = apply_filters('wpp_prettify_numbers', true);
477
478 /**
479 * @ToDo
480 *
481 * Remove this filter, it's a typo and the correct one has been around since 6.3.4
482 *
483 * @since 7.3.7
484 */
485 if ( has_filter('wpp_pretiffy_numbers') ) {
486 $prettify_numbers = apply_filters('wpp_pretiffy_numbers', true);
487
488 if ( defined('WP_DEBUG') && WP_DEBUG ) {
489 trigger_error('WP Popular Posts - wpp_pretiffy_numbers has been deprecated. Please use wpp_prettify_numbers instead.', E_USER_WARNING);
490 }
491 }
492
493 // Build custom HTML output
494 if ( $this->public_options['markup']['custom_html'] ) {
495 $data = [
496 'id' => $post_id,
497 'is_current_post' => $is_current_post,
498 'title' => '<a href="' . $permalink . '" ' . ($post_title_attr !== $post_title ? 'title="' . $post_title_attr . '" ' : '' ) . 'class="wpp-post-title" target="' . esc_attr($this->admin_options['tools']['link']['target']) . '">' . $post_title . '</a>',
499 'title_attr' => $post_title_attr,
500 'summary' => $post_excerpt,
501 'stats' => $post_meta,
502 'img' => ( ! empty($post_thumbnail) ) ? '<a href="' . $permalink . '" ' . ($post_title_attr !== $post_title ? 'title="' . $post_title_attr . '" ' : '' ) . 'target="' . esc_attr($this->admin_options['tools']['link']['target']) . '">' . $post_thumbnail . '</a>' : '',
503 'img_no_link' => $post_thumbnail,
504 'url' => $permalink,
505 'text_title' => $post_title,
506 'taxonomy' => $post_taxonomies,
507 'taxonomy_copy' => isset($meta_arr['taxonomy']) ? $meta_arr['taxonomy'] : null,
508 'author' => ( ! empty($post_author) ) ? '<a href="' . esc_url(get_author_posts_url($post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid )) . '">' . esc_html($post_author) . '</a>' : '',
509 'author_copy' => isset($meta_arr['author']) ? $meta_arr['author'] : null,
510 'author_name' => esc_html($post_author),
511 'author_url' => ( ! empty($post_author) ) ? esc_url(get_author_posts_url($post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid)) : '',
512 'views' => ( $this->public_options['order_by'] == 'views' || $this->public_options['order_by'] == 'comments' ) ? ($prettify_numbers ? Helper::prettify_number($post_views) : number_format_i18n($post_views)) : ($prettify_numbers ? Helper::prettify_number($post_views, 2) : number_format_i18n($post_views, 2)),
513 'views_copy' => isset($meta_arr['views']) ? $meta_arr['views'] : null,
514 'comments' => $prettify_numbers ? Helper::prettify_number($post_comments) : number_format_i18n($post_comments),
515 'comments_copy' => isset($meta_arr['comments']) ? $meta_arr['comments'] : null,
516 'date' => $post_date,
517 'date_copy' => isset($meta_arr['date']) ? $meta_arr['date'] : null,
518 'total_items' => count($this->data),
519 'item_position' => $position
520 ];
521 $post = $this->format_content(htmlspecialchars_decode($this->public_options['markup']['post-html'], ENT_QUOTES), $data, $this->public_options['rating']) . "\n";
522 } // Use the "stock" HTML output
523 else {
524 $wpp_post_class = [];
525
526 if ( $is_current_post ) {
527 $wpp_post_class[] = 'current';
528 }
529
530 // Allow themers / plugin developer
531 // to add custom classes to each post
532 $wpp_post_class = apply_filters('wpp_post_class', $wpp_post_class, $post_id);
533
534 $post_thumbnail = ( ! empty($post_thumbnail) )
535 ? "<a href=\"{$permalink}\" " . ($post_title_attr !== $post_title ? "title=\"{$post_title_attr}\" " : '') . 'target="' . esc_attr($this->admin_options['tools']['link']['target']) . "\">{$post_thumbnail}</a>\n"
536 : '';
537
538 $post_excerpt = ( ! empty($post_excerpt) )
539 ? " <span class=\"wpp-excerpt\">{$post_excerpt}</span>\n"
540 : '';
541
542 $post_meta = ( ! empty($post_meta) )
543 ? " <span class=\"wpp-meta post-stats\">{$post_meta}</span>\n"
544 : '';
545
546 $post_rating = ( ! empty($post_rating) )
547 ? " <span class=\"wpp-rating\">{$post_rating}</span>\n"
548 : '';
549
550 $post =
551 '<li' . ( ( is_array($wpp_post_class) && ! empty($wpp_post_class) ) ? ' class="' . esc_attr(implode(' ', $wpp_post_class)) . '"' : '') . ">\n"
552 . $post_thumbnail
553 . "<a href=\"{$permalink}\" " . ($post_title_attr !== $post_title ? "title=\"{$post_title_attr}\" " : '') . 'class="wpp-post-title" target="' . esc_attr($this->admin_options['tools']['link']['target']) . "\">{$post_title}</a>\n"
554 . $post_excerpt
555 . $post_meta
556 . $post_rating
557 . "</li>\n";
558 }
559
560 return apply_filters('wpp_post', $post, $post_object, $this->public_options);
561 }
562
563 /**
564 * Return the processed post/page title.
565 *
566 * @since 3.0.0
567 * @access private
568 * @param object $post_object
569 * @param integer $post_id
570 * @return string
571 */
572 private function get_title(\stdClass $post_object, int $post_id)
573 {
574 $title = '';
575
576 if ( $post_object->id != $post_id ) {
577 $title = get_the_title($post_id);
578 } else {
579 $title = $post_object->title;
580 }
581
582 // Run the_title filter so core/plugin title hooks can
583 // be applied to the post title
584 $title = apply_filters('the_title', $title, $post_object->id);
585
586 return apply_filters('wpp_the_title', $title, $post_object->id, $post_id);
587 }
588
589 /**
590 * Return the permalink.
591 *
592 * @since 4.0.12
593 * @access private
594 * @param object $post_object
595 * @param integer $post_id
596 * @return string
597 */
598 private function get_permalink(\stdClass $post_object, int $post_id) {
599 if ( $post_object->id != $post_id ) {
600 return get_permalink($post_id);
601 }
602
603 return get_permalink($post_object->id);
604 }
605
606 /**
607 * Return the processed thumbnail.
608 *
609 * @since 3.0.0
610 * @access private
611 * @param int $post_id
612 * @return string
613 */
614 private function get_thumbnail(int $post_id)
615 {
616 $thumbnail = '';
617
618 if ( $this->public_options['thumbnail']['active'] ) {
619 $thumbnail = $this->thumbnail->get(
620 $post_id,
621 [
622 $this->public_options['thumbnail']['width'],
623 $this->public_options['thumbnail']['height']
624 ],
625 $this->admin_options['tools']['thumbnail']['source'],
626 $this->public_options['thumbnail']['crop'],
627 $this->public_options['thumbnail']['build']
628 );
629 }
630
631 return $thumbnail;
632 }
633
634 /**
635 * Return post excerpt.
636 *
637 * @since 3.0.0
638 * @access private
639 * @param object $post_object
640 * @param integer $post_id
641 * @return string
642 */
643 private function get_excerpt(\stdClass $post_object, int $post_id)
644 {
645 $excerpt = '';
646
647 if ( $this->public_options['post-excerpt']['active'] ) {
648
649 if ( $post_object->id != $post_id ) {
650 $the_post = get_post($post_id);
651
652 $excerpt = ( empty($the_post->post_excerpt) )
653 ? $the_post->post_content
654 : $the_post->post_excerpt;
655 }
656 else {
657 $excerpt = ( empty($post_object->post_excerpt) )
658 ? $post_object->post_content
659 : $post_object->post_excerpt;
660 }
661
662 // remove caption tags
663 $excerpt = preg_replace('/\[caption.*\[\/caption\]/', '', $excerpt);
664
665 // remove Flash objects
666 $excerpt = preg_replace("/<object[0-9 a-z_?*=\":\-\/\.#\,\\n\\r\\t]+/smi", '', $excerpt);
667
668 // remove iframes
669 $excerpt = preg_replace('/<iframe.*?\/iframe>/i', '', $excerpt);
670
671 // remove WP shortcodes
672 $excerpt = strip_shortcodes($excerpt);
673
674 // remove style/script tags
675 $excerpt = preg_replace('@<(script|style)[^>]*?>.*?</\\1>@si', '', $excerpt);
676
677 // remove blocks that are not appropriate for the excerpt
678 $excerpt = excerpt_remove_blocks($excerpt);
679
680 // remove HTML tags if requested
681 if ( $this->public_options['post-excerpt']['keep_format'] ) {
682 $excerpt = wp_kses(
683 $excerpt,
684 [
685 'a' => [
686 'href' => [],
687 'title' => []
688 ],
689 'em' => [],
690 'strong' => []
691 ]
692 );
693 } else {
694 $excerpt = wp_kses($excerpt, []);
695
696 // remove URLs, too
697 $excerpt = preg_replace('_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS', '', $excerpt);
698 }
699
700 // remove HTML comments
701 $excerpt = preg_replace('/<!--.*?-->/ms', '', $excerpt);
702
703 // remove extra whitespaces
704 $excerpt = preg_replace('/\s\s+/', ' ', $excerpt);
705
706 $excerpt = trim($excerpt);
707
708 }
709
710 // Balance tags, if needed
711 if ( '' !== $excerpt ) {
712
713 $more = $this->public_options['post-excerpt']['words'] ? ' ' . $this->more : $this->more;
714 $more = apply_filters('wpp_excerpt_more', $more);
715 $excerpt = Helper::truncate($excerpt, $this->public_options['post-excerpt']['length'], $this->public_options['post-excerpt']['words'], $more);
716
717 if ( $this->public_options['post-excerpt']['keep_format'] ) {
718 $excerpt = force_balance_tags($excerpt);
719 }
720 }
721
722 return $excerpt;
723 }
724
725 /**
726 * Return post rating.
727 *
728 * @since 3.0.0
729 * @access private
730 * @param object $post_object
731 * @return string
732 */
733 private function get_rating(\stdClass $post_object)
734 {
735 $rating = '';
736
737 if ( function_exists('the_ratings_results') && $this->public_options['rating'] ) {
738 $rating = the_ratings_results($post_object->id);
739 }
740
741 return $rating;
742 }
743
744 /**
745 * Get post date.
746 *
747 * @since 3.0.0
748 * @access private
749 * @param object $post_object
750 * @return string
751 */
752 private function get_date(\stdClass $post_object)
753 {
754 $date = '';
755
756 if ( $this->public_options['stats_tag']['date']['active'] ) {
757 if ( 'relative' == $this->public_options['stats_tag']['date']['format'] ) {
758 $date = sprintf(
759 __('%s ago', 'wordpress-popular-posts'),
760 human_time_diff(
761 strtotime($post_object->date),
762 Helper::timestamp()
763 )
764 );
765 } else {
766 $date = date_i18n(
767 ( 'wp_date_format' == $this->public_options['stats_tag']['date']['format'] ? $this->wp_date_format : $this->public_options['stats_tag']['date']['format'] ),
768 strtotime($post_object->date)
769 );
770 }
771 }
772
773 return apply_filters('wpp_the_date', $date, $post_object->id);
774 }
775
776 /**
777 * Get post taxonomies.
778 *
779 * @since 3.0.0
780 * @access private
781 * @param integer $post_id
782 * @return string
783 */
784 private function get_taxonomies(int $post_id)
785 {
786 $post_tax = '';
787
788 if (
789 (isset($this->public_options['stats_tag']['category']) && $this->public_options['stats_tag']['category'])
790 || $this->public_options['stats_tag']['taxonomy']['active']
791 ) {
792
793 $taxonomy = 'category';
794
795 if (
796 $this->public_options['stats_tag']['taxonomy']['active']
797 && ! empty($this->public_options['stats_tag']['taxonomy']['name'])
798 ) {
799 $taxonomy = $this->public_options['stats_tag']['taxonomy']['name'];
800 }
801
802 $terms = wp_get_post_terms($post_id, $taxonomy);
803
804 if ( ! is_wp_error($terms) ) {
805 // Usage: https://wordpress.stackexchange.com/a/46824
806 if ( has_filter('wpp_post_exclude_terms') ) {
807 $args = apply_filters('wpp_post_exclude_terms', []);
808 $terms = wp_list_filter($terms, $args, 'NOT');
809 }
810
811 $terms = apply_filters('wpp_post_terms', $terms);
812
813 if (
814 is_array($terms)
815 && ! empty($terms)
816 ) {
817 $taxonomy_separator = esc_html(apply_filters('wpp_taxonomy_separator', ', '));
818
819 // We're going to use the taxonomy slug as a CSS class so let's escape it just in case
820 $taxonomy = esc_attr($taxonomy);
821
822 foreach ($terms as $term) {
823 $term_link = get_term_link($term);
824
825 if ( is_wp_error($term_link) ) {
826 continue;
827 }
828
829 $term_link = esc_url($this->translate->url($term_link, $this->translate->get_current_language()));
830 $post_tax .= "<a href=\"{$term_link}\" class=\"wpp-taxonomy {$taxonomy} {$taxonomy}-{$term->term_id}\">" . esc_html($term->name) . '</a>' . $taxonomy_separator;
831 }
832 }
833 }
834
835 if ( '' != $post_tax ) {
836 $post_tax = rtrim($post_tax, $taxonomy_separator);
837 }
838
839 }
840
841 return $post_tax;
842 }
843
844 /**
845 * Get post author.
846 *
847 * @since 3.0.0
848 * @access private
849 * @param object $post_object
850 * @param integer $post_id
851 * @return string
852 */
853 private function get_author(\stdClass $post_object, int $post_id)
854 {
855 $author = ( $this->public_options['stats_tag']['author'] )
856 ? get_the_author_meta('display_name', $post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid)
857 : '';
858
859 return $author;
860 }
861
862 /**
863 * Return post views count.
864 *
865 * @since 3.0.0
866 * @access private
867 * @param object $post_object
868 * @return int|float
869 */
870 private function get_pageviews(\stdClass $post_object)
871 {
872 $pageviews = 0;
873
874 if (
875 (
876 $this->public_options['order_by'] == 'views'
877 || $this->public_options['order_by'] == 'avg'
878 || $this->public_options['stats_tag']['views']
879 )
880 && ( isset($post_object->pageviews) || isset($post_object->avg_views) )
881 ) {
882 $pageviews = ( $this->public_options['order_by'] == 'views' || $this->public_options['order_by'] == 'comments' )
883 ? $post_object->pageviews
884 : $post_object->avg_views;
885 }
886
887 return $pageviews;
888 }
889
890 /**
891 * Return post comment count.
892 *
893 * @since 3.0.0
894 * @access private
895 * @param object $post_object
896 * @return int
897 */
898 private function get_comments(\stdClass $post_object)
899 {
900 $comments = ( ( $this->public_options['order_by'] == 'comments' || $this->public_options['stats_tag']['comment_count'] ) && isset($post_object->comment_count) )
901 ? $post_object->comment_count
902 : 0;
903
904 return $comments;
905 }
906
907 /**
908 * Return post metadata.
909 *
910 * @since 3.0.0
911 * @access private
912 * @param object $post_object
913 * @param integer $post_id
914 * @return array
915 */
916 //private function get_metadata(\stdClass $post_object, $post_id)
917 private function get_metadata(\stdClass $post_object, int $post_id, string $date, string $post_tax, string $author, $pageviews, int $comments) /** @TODO: starting PHP 8.0 $pageviews can be declared as mixed $pageviews */
918 {
919 $stats = [];
920
921 $prettify_numbers = apply_filters('wpp_prettify_numbers', true);
922
923 /**
924 * @ToDo
925 *
926 * Remove this filter, it's a typo and the correct one has been around since 6.3.4
927 *
928 * @since 7.3.7
929 */
930 if ( has_filter('wpp_pretiffy_numbers') ) {
931 $prettify_numbers = apply_filters('wpp_pretiffy_numbers', true);
932
933 if ( defined('WP_DEBUG') && WP_DEBUG ) {
934 trigger_error('WP Popular Posts - wpp_pretiffy_numbers has been deprecated. Please use wpp_prettify_numbers instead..', E_USER_WARNING);
935 }
936 }
937
938 // comments
939 if ( $this->public_options['stats_tag']['comment_count'] ) {
940 $comments_text = sprintf(
941 _n('%s comment', '%s comments', $comments, 'wordpress-popular-posts'),
942 $prettify_numbers ? Helper::prettify_number($comments) : number_format_i18n($comments)
943 );
944
945 $stats['comments'] = '<span class="wpp-comments">' . $comments_text . '</span>';
946 }
947
948 // views
949 if ( $this->public_options['stats_tag']['views'] ) {
950 if ( $this->public_options['order_by'] == 'avg' ) {
951 $views_text = sprintf(
952 _n('%s view per day', '%s views per day', $pageviews, 'wordpress-popular-posts'),
953 $prettify_numbers ? Helper::prettify_number($pageviews, 2) : number_format_i18n($pageviews, (fmod($pageviews, 1) !== 0.0 ? 2 : 0))
954 );
955 }
956 else {
957 $views_text = sprintf(
958 _n('%s view', '%s views', $pageviews, 'wordpress-popular-posts'),
959 $prettify_numbers ? Helper::prettify_number($pageviews) : number_format_i18n($pageviews)
960 );
961 }
962
963 $stats['views'] = '<span class="wpp-views">' . $views_text . '</span>';
964 }
965
966 // author
967 if ( $this->public_options['stats_tag']['author'] ) {
968 $author_url = get_author_posts_url($post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid);
969 $display_name = '<a href="' . esc_url($this->translate->url($author_url, $this->translate->get_current_language())) . '">' . esc_html($author) . '</a>';
970 $stats['author'] = '<span class="wpp-author">' . sprintf(__('by %s', 'wordpress-popular-posts'), $display_name) . '</span>';
971 }
972
973 // date
974 if ( $this->public_options['stats_tag']['date']['active'] ) {
975 $stats['date'] = '<span class="wpp-date">' . ( 'relative' == $this->public_options['stats_tag']['date']['format'] ? sprintf(__('posted %s', 'wordpress-popular-posts'), $date) : sprintf(__('posted on %s', 'wordpress-popular-posts'), $date) ) . '</span>';
976 }
977
978 // taxonomy
979 if ( ($this->public_options['stats_tag']['category'] || $this->public_options['stats_tag']['taxonomy']['active']) && $post_tax != '' ) {
980 $stats['taxonomy'] = '<span class="wpp-category">' . sprintf(__('under %s', 'wordpress-popular-posts'), $post_tax) . '</span>';
981 }
982
983 return $stats;
984 }
985
986 /**
987 * Parse content tags.
988 *
989 * @since 1.4.6
990 * @access private
991 * @param string HTML string with content tags
992 * @param array Post data
993 * @param bool Used to display post rating (if functionality is available)
994 * @return string
995 */
996 private function format_content(string $string, array $data, bool $rating) {
997
998 if ( empty($string) || ( empty($data) || ! is_array($data) ) ) {
999 return false;
1000 }
1001
1002 $params = [];
1003 $pattern = '/\{(pid|current_class|excerpt|summary|meta|stats|title|title_attr|image|thumb|thumb_img|thumb_url|rating|score|url|text_title|author|author_copy|author_name|author_url|taxonomy|taxonomy_copy|category|category_copy|views|views_copy|comments|comments_copy|date|date_copy|total_items|item_position)\}/i';
1004 preg_match_all($pattern, $string, $matches);
1005
1006 array_map('strtolower', $matches[0]);
1007
1008 if ( in_array('{pid}', $matches[0]) ) {
1009 $string = str_replace('{pid}', $data['id'], $string);
1010 }
1011
1012 if ( in_array('{current_class}', $matches[0]) ) {
1013 $string = str_replace('{current_class}', ( $data['is_current_post'] ? 'current' : '' ), $string);
1014 }
1015
1016 if ( in_array('{title}', $matches[0]) ) {
1017 $string = str_replace('{title}', $data['title'], $string);
1018 }
1019
1020 if ( in_array('{title_attr}', $matches[0]) ) {
1021 $string = str_replace('{title_attr}', $data['title_attr'], $string);
1022 }
1023
1024 if ( in_array('{meta}', $matches[0]) || in_array('{stats}', $matches[0]) ) {
1025 $string = str_replace(['{meta}', '{stats}'], $data['stats'], $string);
1026 }
1027
1028 if ( in_array('{excerpt}', $matches[0]) || in_array('{summary}', $matches[0]) ) {
1029 $string = str_replace(['{excerpt}', '{summary}'], $data['summary'], $string);
1030 }
1031
1032 if ( in_array('{image}', $matches[0]) || in_array('{thumb}', $matches[0]) ) {
1033 $string = str_replace(['{image}', '{thumb}'], $data['img'], $string);
1034 }
1035
1036 if ( in_array('{thumb_img}', $matches[0]) ) {
1037 $string = str_replace('{thumb_img}', $data['img_no_link'], $string);
1038 }
1039
1040 if ( in_array('{thumb_url}', $matches[0]) && ! empty($data['img_no_link']) ) {
1041 $dom = new \DOMDocument();
1042
1043 if ( $dom->loadHTML($data['img_no_link']) ) {
1044 $img_tag = $dom->getElementsByTagName('img');
1045
1046 if ( $img_tag->length ) {
1047 foreach( $img_tag as $node ) {
1048 if ( $node->hasAttribute('src') ) {
1049 $src = $node->getAttribute('src');
1050 $string = str_replace('{thumb_url}', $src, $string);
1051 }
1052 }
1053 }
1054 }
1055 }
1056
1057 // WP-PostRatings check
1058 if ( $rating ) {
1059 if ( function_exists('the_ratings_results') && in_array('{rating}', $matches[0]) ) {
1060 $string = str_replace('{rating}', the_ratings_results($data['id']), $string);
1061 }
1062
1063 if ( function_exists('expand_ratings_template') && in_array('{score}', $matches[0]) ) {
1064 $string = str_replace('{score}', expand_ratings_template('%RATINGS_SCORE%', $data['id']), $string);
1065 // removing the redundant plus sign
1066 $string = str_replace('+', '', $string);
1067 }
1068 }
1069
1070 if ( in_array('{url}', $matches[0]) ) {
1071 $string = str_replace('{url}', $data['url'], $string);
1072 }
1073
1074 if ( in_array('{text_title}', $matches[0]) ) {
1075 $string = str_replace('{text_title}', $data['text_title'], $string);
1076 }
1077
1078 if ( in_array('{author}', $matches[0]) ) {
1079 $string = str_replace('{author}', $data['author'], $string);
1080 }
1081
1082 if ( in_array('{author_copy}', $matches[0]) ) {
1083 $string = str_replace('{author_copy}', $data['author_copy'], $string);
1084 }
1085
1086 if ( in_array('{author_name}', $matches[0]) ) {
1087 $string = str_replace('{author_name}', $data['author_name'], $string);
1088 }
1089
1090 if ( in_array('{author_url}', $matches[0]) ) {
1091 $string = str_replace('{author_url}', $data['author_url'], $string);
1092 }
1093
1094 if ( in_array('{taxonomy}', $matches[0]) || in_array('{category}', $matches[0]) ) {
1095 $string = str_replace(['{taxonomy}', '{category}'], $data['taxonomy'], $string);
1096 }
1097
1098 if ( in_array('{taxonomy_copy}', $matches[0]) || in_array('{category_copy}', $matches[0]) ) {
1099 $string = str_replace(['{taxonomy_copy}', '{category_copy}'], $data['taxonomy_copy'], $string);
1100 }
1101
1102 if ( in_array('{views}', $matches[0]) ) {
1103 $string = str_replace('{views}', $data['views'], $string);
1104 }
1105
1106 if ( in_array('{views_copy}', $matches[0]) ) {
1107 $string = str_replace('{views_copy}', $data['views_copy'], $string);
1108 }
1109
1110 if ( in_array('{comments}', $matches[0]) ) {
1111 $string = str_replace('{comments}', $data['comments'], $string);
1112 }
1113
1114 if ( in_array('{comments_copy}', $matches[0]) ) {
1115 $string = str_replace('{comments_copy}', $data['comments_copy'], $string);
1116 }
1117
1118 if ( in_array('{date}', $matches[0]) ) {
1119 $string = str_replace('{date}', $data['date'], $string);
1120 }
1121
1122 if ( in_array('{date_copy}', $matches[0]) ) {
1123 $string = str_replace('{date_copy}', $data['date_copy'], $string);
1124 }
1125
1126 if ( in_array('{total_items}', $matches[0]) ) {
1127 $string = str_replace('{total_items}', $data['total_items'], $string);
1128 }
1129
1130 if ( in_array('{item_position}', $matches[0]) ) {
1131 $string = str_replace('{item_position}', $data['item_position'], $string);
1132 }
1133
1134 return apply_filters('wpp_parse_custom_content_tags', $string, $data['id']);
1135 }
1136
1137 /**
1138 * Checks whether we're currently seeing a single post/page/CPT.
1139 *
1140 * @since 5.0.0
1141 * @return int
1142 */
1143 public function is_single()
1144 {
1145 return apply_filters('wpp_is_single', Helper::is_single());
1146 }
1147 }
1148