=== The Hits List ===
Contributors: adamgreenwell
Tags: popular posts, post views, page views, migration, blocks
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.6.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Track post views locally, display popular posts, and migrate Top 10 data into a completely free, drop-in replacement.

== Description ==

The Hits List keeps post view tracking on your own WordPress site and gives you block, widget, and template-tag tools for displaying popular content.

Key features:

* Local view tracking stored in WordPress database tables, with hourly precision for recent activity
* Import and export your view counts as CSV or JSON
* Trending posts ranked by smoothed changes in recorded daily rates, with history coverage labels
* Refresh candidates highlighting well-read posts that have gone quiet
* Snooze refresh candidates for 30 days, undo a snooze, and record an intentional refresh with seven-day before/after reports
* CSV and JSON export of your view counts
* Overall and daily popular-post counts
* Popular Posts block and widget support with public post type selection
* Optional current-post exclusion across blocks, widgets, shortcodes, and template tags
* Optional publication-age limit across blocks, widgets, shortcodes, and template tags
* Automatic count cleanup after permanent content deletion; trash retains view history by default
* Resumable background cleanup of old orphaned count rows, with a visible removal report
* WP-CLI count removal for selected posts, with previews and multisite targeting
* WordPress dashboard cards for daily top posts, all-time top posts, and views over time
* Additional-role dashboard access controls, with existing capability filters taking precedence
* Sortable view columns for Posts, Pages, and selected public custom post types, with batched count reads
* Editor metabox controls for post counts, per-post exclusions, display suppression, and manual thumbnail URLs
* Article performance in the editor, with lifetime views, 7-/30-day charts, accessible daily tables, and history-aware comparisons
* Post, category, and tag suppression across public lists and dashboard rankings without losing view counts
* Top 10 migration compatibility with resumable rerun-safe imports, WP-CLI tools, legacy widget support, native widget conversion, cleanup for old Top 10 leftovers, and optional Top 10 plugin removal
* REST-based tracking with AJAX fallback
* Privacy-policy integration for site owners

== Installation ==

1. Upload the `the-hits-list` folder to `/wp-content/plugins/`.
2. Activate The Hits List through the Plugins screen in WordPress.
3. Go to Settings > The Hits List to configure tracking and display options.
4. If Top 10 data is detected, use the import button on the settings screen to migrate existing counts.
5. After importing and converting widgets, use Clean Up Top 10 to remove old Top 10 tables, options, transients, metadata, and cron hooks.
6. Use Remove Top 10 Plugin to deactivate and delete the old plugin when you are ready.

== Frequently Asked Questions ==

= Does this send data to a third-party service? =

No. The Hits List stores tracking data locally in your WordPress database and does not call a third-party analytics endpoint.

= Can I migrate from Top 10? =

Yes. The plugin can import compatible overall and daily counts from Top 10 tables. Imports are SQL-backed, resumable, and rerun-safe, so repeating the import does not double-count unchanged source data. Very large imports can be monitored from the settings screen or run through WP-CLI. If Top 10 is deactivated, existing Top 10 Popular Posts widgets can continue rendering through The Hits List using their saved settings, and the settings screen can convert those placements into native The Hits List widgets. After the import and widget conversion are complete, the settings screen also exposes Clean Up Top 10 and Remove Top 10 Plugin buttons to remove leftover Top 10 database data and delete the old plugin.

= How long is daily data kept? =

Daily buckets are retained for 180 days by default. Older daily rows are pruned automatically by a scheduled cleanup task.

= What does the Trend score mean? =

Trending compares the last three completed days with the preceding fourteen completed days in your site's timezone, excluding today. The score is `(recent + k) / (prior * recent_days / prior_days + k)`, where recent and prior are recorded view totals and k is 200 pseudo views in the recent window by default. A score of 1.00 means a steady recorded daily rate. It is a smoothed ranking score, not a literal growth multiplier or percentage.

The dashboard labels new posts, no recorded prior views, thin prior evidence, and unknown or incomplete history. Tracking coverage is recorded prospectively; legacy or imported rows do not establish uninterrupted tracking. Missing history can affect scores. The `thehili_trending_recent_days`, `thehili_trending_prior_days`, and `thehili_trending_smoothing` filters can adjust the windows and smoothing.

= Why might Views Over Time show insufficient history? =

The card charts the last 30 completed local days and compares them with the preceding 30 completed days. Today is excluded, and exact dates and the site's timezone are shown. Percentages require covered retained history in both periods and a nonzero recorded baseline. Unknown or incomplete coverage, a known zero baseline, and a failed data read each get a clear message. When a complete daily subset is known, its available date range is shown beside an insufficient-history report.

Expand **Daily counts and coverage** to inspect recorded totals and each day's coverage. A zero with unknown or incomplete coverage does not establish zero readership. Coverage describes prospectively observed, retained recorded counts; legacy or imported rows do not prove uninterrupted tracking, and coverage does not guarantee every browser view reached the tracker. Suppressed posts remain included in site totals.

= Can I keep a post or category out of rankings? =

Yes. Check **Suppress this post from rankings** in the post editor and save, or go to **Settings > The Hits List > Ranking Suppression**, select categories and tags by name, and save changes. Search keeps selected terms visible, and the checkboxes work without JavaScript.

A match against any selected category or tag suppresses the post from public popular/trending lists and dashboard rankings, including Refresh Candidates. **Child Categories** is off by default; enable it to include all descendants of selected categories.

View tracking, individual counts, and existing count history remain intact; normal retention still applies. Clear the post checkbox and any matching term rules to restore eligibility. Other list filters and ranking limits still determine whether the post appears. If your site caches rendered pages, purge that page cache to update lists already stored in page HTML.

= Can editors inspect an article's recent performance? =

Yes. The Hits List panel on the edit screen shows lifetime views and selectable 7-/30-day daily charts, with an accessible table. Both periods use completed days in the site's timezone, and the comparison labels insufficient history, a new article's limited baseline, and a zero prior total. Editors who can edit the article can read its performance; the admins-only metabox setting continues to restrict editable controls. Switching periods and saving ordinary content do not change counts.

= Can I limit popular lists to recently published posts? =

Yes. Set **Maximum post age (days)** in the Popular Posts block or widget, use `[thl_list max_age_days="30"]`, or pass `max_age_days` to a list template tag. Values range from 0 to 36500: 0 (the default) leaves age unlimited, 1 includes today, and 30 includes today plus the preceding 29 calendar days. Negative or invalid values leave age unlimited.

The inclusive cutoff is local midnight in the site's timezone and uses the publication date, not the last edit. This filter works with lifetime, daily/hourly, and trending rankings, separately from their view-count windows. It combines with other filters before the list limit and offset; tracking, stored counts, and individual reports remain intact.

= Can I remove an article's counts through WP-CLI? =

Yes. Preview with `wp the-hits-list delete-post-counts 123 456 --dry-run`, then remove those IDs' lifetime, daily, and hourly rows with `wp the-hits-list delete-post-counts 123 456`. The articles remain intact. All IDs are validated before any deletion, affected caches are refreshed, and the command reports per-table rows and warns about IDs with no counts.

On multisite, the default is the current WP-CLI site; `--blog-id=2` selects another existing site. Partial failures exit nonzero and report confirmed progress. Rerunning removes remaining rows; deletions are not transactional.

== Privacy ==

The Hits List stores post view counts locally in the `hits_list`, `hits_list_daily`, and `hits_list_hourly` database tables (table prefix may vary).

It does not send tracking data to a third-party analytics service.

Daily tracking buckets are retained for 180 days and hourly buckets for 48 hours by default. Older rows are removed automatically. Permanent content deletion removes its stored counts; moving content to Trash keeps its history by default.

You are responsible for describing your use of visitor analytics on your own site as needed.

== Source Code and Build Tools ==

The compiled block assets in `blocks/popular-posts/build/` are generated from the human-readable source files included in `blocks/popular-posts/src/`.

To rebuild the block assets:

1. Go to `blocks/popular-posts/`.
2. Run `npm install`.
3. Run `npm run build`.

The block build uses `@wordpress/scripts`. Runtime PHP, vanilla JavaScript, and CSS assets outside the block build are maintained directly in the plugin source.

== Changelog ==

= 1.6.0 =

* Added ranking suppression for individual posts and selected categories or tags, with optional child categories, while preserving tracking and view history.
* Added editor article-performance charts, daily tables, and history-aware comparisons for completed 7-/30-day periods.
* Added refresh-candidate snooze/undo controls and intentional-refresh reports with seven-day before/after periods.
* Corrected trending comparisons to account for unequal windows and label thin or incomplete history; improved Views Over Time coverage reports.
* Added optional current-post exclusion and maximum publication age to popular lists across blocks, widgets, shortcodes, and template tags.
* Added configurable dashboard access for additional roles and sortable, batched view columns for Posts, Pages, and public custom post types.
* Fixed orphaned counts after permanent post deletion and site removal, covering lifetime, daily, and hourly tables.
* Added a one-time, resumable background sweep of older orphaned count rows, with per-table progress and removal totals on the settings screen.
* Added WP-CLI count deletion for selected posts, with dry-run previews, multisite targeting, and partial-failure reporting.
* Restored block editor compatibility with the advertised WordPress 6.2 minimum.

= 1.5.0 =

* Added count import, so an export can be restored or merged into another site. Replace mode is safe to rerun; add mode deliberately is not, and the screen says so.
* Added hourly view tracking. Sub-day windows now resolve exactly for roughly the last two days, and the hour controls return to the block and widget.
* Added a first-image fallback for thumbnails, so a post with an inline image but no featured image still shows one.
* Added a standalone Post Views block for displaying a single post's count.

= 1.4.0 =

* Added trending, which ranks posts by how fast views are rising relative to their own baseline rather than by total views. Available in the block, widget, shortcodes, template tags, and a new dashboard card.
* Added a Refresh Candidates dashboard card listing established posts whose readership has fallen away.
* Added period comparison to the views-over-time card, showing how the window compares with the one before it.
* Gave the block and widget the same capabilities. The block can now filter by category and post ID; the widget can now render grids and list markers.
* Made daily data retention configurable from the settings screen instead of filter-only.
* Added `[thl_list]` and `[thl_views]` shortcodes, plus `[tptn_list]` and `[tptn_views]` aliases that only register when Top 10 has not claimed them, so migrating sites do not end up with broken shortcodes in post content.
* Added CSV and JSON export of view counts.
* Batched popular-post count lookups so a list render no longer issues one database query per row.
* Added a filterable object cache over popular-post queries and post counts, with `thehili_cache_ttl` to tune or disable it.
* Primed post and term caches before rendering, removing further per-row queries when filtering by category.
* Removed the block and widget hour range control. The daily table stores one row per post per day, so an hour value could not narrow a window below a day and the range a reader actually got varied with the time of day. Saved values are still honoured.

= 1.3.02 =

* Added an explicit Clean Up Top 10 section on the settings screen for removing leftover Top 10 tables, options, transients, metadata, and cron hooks after migration.
* Added an explicit Remove Top 10 Plugin action that can deactivate/delete the old plugin and run one final cleanup pass.
* Added guardrails so cleanup is blocked until active Top 10 imports and placed legacy Top 10 widgets are handled.
* Added editor metabox controls for adjusting counts, excluding individual posts, suppressing popular lists on a post, and setting manual thumbnail URLs.
* Preserved legacy Top 10 per-post metabox flags when cleaning up old Top 10 metadata.
* Confirmed compatibility with WordPress 7.1 and aligned the block build toolchain with the 7.1 release.

= 1.3.01 =

* Added public post type selection to the Popular Posts block and widget.
* Added a legacy Top 10 widget compatibility bridge so existing `widget_tptn_pop` placements keep rendering after Top 10 is deactivated.
* Added a native Top 10 widget migrator that rewrites saved sidebar placements while preserving third-party widget metadata.
* Reworked Top 10 imports to run in resumable batches with status, progress, cancel/restart controls, diagnostics, and WP-CLI commands for large migrations.
* Reworked Top 10 import tracking to store large rerun state in database tables instead of oversized options.
* Improved Top 10 import error messages and asset cache busting in the admin UI.
* Added the `get_thl_posts()` helper alias for older integration examples.
* Avoided empty `.thl-meta` wrappers when all post meta display options are disabled.
* Confirmed compatibility metadata for WordPress 7.0.

= 1.3.0 =

* Added WordPress dashboard cards for daily top posts, all-time top posts, and views over time.
* Added post list table columns for total, daily, and condensed view counts.
* Hardened public REST tracking and counter endpoints.
* Removed the legacy query-string tracking endpoint.

= 1.2.0 =

* Normalized tracking to use server-derived blog IDs and validated post targets.
* Reworked daily tracking into true site-local day buckets with upgrade migration support.
* Added rerun-safe Top 10 import state handling.

== Upgrade Notice ==

= 1.6.0 =

Adds ranking controls and article insights. A background sweep removes counts only when their content no longer exists; live content and Trash history are preserved. Review cleanup progress in Settings > The Hits List.
