PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 7.4.0
WP Popular Posts v7.4.0
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 8 months ago Admin 3 weeks ago Block 3 weeks ago Compatibility 8 months ago Container 8 months ago Front 1 year ago Rest 8 months ago Shortcode 1 year ago Traits 4 years ago Widget 3 weeks ago Bootstrap.php 5 years ago Cache.php 8 months ago Helper.php 3 weeks ago I18N.php 5 years ago Image.php 8 months ago Output.php 3 weeks ago Query.php 3 weeks ago Settings.php 3 weeks ago Themer.php 8 months ago Translate.php 3 years ago Upgrader.php 8 months ago WordPressPopularPosts.php 8 months ago deprecated.php 4 years ago template-tags.php 3 weeks ago
Output.php
1152 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 // @TODO
242 // Causes encoding artifacts in some languages
243 // See: https://wordpress.org/support/topic/encoding-issue-with-titles-and-excerpts-in-wpp-shortcode/
244 // Using the /u modifier may fix this, requires further testing
245 // $this->output = preg_replace('/\R+/', "\n", $this->output);
246
247 // Sanitize HTML
248 $this->output = Helper::sanitize_html($this->output, $this->public_options);
249 }
250
251 return $this->output;
252 }
253
254 /**
255 * Build the HTML output.
256 *
257 * @since 4.0.0
258 */
259 public function build_output()
260 {
261 // Got some posts, format 'em!
262 if ( ! empty($this->data) ) {
263
264 $this->output = '';
265
266 // Allow WP themers / coders access to raw data
267 // so they can build their own output
268 if ( has_filter('wpp_custom_html') ) {
269 $this->output .= apply_filters('wpp_custom_html', $this->data, $this->public_options);
270 return;
271 }
272
273 if (
274 isset($this->public_options['theme']['name'])
275 && $this->public_options['theme']['name']
276 ) {
277 $this->output .= '<div class="popular-posts-sr">';
278
279 if ( @file_exists(get_stylesheet_directory() . '/wordpress-popular-posts/themes/' . $this->public_options['theme']['name'] . '/style.css') ) {
280 $theme_stylesheet = get_stylesheet_directory() . '/wordpress-popular-posts/themes/' . $this->public_options['theme']['name'] . '/style.css';
281 } else {
282 $theme_stylesheet = $this->themer->get_theme($this->public_options['theme']['name'])['path'] . '/style.css';
283 }
284
285 $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
286 $additional_styles = '';
287
288 if ( has_filter('wpp_additional_theme_styles') ) {
289 $additional_styles = wp_strip_all_tags(apply_filters('wpp_additional_theme_styles', '', $this->public_options['theme']['name']), true);
290
291 if ( $additional_styles ) {
292 $additional_styles = ' /* additional rules */ ' . $additional_styles;
293 }
294 }
295
296 $this->output .= '<style>' . $theme_css_rules . $additional_styles . '</style>';
297 }
298
299 // Opening HTML wrapper
300 $this->output .= $this->build_opening_wrapper();
301
302 // Format each post
303 $position = 0;
304
305 foreach( $this->data as $post_object ) {
306 $position++;
307 $this->output .= $this->render_post($post_object, $position);
308 }
309
310 // Closing HTML wrapper
311 $this->output .= $this->build_closing_wrapper();
312
313 if (
314 isset($this->public_options['theme']['name'])
315 && $this->public_options['theme']['name']
316 ) {
317 $this->output .= '</div>';
318 }
319
320 }
321 // Got nothing to show, give 'em the old "Sorry. No data so far." message!
322 else {
323 $this->output = apply_filters('wpp_no_data', '<p class="wpp-no-data">' . __('Sorry. No data so far.', 'wordpress-popular-posts') . '</p>');
324 }
325 }
326
327 /**
328 * Builds the opening HTML wrapper for the list.
329 *
330 * @since 7.3.3
331 * @return string
332 */
333 public function build_opening_wrapper()
334 {
335 // Custom opening HTML wrapper
336 if (
337 isset($this->public_options['markup']['custom_html'])
338 && $this->public_options['markup']['custom_html']
339 && isset($this->public_options['markup']['wpp-start'])
340 && isset($this->public_options['markup']['wpp-end'])
341 ){
342 return "\n" . htmlspecialchars_decode($this->public_options['markup']['wpp-start'], ENT_QUOTES) . "\n";
343 }
344
345 // Default opening HTML wrapper
346 $classes = 'wpp-list';
347
348 if ( $this->public_options['thumbnail']['active'] ) {
349 $classes .= ' wpp-list-with-thumbnails';
350 }
351
352 return "\n<ul class=\"{$classes}\">\n";
353 }
354
355 /**
356 * Builds the closing HTML wrapper for the list.
357 *
358 * @since 7.3.3
359 * @return string
360 */
361 public function build_closing_wrapper()
362 {
363 // Custom closing HTML wrapper
364 if (
365 isset($this->public_options['markup']['custom_html'])
366 && $this->public_options['markup']['custom_html']
367 && isset($this->public_options['markup']['wpp-start'])
368 && isset($this->public_options['markup']['wpp-end'])
369 ){
370 return "\n" . htmlspecialchars_decode($this->public_options['markup']['wpp-end'], ENT_QUOTES) . "\n";
371 }
372
373 // Default closing HTML wrapper
374 return '</ul>' . "\n";
375 }
376
377 /**
378 * Build the HTML markup for a single post.
379 *
380 * @since 4.0.0
381 * @access private
382 * @param object $post_object
383 * @param integer $position
384 * @return string
385 */
386 private function render_post(\stdClass $post_object, int $position = 1)
387 {
388 $is_single = $this->is_single();
389 $post = '';
390 $post_id = $post_object->id;
391 $trid = $this->translate->get_object_id(
392 $post_object->id,
393 get_post_type($post_object->id)
394 );
395
396 if ( $trid && $post_id != $trid ) {
397 $post_id = $trid;
398 }
399
400 $is_current_post = ( $is_single && ($is_single == $post_id || $is_single == $post_object->id) ) ? true : false;
401
402 // Permalink
403 $permalink = esc_url($this->get_permalink($post_object, $post_id));
404
405 // Post title (and title attribute)
406 $post_title_attr = esc_attr(wp_strip_all_tags($this->get_title($post_object, $post_id)));
407 $post_title = $this->get_title($post_object, $post_id);
408
409 if ( $this->public_options['shorten_title']['active'] ) {
410 $length = ( filter_var($this->public_options['shorten_title']['length'], FILTER_VALIDATE_INT) && $this->public_options['shorten_title']['length'] > 0 )
411 ? $this->public_options['shorten_title']['length']
412 : 25;
413
414 $more = $this->public_options['shorten_title']['words'] ? ' ' . $this->more : $this->more;
415 $more = apply_filters('wpp_title_more', $more);
416 $post_title = Helper::truncate($post_title, $length, $this->public_options['shorten_title']['words'], $more);
417 }
418
419 // Thumbnail
420 $post_thumbnail = $this->get_thumbnail($post_id);
421
422 // Post excerpt
423 $post_excerpt = $this->get_excerpt($post_object, $post_id);
424
425 // Post rating
426 $post_rating = $this->get_rating($post_object);
427
428 /**
429 * Post meta
430 */
431
432 // Post date
433 $post_date = $this->get_date($post_object);
434
435 // Post taxonomies
436 $post_taxonomies = $this->get_taxonomies($post_id);
437
438 // Post author
439 $post_author = $this->get_author($post_object, $post_id);
440
441 // Post views count
442 $post_views = $this->get_pageviews($post_object);
443
444 // Post comments count
445 $post_comments = $this->get_comments($post_object);
446
447 // Post meta
448 $meta_arr = $this->get_metadata(
449 $post_object,
450 $post_id,
451 $post_date,
452 $post_taxonomies,
453 $post_author,
454 $post_views,
455 $post_comments
456 );
457
458 if (
459 is_array($meta_arr)
460 && ! empty($meta_arr)
461 && 'views' == $this->public_options['order_by']
462 ) {
463 $keys = ['views', 'comments', 'author', 'date', 'taxonomy'];
464 $new_meta_arr = [];
465
466 foreach($keys as $key) {
467 if ( isset($meta_arr[$key])) {
468 $new_meta_arr[$key] = $meta_arr[$key];
469 }
470 }
471
472 if ( ! empty($new_meta_arr) ) {
473 $meta_arr = $new_meta_arr;
474 }
475 }
476
477 $post_meta_separator = esc_html(apply_filters('wpp_post_meta_separator', ' | '));
478 $post_meta = join($post_meta_separator, $meta_arr);
479
480 $prettify_numbers = apply_filters('wpp_prettify_numbers', true);
481
482 /**
483 * @ToDo
484 *
485 * Remove this filter, it's a typo and the correct one has been around since 6.3.4
486 *
487 * @since 7.3.7
488 */
489 if ( has_filter('wpp_pretiffy_numbers') ) {
490 $prettify_numbers = apply_filters('wpp_pretiffy_numbers', true);
491
492 if ( defined('WP_DEBUG') && WP_DEBUG ) {
493 trigger_error('WP Popular Posts - wpp_pretiffy_numbers has been deprecated. Please use wpp_prettify_numbers instead.', E_USER_WARNING);
494 }
495 }
496
497 // Build custom HTML output
498 if ( $this->public_options['markup']['custom_html'] ) {
499 $data = [
500 'id' => $post_id,
501 'is_current_post' => $is_current_post,
502 '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>',
503 'title_attr' => $post_title_attr,
504 'summary' => $post_excerpt,
505 'stats' => $post_meta,
506 '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>' : '',
507 'img_no_link' => $post_thumbnail,
508 'url' => $permalink,
509 'text_title' => $post_title,
510 'taxonomy' => $post_taxonomies,
511 'taxonomy_copy' => isset($meta_arr['taxonomy']) ? $meta_arr['taxonomy'] : null,
512 '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>' : '',
513 'author_copy' => isset($meta_arr['author']) ? $meta_arr['author'] : null,
514 'author_name' => esc_html($post_author),
515 '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)) : '',
516 '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)),
517 'views_copy' => isset($meta_arr['views']) ? $meta_arr['views'] : null,
518 'comments' => $prettify_numbers ? Helper::prettify_number($post_comments) : number_format_i18n($post_comments),
519 'comments_copy' => isset($meta_arr['comments']) ? $meta_arr['comments'] : null,
520 'date' => $post_date,
521 'date_copy' => isset($meta_arr['date']) ? $meta_arr['date'] : null,
522 'total_items' => count($this->data),
523 'item_position' => $position
524 ];
525 $post = $this->format_content(htmlspecialchars_decode($this->public_options['markup']['post-html'], ENT_QUOTES), $data, $this->public_options['rating']) . "\n";
526 } // Use the "stock" HTML output
527 else {
528 $wpp_post_class = [];
529
530 if ( $is_current_post ) {
531 $wpp_post_class[] = 'current';
532 }
533
534 // Allow themers / plugin developer
535 // to add custom classes to each post
536 $wpp_post_class = apply_filters('wpp_post_class', $wpp_post_class, $post_id);
537
538 $post_thumbnail = ( ! empty($post_thumbnail) )
539 ? "<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"
540 : '';
541
542 $post_excerpt = ( ! empty($post_excerpt) )
543 ? " <span class=\"wpp-excerpt\">{$post_excerpt}</span>\n"
544 : '';
545
546 $post_meta = ( ! empty($post_meta) )
547 ? " <span class=\"wpp-meta post-stats\">{$post_meta}</span>\n"
548 : '';
549
550 $post_rating = ( ! empty($post_rating) )
551 ? " <span class=\"wpp-rating\">{$post_rating}</span>\n"
552 : '';
553
554 $post =
555 '<li' . ( ( is_array($wpp_post_class) && ! empty($wpp_post_class) ) ? ' class="' . esc_attr(implode(' ', $wpp_post_class)) . '"' : '') . ">\n"
556 . $post_thumbnail
557 . "<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"
558 . $post_excerpt
559 . $post_meta
560 . $post_rating
561 . "</li>\n";
562 }
563
564 return apply_filters('wpp_post', $post, $post_object, $this->public_options);
565 }
566
567 /**
568 * Return the processed post/page title.
569 *
570 * @since 3.0.0
571 * @access private
572 * @param object $post_object
573 * @param integer $post_id
574 * @return string
575 */
576 private function get_title(\stdClass $post_object, int $post_id)
577 {
578 $title = '';
579
580 if ( $post_object->id != $post_id ) {
581 $title = get_the_title($post_id);
582 } else {
583 $title = $post_object->title;
584 }
585
586 // Run the_title filter so core/plugin title hooks can
587 // be applied to the post title
588 $title = apply_filters('the_title', $title, $post_object->id);
589
590 return apply_filters('wpp_the_title', $title, $post_object->id, $post_id);
591 }
592
593 /**
594 * Return the permalink.
595 *
596 * @since 4.0.12
597 * @access private
598 * @param object $post_object
599 * @param integer $post_id
600 * @return string
601 */
602 private function get_permalink(\stdClass $post_object, int $post_id) {
603 if ( $post_object->id != $post_id ) {
604 return get_permalink($post_id);
605 }
606
607 return get_permalink($post_object->id);
608 }
609
610 /**
611 * Return the processed thumbnail.
612 *
613 * @since 3.0.0
614 * @access private
615 * @param int $post_id
616 * @return string
617 */
618 private function get_thumbnail(int $post_id)
619 {
620 $thumbnail = '';
621
622 if ( $this->public_options['thumbnail']['active'] ) {
623 $thumbnail = $this->thumbnail->get(
624 $post_id,
625 [
626 $this->public_options['thumbnail']['width'],
627 $this->public_options['thumbnail']['height']
628 ],
629 $this->admin_options['tools']['thumbnail']['source'],
630 $this->public_options['thumbnail']['crop'],
631 $this->public_options['thumbnail']['build']
632 );
633 }
634
635 return $thumbnail;
636 }
637
638 /**
639 * Return post excerpt.
640 *
641 * @since 3.0.0
642 * @access private
643 * @param object $post_object
644 * @param integer $post_id
645 * @return string
646 */
647 private function get_excerpt(\stdClass $post_object, int $post_id)
648 {
649 $excerpt = '';
650
651 if ( $this->public_options['post-excerpt']['active'] ) {
652
653 if ( $post_object->id != $post_id ) {
654 $the_post = get_post($post_id);
655
656 $excerpt = ( empty($the_post->post_excerpt) )
657 ? $the_post->post_content
658 : $the_post->post_excerpt;
659 }
660 else {
661 $excerpt = ( empty($post_object->post_excerpt) )
662 ? $post_object->post_content
663 : $post_object->post_excerpt;
664 }
665
666 // remove caption tags
667 $excerpt = preg_replace('/\[caption.*\[\/caption\]/', '', $excerpt);
668
669 // remove Flash objects
670 $excerpt = preg_replace("/<object[0-9 a-z_?*=\":\-\/\.#\,\\n\\r\\t]+/smi", '', $excerpt);
671
672 // remove iframes
673 $excerpt = preg_replace('/<iframe.*?\/iframe>/i', '', $excerpt);
674
675 // remove WP shortcodes
676 $excerpt = strip_shortcodes($excerpt);
677
678 // remove style/script tags
679 $excerpt = preg_replace('@<(script|style)[^>]*?>.*?</\\1>@si', '', $excerpt);
680
681 // remove blocks that are not appropriate for the excerpt
682 $excerpt = excerpt_remove_blocks($excerpt);
683
684 // remove HTML tags if requested
685 if ( $this->public_options['post-excerpt']['keep_format'] ) {
686 $excerpt = wp_kses(
687 $excerpt,
688 [
689 'a' => [
690 'href' => [],
691 'title' => []
692 ],
693 'em' => [],
694 'strong' => []
695 ]
696 );
697 } else {
698 $excerpt = wp_kses($excerpt, []);
699
700 // remove URLs, too
701 $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);
702 }
703
704 // remove HTML comments
705 $excerpt = preg_replace('/<!--.*?-->/ms', '', $excerpt);
706
707 // remove extra whitespaces
708 $excerpt = preg_replace('/\s\s+/', ' ', $excerpt);
709
710 $excerpt = trim($excerpt);
711
712 }
713
714 // Balance tags, if needed
715 if ( '' !== $excerpt ) {
716
717 $more = $this->public_options['post-excerpt']['words'] ? ' ' . $this->more : $this->more;
718 $more = apply_filters('wpp_excerpt_more', $more);
719 $excerpt = Helper::truncate($excerpt, $this->public_options['post-excerpt']['length'], $this->public_options['post-excerpt']['words'], $more);
720
721 if ( $this->public_options['post-excerpt']['keep_format'] ) {
722 $excerpt = force_balance_tags($excerpt);
723 }
724 }
725
726 return $excerpt;
727 }
728
729 /**
730 * Return post rating.
731 *
732 * @since 3.0.0
733 * @access private
734 * @param object $post_object
735 * @return string
736 */
737 private function get_rating(\stdClass $post_object)
738 {
739 $rating = '';
740
741 if ( function_exists('the_ratings_results') && $this->public_options['rating'] ) {
742 $rating = the_ratings_results($post_object->id);
743 }
744
745 return $rating;
746 }
747
748 /**
749 * Get post date.
750 *
751 * @since 3.0.0
752 * @access private
753 * @param object $post_object
754 * @return string
755 */
756 private function get_date(\stdClass $post_object)
757 {
758 $date = '';
759
760 if ( $this->public_options['stats_tag']['date']['active'] ) {
761 if ( 'relative' == $this->public_options['stats_tag']['date']['format'] ) {
762 $date = sprintf(
763 __('%s ago', 'wordpress-popular-posts'),
764 human_time_diff(
765 strtotime($post_object->date),
766 Helper::timestamp()
767 )
768 );
769 } else {
770 $date = date_i18n(
771 ( 'wp_date_format' == $this->public_options['stats_tag']['date']['format'] ? $this->wp_date_format : $this->public_options['stats_tag']['date']['format'] ),
772 strtotime($post_object->date)
773 );
774 }
775 }
776
777 return apply_filters('wpp_the_date', $date, $post_object->id);
778 }
779
780 /**
781 * Get post taxonomies.
782 *
783 * @since 3.0.0
784 * @access private
785 * @param integer $post_id
786 * @return string
787 */
788 private function get_taxonomies(int $post_id)
789 {
790 $post_tax = '';
791
792 if (
793 (isset($this->public_options['stats_tag']['category']) && $this->public_options['stats_tag']['category'])
794 || $this->public_options['stats_tag']['taxonomy']['active']
795 ) {
796
797 $taxonomy = 'category';
798
799 if (
800 $this->public_options['stats_tag']['taxonomy']['active']
801 && ! empty($this->public_options['stats_tag']['taxonomy']['name'])
802 ) {
803 $taxonomy = $this->public_options['stats_tag']['taxonomy']['name'];
804 }
805
806 $terms = wp_get_post_terms($post_id, $taxonomy);
807
808 if ( ! is_wp_error($terms) ) {
809 // Usage: https://wordpress.stackexchange.com/a/46824
810 if ( has_filter('wpp_post_exclude_terms') ) {
811 $args = apply_filters('wpp_post_exclude_terms', []);
812 $terms = wp_list_filter($terms, $args, 'NOT');
813 }
814
815 $terms = apply_filters('wpp_post_terms', $terms);
816
817 if (
818 is_array($terms)
819 && ! empty($terms)
820 ) {
821 $taxonomy_separator = esc_html(apply_filters('wpp_taxonomy_separator', ', '));
822
823 // We're going to use the taxonomy slug as a CSS class so let's escape it just in case
824 $taxonomy = esc_attr($taxonomy);
825
826 foreach ($terms as $term) {
827 $term_link = get_term_link($term);
828
829 if ( is_wp_error($term_link) ) {
830 continue;
831 }
832
833 $term_link = esc_url($this->translate->url($term_link, $this->translate->get_current_language()));
834 $post_tax .= "<a href=\"{$term_link}\" class=\"wpp-taxonomy {$taxonomy} {$taxonomy}-{$term->term_id}\">" . esc_html($term->name) . '</a>' . $taxonomy_separator;
835 }
836 }
837 }
838
839 if ( '' != $post_tax ) {
840 $post_tax = rtrim($post_tax, $taxonomy_separator);
841 }
842
843 }
844
845 return $post_tax;
846 }
847
848 /**
849 * Get post author.
850 *
851 * @since 3.0.0
852 * @access private
853 * @param object $post_object
854 * @param integer $post_id
855 * @return string
856 */
857 private function get_author(\stdClass $post_object, int $post_id)
858 {
859 $author = ( $this->public_options['stats_tag']['author'] )
860 ? get_the_author_meta('display_name', $post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid)
861 : '';
862
863 return $author;
864 }
865
866 /**
867 * Return post views count.
868 *
869 * @since 3.0.0
870 * @access private
871 * @param object $post_object
872 * @return int|float
873 */
874 private function get_pageviews(\stdClass $post_object)
875 {
876 $pageviews = 0;
877
878 if (
879 (
880 $this->public_options['order_by'] == 'views'
881 || $this->public_options['order_by'] == 'avg'
882 || $this->public_options['stats_tag']['views']
883 )
884 && ( isset($post_object->pageviews) || isset($post_object->avg_views) )
885 ) {
886 $pageviews = ( $this->public_options['order_by'] == 'views' || $this->public_options['order_by'] == 'comments' )
887 ? $post_object->pageviews
888 : $post_object->avg_views;
889 }
890
891 return $pageviews;
892 }
893
894 /**
895 * Return post comment count.
896 *
897 * @since 3.0.0
898 * @access private
899 * @param object $post_object
900 * @return int
901 */
902 private function get_comments(\stdClass $post_object)
903 {
904 $comments = ( ( $this->public_options['order_by'] == 'comments' || $this->public_options['stats_tag']['comment_count'] ) && isset($post_object->comment_count) )
905 ? $post_object->comment_count
906 : 0;
907
908 return $comments;
909 }
910
911 /**
912 * Return post metadata.
913 *
914 * @since 3.0.0
915 * @access private
916 * @param object $post_object
917 * @param integer $post_id
918 * @return array
919 */
920 //private function get_metadata(\stdClass $post_object, $post_id)
921 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 */
922 {
923 $stats = [];
924
925 $prettify_numbers = apply_filters('wpp_prettify_numbers', true);
926
927 /**
928 * @ToDo
929 *
930 * Remove this filter, it's a typo and the correct one has been around since 6.3.4
931 *
932 * @since 7.3.7
933 */
934 if ( has_filter('wpp_pretiffy_numbers') ) {
935 $prettify_numbers = apply_filters('wpp_pretiffy_numbers', true);
936
937 if ( defined('WP_DEBUG') && WP_DEBUG ) {
938 trigger_error('WP Popular Posts - wpp_pretiffy_numbers has been deprecated. Please use wpp_prettify_numbers instead..', E_USER_WARNING);
939 }
940 }
941
942 // comments
943 if ( $this->public_options['stats_tag']['comment_count'] ) {
944 $comments_text = sprintf(
945 _n('%s comment', '%s comments', $comments, 'wordpress-popular-posts'),
946 $prettify_numbers ? Helper::prettify_number($comments) : number_format_i18n($comments)
947 );
948
949 $stats['comments'] = '<span class="wpp-comments">' . $comments_text . '</span>';
950 }
951
952 // views
953 if ( $this->public_options['stats_tag']['views'] ) {
954 if ( $this->public_options['order_by'] == 'avg' ) {
955 $views_text = sprintf(
956 _n('%s view per day', '%s views per day', $pageviews, 'wordpress-popular-posts'),
957 $prettify_numbers ? Helper::prettify_number($pageviews, 2) : number_format_i18n($pageviews, (fmod($pageviews, 1) !== 0.0 ? 2 : 0))
958 );
959 }
960 else {
961 $views_text = sprintf(
962 _n('%s view', '%s views', $pageviews, 'wordpress-popular-posts'),
963 $prettify_numbers ? Helper::prettify_number($pageviews) : number_format_i18n($pageviews)
964 );
965 }
966
967 $stats['views'] = '<span class="wpp-views">' . $views_text . '</span>';
968 }
969
970 // author
971 if ( $this->public_options['stats_tag']['author'] ) {
972 $author_url = get_author_posts_url($post_object->uid != $post_id ? get_post_field('post_author', $post_id) : $post_object->uid);
973 $display_name = '<a href="' . esc_url($this->translate->url($author_url, $this->translate->get_current_language())) . '">' . esc_html($author) . '</a>';
974 $stats['author'] = '<span class="wpp-author">' . sprintf(__('by %s', 'wordpress-popular-posts'), $display_name) . '</span>';
975 }
976
977 // date
978 if ( $this->public_options['stats_tag']['date']['active'] ) {
979 $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>';
980 }
981
982 // taxonomy
983 if ( ($this->public_options['stats_tag']['category'] || $this->public_options['stats_tag']['taxonomy']['active']) && $post_tax != '' ) {
984 $stats['taxonomy'] = '<span class="wpp-category">' . sprintf(__('under %s', 'wordpress-popular-posts'), $post_tax) . '</span>';
985 }
986
987 return $stats;
988 }
989
990 /**
991 * Parse content tags.
992 *
993 * @since 1.4.6
994 * @access private
995 * @param string HTML string with content tags
996 * @param array Post data
997 * @param bool Used to display post rating (if functionality is available)
998 * @return string
999 */
1000 private function format_content(string $string, array $data, bool $rating) {
1001
1002 if ( empty($string) || ( empty($data) || ! is_array($data) ) ) {
1003 return false;
1004 }
1005
1006 $params = [];
1007 $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';
1008 preg_match_all($pattern, $string, $matches);
1009
1010 array_map('strtolower', $matches[0]);
1011
1012 if ( in_array('{pid}', $matches[0]) ) {
1013 $string = str_replace('{pid}', $data['id'], $string);
1014 }
1015
1016 if ( in_array('{current_class}', $matches[0]) ) {
1017 $string = str_replace('{current_class}', ( $data['is_current_post'] ? 'current' : '' ), $string);
1018 }
1019
1020 if ( in_array('{title}', $matches[0]) ) {
1021 $string = str_replace('{title}', $data['title'], $string);
1022 }
1023
1024 if ( in_array('{title_attr}', $matches[0]) ) {
1025 $string = str_replace('{title_attr}', $data['title_attr'], $string);
1026 }
1027
1028 if ( in_array('{meta}', $matches[0]) || in_array('{stats}', $matches[0]) ) {
1029 $string = str_replace(['{meta}', '{stats}'], $data['stats'], $string);
1030 }
1031
1032 if ( in_array('{excerpt}', $matches[0]) || in_array('{summary}', $matches[0]) ) {
1033 $string = str_replace(['{excerpt}', '{summary}'], $data['summary'], $string);
1034 }
1035
1036 if ( in_array('{image}', $matches[0]) || in_array('{thumb}', $matches[0]) ) {
1037 $string = str_replace(['{image}', '{thumb}'], $data['img'], $string);
1038 }
1039
1040 if ( in_array('{thumb_img}', $matches[0]) ) {
1041 $string = str_replace('{thumb_img}', $data['img_no_link'], $string);
1042 }
1043
1044 if ( in_array('{thumb_url}', $matches[0]) && ! empty($data['img_no_link']) ) {
1045 $dom = new \DOMDocument();
1046
1047 if ( $dom->loadHTML($data['img_no_link']) ) {
1048 $img_tag = $dom->getElementsByTagName('img');
1049
1050 if ( $img_tag->length ) {
1051 foreach( $img_tag as $node ) {
1052 if ( $node->hasAttribute('src') ) {
1053 $src = $node->getAttribute('src');
1054 $string = str_replace('{thumb_url}', $src, $string);
1055 }
1056 }
1057 }
1058 }
1059 }
1060
1061 // WP-PostRatings check
1062 if ( $rating ) {
1063 if ( function_exists('the_ratings_results') && in_array('{rating}', $matches[0]) ) {
1064 $string = str_replace('{rating}', the_ratings_results($data['id']), $string);
1065 }
1066
1067 if ( function_exists('expand_ratings_template') && in_array('{score}', $matches[0]) ) {
1068 $string = str_replace('{score}', expand_ratings_template('%RATINGS_SCORE%', $data['id']), $string);
1069 // removing the redundant plus sign
1070 $string = str_replace('+', '', $string);
1071 }
1072 }
1073
1074 if ( in_array('{url}', $matches[0]) ) {
1075 $string = str_replace('{url}', $data['url'], $string);
1076 }
1077
1078 if ( in_array('{text_title}', $matches[0]) ) {
1079 $string = str_replace('{text_title}', $data['text_title'], $string);
1080 }
1081
1082 if ( in_array('{author}', $matches[0]) ) {
1083 $string = str_replace('{author}', $data['author'], $string);
1084 }
1085
1086 if ( in_array('{author_copy}', $matches[0]) ) {
1087 $string = str_replace('{author_copy}', $data['author_copy'], $string);
1088 }
1089
1090 if ( in_array('{author_name}', $matches[0]) ) {
1091 $string = str_replace('{author_name}', $data['author_name'], $string);
1092 }
1093
1094 if ( in_array('{author_url}', $matches[0]) ) {
1095 $string = str_replace('{author_url}', $data['author_url'], $string);
1096 }
1097
1098 if ( in_array('{taxonomy}', $matches[0]) || in_array('{category}', $matches[0]) ) {
1099 $string = str_replace(['{taxonomy}', '{category}'], $data['taxonomy'], $string);
1100 }
1101
1102 if ( in_array('{taxonomy_copy}', $matches[0]) || in_array('{category_copy}', $matches[0]) ) {
1103 $string = str_replace(['{taxonomy_copy}', '{category_copy}'], $data['taxonomy_copy'], $string);
1104 }
1105
1106 if ( in_array('{views}', $matches[0]) ) {
1107 $string = str_replace('{views}', $data['views'], $string);
1108 }
1109
1110 if ( in_array('{views_copy}', $matches[0]) ) {
1111 $string = str_replace('{views_copy}', $data['views_copy'], $string);
1112 }
1113
1114 if ( in_array('{comments}', $matches[0]) ) {
1115 $string = str_replace('{comments}', $data['comments'], $string);
1116 }
1117
1118 if ( in_array('{comments_copy}', $matches[0]) ) {
1119 $string = str_replace('{comments_copy}', $data['comments_copy'], $string);
1120 }
1121
1122 if ( in_array('{date}', $matches[0]) ) {
1123 $string = str_replace('{date}', $data['date'], $string);
1124 }
1125
1126 if ( in_array('{date_copy}', $matches[0]) ) {
1127 $string = str_replace('{date_copy}', $data['date_copy'], $string);
1128 }
1129
1130 if ( in_array('{total_items}', $matches[0]) ) {
1131 $string = str_replace('{total_items}', $data['total_items'], $string);
1132 }
1133
1134 if ( in_array('{item_position}', $matches[0]) ) {
1135 $string = str_replace('{item_position}', $data['item_position'], $string);
1136 }
1137
1138 return apply_filters('wpp_parse_custom_content_tags', $string, $data['id']);
1139 }
1140
1141 /**
1142 * Checks whether we're currently seeing a single post/page/CPT.
1143 *
1144 * @since 5.0.0
1145 * @return int
1146 */
1147 public function is_single()
1148 {
1149 return apply_filters('wpp_is_single', Helper::is_single());
1150 }
1151 }
1152