Skip to main content

Directory Theme class reference

29 min read

Cashback Theme — Class reference

The 36 classes that make _cb behave differently from the shared Directory core. Generated from the theme source.

← Back to Cashback Theme features

AdminClaims AdminClaims.php​

Admin ▸ Checkout ▸ Cashback claims — the owner's queue for member claims (Cashback Theme, _cb). Each claim is checked against the affiliate network's report and moved along: accept (pending with the store), confirm (the cashback lands in the member's balance), or decline with a reason the member sees. Paying out stays on the Payouts screen, where confirmed cashback shows up as an amount owed / a withdrawal request.

API
register()available()url()menu()render()handleSet()handleSave()flash()handleUpload()handleMap()handleApply()handleNetworks()handleRewards()handleStacking()subnav()handleAwinConnect()ajaxAwinStart()ajaxAwinStep()handleCancel()
inc/_cb/AdminClaims.php

AwinStores AwinStores.php​

Import stores from Awin — every programme the owner has JOINED becomes a draft store, with its logo, website, description, category, tracking link and cashback rate (Cashback Theme, _cb). The Coupon Theme has the same idea (_cp\StoreBrands + _cp\AwinApi), but there a store is a TERM and nothing is created - only empty fields filled. Here a store is a listing and IS created, as a draft for the owner to review. The HTTP bits are cloned rather than called: _cp classes are not in a Cashback Theme build. Flow: connect (token -> the publisher id is read from the token, never typed) -> start (one call: the joined-programmes list, streamed to disk - Awin's catalogue can be 13 MB) -> steps (a few programmes each: one programmedetails call apiece for the commission range). Awin allows 20 API calls a minute, so the admin screen paces the steps and backs off on a 429. Per programme: new -> a DRAFT store: title, description, website, category (Awin's primary sector), logo (downloaded into the media library - the card reads the store's featured image), the Awin tracking link, and the rate. existing -> found by Awin advertiser id; EMPTY fields filled only - the owner's edits always win. The rate is refreshed only while it still equals what the last import set (so an owner-edited rate is never overwritten). The rate a member sees = Awin's commission x the member's share (Networks::share) - that is what automatic tracking actually pays, so the store page never promises more. A percentage wins over an amount; "up to" when the range has a spread. A fixed amount in another currency is not converted - the rate is left for the owner.

API
settings()connected()connect()disconnect()rateFrom()job()start()step()upsert()trackingLink()eachRecord()
inc/_cb/AwinStores.php

Cashback Cashback.php​

Cashback — the rate a store pays its members, and the single place that turns it into words (Cashback Theme, _cb). A cashback rate is not one number. A live cashback grid carries every one of these at once, so the model has to as well: "8% cashback" percentage "$45 cashback" fixed amount "Up to 12% cashback" "up to" on a percentage (the store has tiers) "Up to $120 cashback" "up to" on a fixed amount "$25 cashback on annual plans" a free-text qualifier after the figure "Cashback temporarily unavailable" a paused state, not an empty one plus whether it pays ONCE (first order) or on EVERY RENEWAL — the thing that matters most for subscriptions, and the reason hosting and AI tools are worth building at all. Everything that shows a rate reads it through label(): the store editor, the search card (ListingCard), the home-page grids (via the LiveItems row hook below) and the store page. So a rate is phrased identically everywhere, and a store with no rate set shows NO rate rather than an invented one. Stored as post meta on the store listing (a store IS the listing on this profile): ppt_cb_rate number, > 0 ("12", "7.5", "45") ppt_cb_rate_type 'percent' | 'fixed' ppt_cb_rate_upto '1' when the figure is the top of a tier set ppt_cb_rate_note qualifier after the figure ("on annual plans") ppt_cb_pays 'recurring' | 'once' | 'booking' (travel: per booking, after the trip) | 'order' (fashion/retail: every order) ppt_cb_paused '1' when the store has stopped paying for now ppt_cb_tiers JSON list of {product, rate, type, pays} - what "up to" is hiding; type 'none' is an excluded product ("No cashback") ppt_cb_track free text, how long a sale takes to track ("About 2 hours") ppt_cb_confirm free text, how long until the store confirms it ("30-60 days") ppt_cb_claim free text, how long a member has to claim a missing one Tiers and timings are optional: a store page draws the rates table and each timing row only when the owner has filled them in - blank is hidden, never invented. The store's outbound (affiliate) link and its terms stay on the meta the fork already had (ListingEditor::COUPON_URL_META / COUPON_TERMS_META), so nothing that reads them moves.

API
register()hideJobTaxonomies()value()demoStoreUrl()label()paysLabel()labelFor()figure()storeValue()backOn()tiers()tierLabel()timings()terms()save()renderFormCard()liveRow()onDemoFlag()applySample()sampleLogo()sampleRow()storeId()storeFigure()storeUrl()
inc/_cb/Cashback.php

Claims Claims.php​

Cashback claims — how a member's purchase becomes money on the Cashback Theme (_cb). The product promise is MANUAL tracking: members click through a store's link, buy, then submit their order ID; the owner checks it against the affiliate network's report and pays out. This class is that whole chain, network-agnostic: Click a SIGNED-IN member hits "Shop now" on a store page (Redeem's beacon calls recordClick()). Guests go straight to the store and earn nothing - the page tells them to sign in first. Claim within the claim window of that click, the member submits order ID, date, total (and which product, when the store has rate tiers). The site SUGGESTS the cashback from the store's rate; the owner can change it. Status submitted -> tracked (accepted, waiting on the store) -> confirmed, or declined at any point. Declining a CONFIRMED claim claws the money back - refused once it has been paid out or sits in an open withdrawal request. Money confirming ACCRUES the amount into the shared Payouts\Ledger (ref cb-claim-{id}, rate 100 so earned == amount). From there the existing Earnings balance, Withdrawals and the admin Payouts screen do the rest, unchanged. "Paid" is never stored here: it is read back from the ledger entry, so there is one source of truth for money that has left. Why a table of its own and not the ledger: the ledger is shared by ten product lines, knows only pending/paid, and lives in user meta. Claims carry a status ladder, need querying across members (the admin queue) and grow with every order - so they get a table, and only confirmed money crosses into the ledger.

API
register()clicksTable()table()install()defaultWindow()saveSettings()window()recordClick()eligibleStores()canClaimOn()suggest()submit()ajaxSubmit()get()forUser()query()counts()duplicates()clickTime()ledgerRef()ledgerStatus()displayStatus()statusLabel()statusHelp()
inc/_cb/Claims.php

ClaimsPanel ClaimsPanel.php​

The member's Cashback tab (Cashback Theme, _cb) — drawn in the hub's #earnings panel in place of the generic Earnings statement (Payouts\Account::renderPanel hands over). Top to bottom: the balance cards, the SAME withdraw box every payout profile uses, the claim form, and the member's claims with their status. The form only offers stores the member clicked through to inside the claim window (Claims::eligibleStores), so a claim always has a click behind it. ?cb_store=ID preselects a store - the store page's "Claim cashback" link uses it.

API
render()
inc/_cb/ClaimsPanel.php

DemoAccounts DemoAccounts.php​

Cashback — what the DEMO member walks into. A cashback site has one kind of member, so the home-demo offers the single "members area" login (the plain test member). Empty, its Cashback tab is three $0.00 cards and "No claims yet". This gives the member the history a few months of shopping would leave: - 12 real store pages, mixed across the built niches (a hotel, a hosting renewal, a sofa, pet food, a keyboard...), each with its showroom rate and logo. Demo-marked, owned by an admin, deleted when the member is purged at go-live. - 10 claims covering every status: 3 paid (settled by one PayPal withdrawal last month), 2 confirmed (the balance to withdraw), 2 pending, 2 submitted, 1 declined with a reason. - A click behind every claim, plus two recent clicks, so the claim form has stores to offer. - 4 favourite stores. Everything goes through the real Claims / Ledger / Withdrawals code paths (emails muted while seeding), so the balance cards, the withdraw box and the claim list read it exactly as they read a real member's. Visitors share this one account and may use it for real - send a claim, press Withdraw. So the sample is REBUILT once it is a day old: the next sign-in after RESET_AFTER wipes the member's cashback (claims, clicks, ledger, withdrawals, favourites) and furnishes it again. The stores are kept across resets. Gated to the cashback profile by Theme::boot()'s fork-gating (this is Ppt_cb*).

API
register()onLogin()furnish()unfurnish()
inc/_cb/DemoAccounts.php

DemoProfiles DemoProfiles.php​

Cashback Theme — the demo copy for a store. Picked up automatically by Tools\SampleData::profileCopy('body', …), which both demo paths call: the showroom's virtual store page (Frontend\DemoListing) and the seeded sample stores. One writer keeps them saying the same thing. WHY THIS EXISTS. Without a fork copy, a store fell back to the shared filler, which is shop sales copy ("…each order is packed with care and dispatched quickly…") on the virtual page, and a local-business profile ("…a trusted managed hosting based in …") on a seeded row. A cashback store is neither: it is what the merchant sells, and how its cashback pays. Kept to one short paragraph pair - the store page leads with it and then shows the rates, terms and timings as their own sections, so repeating them here would make two sources of truth.

API
body()
inc/_cb/DemoProfiles.php

Expiry Expiry.php​

PPT — listing expiry / "Listing lifetime". The Settings ▸ Listings "Listing lifetime" (days) governs how long a listing stays live. 0 = never expires. A value > 0 stamps an expiry timestamp on each listing when it's published; a daily cron then runs the configured "On expiry" action (listings_expiry_action: nothing / draft / pending / trash) once the time is up. The expiry timestamp meta is SHARED with the pricing-plan expiry (PricingPlans::LISTING_EXPIRES_META). A listing that carries a pricing plan is governed by that plan's own duration (set in the editor / at checkout), so the global lifetime only applies to listings WITHOUT a plan. Either way, the cron here enforces whatever expiry timestamp a listing ends up with, and both editors show the time remaining.

API
register()metaKey()schedule()unschedule()onTransition()applyLifetime()runCheck()timestamp()remaining()dateLabel()
inc/_cb/Expiry.php

Extension Extension.php​

The browser extension — a Chrome / Edge toolbar button the site owner publishes under their own name, which tells members when they are on a store that pays cashback (Cashback Theme, _cb). The site side is three things: GET /wp-json/ppt/v1/cb-ext/stores public: every published store the extension can recognise (its website domain), with its rate words. GET /wp-json/ppt/v1/cb-ext/me the signed-in member's balance + last claims, for the popup. Answers extension origins only. Admin ▸ Cashback claims ▸ Browser extension name / icon / colour, a ready-to-upload .zip (built here from inc/_cb/extension/), the publishing steps, and the two store-listing links that switch on the "Get our extension" card in the member's Cashback tab. Activating cashback in the extension is just a visit to Outbound (?cb_go=``) - the click is recorded and tagged exactly as the store page's Shop now button does it. The extension never sends the pages a member visits anywhere: it downloads the store list and matches the current tab's address against it on the member's computer. Amazon is never in the list: its Associates agreement bans cashback on its links.

API
register()settings()name()description()color()brandColor()iconId()iconDims()listingUrls()saveSettings()isListingUrl()hostOf()isAmazon()domain()linkDomain()flush()stores()storeRow()badge()routes()restStores()restMe()me()isExtensionOrigin()
inc/_cb/Extension.php

Single-listing gallery styles. The admin picks one of four layouts on the Design ▸ Listings tab (stored in the ppt_design option via Branding), and the single-listing template renders the chosen layout for the listing's images. standard — large hero photo + thumbnail strip (click to swap) [default] grid — all photos in a tiled grid (first one featured) carousel — a swipeable/scroll-snap slider with prev/next tall — full-width photos stacked vertically (good for portraits)

API
styles()locked()current()isFullSpan()render()css()
inc/_cb/Gallery.php

Geocoder Geocoder.php​

Bulk geocoder — fills in lat/lng for existing listings that have an address but no coordinates (e.g. listings created before the editor's map picker existed), so they get a precise map marker and the "Distance from me" feature. Uses the site's Maps provider (Settings ▸ API keys): Google / Mapbox geocoding APIs (their key), or OpenStreetMap Nominatim (keyless, rate-limited to ~1 req/sec with an identifying UA). A small box on the PPT Listings screen runs it in batches over AJAX. Listings that can't be geocoded are flagged (_ppt_geo_failed) so they aren't retried forever.

API
register()geocode()pendingCount()ajaxBatch()assets()box()
inc/_cb/Geocoder.php

ListingActions ListingActions.php​

Per-listing visitor actions shown on the single-listing section nav: - Add to favorites — toggles the listing in the member's saved list (user meta ppt_favorites); the account page shows them. Logged-in only; guests are routed to sign-in. - Report — flags the listing to the site admin. The reason is stored as a comment on the listing (type ppt_report, a custom approval status so it stays hidden from the front-end and the normal comment-moderation tabs) and also emailed to the admin. Reports are reviewed in the admin Comments screen's dedicated "Reports" tab. Open to guests and members.

API
register()favIds()favHas()favToggle()favListingIds()ajaxFav()ajaxReport()assets()favButton()reportButton()
inc/_cb/ListingActions.php

ListingCard ListingCard.php​

The store card — the ONE way a cashback store is drawn, on every surface: the search results, the related-stores row on a store page, and every home-page grid (the design blocks render through here rather than repeating the markup). So a store looks the same wherever a member meets it, which is the whole point of this class. Shape (approved on the Hosting niche, 2026-09-23, after petbux.com/brands): a 16:10 brand panel carrying the store's logo, the name, a category chip, a full-width rate block, whether it pays on every renewal or the first order only, and a full-width "Shop now". Every colour, radius and face comes from the active design's tokens, so the same markup is Conduit on a dark console, Altus in cobalt and Ferrule in copper. Data-source agnostic: fromPost() maps a real store and fromSample() maps a curated row (a SampleListings card or a design block's item fields) into the same normalised shape. Normalised shape: id, name, link, img, cat, desc, cashback, pays, state ('on'|'off'|''), badge, featured (bool), highlighted (bool) Omit, never invent: a store with no rate set renders no rate block, and one with no logo renders a monogram — never another store's figure or picture. The CSS is printed once per request by the first card (css()), not enqueued from assets/, so no stylesheet has to reach the CDN for the card to look right.

API
fromPost()logoUrl()fromSample()render()css()
inc/_cb/ListingCard.php

ListingEditor ListingEditor.php​

Listing editor — the shared brain behind the two listing-edit screens: - Admin : PPT-styled screen at ?page=ppt_listings&edit=<id> (Chrome shell), rendered by Admin\Listings when the edit param is present. - Member : front-end screen at /account/listing/<id>/ inside the Member Hub, rendered by Account\MembersPage for a listing the member owns. Both POST to admin-post.php (action ppt_listing_save) and run through the one save() below, so the field set, sanitising and persistence live in a single place. The mode (admin|member) decides which extras are honoured: admins get status, author, slug, featured/verified and the "edit only" custom fields; members get a friendly subset scoped to their own listing. Fields = core (title, description, category, tags, featured image, gallery, FAQ) + every field defined in the Custom Fields admin (Admin\Fields / option ppt_fields), stored as post meta keyed by the field key — which is what the single-listing template already reads.

API
register()newUrl()handleNew()submissionsOpen()memberCanAdd()canEdit()adminEditUrl()memberEditUrl()applicableFields()locationKeys()coordKeys()mapPickerReady()mapConfig()locationFields()couponValue()couponBadge()couponExpired()socialNetworks()socialValue()socialIcon()hoursDays()hoursValue()normTime()bookingValue()
inc/_cb/ListingEditor.php

ListingFaq ListingFaq.php​

Single-listing FAQ. Reads per-listing FAQ items from the faq post meta (an array of ['q' => …, 'a' => …]). A live listing shows only its own saved FAQ — when it has none the section is omitted rather than filled with fabricated entries. Demo/preview mode supplies sample FAQ via DemoContent so a fresh design isn't empty. Populate a listing's own FAQ by saving faq meta, inject a site-wide default set via the ppt_listing_faq_default filter, or adjust the final list with the ppt_listing_faq filter.

API
items()
inc/_cb/ListingFaq.php

ListingHours ListingHours.php​

Business hours display for the single-listing sidebar. Reads the business_hours meta written by the listing editor (ListingEditor::HOURS_META) and renders a Google-Business-style day list with an "Open now / Closed now" badge computed in the site's timezone. Times are formatted with the site's time format.

API
has()isOpenNow()renderSidebar()
inc/_cb/ListingHours.php

ListingLocation ListingLocation.php​

Single-listing "Location" section — the richer location block: the full formatted address, an interactive map (ListingMap, precise when the listing has coordinates), a "Get directions" button, and a "Distance from you" control (browser geolocation → straight-line distance to the listing). Self-contained: it prints its own scoped CSS + JS once, so single.php just calls render().

API
address()coords()has()render()
inc/_cb/ListingLocation.php

ListingMap ListingMap.php​

The single-listing "Location" map. Uses the site's Maps provider setting (Settings ▸ API keys — the same ppt_maps_provider filter the search map reads). When the listing has stored coordinates (lat/lng, set by the editor's map picker) the map centres on them precisely and drops a named marker; otherwise it falls back to geocoding the address string: google / mapbox(fallback) → a keyless Google Maps embed osm → a Leaflet map (marker from coords, else Nominatim) Map libraries load from their CDNs — the owner-approved front-end map exception.

API
render()
inc/_cb/ListingMap.php

ListingPricing ListingPricing.php​

Single-listing pricing plans. A listing owner enters one or more plans / packages on the submission form (a name, a price, and a short description); they're stored in the pricing_plans post meta as a list of ['name' => …, 'price' => float|null, 'desc' => …]. The LOWEST priced plan is mirrored into the searchable price meta (Ppt_cb\SearchPage::PRICE_META) by the editor on save, so the price range filter, the price sort, and the result-card price all work — that's the link between "enter your pricing" and "find listings by price". Prices are stored in the site's base currency; render() converts + formats via Ppt\Content\Currencies for the viewer's chosen currency.

API
plans()demoPlans()has()lowestPrice()normalise()render()
inc/_cb/ListingPricing.php

ListingSections ListingSections.php​

PPT — configurable single-listing sections. The admin (Design ▸ Listings ▸ "Listing page sections") gets a sortable list of on/off toggles for the optional feature blocks a listing page can carry — Booking, Business hours, Maps & Location, FAQ, Reviews. Turning one OFF removes it from BOTH the single listing page and the listing submission/edit form; the order of the list controls the order the (main-column) sections appear in. Config lives in the shared design option (Branding::OPTION = ppt_design) under the listing_sections key: { order:[keys…], enabled:{key:0|1} }. Anything not saved yet defaults to ON, in the catalog order — so existing sites are unchanged until the admin edits the list.

API
supported()catalog()config()order()enabled()orderedAmong()sanitize()
inc/_cb/ListingSections.php

ListingViews ListingViews.php​

PPT listing analytics — records a "hit" every time a single listing page is viewed, and serves that data back to the listing owner as a graph in the member area (/account ▸ My listings ▸ the analytics icon). Each view stores the listing id, the timestamp (UTC), and — when the visitor is logged in — their user id + display name, so an owner can see who has been looking. Bots/crawlers are skipped so counts reflect real visitors. The owner (and admins) can read the analytics for a listing over the AJAX endpoint, which returns a daily series (for the chart), rolling-window totals (7/30/60/90 days) and a list of recent named viewers. Storage: a custom table {prefix}ppt_listing_views (dbDelta on init).

API
register()table()install()maybeRecord()total()countSince()series()recentViewers()ajaxData()
inc/_cb/ListingViews.php

Media Media.php​

Listing images — the single, WordPress-native resolver for the PPT image system. A listing's photos are its WP featured image (primary) plus any image attachments parented to it (the gallery). No legacy ?imgid refs, no image-URL meta strings — everything flows through the media library.

API
primary()gallery()videos()media()counts()
inc/_cb/Media.php

Networks Networks.php​

Affiliate networks — which network a store's link belongs to, how to tag that link with the member's click, and the site's postback settings (Cashback Theme, _cb). The tag is the whole mechanic of automatic tracking: every network lets a publisher add its own reference to a tracking link and echoes it back with the sale. We put the CLICK id there (cb4812), never a user id, name or email - the click row says which member and which store, so the tag means nothing to anyone else. Each network names the tag differently and it must go on the network's own tracking URL, not on the retailer URL inside it. A store's network is detected from its link's host; the store editor can override it (a custom tracking domain, or "don't tag"). A store on no known network keeps working exactly as before: untagged link, manual claims, report upload. Settings (option ppt_cb_networks): the postback secret and the member's share of the reported commission (per-store override: ppt_cb_share meta).

API
all()get()detect()forStore()token()clickFromToken()tag()settings()saveSettings()share()postbackBase()postbackUrl()storeCounts()
inc/_cb/Networks.php

Onboarding Onboarding.php​

A new member's first steps on the Cashback Theme (_cb): what /join/ says, where it sends them, and the two checks before their first payout. Sign-up the opening screen carries the offer (welcome bonus / "a friend invited you", Rewards::pitch) and the closing screen explains how cashback reaches the balance, plus the browser extension when the owner has one. Back to store a guest on a store page is offered Join AND Sign in, both carrying the store page as the destination (SignupWizard honours redirect_to), and lands back there with "You're signed in - click Shop now". First payout a confirmed email address and "I am 18 or over". Sign-up stays one minute long and email delivery never blocks joining; both are asked for only when there is money to send (Withdrawals' ppt_withdraw_refusal filter enforces it, the Cashback tab shows the box that clears it).

API
register()returnUrl()joinUrl()signInUrl()justArrived()intro()donePanel()payoutChecks()refusal()checksBox()handleCheck()
inc/_cb/Onboarding.php

Outbound Outbound.php​

"Shop now" — the site's own outbound address for a store (the cloaked link the product page promises), Cashback Theme (_cb). https://site/?cb_go= For a signed-in member it records the click (Claims::recordClick - the same record a claim is filed against) and adds the click tag to the network link (Networks::tag), so the network reports the sale back against that member. A guest goes straight to the store, untagged - the site still earns its commission, the member just earns nothing, and the store page says so beside the button. Replaces the old "Shop now" beacon: the click has to exist BEFORE the browser leaves, because its id travels in the link, and a redirect cannot be blocked like a beacon.

API
register()url()maybeRedirect()destination()
inc/_cb/Outbound.php

Postback Postback.php​

The postback receiver — the address an affiliate network calls when it records a sale or changes its status (Cashback Theme, _cb). GET|POST /wp-json/ppt/v1/cb-postback?key=<secret>&subid=...&order=...&status=... key must equal the site's secret (Networks::settings) - no network documents a signing scheme, so the secret in the URL is the lock; the owner can regenerate it. Fields may use our names or the network's own (Tracking::ALIASES), in the query string, a form body or a JSON body, so Rakuten's standard field names work with no mapping at all. Always answers quickly with a short plain-text body; networks retry on errors, so a sale we cannot use (no order/transaction id) is 400 and a wrong key is 403, while anything we could store is 200 even when no member matched.

API
register()routes()receive()handle()
inc/_cb/Postback.php

Redeem Redeem.php​

Coupon redemption tracking (Coupon fork). Records two lightweight signals from the single coupon page — a "reveal" (the shopper unmasked the code) and a "click" (the shopper hit "Go to store") — as running counters in post meta (coupon_reveals / coupon_clicks). No new tables and no rewrite rules: the page fires a nonce-checked beacon to admin-ajax, mirroring how ListingViews serves its analytics endpoint. These counts feed the "used N times" social proof on the single page and give the owner a rough sense of which deals convert. They are best-effort (a blocked beacon just means an uncounted reveal), never used for access control.

API
register()nonce()reveals()clicks()record()
inc/_cb/Redeem.php

ReportImport ReportImport.php​

Network report import — the owner uploads an affiliate network's transactions CSV and the site matches it to member claims (Cashback Theme, _cb). The manual flow is: a member claims an order, the owner looks it up in the network's report, then confirms or declines. This does the looking-up in bulk: Read the CSV (comma / semicolon / tab, BOM-safe). Awin, CJ, Impact and Rakuten column names are recognised; any other layout is mapped once by the owner and remembered by its header row (option ppt_cb_import_maps). Match each row to a claim by ORDER NUMBER. Where the same number was claimed at more than one store, the report's retailer name decides. Plan approved -> Confirm, pending -> Pending, declined/reversed -> Decline (a claw-back if it was confirmed). Only FORWARD moves are automatic; anything that needs judgement is FLAGGED and left alone: the sale amount differs from what the member claimed, a claim the owner already declined, a claim with no cashback amount, an order claimed by several members. Apply only after the owner has seen the preview. The plan is rebuilt at apply time, so a claim that moved in between is judged on where it is NOW. Keep every row, claimed or not, in {prefix}ppt_cb_transactions. A member who claims a stored order later is matched the moment they submit (onNewClaim): approved -> confirmed straight away, pending -> pending. The cashback amount is ALWAYS the claim's own (the rate-based suggestion or the owner's edit) - the report's commission is shown for reference, never paid out as-is.

API
register()table()install()readCsv()normHeader()synonyms()signature()detect()saveMap()money()normStatus()normalise()merchantKey()merchantIs()amountOk()judge()plan()storeFor()tally()apply()networkKey()storeRow()unclaimedCount()forClaim()
inc/_cb/ReportImport.php

Reviews Reviews.php​

Listing reviews — built on native WordPress comments so they moderate and store like any comment, but stamped with the comment type "review" (the identifier that distinguishes them from ordinary blog comments in the admin) and a 1–5 star rating saved as comment meta. Reviews are always enabled on the listing CPT. Rendered in the single-listing "Reviews" (summary + breakdown + cards) and "Add Review" (star input + comment form) sections.

API
register()open()stampType()saveRating()onCommentChange()onStatusChange()syncPostRating()count()all()stats()stars()renderSummary()renderList()renderForm()
inc/_cb/Reviews.php

Rewards Rewards.php​

Welcome bonus + invite-a-friend (Cashback Theme, _cb). The two ways a cashback site grows its membership, both set on Admin ▸ Cashback claims ▸ Bonuses & referrals and both OFF until the owner switches them on (the amounts ship filled in, so switching on is one tick): Welcome bonus every new member is promised an amount at sign-up. Invites each member has a link (?invite=CODE). A visitor who arrives through it and joins within 30 days is that member's friend: the friend gets one amount, the member who invited them another. NOTHING IS PAID AT SIGN-UP. The promise is written on the new member (HELD_META, amounts captured then, so a later settings change does not rewrite it) and only becomes money in the shared Payouts ledger when that member's FIRST claim is confirmed — a real purchase a store has approved. That is the whole defence against accounts made for the bonus: an account that never buys anything never costs anything. The friend's first confirmed cashback pays the inviter too, so inviting yourself earns nothing a real sale didn't. If that first confirmed claim is declined later (a late return) and the member is left with no confirmed cashback at all, the bonuses go back to waiting: voided where they are still unpaid and not in a withdrawal request, left alone where the money has gone. The next confirmed claim pays them again (Ledger::accrue is idempotent per ref). Ledger refs, one per promise: cb-bonus-welcome-{member}, cb-bonus-invited-{member} (the friend's share, on the friend) and cb-bonus-invite-{friend} (on the inviter).

API
register()settings()saveSettings()welcomeAmount()invitesOn()code()inviteUrl()memberFor()captureInvite()currentInviter()publicName()onRegister()parts()onClaimStatus()release()takeBack()confirmedCount()inLedger()waiting()inviteStats()pitch()waitingLine()icon()memberCard()
inc/_cb/Rewards.php

SearchPage SearchPage.php​

Server-rendered search / archive for the listing post type — the clean PPT rebuild of DT10's PPT\Custom\Search\SearchPage. Runs the normal WordPress main query (WP_Query) via official hooks (pre_get_posts + template_include) so it is SEO-friendly and every core filter applies. Contexts it owns: - keyword search (/?s=…) - the listing post-type archive (/listing/) - listing category / tag archives Filters (GET, all optional, combinable): s keyword tax-listing_category category term id cbmin minimum cashback band (a % figure — see minBands()) cbtype percent | fixed cbpaused 1 = include paused stores (hidden by default) sort cashback | popular | rating | title | newest | featured paged pagination A store has no price, so the inherited price box and price sorts are gone: the price key is dropped from Admin\Search::filterKeys() on this fork and there is no price branch left here. The postcode radius is dropped the same way (Admin\Search::locationAvailable()) — a cashback store is an online shop.

API
register()shapeQuery()nearCoords()radiusKm()distanceFrom()distanceLabel()radiusClausesMain()radiusClauses()featuredOrderClauses()cashbackOrderClauses()popularOrderClauses()isListingContext()postType()isMapTakeover()categoryTax()terms()taxQueryFromRequest()listingTaxonomies()hasActiveFilters()matchingIds()minBands()minBand()typeChoices()typeValue()
inc/_cb/SearchPage.php

SingleListing SingleListing.php​

Single-listing page for the listing_type post type — the clean PPT rebuild of DT10's PPT\Custom\Single\SingleListing. Owns template_include for singular listings and renders its own token-styled template inside the normal main loop. No legacy $CORE / membership plumbing; standard WordPress access rules apply.

API
register()template()related()
inc/_cb/SingleListing.php

Stacker Stacker.php​

Stacker — "deal stacking" on a store page (Cashback Theme, _cb): the cashback rate PLUS the store's voucher codes PLUS the loyalty scheme and the reward card a member holds, added up into one "your total saving" figure. Four parts, one class: 1. The owner's catalogue (Admin ▸ Cashback claims ▸ Deal stacking): loyalty SCHEMES (points per unit spent, what a point is worth, a member-price discount) and reward CARDS (a cashback credit/debit card's rate). Option ppt_cb_stack. 2. Per store: which schemes it takes (ppt_cb_schemes), its voucher codes (the shared Ppt\Vouchers\Codes list), and whether a code from ELSEWHERE voids the cashback (ppt_cb_codes_void, '' = the site default). Cards are not per store: a cashback card pays on every purchase. 3. The member's wallet (user meta ppt_cb_wallet): the schemes and cards they hold, ticked in the Cashback tab. Picked from the catalogue only — a card the site has no rate for could never change the total. 4. compute(): the waterfall. Discounts are not commutative, so the order is fixed and the same everywhere (PHP here, mirrored in the store page's script): basket - member price (the best scheme discount) -> after member price - voucher code (the ONE code that saves most) -> what the member pays + cashback (the store's rate on what is paid) + card cashback (the card's rate on what is paid) = total saving + points (shown on their own line, never folded into the money) Only codes LISTED on the site are ever counted: those are the ones the store's affiliate programme allows alongside cashback. The void flag is a warning about the others, not an input to the maths. Blank is hidden, never invented: a store with no codes, no schemes and no cards in the catalogue draws neither block. The showroom's virtual store (and a seeded demo store with nothing of its own) reads a fixed sample set, by name, so the demo looks finished — the same rule the rate tiers follow.

API
register()supported()enabled()settings()saveSettings()schemes()cards()storeSchemes()storeVoid()storeCodes()saveStore()wallet()saveWallet()ajaxWallet()catalogueFor()compute()codesBlock()stackBlock()css()js()formCards()memberCard()sampleCodes()sampleSchemes()
inc/_cb/Stacker.php

TermMeta TermMeta.php​

Per-term IMAGE + ICON for the listing taxonomies. Adds two custom fields to the native WordPress term add/edit screens (Categories, Tags and any custom listing taxonomy): a media-library image picker and a swatch picker over the theme icon library. The values are stored as term meta and consumed by the live-category blocks (via Blocks\Support\Categories) so an admin can give each category a branded photo and icon instead of the auto-derived listing photo / fallback glyph. Admin-only UI; the read helpers (image()/imageId()/icon()) are safe to call anywhere. Taxonomies are resolved live from the listing post type, so new custom taxonomies get the fields automatically.

API
register()hookTaxonomies()taxonomies()imageId()image()icon()assets()addFields()editFields()save()
inc/_cb/TermMeta.php

Tracking Tracking.php​

Automatic tracking — a sale reported by an affiliate network becomes the member's cashback without a claim (Cashback Theme, _cb). Two ways in, one path: a network POSTBACK (Postback, the REST receiver) and a report row carrying the click tag (ReportImport). Both hand ingest() a normalised sale: subid our click tag cb4812 -> the click row -> member + store order/txid the shop's order number and/or the network's transaction id amount the sale amount; commission, the publisher's commission status approved | pending | declined (a negative amount or commission = declined) With a member: the sale is written to their claims (source = network) - or, if they already claimed that order themselves, THAT claim is updated - and moved along the same status ladder: pending, then confirmed when the network approves (into the balance, withdrawable), or declined (clawed back where the rules allow). Cashback on a network-created claim is the member's SHARE of the reported commission (Networks:: share); if a network sends no commission, the store's rate on the sale amount. Without a member (no tag, a guest click, a tag we never issued): the sale is only kept in the transactions log - a member who later claims that order is matched instantly. Only safe, forward moves happen automatically; anything else is logged for the owner.

API
normalise()clickFor()findClaim()cashbackFor()ingest()advance()isLive()log()recent()
inc/_cb/Tracking.php

Uploads Uploads.php​

Listing media uploads — the AJAX backend for the Uppy uploader on the listing editor (replacing the WordPress media frame). Uppy's XHRUpload posts one file per request to ppt_listing_upload; each becomes a normal WordPress attachment (so Media::gallery()/Media::videos() and the single-listing template keep working). Files are left unattached (post_parent = 0) until the listing form is saved, which parents the final set (ListingEditor::save()). A companion ppt_listing_media_remove deletes a freshly uploaded, not-yet- saved attachment when the user removes it in the editor, so abandoned uploads don't pile up in the media library.

API
register()imageMimes()videoMimes()ajaxUpload()ajaxRemove()ajaxPoster()ajaxPersist()
inc/_cb/Uploads.php