WP Manifestindependent plugin directory
manifest / integrations / dataflair-toplists

DataFlair Toplists self-updates

WordPress Plugin to Manage Brands and Toplists

by DataFlair · github.com/dataflairai/dataflair-toplists · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/dataflairai/dataflair-toplists/archive/refs/heads/main.zip

Ships its own WordPress updater (Plugin Update Checker), so new versions show up under Dashboard → Updates.

A WordPress plugin that connects your site to the DataFlair affiliate management platform and renders fully styled casino and sportsbook toplist blocks.


What is DataFlair?

DataFlair is a white-label iGaming data service built for casino and sportsbook affiliate publishers. It lets you manage your entire brand catalogue, bonus offers, promo codes, affiliate tracking links, geo rules, and rating data in one central dashboard, then distribute that data to all your WordPress affiliate sites simultaneously.

This plugin is the WordPress-side receiver. It syncs your toplists and brands from the DataFlair API, stores them locally in custom database tables, and renders fully styled casino and sportsbook comparison cards on any page or post, with no live API calls on the front end.


How It Works

  1. Your DataFlair account holds your brand catalogue and toplist configurations (e.g. "Top 10 Casinos in India", "Best Sportsbooks in Italy").
  2. The plugin syncs this data from the DataFlair API on a configurable schedule and caches it locally.
  3. You place the DataFlair Gutenberg block or [dataflair_toplist id="123"] shortcode on any page.
  4. The plugin renders the full toplist from cached data, fast and with no live API dependency on page load.

Features

Toplist Sync

  • Syncs from DataFlair API v1 and v2
  • Full sync and incremental sync modes with conflict detection and preview before overwriting
  • Supports multiple geo-editions, rotation schedules, and locked item positions
  • Stores complete offer, tracker, and geo data as JSON for flexible querying
  • Paginated API fetch handles large brand catalogues automatically

Webhook Sync

  • DataFlair pushes toplist and brand changes to the site the moment they happen, instead of waiting for the next scheduled sync
  • A single "Enable webhook sync" checkbox on DataFlair → Settings → API Connection self-registers the site with DataFlair automatically, reusing the existing API token, no separate credential to manage
  • Live status shown underneath the checkbox: receiving, no activity yet, or a rejected-delivery reason
  • Deliveries are HMAC-SHA256 signed against a per-site secret generated once on first enable, and verified before anything else runs
  • The receiver is idempotent (a retried or duplicate delivery is a safe no-op) and tenant-scoped (rejects a delivery meant for a different DataFlair tenant)
  • toplist.published re-fetches just that toplist; brand.status_changed/brand.updated re-fetches just that brand, rather than re-syncing the full catalogue

Brand Management

  • Syncs your full brand catalogue into a local database table
  • Stores name, slug, logo, star rating, licenses, payment methods, classification types, and restricted countries
  • Admin screen at DataFlair → Brands shows all synced brands with their affiliate data
  • Review URL Override: per-brand custom review URL that wins over all automatic URL generation across the entire site. Field locks after saving and unlocks on Edit.

Casino Card Rendering

  • Renders fully styled casino cards showing: brand logo, name, star rating, bonus offer text, promo code copy button, feature list, affiliate CTA, and Read Review link (the Read Review control appears only when a published review exists or a manual review URL override is set)
  • Promo codes render as a pill-shaped copy-to-clipboard button, matching the design of standalone review pages
  • Review URL resolution priority: manual override, published review post permalink, auto-generated /reviews/{slug}/, affiliate CTA link; published reviews are also matched by _review_brand_id when the live review slug differs from the API slug (for example …-india vs base slug)
  • Supports multiple product types (casino, sportsbook, poker) with type-aware labels
  • Render-time read-only guarantee (1.10.8+): the render chain never issues HTTP, never sideloads media, and never writes to the review CPT. Logo URLs and review-post IDs are pre-computed at sync time on wp_dataflair_brands (local_logo_url, cached_review_post_id) and read verbatim by the template. Enforced by RenderIsReadOnlyTest.

WP-CLI

  • wp dataflair sync [--only=toplists|brands] — runs a full sync. Exits non-zero on failure so a real system cron can react, and backs off automatically on API rate limits. The plugin ships no WP-Cron by design, so this is the supported way to schedule syncs.
  • wp dataflair logs [--since=15m] [--level=warning] [--limit=50] — tails DataFlair log entries.
  • wp dataflair reconcile-reviews [--batch=500] [--dry-run] — backfills cached_review_post_id for existing brand rows. Run once after upgrading to 1.10.8.

Gutenberg Block & Shortcode

  • Native WordPress block with inspector controls for toplist selection, item limit, and display options
  • Includes a testing-focused accordion table layout option to inspect synced data without wide horizontal-scroll tables
  • Server-side rendered, always reflects live synced data
  • Pros and cons overrides in the block editor use stable brand and item IDs when available, so custom copy survives reordered toplists and refreshed sync payloads
  • Shortcode: [dataflair_toplist id="123" limit="10"] works anywhere

Automatic Updates

  • Self-updating via GitHub releases, no WordPress.org required
  • WordPress shows native update notifications when a new release is published on GitHub
  • Powered by plugin-update-checker v5.6, release-based (not branch-based)

Alternative Toplists

  • Geo-specific fallback toplists for better regional conversion
  • Set per-country or per-market alternatives in the admin panel

Admin Interface

  • DataFlair → Dashboard: API health tile, stat tiles (brands synced, toplists, last sync + next-run), recent sync activity feed, scheduled jobs card, shortcode usage count with copy button. One-click Sync Brands and Sync Toplists buttons with live toast feedback.
  • DataFlair → Toplists: search + sort, bulk re-sync and bulk delete, per-row accordion showing Items (position/brand/offer/status pill) and Raw JSON (copy + download) tabs.
  • DataFlair → Brands: full brand table with review URL override inline-edit cell.
  • DataFlair → Tools: Tests runner including the API Contract Check diagnostic (per-test Run + Run All, persisted results), Logs tab (filtered [DataFlair] debug.log entries with severity colouring + Download), API Preview tab.
  • DataFlair → Settings: API Connection (bearer token + Test Connection), Customizations (colour pickers with live preview), Sync Schedule (cadence, retry count, alert email — reschedules WP-Cron on save), Geo-Targeting (site-level on/off toggle for the render gate). Dirty-state amber pill + beforeunload guard.
  • REST API endpoints for the block editor (/wp-json/dataflair/v1/toplists, /wp-json/dataflair/v1/casinos)

Security

  • Nonce verification on all AJAX actions
  • manage_options capability check on all admin routes
  • SQL injection prevention via $wpdb->prepare()
  • XSS protection via esc_html(), esc_attr(), esc_url()
  • Direct file execution blocked via ABSPATH check

Installation

  1. Upload the dataflair-toplists folder to /wp-content/plugins/
  2. Activate the plugin in Plugins → Installed Plugins
  3. Go to DataFlair → Settings and enter your DataFlair API key and endpoint
  4. Click Sync Now to pull your first batch of toplists and brands

Dependencies (vendor/) are committed to the repository, so no composer install is needed on the server.


Usage

Gutenberg Block

Add the DataFlair Toplist block to any page or post. In the block inspector, select a toplist and set an item limit.

Shortcode

[dataflair_toplist id="123" limit="10"]
Attribute Required Description
id Yes The DataFlair API toplist ID
limit No Maximum number of brands to display
title No Override the toplist display title

Requirements

  • WordPress 6.3+
  • PHP 8.1+
  • MySQL 5.7+ or MariaDB 10.3+ (for JSON column support)

Database Tables

Table Purpose
wp_dataflair_toplists Toplist JSON blobs synced from API
wp_dataflair_brands Brand catalogue synced from API
wp_dataflair_alternative_toplists Geo to alternative toplist mappings

Development

Requirements

  • PHP 8.1+
  • Node.js 18+ and npm (for Gutenberg block compilation)

Build Assets

npm install
npm run build    # production build
npm run start    # watch mode for development

Tests

./vendor/bin/phpunit

PHPUnit suite covering sync logic, brand management, REST API, auto-update wiring, and casino card rendering.


File Structure

dataflair-toplists/
├── dataflair-toplists.php          Main plugin file
├── composer.json
├── composer.lock
├── vendor/                         Composer dependencies (committed)
├── build/                          Compiled Gutenberg block assets
├── includes/
│   ├── render-casino-card.php      Casino card HTML template (read-only)
│   ├── ProductTypeLabels.php       Label map for product types
│   ├── DataIntegrityChecker.php    Validates API response structure
│   └── Cli/
│       └── ReconcileReviewsCommand.php, SyncCommand.php  wp dataflair reconcile-reviews
├── src/                            Gutenberg block source (JS/JSX)
├── tests/
│   └── phpunit/                    PHPUnit test suite
├── docs/
│   └── plans/                      Parked feature plans
└── README.md

Upgrading

To 2.1.8

2.1.8 is the Phase 9.12 shortcode + campaign redirect extraction release. The two remaining public entry points leave dataflair-toplists.php for dedicated single-responsibility classes under src/Frontend/Shortcode/ and src/Frontend/Redirect/. No operator action required, no DB migration, no config change.

What moved out of dataflair-toplists.php:

  • Frontend\Shortcode\ToplistShortcode — replaces inline toplist_shortcode(). Validates attrs, performs the slug-or-id ToplistsRepository lookup, runs the H7 batched BrandMetaPrefetcher once before iterating items, then dispatches each item to CardRenderer::render(CasinoCardVM) (cards layout) or to TableRenderer::render(ToplistTableVM) (table layouts).
  • Frontend\Shortcode\ShortcodeRegistrar — owns the add_shortcode('dataflair_toplist', …) registration. Wired through a generic callable so heavy-weight orchestrator construction is deferred to first shortcode invocation — every other plugin has had a chance to register its dataflair_card_renderer / dataflair_table_renderer filters before the renderer resolves.
  • Frontend\Redirect\CampaignRedirectHandler — replaces inline handle_campaign_redirect(). Hooks template_redirect, validates the campaign param, increments the per-campaign hit-counter transient, then wp_safe_redirect()s to the brand affiliate URL.

What stays the same:

  • The shortcode HTML output is byte-identical on tier-S, tier-Sigma, and tier-L fixtures.
  • [dataflair_toplist] shortcode name, attribute schema, and DOM markup unchanged.
  • Campaign redirect URL pattern (/go/?campaign=…) and hit-counter transient unchanged.
  • Phase 0B invariants (render-time read-only, H7 prefetch ordering, no cron) untouched.
  • No public option, table, AJAX action, or REST route changed.

Tests: 21 new unit tests pin attribute validation, repository lookup, prefetch ordering, dispatch, the registrar's function_exists guard, the campaign happy path, the invalid-campaign 404, the already-redirected guard, and the per-campaign hit-counter transient. Suite size 553 → 574 tests, 1,270 assertions, all green. dataflair-toplists.php LOC drops by ~177.

The two extracted god-class methods remain as one-line delegators through v2.1.x. They are deleted in v3.0.0 (Phase 9.13 — god-class symbol removal).

To 2.1.7

2.1.7 is the Phase 9.11 HTTP / URL / Support utility extraction release. Seven stateless helpers leave dataflair-toplists.php for dedicated single-responsibility classes under src/Http/ and src/Support/. No operator action required, no DB migration, no config change.

What moved out of dataflair-toplists.php:

  • Support\UrlValidator — replaces inline is_local_url(). Classifies hosts as local-dev (.test, .local, .localhost, .invalid, .example, plus localhost / 127.0.0.1).
  • Support\UrlTransformer — replaces inline maybe_force_https(). Rewrites http://https:// for non-local URLs (the production redirect strips Authorization headers; up-front HTTPS avoids the rewrite).
  • Support\EnvironmentDetector — replaces inline is_running_in_docker(). Three-way detection: /.dockerenv, /proc/1/cgroup keywords, host.docker.internal DNS resolution. Currently unused after Phase 0A but kept as a discrete unit.
  • Http\ApiBaseUrlDetector — replaces inline get_api_base_url(). Three-tier resolution: stored option → endpoints option (with cache-back) → https://sigma.dataflair.ai/api/v1 fallback. Strips trailing path beyond /api/vN.
  • Http\BrandsApiUrlBuilder — replaces inline get_brands_api_url(). Respects dataflair_brands_api_version (v1 default, v2 opt-in); appends ?page=N.
  • Http\ApiErrorFormatter — replaces inline build_detailed_api_error(). Owns the long status-code switch (401 Basic vs Bearer vs HTML, 403/404/419/429/500/502-504/default), producing actionable admin-UI guidance.
  • Support\RelativeTimeFormatter — replaces inline time_ago() and time_until(). Emits "3 minutes ago" / "in 3 minutes" labels.

What stays the same:

  • The eight extracted god-class methods remain as one-line delegators — call sites and behaviour unchanged.
  • Phase 0B invariants (HTTPS-force, render-time read-only, no cron) untouched.
  • No public option, table, shortcode, AJAX action, or REST route changed.

Tests: 50 new unit tests pin behaviour at the boundary. Suite size 503 → 553 tests, 1,213 assertions, all green. dataflair-toplists.php LOC drops 1,772 → 1,700.

The eight god-class delegators stay in place through v2.1.x. They are deleted in v3.0.0 (Phase 9.13 — god-class symbol removal).

To 2.1.6

2.1.6 is the Phase 9.10 sync pipeline helpers extraction release. Seven helper methods leave dataflair-toplists.php for dedicated single-responsibility classes under src/Sync/ and src/Database/. No operator action required, no DB migration, no config change.

What moved out of dataflair-toplists.php:

  • EndpointDiscovery (src/Sync/) — replaces inline discover_toplist_endpoints(). Walks /toplists?per_page=15&page=N until meta.last_page is reached, returns the show-endpoint URLs. Closure-injected base URL today; replaced by Http\ApiBaseUrlDetector in v2.1.7.
  • ToplistFetcher (src/Sync/) — replaces inline fetch_and_store_toplist(). Owns the GET → JSON parse → data.id guard pipeline; delegates the actual upsert to ToplistDataStore.
  • ToplistDataStore (src/Database/) — replaces inline store_toplist_data(). Owns the wp_dataflair_toplists upsert: integrity validation via DataFlair_DataIntegrityChecker, the canonical ten-column row map, format strings, and the api_toplist_id upsert key.
  • TransientCleaner (src/Sync/) — replaces inline clear_tracker_transients(). Phase 0B H10. Chunked DELETE at 1,000 rows/statement against both _transient_dataflair_tracker_% + _transient_timeout_dataflair_tracker_%. Accepts an optional WallClockBudget for cooperative bail-out.
  • PaginatedDeleter (src/Database/) — replaces inline delete_all_paginated(). Phase 0B H11. Whitelist-enforced chunked DELETE at clamped 50–5,000 rows/chunk; replaces TRUNCATE TABLE (which is not safely replicable on managed-MySQL hosts).
  • JsonValueCollector (src/Database/) — replaces inline collect_distinct_csv_values(). Column-whitelisted DISTINCT collector for CSV-shaped columns (licenses, top_geos, product_types); trims, dedups, sorts.
  • LogoSync (src/Sync/) — replaces inline download_brand_logo() thin wrapper. Sync-side facade onto the existing Http\LogoDownloader.

Render-time read-only invariant preserved: none of the new classes are reachable from the casino-card render path. RenderIsReadOnlyTest continues to enforce this.

dataflair-toplists.php drops by ~167 LOC in this phase (1,939 → 1,772). The seven god-class methods become one-line delegators wired through lazy Container getters. Closure-based DI (\Closure::fromCallable([$this, ...])) keeps still-private god-class helpers (get_api_base_url, build_detailed_api_error) wired through without breaking the strangler-fig contract — both extract to Http\ classes in v2.1.7. Test suite: 503 tests, 1,151 assertions, all green (+25 new unit tests).

To 2.1.5

2.1.5 is the Phase 9.9 review post manager + brand-meta extraction release. Seven helper methods leave dataflair-toplists.php for dedicated single-responsibility classes under src/Frontend/Content/ and src/Frontend/Render/. No operator action required, no DB migration, no config change.

What moved out of dataflair-toplists.php:

  • ReviewPostFinder (src/Frontend/Content/) — replaces inline find_review_post_by_brand_meta(). Owns the slug-tolerant direct-SQL join lookup that finds an existing review CPT when the post slug differs from the API brand slug.
  • ReviewPostManager (src/Frontend/Content/) — replaces inline get_or_create_review_post(). Owns the get-or-create flow: slug match → brand-meta finder → auto-create draft, plus the eight _review_* meta writes.
  • ReviewPostBatchFinder (src/Frontend/Content/) — replaces inline find_review_posts_by_brand_metas(). Wraps the H8 batched lookup, delegating to BrandsRepository::findReviewPostsByApiBrandIds.
  • BrandMetaPrefetcher (src/Frontend/Render/) — replaces inline prefetch_brand_metas_for_items(). Owns the H7 prefetch pipeline: a single IN(…) batch via BrandsRepository::findManyByApiBrandIds plus inline IN(…) for slug + name fallbacks.
  • BrandMetaLookup (src/Frontend/Render/) — replaces inline lookup_brand_meta_from_map(). Pure helper. Owns the cascading per-card resolution from the prefetched map (api_brand_ididslugname).
  • SyncLabelFormatter (src/Frontend/Render/) — replaces inline format_last_sync_label(). Owns the legacy/new option-name fallback and the relative-time math behind the "Last sync: …" admin labels.

Render-time read-only invariant preserved: ReviewPostManager is not called from the casino-card render path — only from sync, WP-CLI reconcile, and admin paths. RenderIsReadOnlyTest continues to enforce this.

dataflair-toplists.php drops by ~200 LOC in this phase. The seven god-class methods become one-line delegators wired through lazy Container getters. resolve_pros_cons_for_table_item() deleted outright (the trait-based ProsConsResolver was already authoritative). Test suite: 478 tests, 1,111 assertions, all green (+26 new tests).

To 2.1.4

2.1.4 is the Phase 9.8 frontend assets + Alpine.js extraction release. The five frontend asset methods leave dataflair-toplists.php for dedicated single-responsibility classes under src/Frontend/Assets/. No operator action required, no DB migration, no config change.

What moved out of dataflair-toplists.php into src/Frontend/Assets/:

  • StylesEnqueuer — replaces inline enqueue_frontend_assets(). Owns the dataflair-toplists stylesheet handle with filemtime cache busting.
  • AlpineJsEnqueuer — replaces inline maybe_enqueue_alpine(). Owns the conditional CDN-load decision: shortcode + block detection across the current post, queried posts, and widgets; four-way already-enqueued check before falling through to the CDN.
  • PromoCopyScript — replaces inline enqueue_promo_copy_script(). Owns the once-per-page copy-to-clipboard footer script. The dataset.promoBound guard preserved verbatim.
  • AlpineDeferAttribute — replaces inline add_alpine_defer_attribute(). Filters script_loader_tag to add defer only when the plugin enqueued Alpine itself (theme/other-plugin Alpine instances are not modified).
  • WidgetShortcodeDetector — replaces inline check_widget_for_shortcode(). Sets the cross-class shortcode-used flag from the widget_text filter.

Hook ordering preserved byte-for-byte: stylesheet at wp_enqueue_scripts, Alpine at wp_footer priority 5, promo-copy at wp_footer priority 20, widget detection at widget_text priority 10. Alpine URL still filterable via dataflair_alpinejs_url.

dataflair-toplists.php shrinks from ~2,347 → ~2,165 LOC (−182). Test suite: 452 tests, 1,062 assertions, all green (+18 new tests).

To 2.1.3

2.1.3 is the Phase 9.7 AJAX handler extraction release. The eleven remaining ajax_* methods leave dataflair-toplists.php for dedicated single-responsibility handler classes under src/Admin/Ajax/. No operator action required, no DB migration, no config change — every wp_ajax_dataflair_* action remains identical to v2.1.2 (same nonce names, same expected POST keys, same JSON response shape).

What moved out of dataflair-toplists.php into src/Admin/Ajax/:

  • SaveSettingsHandler, FetchAllToplistsHandler, SyncToplistsBatchHandler, SyncBrandsBatchHandler, FetchAllBrandsHandler — sync- and settings-related AJAX surfaces.
  • GetAlternativeToplistsHandler, SaveAlternativeToplistHandler, DeleteAlternativeToplistHandler, GetAvailableGeosHandler — alternative-toplist CRUD + geo discovery surface.
  • ApiPreviewHandler — admin API preview tab.
  • SaveReviewUrlHandler — per-brand review URL override save.

Each handler implements AjaxHandlerInterface::handle(array $request): array. Nonce + capability checks remain centralised in AjaxRouter. The directory src/Admin/Handlers/ was renamed to src/Admin/Ajax/ per the v2.1.x plan delta map; namespace declarations and use statements were updated accordingly.

dataflair-toplists.php shrinks from ~2,773 → ~2,347 LOC. grep -c "function ajax_" dataflair-toplists.php now returns 0. Full test suite: 434 tests, 1,028 assertions, all green.

To 2.1.2

2.1.2 is the Phase 9.6 admin UI extraction release. The two largest remaining inline page bodies — settings_page() (~705 LOC) and brands_page() (~1,237 LOC) — leave the god-class for dedicated owners under src/Admin/Pages/. Three more admin-side registrars come along for the ride. No operator action required, no DB migration, no config change — every admin URL, AJAX action, settings option name, capability check, nonce, page slug, and submenu position is preserved byte-for-byte.

What moved out of dataflair-toplists.php into src/:

  • \DataFlair\Toplists\Admin\Pages\SettingsPage — replaces inline settings_page(). Constructor takes typed \Closure dependencies (apiBaseUrlResolver, lastSyncLabelFormatter) so the still-private god-class helpers are reachable without exposing a wider class surface.
  • \DataFlair\Toplists\Admin\Pages\BrandsPage — replaces inline brands_page(). Same closure-injection pattern (distinctCsvValuesCollector, lastSyncLabelFormatter). Markup, jQuery wiring, AJAX endpoints, pagination shape, and filter-dropdown shape preserved byte-for-byte.
  • \DataFlair\Toplists\Admin\MenuRegistrar — owns add_menu_page + the two add_submenu_page calls. Top-level menu icon (dashicons-list-view) and position (30) unchanged.
  • \DataFlair\Toplists\Admin\SettingsRegistrar — owns the nine register_setting calls under the dataflair_settings group. The duplicate dataflair_api_base_url registration in v2.1.1 is preserved here for byte parity.
  • \DataFlair\Toplists\Admin\Notices\PermalinkNotice — owns the plain-permalinks admin warning that prompts the operator to flip permalink mode in WP settings.

Plugin::registerHooks() now wires all five new admin classes through the lazy Container. Strangler-fig contract on DataFlair_Toplists::get_instance() is untouched. Suite at 434 tests, 1,028 assertions, all green.

To 2.1.1

2.1.1 is the Phase 9.5 WPPB-style bootstrap decoupling release. The 5,600-line god-class gave up five single-responsibility chunks to their own dedicated classes under src/, following the WordPress Plugin Boilerplate layout. No operator action required, no DB migration, no config change — every hook, shortcode, block, REST route, AJAX action, and option is preserved byte-for-byte.

What moved out of dataflair-toplists.php into src/:

  • \DataFlair\Toplists\Admin\PluginInfoFilter — the plugins_api "View details" popup (description, changelog, banners).
  • \DataFlair\Toplists\UpdateChecker\GithubUpdateChecker — the YahnisElsts PUC v5 bootstrap + enableReleaseAssets() wiring.
  • \DataFlair\Toplists\I18nload_plugin_textdomain on init (previously the textdomain was declared but never actually loaded).
  • \DataFlair\Toplists\Lifecycle\{Activator, Deactivator}register_activation_hook / register_deactivation_hook targets. The god-class activate() / deactivate() methods are now thin delegators.
  • \DataFlair\Toplists\Database\SchemaMigratorcheck_database_upgrade(), ensure_tables_exist(), ensure_brands_external_id_index(), ensure_alternative_toplists_table(), supports_json_type(), migrate_to_json_type(). All god-class method signatures remain as delegators for backwards compat with any downstream caller.

Plugin::boot() now owns the one-per-request wiring of all four registrars (PluginInfoFilter, I18n, GithubUpdateChecker, SchemaMigrator). The strangler-fig contract on DataFlair_Toplists::get_instance() is untouched — every deprecated path still resolves.

To 2.1.0

2.1.0 closes the v2.0.x migration window on DataFlair_Toplists::get_instance(). Strict-deprecation warnings are now default-on: any call to get_instance() from outside DATAFLAIR_PLUGIN_DIR emits E_USER_DEPRECATED once per unique caller file/line per request, pointing to \DataFlair\Toplists\Plugin::boot(). Internal callers (the god-class's own hook-dispatch re-entry, extracted delegators under src/) are filtered out so error_log sees signal, not noise.

Sites that are mid-migration can silence the notices without breaking anything:

add_filter('dataflair_strict_deprecation', '__return_false');

This remains a supported opt-out for the v2.1.x line. The class symbol itself stays in place — planned removal is tracked for v3.0.0, after the remaining ~80 god-class methods (shortcode, schema upgrades, private DB helpers) extract incrementally during v2.1.x point releases.

To 2.0.0

2.0.0 is the Phase 8 canonical bootstrap seam release. The plugin gains a new entry point, \DataFlair\Toplists\Plugin::boot(), backed by a hand-written lazy service container (\DataFlair\Toplists\Container). The legacy DataFlair_Toplists::get_instance() entry point is deprecated but fully functional through the entire v2.0.x line — the god-class continues to own WordPress hook registrations as a strangler-fig shim. Scheduled removal: v2.1.0.

  • No operator action required for day-to-day use. Every hook, option, table, shortcode, block, REST route, and AJAX action is preserved byte-for-byte.
  • Downstream integrators calling DataFlair_Toplists::get_instance() from a theme, child plugin, or mu-plugin have the entire v2.0.x line to migrate. See UPGRADING.md for the full migration guide.

Recommended pattern:

// Legacy (v1.x → v2.0.x, works but deprecated)
$legacy = DataFlair_Toplists::get_instance();

// Canonical (v2.0.0+)
$plugin    = \DataFlair\Toplists\Plugin::boot();
$container = $plugin->container();
$logger    = $container->get('logger');

Strict-mode deprecation notices (E_USER_DEPRECATED) are opt-in via add_filter('dataflair_strict_deprecation', '__return_true'); — off by default so sites that haven't migrated yet aren't flooded with notices on every internal hook dispatch.

To 1.15.1

1.15.1 is the Phase 7 block registrars release. No operator action required, no DB migration, no config change. The public Gutenberg block contract is preserved byte-for-byte:

  • Block name dataflair-toplists/toplist — unchanged.
  • Block attributes — unchanged (sourced from build/block.json, falls back to src/block.json).
  • Block render output — byte-identical to v1.15.0 for every attribute combination (the render callback still delegates to [dataflair_toplist] shortcode under the hood).
  • Editor CSS handle dataflair-toplist-editor — unchanged.

Internal refactor only: register_block_type is now owned by DataFlair\Toplists\Block\BlockRegistrar, the render callback lives on DataFlair\Toplists\Block\ToplistBlock, and the editor-assets enqueue lives on DataFlair\Toplists\Block\EditorAssets. The god-class's register_block(), render_block($attributes), and enqueue_editor_assets() methods are thin delegators — any downstream code still holding references to those callables continues to work unchanged.

To 1.15.0

1.15.0 is the Phase 6 REST endpoint extraction release. No operator action required, no DB migration, no config change. The public REST surface is preserved byte-for-byte:

  • GET /wp-json/dataflair/v1/toplists — unchanged response shape ([{value, label}, …]).
  • GET /wp-json/dataflair/v1/toplists/{id}/casinos — H12 pagination unchanged (?page, ?per_page default 20 max 100, ?full=1 for the legacy verbose shape), X-WP-Total + X-WP-TotalPages headers emitted on every response.
  • GET /wp-json/dataflair/v1/health — unchanged {status, toplists, plugin_ver, db_error} envelope, still manage_options-gated.

Internal refactor only: the three routes are now registered by DataFlair\Toplists\Rest\RestRouter and served by per-endpoint controllers under DataFlair\Toplists\Rest\Controllers\*. The god-class's register_rest_routes(), get_toplists_rest(), and get_toplist_casinos_rest() methods are now thin delegators — any downstream code still holding references to those callables continues to work unchanged.

To 1.14.0

1.14.0 is the Phase 5 admin pages + AJAX router release. No operator action required, no DB migration, no option rename — every wp_ajax_dataflair_* action name, nonce action, payload shape, and admin-JS integration is preserved byte-for-byte. The god-class's 11 ajax_* methods remain in place (they will stay until Phase 8 — shim birth) so any downstream code that invoked them directly continues to work.

Breaking for direct-include integrators only: the casino-card template forwarding shim at includes/render-casino-card.php — deprecated in v1.13.0 with an explicit one-release removal notice — is deleted in 1.14.0. Downstream themes or plugins that were still including the old path must update to the new location at views/frontend/casino-card.php. Shortcode users, block users, and anyone consuming the rendered HTML are unaffected.

Downstream integrators who want to supply a custom AJAX handler can replace the handler's dependencies via the existing repository/service filters (dataflair_brands_repository, dataflair_toplists_repository, dataflair_alternatives_repository, dataflair_toplist_sync_service, dataflair_brand_sync_service, dataflair_api_client). Swapping the AdminBootstrap itself is intentionally not exposed — the bootstrap is a thin wiring seam, not a public contract.

To 1.13.0

1.13.0 is the Phase 4 rendering + ViewModels release. No operator action required, no DB migration, no option rename — the god-class render methods retain their signatures and return byte-identical HTML. The casino-card template has moved from includes/render-casino-card.php to views/frontend/casino-card.php, but the old path is preserved as a forwarding shim for one release. Downstream integrators who include the template directly should update to the new path before 1.14.0 ships.

Downstream integrators who want to supply a custom renderer can now register one via the dataflair_card_renderer or dataflair_table_renderer filter. Implement the matching interface (CardRendererInterface or TableRendererInterface) and return your instance; the plugin will consume it in place of the default. Filter returns that do not implement the documented interface are rejected and the default kept.

To 1.11.2

1.11.2 is the Phase 1 observability foundation release. Additive only — the old code paths continue to work unchanged. No operator action required.

Downstream integrators who want to capture structured DataFlair events in Sentry or another log aggregator can now register a custom logger via the dataflair_logger filter. Implement DataFlair\Toplists\Logging\LoggerInterface (8 PSR-3-style methods) and return your instance from the filter; subsequent sync, render, and HTTP calls will route through it. The dataflair_logger_level filter controls the minimum level (default: notice). Out-of-the-box behaviour is unchanged: the default ErrorLogLogger writes to the same error_log() destination the plugin used before.

Six telemetry hooks are now emitted at named call sites — dataflair_sync_batch_started, dataflair_sync_batch_finished, dataflair_sync_item_failed, dataflair_render_started, dataflair_render_finished, dataflair_http_call. Structured payloads include elapsed_seconds, memory_peak, pagination, and HTTP size/status. These are the stable telemetry points every later extraction phase will preserve byte-for-byte.

A one-time option rename migration (gated by dataflair_options_renamed_v1_11_2) copies dataflair_last_toplists_cron_run to dataflair_last_toplists_sync (and brands equivalent). The legacy names continue to be written in parallel for one release; format_last_sync_label() falls back to the legacy name when the new one is empty.

New WP-CLI tail: wp dataflair logs [--since=15m] [--level=warning] [--limit=200].

To 1.11.1

1.11.1 is the Phase 0.5 perf-rig + CI gate release. Pure internal tooling — no production-facing behaviour change. Upgrade is a drop-in.

Operators who want to run the perf gate locally need WP-CLI on $PATH and can then invoke composer perf from the plugin root. See docs/PERF.md for thresholds, tiers, scenarios, and how to read the probe output. CI runs the gate on every PR targeting epic/refactor-april or main.

To 1.11.0

1.11.0 is the Phase 0B safety-rails release. It removes the WP-cron auto-sync machinery entirely — sync now runs only when an operator triggers it from the admin Tools page or via WP-CLI. A one-time migration clears legacy cron schedules from prior installs (gated by the persistent option dataflair_cron_cleared_v1_11); nothing on the operator's side to do.

No database migration is required beyond the automatic check_database_upgrade() pass, which is now gated by the dataflair_schema_ok_v{VERSION} transient (12 h) so it does not re-run on every page load.

To 1.10.8

After updating the plugin files, run the reconcile CLI once on the target site to backfill the new cached_review_post_id column:

wp dataflair reconcile-reviews --dry-run   # preview
wp dataflair reconcile-reviews             # execute

Brands that already match a published review post will be linked. Brands without a published review are left unlinked; they will be linked automatically on the next sync once their review CPT is published.


Changelog

2.4.3

  • Fixed: admin Toplists list page could hit MySQL error 1038 "Out of sort memory" once the table grew large enough that the default ORDER BY last_synced DESC filesort exceeded the host's sort_buffer_size. Schema v1.15 adds indexes on last_synced, name, and item_count (ensureToplistsSortIndexes()), covering every column ToplistsQuery::ALLOWED_SORT can hit that didn't already have one.
  • Tests: new wiring test that the method is called from every self-heal/upgrade path. Full suite: 969 tests, 3131 assertions.

2.4.2

  • Fixed: casino-card icons (ribbon star, rating star, feature checks, and others) could render oversized on the live front end. These SVGs ship with only a viewBox, no width/height, and the containment rule for them only ever shipped in the block-editor stylesheet, which never loads on a published page. A page-level reset that expands bare <svg> to 100% width (Tailwind Preflight and similar resets both do this) could stretch them to fill their container. Found live during QA. Ported the same rule already proven correct in assets/editor.css to the front-end stylesheet.

2.4.1

  • Fixed: fatal error rendering the toplist block/shortcode on Roots/Acorn (Sage-based) themes. The Alpine.js already-loaded detection called strpos() directly on every queued script's ->src, assuming it is always a plain string. Acorn-based themes register compiled assets with ->src as an asset value object instead, which threw a TypeError the theme's Blade layer turned into a fatal error on every page rendering the block or shortcode. AlpineJsEnqueuer now coerces via __toString() when available and skips the entry otherwise instead of fataling. Found live on a Roots/Acorn client site during QA; reproduced in an isolated regression test before fixing.
  • Tests: two new cases pin both the non-Stringable-safe-skip and the Stringable-object-still-detected paths. Full suite: 968 tests green.

2.4.0

  • Added: webhook sync. DataFlair pushes toplist and brand changes to the site the moment they happen, instead of waiting for the next scheduled sync. A new "Enable webhook sync" checkbox on Settings › API Connection self-registers the site automatically (reusing the existing API token, no separate credential to manage) and shows live status underneath: receiving, no activity yet, or a rejected-delivery reason. Deliveries are HMAC-SHA256 signed against a per-site secret generated once on first enable. New route POST /wp-json/dataflair/v1/webhooks is idempotent (a retried or duplicate delivery is a safe no-op) and tenant-scoped (rejects a delivery meant for a different DataFlair tenant). toplist.published re-fetches just that toplist; brand.status_changed/brand.updated re-fetches just that brand.
  • Fixed (found during pre-release review, before reaching any site): saving Settings from any tab other than API Connection no longer silently disables webhook sync. The webhook receiver's replay-freshness check now validates the signed payload's own timestamp instead of an unsigned header. The tenant-isolation check now fails closed instead of silently skipping when the site's API base URL can't be resolved. A failed idempotency-ledger write is now logged instead of swallowed. Local/Docker debug logging no longer mislabels a webhook registration call as a plain API fetch.
  • Fixed (found only by a live delivery against a real WordPress install, not the mocked test suite): the entire REST API was fatally erroring on every request — rest_bootstrap() wired the webhook receiver's toplist-persistence dependency to the wrong class. Same root cause as the 2.3.3 TestsRunner incident below: this god-class wiring is deliberately outside PHPUnit's reach.
  • Tests: new coverage for the webhook receiver (signature verification, idempotency, tenant guard, event routing, replay rejection), the self-registration flow, and the settings save-isolation fix. Full suite: 936 tests green.

2.3.3

  • Fixed: fatal error on Tools › Tests & Diagnostics, found by a live WordPress 7.1 smoke test after merge (renderTestsTab() built a TestsRunner missing a required argument). Verified across every admin page this release touches. Tested up to updated to 7.1.
  • **Fixed: Settings no longer implies brand

This README is longer than the copy stored here. Read the rest on GitHub →