PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 6.1.0
WP Popular Posts v6.1.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 / Front / Front.php
wordpress-popular-posts / src / Front Last commit date
Front.php 3 years ago
Front.php
480 lines
1 <?php
2 /**
3 * The public-facing functionality of the plugin.
4 *
5 * Defines hooks to enqueue the public-specific stylesheet and JavaScript.
6 *
7 * @package WordPressPopularPosts
8 * @subpackage WordPressPopularPosts/Front
9 * @author Hector Cabrera <me@cabrerahector.com>
10 */
11
12 namespace WordPressPopularPosts\Front;
13
14 use WordPressPopularPosts\{ Helper, Output, Translate };
15 use WordPressPopularPosts\Traits\QueriesPosts;
16
17 class Front {
18
19 use QueriesPosts;
20
21 /**
22 * Plugin options.
23 *
24 * @var array $config
25 * @access private
26 */
27 private $config;
28
29 /**
30 * Translate object.
31 *
32 * @var \WordPressPopularPosts\Translate $translate
33 * @access private
34 */
35 private $translate;
36
37 /**
38 * Output object.
39 *
40 * @var \WordPressPopularPosts\Output $output
41 * @access private
42 */
43 private $output;
44
45 /**
46 * Construct.
47 *
48 * @since 5.0.0
49 * @param array $config Admin settings.
50 * @param \WordPressPopularPosts\Translate $translate Translate class.
51 * @param \WordPressPopularPosts\Output $output Output class.
52 */
53 public function __construct(array $config, Translate $translate, Output $output)
54 {
55 $this->config = $config;
56 $this->translate = $translate;
57 $this->output = $output;
58 }
59
60 /**
61 * WordPress public-facing hooks.
62 *
63 * @since 5.0.0
64 */
65 public function hooks()
66 {
67 add_shortcode('wpp', [$this, 'wpp_shortcode']);
68 add_action('wp_head', [$this, 'inline_loading_css']);
69 add_action('wp_ajax_update_views_ajax', [$this, 'update_views']);
70 add_action('wp_ajax_nopriv_update_views_ajax', [$this, 'update_views']);
71 add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
72 add_filter('script_loader_tag', [$this, 'convert_inline_js_into_json'], 10, 3);
73 }
74
75 /**
76 * Inserts CSS related to the loading animation into <head>
77 *
78 * @since 5.3.0
79 */
80 public function inline_loading_css()
81 {
82 $wpp_insert_loading_animation_styles = apply_filters('wpp_insert_loading_animation_styles', true);
83
84 if ( $wpp_insert_loading_animation_styles ) :
85 ?>
86 <style id="wpp-loading-animation-styles">@-webkit-keyframes bgslide{from{background-position-x:0}to{background-position-x:-200%}}@keyframes bgslide{from{background-position-x:0}to{background-position-x:-200%}}.wpp-widget-placeholder,.wpp-widget-block-placeholder{margin:0 auto;width:60px;height:3px;background:#dd3737;background:linear-gradient(90deg,#dd3737 0%,#571313 10%,#dd3737 100%);background-size:200% auto;border-radius:3px;-webkit-animation:bgslide 1s infinite linear;animation:bgslide 1s infinite linear}</style>
87 <?php
88 endif;
89 }
90
91 /**
92 * Enqueues public facing assets.
93 *
94 * @since 5.0.0
95 */
96 public function enqueue_assets()
97 {
98 // Enqueue WPP's stylesheet.
99 if ( $this->config['tools']['css'] ) {
100 $theme_file = get_stylesheet_directory() . '/wpp.css';
101
102 if ( @is_file($theme_file) ) {
103 wp_enqueue_style('wordpress-popular-posts-css', get_stylesheet_directory_uri() . "/wpp.css", [], WPP_VERSION, 'all');
104 } // Load stock stylesheet
105 else {
106 wp_enqueue_style('wordpress-popular-posts-css', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/wpp.css', [], WPP_VERSION, 'all');
107 }
108 }
109
110 // Enqueue WPP's library.
111 $is_single = 0;
112
113 if (
114 ( 0 == $this->config['tools']['log']['level'] && ! is_user_logged_in() )
115 || ( 1 == $this->config['tools']['log']['level'] )
116 || ( 2 == $this->config['tools']['log']['level'] && is_user_logged_in() )
117 ) {
118 $is_single = Helper::is_single();
119 }
120
121 $wpp_js = ( defined('WP_DEBUG') && WP_DEBUG )
122 ? plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/wpp.js'
123 : plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/wpp.min.js';
124
125 wp_register_script('wpp-js', $wpp_js, [], WPP_VERSION, false);
126 $params = [
127 'sampling_active' => (int) $this->config['tools']['sampling']['active'],
128 'sampling_rate' => (int) $this->config['tools']['sampling']['rate'],
129 'ajax_url' => esc_url_raw(rest_url('wordpress-popular-posts/v1/popular-posts')),
130 'api_url' => esc_url_raw(rest_url('wordpress-popular-posts')),
131 'ID' => (int) $is_single,
132 'token' => wp_create_nonce('wp_rest'),
133 'lang' => function_exists('PLL') ? $this->translate->get_current_language() : 0,
134 'debug' => (int) WP_DEBUG
135 ];
136 wp_enqueue_script('wpp-js');
137 wp_add_inline_script('wpp-js', json_encode($params), 'before');
138 }
139
140 /**
141 * Converts inline script tag into type=application/json.
142 *
143 * This function mods the original script tag as printed
144 * by WordPress which contains the data for the wpp_params
145 * object into a JSON script. This improves compatibility
146 * with Content Security Policy (CSP).
147 *
148 * @since 5.2.0
149 * @param string $tag
150 * @param string $handle
151 * @param string $src
152 * @return string $tag
153 */
154 function convert_inline_js_into_json(string $tag, string $handle, string $src)
155 {
156 if ( 'wpp-js' === $handle ) {
157 // id attribute found, replace it
158 if ( false !== strpos($tag, 'wpp-js-js-before') ) {
159 $tag = str_replace('wpp-js-js-before', 'wpp-json', $tag);
160 } // id attribute missing, let's add it
161 else {
162 $pos = strpos($tag, '>');
163 $tag = substr_replace($tag, ' id="wpp-json">', $pos, 1);
164 }
165
166 // type attribute found, replace it
167 if ( false !== strpos($tag, 'type') ) {
168 $pos = strpos($tag, 'text/javascript');
169
170 if ( false !== $pos )
171 $tag = substr_replace($tag, 'application/json', $pos, strlen('text/javascript'));
172 } // type attribute missing, let's add it
173 else {
174 $pos = strpos($tag, '>');
175 $tag = substr_replace($tag, ' type="application/json">', $pos, 1);
176 }
177 }
178
179 return $tag;
180 }
181
182 /**
183 * Updates views count on page load via AJAX.
184 *
185 * @since 2.0.0
186 */
187 public function update_views()
188 {
189 if ( ! wp_verify_nonce($_POST['token'], 'wpp-token') || ! Helper::is_number($_POST['wpp_id']) ) {
190 die( "WPP: Oops, invalid request!" );
191 }
192
193 $nonce = $_POST['token'];
194 $post_ID = $_POST['wpp_id'];
195 $exec_time = 0;
196
197 $start = Helper::microtime_float();
198 $result = $this->update_views_count($post_ID);
199 $end = Helper::microtime_float();
200 $exec_time += round($end - $start, 6);
201
202 if ( $result ) {
203 die("WPP: OK. Execution time: " . $exec_time . " seconds");
204 }
205
206 die("WPP: Oops, could not update the views count!");
207 }
208
209 /**
210 * Updates views count.
211 *
212 * @since 1.4.0
213 * @access private
214 * @global object $wpdb
215 * @param int $post_ID
216 * @return bool|int FALSE if query failed, TRUE on success
217 */
218 private function update_views_count(int $post_ID) {
219 global $wpdb;
220 $table = $wpdb->prefix . "popularposts";
221 $wpdb->show_errors();
222
223 // Get translated object ID
224 $post_ID = $this->translate->get_object_id(
225 $post_ID,
226 get_post_type($post_ID),
227 true,
228 $this->translate->get_default_language()
229 );
230 $now = Helper::now();
231 $curdate = Helper::curdate();
232 $views = ( $this->config['tools']['sampling']['active'] )
233 ? $this->config['tools']['sampling']['rate']
234 : 1;
235
236 // Allow WP themers / coders perform an action
237 // before updating views count
238 if ( has_action('wpp_pre_update_views') )
239 do_action('wpp_pre_update_views', $post_ID, $views);
240
241 // Update all-time table
242 $result1 = $wpdb->query($wpdb->prepare(
243 "INSERT INTO {$table}data
244 (postid, day, last_viewed, pageviews) VALUES (%d, %s, %s, %d)
245 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, last_viewed = %s;",
246 $post_ID,
247 $now,
248 $now,
249 $views,
250 $views,
251 $now
252 ));
253
254 // Update range (summary) table
255 $result2 = $wpdb->query($wpdb->prepare(
256 "INSERT INTO {$table}summary
257 (postid, pageviews, view_date, view_datetime) VALUES (%d, %d, %s, %s)
258 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, view_datetime = %s;",
259 $post_ID,
260 $views,
261 $curdate,
262 $now,
263 $views,
264 $now
265 ));
266
267 if ( !$result1 || !$result2 )
268 return false;
269
270 // Allow WP themers / coders perform an action
271 // after updating views count
272 if ( has_action('wpp_post_update_views' ))
273 do_action('wpp_post_update_views', $post_ID);
274
275 return true;
276 }
277
278 /**
279 * WPP shortcode handler.
280 *
281 * @since 1.4.0
282 * @param array $atts User defined attributes in shortcode tag
283 * @return string
284 */
285 public function wpp_shortcode($atts = null) { /** @TODO: starting PHP 8.0 $atts can be declared as mixed $meta_value (if not set WP gives an string, and it set we get an array) */
286 /**
287 * @var string $header
288 * @var int $limit
289 * @var int $offset
290 * @var string $range
291 * @var bool $freshness
292 * @var string $order_by
293 * @var string $post_type
294 * @var string $pid
295 * @var string $cat
296 * @var string $author
297 * @var int $title_length
298 * @var int $title_by_words
299 * @var int $excerpt_length
300 * @var int $excerpt_format
301 * @var int $excerpt_by_words
302 * @var int $thumbnail_width
303 * @var int $thumbnail_height
304 * @var bool $rating
305 * @var bool $stats_comments
306 * @var bool $stats_views
307 * @var bool $stats_author
308 * @var bool $stats_date
309 * @var string $stats_date_format
310 * @var bool $stats_category
311 * @var string $wpp_start
312 * @var string $wpp_end
313 * @var string $header_start
314 * @var string $header_end
315 * @var string $post_html
316 * @var bool $php
317 */
318 extract(shortcode_atts([
319 'header' => '',
320 'limit' => 10,
321 'offset' => 0,
322 'range' => 'daily',
323 'time_unit' => 'hour',
324 'time_quantity' => 24,
325 'freshness' => false,
326 'order_by' => 'views',
327 'post_type' => 'post',
328 'pid' => '',
329 'cat' => '',
330 'taxonomy' => 'category',
331 'term_id' => '',
332 'author' => '',
333 'title_length' => 0,
334 'title_by_words' => 0,
335 'excerpt_length' => 0,
336 'excerpt_format' => 0,
337 'excerpt_by_words' => 0,
338 'thumbnail_width' => 0,
339 'thumbnail_height' => 0,
340 'rating' => false,
341 'stats_comments' => false,
342 'stats_views' => true,
343 'stats_author' => false,
344 'stats_date' => false,
345 'stats_date_format' => 'F j, Y',
346 'stats_category' => false,
347 'stats_taxonomy' => false,
348 'wpp_start' => '<ul class="wpp-list">',
349 'wpp_end' => '</ul>',
350 'header_start' => '<h2>',
351 'header_end' => '</h2>',
352 'post_html' => '',
353 'theme' => '',
354 'php' => false
355 ], $atts, 'wpp'));
356
357 // possible values for "Time Range" and "Order by"
358 $time_units = ["minute", "hour", "day", "week", "month"];
359 $range_values = ["daily", "last24hours", "weekly", "last7days", "monthly", "last30days", "all", "custom"];
360 $order_by_values = ["comments", "views", "avg"];
361
362 $shortcode_ops = [
363 'title' => strip_tags($header),
364 'limit' => ( ! empty($limit ) && Helper::is_number($limit) && $limit > 0 ) ? $limit : 10,
365 'offset' => ( ! empty($offset) && Helper::is_number($offset) && $offset >= 0 ) ? $offset : 0,
366 'range' => ( in_array($range, $range_values) ) ? $range : 'daily',
367 'time_quantity' => ( ! empty($time_quantity ) && Helper::is_number($time_quantity) && $time_quantity > 0 ) ? $time_quantity : 24,
368 'time_unit' => ( in_array($time_unit, $time_units) ) ? $time_unit : 'hour',
369 'freshness' => empty($freshness) ? false : $freshness,
370 'order_by' => ( in_array($order_by, $order_by_values) ) ? $order_by : 'views',
371 'post_type' => empty($post_type) ? 'post' : $post_type,
372 'pid' => rtrim(preg_replace('|[^0-9,]|', '', $pid), ","),
373 'cat' => rtrim(preg_replace('|[^0-9,-]|', '', $cat), ","),
374 'taxonomy' => empty($taxonomy) ? 'category' : $taxonomy,
375 'term_id' => rtrim(preg_replace('|[^0-9,;-]|', '', $term_id), ","),
376 'author' => rtrim(preg_replace('|[^0-9,]|', '', $author), ","),
377 'shorten_title' => [
378 'active' => ( ! empty($title_length) && Helper::is_number($title_length) && $title_length > 0 ),
379 'length' => ( ! empty($title_length) && Helper::is_number($title_length) ) ? $title_length : 0,
380 'words' => ( ! empty($title_by_words) && Helper::is_number($title_by_words) && $title_by_words > 0 ),
381 ],
382 'post-excerpt' => [
383 'active' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) && $excerpt_length > 0 ),
384 'length' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) ) ? $excerpt_length : 0,
385 'keep_format' => ( ! empty($excerpt_format) && Helper::is_number($excerpt_format) && $excerpt_format > 0 ),
386 'words' => ( ! empty($excerpt_by_words) && Helper::is_number($excerpt_by_words) && $excerpt_by_words > 0 ),
387 ],
388 'thumbnail' => [
389 'active' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ),
390 'width' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ) ? $thumbnail_width : 0,
391 'height' => ( ! empty($thumbnail_height) && Helper::is_number($thumbnail_height) && $thumbnail_height > 0 ) ? $thumbnail_height : 0,
392 ],
393 'rating' => empty($rating) ? false : $rating,
394 'stats_tag' => [
395 'comment_count' => empty($stats_comments) ? false : $stats_comments,
396 'views' => empty($stats_views) ? false : $stats_views,
397 'author' => empty($stats_author) ? false : $stats_author,
398 'date' => [
399 'active' => empty($stats_date) ? false : $stats_date,
400 'format' => empty($stats_date_format) ? 'F j, Y' : $stats_date_format
401 ],
402 'category' => empty($stats_category) ? false : $stats_category,
403 'taxonomy' => [
404 'active' => empty($stats_taxonomy) ? false : $stats_taxonomy,
405 'name' => empty($taxonomy) ? 'category' : $taxonomy,
406 ]
407 ],
408 'markup' => [
409 'custom_html' => true,
410 'wpp-start' => empty($wpp_start) ? '' : $wpp_start,
411 'wpp-end' => empty($wpp_end) ? '' : $wpp_end,
412 'title-start' => empty($header_start) ? '' : $header_start,
413 'title-end' => empty($header_end) ? '' : $header_end,
414 'post-html' => empty($post_html) ? '<li>{thumb} {title} <span class="wpp-meta post-stats">{stats}</span></li>' : $post_html
415 ],
416 'theme' => [
417 'name' => trim($theme)
418 ]
419 ];
420
421 // Post / Page / CTP filter
422 $ids = array_filter(explode(",", $shortcode_ops['pid']), 'is_numeric');
423 // Got no valid IDs, clear
424 if ( empty($ids) ) {
425 $shortcode_ops['pid'] = '';
426 }
427
428 // Category filter
429 $ids = array_filter(explode(",", $shortcode_ops['cat']), 'is_numeric');
430 // Got no valid IDs, clear
431 if ( empty($ids) ) {
432 $shortcode_ops['cat'] = '';
433 }
434
435 // Author filter
436 $ids = array_filter(explode( ",", $shortcode_ops['author']), 'is_numeric');
437 // Got no valid IDs, clear
438 if ( empty($ids) ) {
439 $shortcode_ops['author'] = '';
440 }
441
442 $shortcode_content = '';
443 $cached = false;
444
445 // is there a title defined by user?
446 if (
447 ! empty($header)
448 && ! empty($header_start)
449 && ! empty($header_end)
450 ) {
451 $shortcode_content .= htmlspecialchars_decode($header_start, ENT_QUOTES) . $header . htmlspecialchars_decode($header_end, ENT_QUOTES);
452 }
453
454 $popular_posts = $this->maybe_query($shortcode_ops);
455
456 $this->output->set_data($popular_posts->get_posts());
457 $this->output->set_public_options($shortcode_ops);
458 $this->output->build_output();
459
460 $shortcode_content .= $this->output->get_output();
461
462 // Sanitize and return shortcode HTML
463 $allowed_tags = wp_kses_allowed_html('post');
464
465 if ( isset($allowed_tags['form']) ) {
466 unset($allowed_tags['form']);
467 }
468
469 if ( ! empty($shortcode_ops['theme']['name']) ) {
470 $allowed_tags['style'] = [
471 'id' => 1,
472 'nonce' => 1,
473 ];
474 }
475
476 return wp_kses($shortcode_content, $allowed_tags);
477 }
478
479 }
480