PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 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 All 118 releases
← All changes | includes/class-tracker.php +123 -35 4.3.0 → 4.5.2 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,20 +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 ) ) {
191 - global $wp;
192 - }
193 -
194 - 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'] ) ) {
195 206 return;
196 207 }
197 208
198 - 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 + }
199 223
200 - $id = absint( $wp->query_vars['top_ten_id'] );
201 - $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 );
202 227 $activate_counter = absint( $wp->query_vars['activate_counter'] );
203 228
204 229 $is_feed = ! empty( $wp->query_vars['tptn_feed'] );
205 230 $source = $is_feed ? 1 : 0;
@@ -204,8 +229,11 @@
204 229 $is_feed = ! empty( $wp->query_vars['tptn_feed'] );
205 230 $source = $is_feed ? 1 : 0;
206 231
207 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 + }
208 236
209 237 if ( $is_feed ) {
210 238 self::output_tracking_pixel(); // Sends GIF and exits.
211 239 } else {
@@ -228,13 +256,10 @@
228 256 if ( $id > 0 ) {
229 257
230 258 $output = Counter::get_post_count( $id );
231 259
232 - header( 'content-type: application/x-javascript' );
233 - echo 'document.write("' . $output . '");'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
234 -
235 - // Stop anything else from loading as it is not needed.
236 - exit;
260 + nocache_headers();
261 + wp_send_json( array( 'count' => $output ) );
237 262 }
238 263 } else {
239 264 return;
240 265 }
@@ -242,16 +267,10 @@
242 267
243 268 /**
244 269 * Add a tracking pixel to feed content.
245 270 *
246 - * Appends a 1×1 transparent GIF to each feed item. When a feed reader
247 - * loads the image, parse_request() intercepts the request, increments
248 - * the view count, and serves the GIF. Views are merged into the same
249 - * tables as regular web views.
271 + * Feed readers that block remote images by default will not trigger the count.
250 272 *
251 - * Note: feed readers that block remote images by default will not trigger
252 - * the pixel. The count only increments when the reader actually loads images.
253 - *
254 273 * @since 4.3.0
255 274 *
256 275 * @param string $content Feed content.
257 276 * @return string Feed content with the tracker image appended.
@@ -325,15 +344,30 @@
325 344 *
326 345 * @since 2.4.0
327 346 */
328 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
329 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 +
330 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
331 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
332 364 $activate_counter = isset( $_POST['activate_counter'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['activate_counter'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
333 - $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
334 365
335 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 + }
336 370
337 371 // If the debug parameter is set then we output $str else we send a No Content header.
338 372 if ( 1 === $top_ten_debug ) {
339 373 echo esc_html( $str );
@@ -345,8 +379,38 @@
345 379 wp_die();
346 380 }
347 381
348 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 + /**
349 413 * Function to update the count in the database.
350 414 *
351 415 * @since 2.6.0
352 416 *
@@ -374,9 +438,9 @@
374 438 */
375 439 $before_update_count = apply_filters( 'tptn_before_update_count', true, $id, $blog_id, $activate_counter, $source );
376 440
377 441 if ( $id > 0 && $activate_counter > 0 && $before_update_count ) {
378 - $result = Database::append_to_funnel( $id, $blog_id, $activate_counter, $source );
442 + $result = Database::record_view( $id, $blog_id, $activate_counter, $source );
379 443 $str .= ( false === $result ) ? 'loge' : 'log' . $result;
380 444 }
381 445
382 446 /**
@@ -390,6 +454,30 @@
390 454 * @param int $activate_counter Activate counter flag.
391 455 * @param int $source Traffic source: 0 = web, 1 = feed.
392 456 */
393 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 );
394 482 }
395 483 }