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 +123 -31 4.3.14.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 {
@@ -224,13 +256,10 @@
224 256 if ( $id > 0 ) {
225 257
226 258 $output = Counter::get_post_count( $id );
227 259
228 - header( 'content-type: application/x-javascript' );
229 - echo 'document.write("' . $output . '");'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
230 -
231 - // Stop anything else from loading as it is not needed.
232 - exit;
260 + nocache_headers();
261 + wp_send_json( array( 'count' => $output ) );
233 262 }
234 263 } else {
235 264 return;
236 265 }
@@ -238,16 +267,10 @@
238 267
239 268 /**
240 269 * Add a tracking pixel to feed content.
241 270 *
242 - * Appends a 1×1 transparent GIF to each feed item. When a feed reader
243 - * loads the image, parse_request() intercepts the request, increments
244 - * the view count, and serves the GIF. Views are merged into the same
245 - * tables as regular web views.
271 + * Feed readers that block remote images by default will not trigger the count.
246 272 *
247 - * Note: feed readers that block remote images by default will not trigger
248 - * the pixel. The count only increments when the reader actually loads images.
249 - *
250 273 * @since 4.3.0
251 274 *
252 275 * @param string $content Feed content.
253 276 * @return string Feed content with the tracker image appended.
@@ -321,15 +344,30 @@
321 344 *
322 345 * @since 2.4.0
323 346 */
324 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
325 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 +
326 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
327 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
328 364 $activate_counter = isset( $_POST['activate_counter'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['activate_counter'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
329 - $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
330 365
331 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 + }
332 370
333 371 // If the debug parameter is set then we output $str else we send a No Content header.
334 372 if ( 1 === $top_ten_debug ) {
335 373 echo esc_html( $str );
@@ -341,8 +379,38 @@
341 379 wp_die();
342 380 }
343 381
344 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 + /**
345 413 * Function to update the count in the database.
346 414 *
347 415 * @since 2.6.0
348 416 *
@@ -370,9 +438,9 @@
370 438 */
371 439 $before_update_count = apply_filters( 'tptn_before_update_count', true, $id, $blog_id, $activate_counter, $source );
372 440
373 441 if ( $id > 0 && $activate_counter > 0 && $before_update_count ) {
374 - $result = Database::append_to_funnel( $id, $blog_id, $activate_counter, $source );
442 + $result = Database::record_view( $id, $blog_id, $activate_counter, $source );
375 443 $str .= ( false === $result ) ? 'loge' : 'log' . $result;
376 444 }
377 445
378 446 /**
@@ -386,6 +454,30 @@
386 454 * @param int $activate_counter Activate counter flag.
387 455 * @param int $source Traffic source: 0 = web, 1 = feed.
388 456 */
389 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 );
390 482 }
391 483 }