Raatt's Classified Ads Pro
==========================
INTERNAL DOCUMENT - not for public distribution.

Plugin:      Raatt's Classified Ads Pro
Slug:        raatts-classified-ads-pro
Version:     1.1.1
Author:      Matt Faler
License:     GPL-3.0-or-later (it extends the GPLv3 "Classified Listing" plugin)
Copyright:   (C) 2026 Matt Faler
Requires:    WordPress 6.5+, PHP 7.4+, Classified Listing (free) 6.1.5+
Code prefix: rcap_ / RCAP_ / CSS+JS handles rcap- / shortcode [rcap_compare]

All code is original. It only uses the free plugin's public hooks and data.
It never fakes, defines or checks a vendor license, and never defines another
plugin's classes or constants.


LICENSE KEYS (v1.1.0)
---------------------
* All six features need a valid license key. Without one, every feature is
  off and its toggle is greyed out ("Locked"). Saved settings and listing data
  are kept, and the features come back as soon as a valid key is entered.
* Enter the key in Listings -> Raatt's Ads Pro -> License (paste, then Activate;
  Deactivate removes it). WP-CLI alternative:
    wp option update rcap_license_key "$(tr -d '\n' < key.txt)"
    wp eval 'RCAP_License::revalidate();'
* Keys are offline Ed25519 signatures: RCAP-<base64url payload>.<base64url signature>.
  The payload holds id, name, email, domain, features, issued and expires
  (0 = lifetime). Line breaks/spaces in a pasted key are ignored.
* The plugin holds only the PUBLIC key (includes/license-config.php). Keys are
  issued with the separate private tools (never shipped with the plugin).
* Checks: signature, built-in revoked-ID list, expiry, and domain.
  Domain rules: exact host, www and no-www both match; ".example.com" also
  covers subdomains; "*" = any site.
* The result is cached in the option rcap_license_status. It is re-checked
  daily (cron rcap_license_check), on settings save, on Activate, whenever the
  site address or key changes, and as soon as a cached key passes its expiry.
* Revocation: add the ID to RCAP_REVOKED_LICENSE_IDS (tools: revoke.php) and
  ship a new build. There is no server, so revocation reaches a site only when
  it installs that build.
* Needs PHP sodium (bundled since PHP 7.2). WordPress also ships a sodium_compat
  fallback that provides the same verify function.

GPL NOTE: the plugin is GPL-3.0-or-later because it extends the GPLv3
Classified Listing plugin. License keys gate features, updates and support,
but the GPL lets anyone who receives the code study, modify and redistribute
it, including removing the check. The keys are a convenience and a
commercial/support control, not DRM.


SAFETY BEHAVIOUR
----------------
* If Classified Listing is missing, inactive or older than 6.1.5, the plugin
  does nothing except show an admin notice.
* If the vendor's own commercial add-on is active (read-only check
  rtcl()->has_pro()), all features here switch off so the two never conflict.
* Every feature is OFF after activation. Nothing public changes until a valid
  license key is entered AND a feature is enabled in Listings -> Raatt's Ads Pro.
* Uninstall removes only this plugin's settings, license key/status and cron events. Listing data
  (sold flag, promotion dates) is shared with Classified Listing and is kept.


PHASE 1 FEATURES (built)
------------------------
1. Grid / list switcher
   - Buttons in the archive toolbar (rtcl_listing_loop_action).
   - ?view=grid|list or cookie "rcap_view" overrides the default view via
     the rtcl_archive_listings_default_view filter (free already ships grid CSS).

2. Mark as sold
   - "Mark as sold / Mark as available" link in My Account -> Listings
     (rtcl_my_listing_actions). AJAX with nonce; only the listing owner or an
     editor can toggle.
   - "Sold status" checkbox in the admin listing editor.
   - Data: post meta _rtcl_mark_as_sold = 1.
   - "Sold" badge on cards (rtcl_listing_badges), "This item has been sold."
     banner on the single page, and an rcap-is-sold CSS class on cards.

3. Quick view
   - Eye button on listing cards (rtcl_listing_meta_buttons).
   - AJAX popup (nonce-checked; published listings only; escaped output).
   - Template: templates/quick-view.php. Override it in
     yourtheme/raatts-classified-ads-pro/quick-view.php.

4. Compare (up to 4)
   - Compare button on cards. The selection is kept in the browser
     (localStorage), with a floating "Compare / Clear" bar.
   - Compare page: create a page containing [rcap_compare] and select it in
     settings. The page reads ?ids=1,2,3 so it is cache-friendly and shareable.
   - Rows: price, ad type, category, location, posted, views, status. Add rows
     with the rcap_compare_rows filter. Template: templates/compare.php.

5. Archive map view
   - "Show map" toggle above the archive. It reuses Classified Listing's own
     map script and settings (OSM or Google: Settings -> Misc -> Map) and adds
     marker data to cards in the archive loop.
   - Hidden unless maps are enabled and configured in Classified Listing.
     Listings need latitude/longitude (Location type "GEO", or map pin).

6. Top and bump-up promotions
   - Adds "Top" and "Bump Up" to Classified Listing's promotion list
     (rtcl_listing_promotions). They appear on pricing plans; this plugin saves
     those checkboxes (free saves only "featured").
   - Classified Listing's own payment flow applies them when an order
     completes (_top/_top_expiry_date, _bump_up/_bump_up_expiry_date).
   - Admin listing editor box "Top / Bump Up" to set or clear dates by hand.
   - Top: up to N active Top ads (setting, default 4) are pinned above the
     archive, filtered to the current category/location, with a "Top" badge.
   - Bump-up: archive "newest first" sorting uses the later of the publish
     time and the last bump time (_rcap_bumped_at). A bump happens when the
     promotion is applied, then every N days (setting, default 1).
   - Daily WP-Cron "rcap_daily_promotions" expires finished promotions and
     re-bumps active ones. Low-traffic sites should run real cron:
     */15 * * * * wp cron event run --due-now --path=/path/to/site


SETTINGS
--------
Listings -> Raatt's Ads Pro (wp-admin/edit.php?post_type=rtcl_listing&page=rcap-settings)
Option name: rcap_settings
Keys: view_switcher, mark_sold, quick_view, compare, map_view, promotions (0/1),
      compare_page_id, top_count (1-12), bump_interval (days, 1-30).

WP-CLI example (set everything at once; 1 = on, 0 = off):
  wp option update rcap_settings --format=json '{"view_switcher":0,"mark_sold":1,"quick_view":1,"compare":0,"map_view":0,"promotions":0,"compare_page_id":0,"top_count":4,"bump_interval":1}'


KNOWN LIMITATIONS (v1.0.0)
--------------------------
* Gutenberg "Listing Ads" block cards: the free plugin fills the sold, compare
  and quick-view slots there only for its own add-on. Our badges show up there
  through rtcl_listing_badges, but the block's built-in slots stay empty.
* Top ads are pinned on full page loads. When visitors use the AJAX filter
  widget, the refreshed list is sorted with bump-up but has no pinned strip.
* Top ads also appear in their normal position in the list (as on most
  classifieds sites).
* Public AJAX (quick view) uses a WordPress nonce. Full-page caches that keep
  pages longer than about 12 hours can serve stale nonces. Exclude the archive
  from long caching, or keep the cache under 12 hours.
* The vendor's form-builder "Pro" switches (filterable/listable/dependent
  fields) are not touched.


ROADMAP
-------
Phase 2 (needs keys/accounts):
* Stripe gateway (Stripe Checkout + webhook via ?rtcl-api=rcap_stripe),
  registered through rtcl_load_payment_gateways.
* Authorize.net gateway (Accept Hosted).
* Filterable / listable custom fields through own settings.
* Pinned Top ads in AJAX-filtered results.

Phase 3:
* WooCommerce payment bridge.
* Store pages and membership plans (quotas, renewals, Stripe Billing).
* Seller live chat.
* Own REST API for a custom mobile app.
* AI writing helper for any listing field.
* wpml-config.xml for settings strings.


FILES
-----
raatts-classified-ads-pro.php        Header, constants, bootstrap
includes/class-rcap-plugin.php       Dependency checks, feature loader, assets
includes/class-rcap-settings.php     Settings page
includes/class-rcap-license.php      License key verification, License box, notice
includes/license-config.php          Embedded PUBLIC key + revoked license IDs (generated)
includes/class-rcap-helpers.php      Shared helpers, template loader
includes/class-rcap-view-switcher.php
includes/class-rcap-mark-sold.php
includes/class-rcap-quick-view.php
includes/class-rcap-compare.php
includes/class-rcap-map-view.php
includes/class-rcap-promotions.php
templates/quick-view.php, templates/compare.php
assets/css/rcap-frontend.css, assets/js/rcap-frontend.js
uninstall.php

1.1.1 - License: accepts Friends and Family Keys that list 2 exact domains. Existing keys work as before.
