PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.1
WebberZone Top 10 — Popular Posts v4.5.1
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
← All changes | includes/class-tracker.php +120 -25 4.4.04.5.1 View file →
@@ -15,9 +15,9 @@
15 15 exit;
16 16 }
17 17
18 18 /**
19 - * Admin Columns Class.
19 + * Tracks post views via the tracker endpoint and its AJAX handlers.
20 20 *
21 21 * @since 3.3.0
22 22 */
23 23 class Tracker {
@@ -43,12 +43,24 @@
43 43 */
44 44 public static function enqueue_scripts() {
45 45 global $post, $ajax_tptn_tracker;
46 46
47 - if ( ! is_object( $post ) ) {
47 + $is_singular = is_singular();
48 + $tracker_all_pages = (bool) \tptn_get_option( 'tracker_all_pages' );
49 + /**
50 + * Filters the site-wide context key tracked for the current request.
51 + *
52 + * @since 4.5.0
53 + *
54 + * @param string $sitewide_context Context key, or an empty string when the request is not tracked site-wide.
55 + */
56 + $sitewide_context = (string) apply_filters( 'tptn_tracker_sitewide_context', '' );
57 + $has_sitewide_context = '' !== $sitewide_context;
58 +
59 + if ( ! is_object( $post ) && ! $tracker_all_pages && ! $has_sitewide_context ) {
48 60 return;
49 61 }
50 - if ( 'draft' === $post->post_status || is_customize_preview() ) {
62 + if ( ( is_object( $post ) && 'draft' === $post->post_status ) || is_customize_preview() ) {
51 63 return;
52 64 }
53 65
54 66 $track_users = wp_parse_list( \tptn_get_option( 'track_users' ) );
@@ -53,12 +65,12 @@
53 65
54 66 $track_users = wp_parse_list( \tptn_get_option( 'track_users' ) );
55 67 $trackers = wp_parse_list( \tptn_get_option( 'trackers' ) );
56 68
57 - if ( is_singular() || \tptn_get_option( 'tracker_all_pages' ) ) {
69 + if ( $is_singular || $tracker_all_pages || $has_sitewide_context ) {
58 70
59 71 $current_user = wp_get_current_user(); // Let's get the current user.
60 - $post_author = ( (int) $current_user->ID === (int) $post->post_author ) ? true : false; // Is the current user the post author?
72 + $post_author = is_object( $post ) && ( (int) $current_user->ID === (int) $post->post_author ); // Is the current user the post author?
61 73 $current_user_admin = ( current_user_can( 'manage_options' ) ) ? true : false; // Is the current user an admin?
62 74 $current_user_editor = ( ( current_user_can( 'edit_others_posts' ) ) && ( ! current_user_can( 'manage_options' ) ) ) ? true : false; // Is the current user an editor?
63 75 $is_bot = Helpers::is_bot();
64 76
@@ -80,9 +92,9 @@
80 92 }
81 93
82 94 if ( $include_code ) {
83 95
84 - $id = is_singular() ? absint( $post->ID ) : 0;
96 + $id = $is_singular && is_object( $post ) ? absint( $post->ID ) : 0;
85 97 $blog_id = get_current_blog_id();
86 98 $activate_counter = in_array( 'overall', $trackers, true ) ? 1 : 0; // It's 1 if we're updating the overall count.
87 99 $activate_counter = $activate_counter + ( in_array( 'daily', $trackers, true ) ? 10 : 0 ); // It's 10 if we're updating the daily count.
88 100 $top_ten_debug = absint( \tptn_get_option( 'debug_mode' ) );
@@ -121,14 +133,16 @@
121 133 // Strip any query strings since we don't need them.
122 134 $home_url = strtok( $home_url, '?' );
123 135
124 136 $ajax_tptn_tracker = array(
125 - 'ajax_url' => $home_url,
126 - 'top_ten_id' => $id,
127 - 'top_ten_blog_id' => $blog_id,
128 - 'activate_counter' => $activate_counter,
129 - 'top_ten_debug' => $top_ten_debug,
130 - 'tptn_rnd' => wp_rand( 1, time() ),
137 + 'ajax_url' => $home_url,
138 + 'top_ten_id' => $id,
139 + 'top_ten_sitewide_context' => $sitewide_context,
140 + 'top_ten_blog_id' => $blog_id,
141 + 'activate_counter' => $activate_counter,
142 + 'top_ten_debug' => $top_ten_debug,
143 + 'tracker_type' => $tracker_type,
144 + 'tptn_rnd' => wp_rand( 1, time() ),
131 145 );
132 146
133 147 /**
134 148 * Filter the localize script arguments for the Top 10 tracker.
@@ -138,9 +152,9 @@
138 152 $ajax_tptn_tracker = apply_filters( 'tptn_tracker_script_args', $ajax_tptn_tracker );
139 153
140 154 wp_enqueue_script(
141 155 'tptn_tracker',
142 - plugins_url( 'includes/js/top-10-tracker.min.js', TOP_TEN_PLUGIN_FILE ),
156 + plugins_url( 'includes/js/top-10.min.js', TOP_TEN_PLUGIN_FILE ),
143 157 array(),
144 158 TOP_TEN_VERSION,
145 159 true
146 160 );
@@ -161,8 +175,9 @@
161 175 */
162 176 public static function query_vars( $vars ) {
163 177 // Add these to the list of queryvars that WP gathers.
164 178 $vars[] = 'top_ten_id';
179 + $vars[] = 'top_ten_sitewide_context';
165 180 $vars[] = 'top_ten_blog_id';
166 181 $vars[] = 'activate_counter';
167 182 $vars[] = 'view_counter';
168 183 $vars[] = 'top_ten_debug';
@@ -186,16 +201,30 @@
186 201 * @param \WP $wp Current WordPress environment instance.
187 202 */
188 203 public static function parse_request( $wp ) {
189 204
190 - if ( empty( $wp->query_vars['top_ten_id'] ) ) {
205 + if ( empty( $wp->query_vars['top_ten_id'] ) && empty( $wp->query_vars['top_ten_sitewide_context'] ) ) {
191 206 return;
192 207 }
193 208
194 - if ( array_key_exists( 'top_ten_id', $wp->query_vars ) && array_key_exists( 'activate_counter', $wp->query_vars ) ) {
209 + if (
210 + array_key_exists( 'activate_counter', $wp->query_vars )
211 + && ( array_key_exists( 'top_ten_id', $wp->query_vars ) || array_key_exists( 'top_ten_sitewide_context', $wp->query_vars ) )
212 + ) {
213 + if ( ! self::is_tracking_request_allowed() ) {
214 + if ( array_key_exists( 'top_ten_debug', $wp->query_vars ) && 1 === absint( $wp->query_vars['top_ten_debug'] ) ) {
215 + header( 'content-type: application/x-javascript' );
216 + wp_send_json( 'blocked' );
217 + } else {
218 + header( 'HTTP/1.0 204 No Content' );
219 + header( 'Cache-Control: max-age=15, s-maxage=0' );
220 + }
221 + exit;
222 + }
195 223
196 - $id = absint( $wp->query_vars['top_ten_id'] );
197 - $blog_id = absint( $wp->query_vars['top_ten_blog_id'] );
224 + $id = absint( $wp->query_vars['top_ten_id'] ?? 0 );
225 + $sitewide_context = sanitize_text_field( $wp->query_vars['top_ten_sitewide_context'] ?? '' );
226 + $blog_id = absint( $wp->query_vars['top_ten_blog_id'] ?? 0 );
198 227 $activate_counter = absint( $wp->query_vars['activate_counter'] );
199 228
200 229 $is_feed = ! empty( $wp->query_vars['tptn_feed'] );
201 230 $source = $is_feed ? 1 : 0;
@@ -200,8 +229,11 @@
200 229 $is_feed = ! empty( $wp->query_vars['tptn_feed'] );
201 230 $source = $is_feed ? 1 : 0;
202 231
203 232 $str = self::update_count( $id, $blog_id, $activate_counter, $source );
233 + if ( '' !== $sitewide_context ) {
234 + $str .= self::update_sitewide_count( $sitewide_context, $blog_id, $activate_counter, $source );
235 + }
204 236
205 237 if ( $is_feed ) {
206 238 self::output_tracking_pixel(); // Sends GIF and exits.
207 239 } else {
@@ -235,16 +267,10 @@
235 267
236 268 /**
237 269 * Add a tracking pixel to feed content.
238 270 *
239 - * Appends a 1×1 transparent GIF to each feed item. When a feed reader
240 - * loads the image, parse_request() intercepts the request, increments
241 - * the view count, and serves the GIF. Views are merged into the same
242 - * tables as regular web views.
271 + * Feed readers that block remote images by default will not trigger the count.
243 272 *
244 - * Note: feed readers that block remote images by default will not trigger
245 - * the pixel. The count only increments when the reader actually loads images.
246 - *
247 273 * @since 4.3.0
248 274 *
249 275 * @param string $content Feed content.
250 276 * @return string Feed content with the tracker image appended.
@@ -318,15 +344,30 @@
318 344 *
319 345 * @since 2.4.0
320 346 */
321 347 public static function tracker_parser() {
348 + $top_ten_debug = isset( $_POST['top_ten_debug'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['top_ten_debug'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
322 349
350 + if ( ! self::is_tracking_request_allowed() ) {
351 + if ( 1 === $top_ten_debug ) {
352 + echo esc_html( 'blocked' );
353 + wp_die();
354 + }
355 +
356 + header( 'HTTP/1.0 204 No Content' );
357 + header( 'Cache-Control: max-age=15, s-maxage=0' );
358 + wp_die( '', '', array( 'response' => 204 ) );
359 + }
360 +
323 361 $id = isset( $_POST['top_ten_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['top_ten_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
362 + $sitewide_context = isset( $_POST['top_ten_sitewide_context'] ) ? sanitize_text_field( wp_unslash( $_POST['top_ten_sitewide_context'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
324 363 $blog_id = isset( $_POST['top_ten_blog_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['top_ten_blog_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
325 364 $activate_counter = isset( $_POST['activate_counter'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['activate_counter'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
326 - $top_ten_debug = isset( $_POST['top_ten_debug'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['top_ten_debug'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
327 365
328 366 $str = self::update_count( $id, $blog_id, $activate_counter, 0 );
367 + if ( '' !== $sitewide_context ) {
368 + $str .= self::update_sitewide_count( $sitewide_context, $blog_id, $activate_counter, 0 );
369 + }
329 370
330 371 // If the debug parameter is set then we output $str else we send a No Content header.
331 372 if ( 1 === $top_ten_debug ) {
332 373 echo esc_html( $str );
@@ -338,8 +379,38 @@
338 379 wp_die();
339 380 }
340 381
341 382 /**
383 + * Check whether a tracker request should be processed.
384 + *
385 + * Cached pages can enqueue the tracker for a bot, so bot detection must also run
386 + * at the endpoint. Browser prefetches and direct navigations must not create views.
387 + *
388 + * @since 4.5.0
389 + * @return bool True when the request may be tracked.
390 + */
391 + public static function is_tracking_request_allowed() {
392 + foreach ( array( 'HTTP_SEC_PURPOSE', 'HTTP_PURPOSE' ) as $header ) {
393 + $value = isset( $_SERVER[ $header ] ) && is_string( $_SERVER[ $header ] )
394 + ? sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) )
395 + : '';
396 +
397 + if ( preg_match( '/(?:^|[\s,;])(?:prefetch|prerender)(?:$|[\s,;])/i', $value ) ) {
398 + return false;
399 + }
400 + }
401 +
402 + $fetch_mode = isset( $_SERVER['HTTP_SEC_FETCH_MODE'] ) && is_string( $_SERVER['HTTP_SEC_FETCH_MODE'] )
403 + ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_SEC_FETCH_MODE'] ) )
404 + : '';
405 + if ( 'navigate' === strtolower( $fetch_mode ) ) {
406 + return false;
407 + }
408 +
409 + return ! ( \tptn_get_option( 'no_bots' ) && Helpers::is_bot() );
410 + }
411 +
412 + /**
342 413 * Function to update the count in the database.
343 414 *
344 415 * @since 2.6.0
345 416 *
@@ -383,6 +454,30 @@
383 454 * @param int $activate_counter Activate counter flag.
384 455 * @param int $source Traffic source: 0 = web, 1 = feed.
385 456 */
386 457 return apply_filters( 'tptn_update_count', $str, $id, $blog_id, $activate_counter, $source );
458 + }
459 +
460 + /**
461 + * Update a site-wide context count.
462 + *
463 + * @param string $context Context key.
464 + * @param int $blog_id Blog ID.
465 + * @param int $activate_counter Counter flag.
466 + * @param int $source Traffic source.
467 + * @return string Response on database update.
468 + */
469 + public static function update_sitewide_count( $context, $blog_id, $activate_counter, $source = 0 ) {
470 + /**
471 + * Filters the response returned after recording a site-wide view.
472 + *
473 + * @since 4.5.0
474 + *
475 + * @param string $response Response text.
476 + * @param string $context Site-wide context key.
477 + * @param int $blog_id Blog ID.
478 + * @param int $activate_counter Which counters are active.
479 + * @param int $source Traffic source.
480 + */
481 + return (string) apply_filters( 'tptn_tracker_sitewide_count', '', $context, $blog_id, $activate_counter, $source );
387 482 }
388 483 }