Skip to content

Commit 0455459

Browse files
committed
Sync 4.3.0 features from pro: feed tracking, At a Glance widget, column fixes
* Fixed: Check for caching was referencing the incorrect setting resulting in posts never being cached.
1 parent aaa35d9 commit 0455459

12 files changed

Lines changed: 292 additions & 49 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
44

55
## Plugin Overview
66

7-
WebberZone Top 10 (free) counts daily and total post views and displays popular posts lists. Version 4.2.2. Namespace: `WebberZone\Top_Ten`. Function prefix: `tptn_`. Requires WordPress 6.6+, PHP 7.4+. DB version: `6.0`.
7+
WebberZone Top 10 (free) counts daily and total post views and displays popular posts lists. Version 4.3.0. Namespace: `WebberZone\Top_Ten`. Function prefix: `tptn_`. Requires WordPress 6.6+, PHP 7.4+. DB version: `6.0`.
88

99
Constants defined in `top-10.php`: `TOP_TEN_VERSION`, `TOP_TEN_PLUGIN_FILE`, `TOP_TEN_PLUGIN_DIR`, `TOP_TEN_PLUGIN_URL`, `TOP_TEN_DEFAULT_THUMBNAIL_URL`, `TOP_TEN_STORE_DATA` (180 days default).
1010

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Plugin Overview
66

7-
WebberZone Top 10 (free) counts daily and total post views and displays popular posts lists. Version 4.2.2. Namespace: `WebberZone\Top_Ten`. Function prefix: `tptn_`. Requires WordPress 6.6+, PHP 7.4+. DB version: `6.0`.
7+
WebberZone Top 10 (free) counts daily and total post views and displays popular posts lists. Version 4.3.0. Namespace: `WebberZone\Top_Ten`. Function prefix: `tptn_`. Requires WordPress 6.6+, PHP 7.4+. DB version: `6.0`.
88

99
Constants defined in `top-10.php`: `TOP_TEN_VERSION`, `TOP_TEN_PLUGIN_FILE`, `TOP_TEN_PLUGIN_DIR`, `TOP_TEN_PLUGIN_URL`, `TOP_TEN_DEFAULT_THUMBNAIL_URL`, `TOP_TEN_STORE_DATA` (180 days default).
1010

includes/admin/class-columns.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,39 @@ public static function add_columns_clauses( $clauses, $wp_query ) {
189189

190190
$is_daily = ( 'tptn_daily' === $orderby );
191191
$table_name = Database::get_table( $is_daily );
192-
193-
$clauses['join'] .= " LEFT OUTER JOIN {$table_name} ON {$wpdb->posts}.ID = {$table_name}.postnumber";
192+
$blog_id = get_current_blog_id();
193+
$order = ( 'ASC' === strtoupper( (string) $wp_query->get( 'order' ) ) ) ? 'ASC' : 'DESC';
194194

195195
if ( $is_daily ) {
196-
$from_date = Helpers::get_from_date();
197-
$clauses['where'] .= $wpdb->prepare( " AND {$table_name}.dp_date >= %s", $from_date ); //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
198-
$clauses['groupby'] = "{$table_name}.postnumber";
199-
$clauses['orderby'] = "SUM({$table_name}.cntaccess)";
196+
$from_date = Helpers::get_from_date();
197+
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated internally.
198+
$clauses['join'] .= $wpdb->prepare(
199+
" LEFT JOIN {$table_name} AS tptn_counts
200+
ON {$wpdb->posts}.ID = tptn_counts.postnumber
201+
AND tptn_counts.blog_id = %d
202+
AND tptn_counts.dp_date >= %s",
203+
$blog_id,
204+
$from_date
205+
);
206+
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
207+
208+
$clauses['fields'] .= ', COALESCE(SUM(tptn_counts.cntaccess), 0) AS tptn_visits';
209+
if ( empty( $clauses['groupby'] ) ) {
210+
$clauses['groupby'] = "{$wpdb->posts}.ID";
211+
} elseif ( false === strpos( $clauses['groupby'], "{$wpdb->posts}.ID" ) ) {
212+
$clauses['groupby'] .= ", {$wpdb->posts}.ID";
213+
}
200214
} else {
201-
$clauses['orderby'] = "{$table_name}.cntaccess";
215+
$clauses['fields'] .= ', COALESCE(tptn_counts.cntaccess, 0) AS tptn_visits';
216+
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated internally.
217+
$clauses['join'] .= $wpdb->prepare(
218+
" LEFT JOIN {$table_name} AS tptn_counts ON {$wpdb->posts}.ID = tptn_counts.postnumber AND tptn_counts.blog_id = %d",
219+
$blog_id
220+
);
221+
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
202222
}
203223

204-
$clauses['orderby'] .= ( 'ASC' === strtoupper( (string) $wp_query->get( 'order' ) ) ) ? ' ASC' : ' DESC';
224+
$clauses['orderby'] = "tptn_visits {$order}";
205225

206226
return apply_filters( 'tptn_posts_clauses', $clauses, $wp_query );
207227
}

includes/admin/class-dashboard-widgets.php

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,125 @@ class Dashboard_Widgets {
2828
public function __construct() {
2929
add_filter( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
3030
add_filter( 'wp_network_dashboard_setup', array( $this, 'wp_network_dashboard_setup' ) );
31+
add_filter( 'dashboard_glance_items', array( $this, 'dashboard_glance_items' ) );
32+
add_action( 'admin_head-index.php', array( $this, 'dashboard_glance_styles' ) );
33+
}
34+
35+
/**
36+
* Add Top 10 views from the current hour to the At a Glance widget.
37+
*
38+
* @since 4.3.0
39+
*
40+
* @param string[] $items Array of extra At a Glance items.
41+
* @return string[] Modified array of extra At a Glance items.
42+
*/
43+
public function dashboard_glance_items( $items ) {
44+
if ( ! current_user_can( 'manage_options' ) && ! \tptn_get_option( 'show_count_non_admins' ) ) {
45+
return $items;
46+
}
47+
48+
$current_hour = current_time( 'Y-m-d H' );
49+
$hour_views = self::get_current_hour_views();
50+
$day_views = self::get_views_for_period( current_time( 'Y-m-d 00' ), $current_hour );
51+
52+
$items[] = self::get_glance_item(
53+
$hour_views,
54+
/* translators: %s: Number of views in the last hour. */
55+
_n( '%s view in the last hour', '%s views in the last hour', $hour_views, 'top-10' ),
56+
admin_url( 'admin.php?page=tptn_dashboard' )
57+
);
58+
59+
$items[] = self::get_glance_item(
60+
$day_views,
61+
/* translators: %s: Number of views in the past day. */
62+
_n( '%s view in the past day', '%s views in the past day', $day_views, 'top-10' ),
63+
wp_nonce_url(
64+
add_query_arg(
65+
array(
66+
'page' => 'tptn_dashboard',
67+
'post-date-filter-from' => current_time( 'd M Y' ),
68+
'post-date-filter-to' => current_time( 'd M Y' ),
69+
),
70+
admin_url( 'admin.php' )
71+
),
72+
'tptn-dashboard'
73+
)
74+
);
75+
76+
return $items;
77+
}
78+
79+
/**
80+
* Add a better icon for Top 10 At a Glance items.
81+
*
82+
* @since 4.3.0
83+
*/
84+
public function dashboard_glance_styles() {
85+
?>
86+
<style>
87+
#dashboard_right_now li a.tptn-glance-views:before {
88+
content: "\f239";
89+
content: "\f239" / "";
90+
}
91+
</style>
92+
<?php
93+
}
94+
95+
/**
96+
* Get the number of views recorded in the current hour.
97+
*
98+
* Top 10 stores daily counts in hourly buckets, so this returns the current
99+
* hour bucket rather than a rolling 60-minute window.
100+
*
101+
* @since 4.3.0
102+
*
103+
* @return int Number of views in the current hour.
104+
*/
105+
public static function get_current_hour_views() {
106+
return self::get_views_for_period( current_time( 'Y-m-d H' ), current_time( 'Y-m-d H' ) );
107+
}
108+
109+
/**
110+
* Get the number of views recorded in a period.
111+
*
112+
* @since 4.3.0
113+
*
114+
* @param string $from_hour Start hour in Y-m-d H format.
115+
* @param string $to_hour End hour in Y-m-d H format.
116+
* @return int Number of views in the period.
117+
*/
118+
public static function get_views_for_period( $from_hour, $to_hour ) {
119+
global $wpdb;
120+
121+
$table = $wpdb->base_prefix . 'top_ten_daily';
122+
$sql = $wpdb->prepare(
123+
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
124+
"SELECT SUM(cntaccess) FROM {$table} WHERE dp_date >= %s AND dp_date <= %s AND blog_id = %d",
125+
$from_hour,
126+
$to_hour,
127+
get_current_blog_id()
128+
);
129+
$views = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
130+
131+
return (int) $views;
132+
}
133+
134+
/**
135+
* Build a Top 10 At a Glance item.
136+
*
137+
* @since 4.3.0
138+
*
139+
* @param int $views Views.
140+
* @param string $label Label with a %s placeholder for the formatted count.
141+
* @param string $url Link URL.
142+
* @return string At a Glance item markup.
143+
*/
144+
public static function get_glance_item( $views, $label, $url ) {
145+
return sprintf(
146+
'<a class="tptn-glance-views" href="%1$s">%2$s</a>',
147+
esc_url( $url ),
148+
esc_html( sprintf( $label, Helpers::number_format_i18n( $views ) ) )
149+
);
31150
}
32151

33152
/**
@@ -189,7 +308,18 @@ public static function pop_display( $daily = false, $page = 0, $limit = 0, $widg
189308
if ( $network ) {
190309
$output .= '<a href="' . network_admin_url( 'admin.php?page=tptn_network_pop_posts_page&orderby=daily_count&order=desc' ) . '">' . __( 'View all network daily popular posts', 'top-10' ) . '</a>';
191310
} else {
192-
$output .= '<a href="' . admin_url( 'admin.php?page=tptn_popular_posts&orderby=daily_count&order=desc' ) . '">' . __( 'View all daily popular posts', 'top-10' ) . '</a>';
311+
$output .= '<a href="' . esc_url(
312+
add_query_arg(
313+
array(
314+
'page' => 'tptn_popular_posts',
315+
'orderby' => 'daily_count',
316+
'order' => 'desc',
317+
'post-date-filter-from' => current_time( 'd M Y' ),
318+
'post-date-filter-to' => current_time( 'd M Y' ),
319+
),
320+
admin_url( 'admin.php' )
321+
)
322+
) . '">' . __( 'View all daily popular posts', 'top-10' ) . '</a>';
193323
}
194324
} elseif ( $network ) {
195325
$output .= '<a href="' . network_admin_url( 'admin.php?page=tptn_network_pop_posts_page&orderby=total_count&order=desc' ) . '">' . __( 'View all network popular posts', 'top-10' ) . '</a>';

includes/admin/class-dashboard.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function render_page() {
126126
<div id="<?php echo esc_attr( $tab_id ); ?>">
127127
<table class="form-table">
128128
<?php
129-
$output = $this->display_popular_posts( $tab_name );
129+
$output = empty( $tab_name['hide'] ) ? $this->display_popular_posts( $tab_name ) : '';
130130
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
131131
?>
132132
</table>

includes/admin/class-settings.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,14 @@ public static function settings_counter() {
487487
'type' => 'checkbox',
488488
'default' => false,
489489
),
490+
'track_feed_views' => array(
491+
'id' => 'track_feed_views',
492+
'name' => esc_html__( 'Track feed views', 'top-10' ),
493+
'desc' => esc_html__( 'Count post views from RSS/Atom feed readers via a tracking pixel. Views are added to the post\'s regular view count. Note: some feed readers block remote images by default; views are only counted when the reader loads images.', 'top-10' ),
494+
'type' => 'checkbox',
495+
'default' => false,
496+
'pro' => true,
497+
),
490498
'track_users' => array(
491499
'id' => 'track_users',
492500
'name' => esc_html__( 'Track user groups', 'top-10' ) . ':',

includes/class-top-ten-core-query.php

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -611,17 +611,10 @@ public function posts_where( $where, $query ) {
611611
}
612612

613613
/**
614-
* Modify the posts_orderby clause.
614+
* Modify the ORDER BY clause of the query.
615615
*
616616
* @since 3.0.0
617617
*
618-
* @param string $orderby The ORDER BY clause of the query.
619-
* @param \WP_Query $query The \WP_Query instance.
620-
* @return string Updated ORDER BY
621-
*/
622-
/**
623-
* Modify the ORDER BY clause of the query.
624-
*
625618
* @param string $orderby The current ORDER BY clause.
626619
* @param \WP_Query $query The current query instance.
627620
* @return string Modified ORDER BY clause.
@@ -633,19 +626,17 @@ public function posts_orderby( $orderby, $query ) {
633626
}
634627

635628
// Use WP_Query's parse_order method to get a valid order.
629+
$query_orderby = $query->get( 'orderby' );
636630
$order = method_exists( $query, 'parse_order' ) ? $query->parse_order( $query->get( 'order' ) ) : 'DESC';
637-
$orderby_count = $this->is_daily ? "SUM({$this->table_name}.cntaccess) $order" : "{$this->table_name}.cntaccess $order";
638631

639-
// If orderby is set, then this was done intentionally and we don't make any modifications.
640-
if ( ! empty( $query->get( 'orderby' ) ) ) {
641-
if ( 'visits' === $query->get( 'orderby' ) ) {
642-
$orderby = $orderby_count;
643-
}
644-
return apply_filters_ref_array( 'top_ten_query_posts_orderby', array( $orderby, &$this ) );
632+
// Preserve intentional orderby values except the plugin's visits alias.
633+
if ( is_array( $query_orderby ) && isset( $query_orderby['visits'] ) ) {
634+
$visits_order = method_exists( $query, 'parse_order' ) ? $query->parse_order( $query_orderby['visits'] ) : $order;
635+
$orderby = empty( $orderby ) ? "visits $visits_order" : "visits $visits_order, {$orderby}";
636+
} elseif ( empty( $query_orderby ) || 'visits' === $query_orderby ) {
637+
$orderby = "visits $order";
645638
}
646639

647-
$orderby = $orderby_count;
648-
649640
/**
650641
* Filters the ORDER BY clause of Top_Ten_Core_Query.
651642
*
@@ -957,6 +948,6 @@ public function the_posts( $posts, $query ) {
957948
* @return bool Whether caching should be enabled.
958949
*/
959950
protected function should_cache() {
960-
return ! empty( $this->query_args['cache'] ) && ! ( is_preview() || is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
951+
return ! empty( $this->query_args['cache_posts'] ) && ! ( is_preview() || is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
961952
}
962953
}

0 commit comments

Comments
 (0)