PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 6.0.0
WP Popular Posts v6.0.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 4 years ago
Front.php
481 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 wp_register_script('wpp-js', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/wpp.min.js', [], WPP_VERSION, false);
122 $params = [
123 'sampling_active' => (int) $this->config['tools']['sampling']['active'],
124 'sampling_rate' => (int) $this->config['tools']['sampling']['rate'],
125 'ajax_url' => esc_url_raw(rest_url('wordpress-popular-posts/v1/popular-posts')),
126 'api_url' => esc_url_raw(rest_url('wordpress-popular-posts')),
127 'ID' => (int) $is_single,
128 'token' => wp_create_nonce('wp_rest'),
129 'lang' => function_exists('PLL') ? $this->translate->get_current_language() : 0,
130 'debug' => (int) WP_DEBUG
131 ];
132 wp_enqueue_script('wpp-js');
133 wp_add_inline_script('wpp-js', json_encode($params), 'before');
134 }
135
136 /**
137 * Converts inline script tag into type=application/json.
138 *
139 * This function mods the original script tag as printed
140 * by WordPress which contains the data for the wpp_params
141 * object into a JSON script. This improves compatibility
142 * with Content Security Policy (CSP).
143 *
144 * @since 5.2.0
145 * @param string $tag
146 * @param string $handle
147 * @param string $src
148 * @return string $tag
149 */
150 function convert_inline_js_into_json(string $tag, string $handle, string $src)
151 {
152 if ( 'wpp-js' === $handle ) {
153 // id attribute found, replace it
154 if ( false !== strpos($tag, 'wpp-js-js-before') ) {
155 $tag = str_replace('wpp-js-js-before', 'wpp-json', $tag);
156 } // id attribute missing, let's add it
157 else {
158 $pos = strpos($tag, '>');
159 $tag = substr_replace($tag, ' id="wpp-json">', $pos, 1);
160 }
161
162 // type attribute found, replace it
163 if ( false !== strpos($tag, 'type') ) {
164 $pos = strpos($tag, 'text/javascript');
165
166 if ( false !== $pos )
167 $tag = substr_replace($tag, 'application/json', $pos, strlen('text/javascript'));
168 } // type attribute missing, let's add it
169 else {
170 $pos = strpos($tag, '>');
171 $tag = substr_replace($tag, ' type="application/json">', $pos, 1);
172 }
173 }
174
175 return $tag;
176 }
177
178 /**
179 * Updates views count on page load via AJAX.
180 *
181 * @since 2.0.0
182 */
183 public function update_views()
184 {
185 if ( ! wp_verify_nonce($_POST['token'], 'wpp-token') || ! Helper::is_number($_POST['wpp_id']) ) {
186 die( "WPP: Oops, invalid request!" );
187 }
188
189 $nonce = $_POST['token'];
190 $post_ID = $_POST['wpp_id'];
191 $exec_time = 0;
192
193 $start = Helper::microtime_float();
194 $result = $this->update_views_count($post_ID);
195 $end = Helper::microtime_float();
196 $exec_time += round($end - $start, 6);
197
198 if ( $result ) {
199 die("WPP: OK. Execution time: " . $exec_time . " seconds");
200 }
201
202 die("WPP: Oops, could not update the views count!");
203 }
204
205 /**
206 * Updates views count.
207 *
208 * @since 1.4.0
209 * @access private
210 * @global object $wpdb
211 * @param int $post_ID
212 * @return bool|int FALSE if query failed, TRUE on success
213 */
214 private function update_views_count(int $post_ID) {
215 /*
216 TODO:
217 For WordPress Multisite, we must define the DIEONDBERROR constant for database errors to display like so:
218 <?php define( 'DIEONDBERROR', true ); ?>
219 */
220 global $wpdb;
221 $table = $wpdb->prefix . "popularposts";
222 $wpdb->show_errors();
223
224 // Get translated object ID
225 $post_ID = $this->translate->get_object_id(
226 $post_ID,
227 get_post_type($post_ID),
228 true,
229 $this->translate->get_default_language()
230 );
231 $now = Helper::now();
232 $curdate = Helper::curdate();
233 $views = ( $this->config['tools']['sampling']['active'] )
234 ? $this->config['tools']['sampling']['rate']
235 : 1;
236
237 // Allow WP themers / coders perform an action
238 // before updating views count
239 if ( has_action('wpp_pre_update_views') )
240 do_action('wpp_pre_update_views', $post_ID, $views);
241
242 // Update all-time table
243 $result1 = $wpdb->query($wpdb->prepare(
244 "INSERT INTO {$table}data
245 (postid, day, last_viewed, pageviews) VALUES (%d, %s, %s, %d)
246 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, last_viewed = %s;",
247 $post_ID,
248 $now,
249 $now,
250 $views,
251 $views,
252 $now
253 ));
254
255 // Update range (summary) table
256 $result2 = $wpdb->query($wpdb->prepare(
257 "INSERT INTO {$table}summary
258 (postid, pageviews, view_date, view_datetime) VALUES (%d, %d, %s, %s)
259 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, view_datetime = %s;",
260 $post_ID,
261 $views,
262 $curdate,
263 $now,
264 $views,
265 $now
266 ));
267
268 if ( !$result1 || !$result2 )
269 return false;
270
271 // Allow WP themers / coders perform an action
272 // after updating views count
273 if ( has_action('wpp_post_update_views' ))
274 do_action('wpp_post_update_views', $post_ID);
275
276 return true;
277 }
278
279 /**
280 * WPP shortcode handler.
281 *
282 * @since 1.4.0
283 * @param array $atts User defined attributes in shortcode tag
284 * @return string
285 */
286 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) */
287 /**
288 * @var string $header
289 * @var int $limit
290 * @var int $offset
291 * @var string $range
292 * @var bool $freshness
293 * @var string $order_by
294 * @var string $post_type
295 * @var string $pid
296 * @var string $cat
297 * @var string $author
298 * @var int $title_length
299 * @var int $title_by_words
300 * @var int $excerpt_length
301 * @var int $excerpt_format
302 * @var int $excerpt_by_words
303 * @var int $thumbnail_width
304 * @var int $thumbnail_height
305 * @var bool $rating
306 * @var bool $stats_comments
307 * @var bool $stats_views
308 * @var bool $stats_author
309 * @var bool $stats_date
310 * @var string $stats_date_format
311 * @var bool $stats_category
312 * @var string $wpp_start
313 * @var string $wpp_end
314 * @var string $header_start
315 * @var string $header_end
316 * @var string $post_html
317 * @var bool $php
318 */
319 extract(shortcode_atts([
320 'header' => '',
321 'limit' => 10,
322 'offset' => 0,
323 'range' => 'daily',
324 'time_unit' => 'hour',
325 'time_quantity' => 24,
326 'freshness' => false,
327 'order_by' => 'views',
328 'post_type' => 'post',
329 'pid' => '',
330 'cat' => '',
331 'taxonomy' => 'category',
332 'term_id' => '',
333 'author' => '',
334 'title_length' => 0,
335 'title_by_words' => 0,
336 'excerpt_length' => 0,
337 'excerpt_format' => 0,
338 'excerpt_by_words' => 0,
339 'thumbnail_width' => 0,
340 'thumbnail_height' => 0,
341 'rating' => false,
342 'stats_comments' => false,
343 'stats_views' => true,
344 'stats_author' => false,
345 'stats_date' => false,
346 'stats_date_format' => 'F j, Y',
347 'stats_category' => false,
348 'stats_taxonomy' => false,
349 'wpp_start' => '<ul class="wpp-list">',
350 'wpp_end' => '</ul>',
351 'header_start' => '<h2>',
352 'header_end' => '</h2>',
353 'post_html' => '',
354 'theme' => '',
355 'php' => false
356 ], $atts, 'wpp'));
357
358 // possible values for "Time Range" and "Order by"
359 $time_units = ["minute", "hour", "day", "week", "month"];
360 $range_values = ["daily", "last24hours", "weekly", "last7days", "monthly", "last30days", "all", "custom"];
361 $order_by_values = ["comments", "views", "avg"];
362
363 $shortcode_ops = [
364 'title' => strip_tags($header),
365 'limit' => ( ! empty($limit ) && Helper::is_number($limit) && $limit > 0 ) ? $limit : 10,
366 'offset' => ( ! empty($offset) && Helper::is_number($offset) && $offset >= 0 ) ? $offset : 0,
367 'range' => ( in_array($range, $range_values) ) ? $range : 'daily',
368 'time_quantity' => ( ! empty($time_quantity ) && Helper::is_number($time_quantity) && $time_quantity > 0 ) ? $time_quantity : 24,
369 'time_unit' => ( in_array($time_unit, $time_units) ) ? $time_unit : 'hour',
370 'freshness' => empty($freshness) ? false : $freshness,
371 'order_by' => ( in_array($order_by, $order_by_values) ) ? $order_by : 'views',
372 'post_type' => empty($post_type) ? 'post' : $post_type,
373 'pid' => rtrim(preg_replace('|[^0-9,]|', '', $pid), ","),
374 'cat' => rtrim(preg_replace('|[^0-9,-]|', '', $cat), ","),
375 'taxonomy' => empty($taxonomy) ? 'category' : $taxonomy,
376 'term_id' => rtrim(preg_replace('|[^0-9,;-]|', '', $term_id), ","),
377 'author' => rtrim(preg_replace('|[^0-9,]|', '', $author), ","),
378 'shorten_title' => [
379 'active' => ( ! empty($title_length) && Helper::is_number($title_length) && $title_length > 0 ),
380 'length' => ( ! empty($title_length) && Helper::is_number($title_length) ) ? $title_length : 0,
381 'words' => ( ! empty($title_by_words) && Helper::is_number($title_by_words) && $title_by_words > 0 ),
382 ],
383 'post-excerpt' => [
384 'active' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) && $excerpt_length > 0 ),
385 'length' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) ) ? $excerpt_length : 0,
386 'keep_format' => ( ! empty($excerpt_format) && Helper::is_number($excerpt_format) && $excerpt_format > 0 ),
387 'words' => ( ! empty($excerpt_by_words) && Helper::is_number($excerpt_by_words) && $excerpt_by_words > 0 ),
388 ],
389 'thumbnail' => [
390 'active' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ),
391 'width' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ) ? $thumbnail_width : 0,
392 'height' => ( ! empty($thumbnail_height) && Helper::is_number($thumbnail_height) && $thumbnail_height > 0 ) ? $thumbnail_height : 0,
393 ],
394 'rating' => empty($rating) ? false : $rating,
395 'stats_tag' => [
396 'comment_count' => empty($stats_comments) ? false : $stats_comments,
397 'views' => empty($stats_views) ? false : $stats_views,
398 'author' => empty($stats_author) ? false : $stats_author,
399 'date' => [
400 'active' => empty($stats_date) ? false : $stats_date,
401 'format' => empty($stats_date_format) ? 'F j, Y' : $stats_date_format
402 ],
403 'category' => empty($stats_category) ? false : $stats_category,
404 'taxonomy' => [
405 'active' => empty($stats_taxonomy) ? false : $stats_taxonomy,
406 'name' => empty($taxonomy) ? 'category' : $taxonomy,
407 ]
408 ],
409 'markup' => [
410 'custom_html' => true,
411 'wpp-start' => empty($wpp_start) ? '' : $wpp_start,
412 'wpp-end' => empty($wpp_end) ? '' : $wpp_end,
413 'title-start' => empty($header_start) ? '' : $header_start,
414 'title-end' => empty($header_end) ? '' : $header_end,
415 'post-html' => empty($post_html) ? '<li>{thumb} {title} <span class="wpp-meta post-stats">{stats}</span></li>' : $post_html
416 ],
417 'theme' => [
418 'name' => trim($theme)
419 ]
420 ];
421
422 // Post / Page / CTP filter
423 $ids = array_filter(explode(",", $shortcode_ops['pid']), 'is_numeric');
424 // Got no valid IDs, clear
425 if ( empty($ids) ) {
426 $shortcode_ops['pid'] = '';
427 }
428
429 // Category filter
430 $ids = array_filter(explode(",", $shortcode_ops['cat']), 'is_numeric');
431 // Got no valid IDs, clear
432 if ( empty($ids) ) {
433 $shortcode_ops['cat'] = '';
434 }
435
436 // Author filter
437 $ids = array_filter(explode( ",", $shortcode_ops['author']), 'is_numeric');
438 // Got no valid IDs, clear
439 if ( empty($ids) ) {
440 $shortcode_ops['author'] = '';
441 }
442
443 $shortcode_content = '';
444 $cached = false;
445
446 // is there a title defined by user?
447 if (
448 ! empty($header)
449 && ! empty($header_start)
450 && ! empty($header_end)
451 ) {
452 $shortcode_content .= htmlspecialchars_decode($header_start, ENT_QUOTES) . $header . htmlspecialchars_decode($header_end, ENT_QUOTES);
453 }
454
455 $popular_posts = $this->maybe_query($shortcode_ops);
456
457 $this->output->set_data($popular_posts->get_posts());
458 $this->output->set_public_options($shortcode_ops);
459 $this->output->build_output();
460
461 $shortcode_content .= $this->output->get_output();
462
463 // Sanitize and return shortcode HTML
464 $allowed_tags = wp_kses_allowed_html('post');
465
466 if ( isset($allowed_tags['form']) ) {
467 unset($allowed_tags['form']);
468 }
469
470 if ( ! empty($shortcode_ops['theme']['name']) ) {
471 $allowed_tags['style'] = [
472 'id' => 1,
473 'nonce' => 1,
474 ];
475 }
476
477 return wp_kses($shortcode_content, $allowed_tags);
478 }
479
480 }
481