Directory Theme class reference
Football Theme — Class reference
The 38 classes that make _football behave differently from the shared Directory core. Generated from the theme source.
← Back to Football Theme features
AdminMatches AdminMatches.php
Themed "Matches" admin screen for the Football fork (_football). Matches are stored as the ppt_match CPT (Ppt_football\Matches) — the WordPress post table stays the source of truth. This presents the whole experience inside the PremiumPress admin chrome: a themed fixtures/results list AND a themed add/edit form (fixture facts + tickets + match report + photo), so the club never drops into the raw WordPress post editor. The CPT's own WP menu is hidden (Matches::postType show_in_menu = false); this themed screen is the single entry point. Self-gates to the football profile.
BuyerProtection BuyerProtection.php
Classifieds — Buyer Protection fee. The classifieds "Buy now" flow (Ppt_football\Checkout) charges a small buyer-protection fee on top of the item price, the way marketplaces like Gumtree/Vinted do. The admin turns it on and sets a percentage of the item, a fixed amount, or both (Payments ▸ Checkout ▸ Buyer Protection). It's stored in the shared ppt_checkout option (read via Ppt\Admin\Checkout::get()), alongside tax/shipping/commission, and shown as its own line on the buy box, the checkout summary and the buyer's invoice. Ships ENABLED by default (5% + a small fixed fee) so a fresh classifieds site has a working buy box out of the box; the admin can lower it or switch it off entirely.
Checkout Checkout.php
Classifieds — "Buy now" checkout for a single ad. A buyer on a classifieds single hits Buy now (see the buy box in the fork's single template + Ppt_football\BuyerProtection), lands on the shared /checkout/ page with ?buy=
ppt_gateway_start / ppt_gateway_verify filter contract, the ppt_orders store, the shared Ppt\Checkout\Address + Ppt\Checkout\Shipping helpers, and the shared tax math in Ppt\Payments\CheckoutFlow::totals(). Orders carry a FTBL- reference prefix (cf. the shop's SHOP- / auction's AUCT-) so Pages::content() can route /callback/ to this class. Registered only when the Classifieds profile is active (Theme::boot fork-gating), so its files never touch another profile. This is a single-item, buy-on-the-spot flow (like the marketplaces it mirrors), so — unlike the shop — there is no multi-item cart: the order is created at pay time for exactly the ad being bought.
ClubSettings ClubSettings.php
Club identity & kit colours for the Football fork (_football). One themed admin screen (under the PremiumPress menu) where the club sets its name, crest, ground, founded year and its two kit colours. The kit colours are the source the Kit-Colour Customiser re-skins the whole site from (P4 wires these into Design\Tokens); every block reads the club name/crest from the static getters here. Self-gates to the football profile like Admin\Merchants. Stored as one option array.
Comments Comments.php
Listing comments (member Q&A) — the Classifieds fork's replacement for star reviews. Built on native WordPress comments so they moderate and store like any comment, but stamped with the comment type "ppt_qa" (the identifier that distinguishes them from ordinary blog comments, listing reviews and reports in the admin). There is no rating — this is a plain "ask a question / leave a comment" thread, which fits a classifieds ad (buyers ask the seller questions) better than a public 1–5 rating. Mirrors Ppt_at\Comments (the Auction fork's identical swap); reuses the shared 'ppt_qa' comment type so Ppt\Admin\Comments' type filter/badge and the demo questions (Ppt\Content\DemoContent::questions) work here unchanged. Rendered in the single-listing "Comments" section (list + add-comment form).
DefaultFields DefaultFields.php
The Football theme's factory custom fields — the squad-profile facts a supporter wants about a player that PlayerSpecs does not already model. Football shipped no default fields, which had two consequences. A fresh club site's player pages had no Details block until an admin invented fields themselves; and because `
Fields::factorySeeds()
was empty the "Reset to defaults" control was hidden, so the product had NO recovery UI at all — which is how a football install carrying another product's leftovers (a Player editor asking about Licence covers and Refund policy) could only be cleaned up by hand. See Admin\Fields::maybeRescope(). NOTHING HERE DUPLICATES PlayerSpecs. Squad number, position, team, preferred foot, nationality, date of birth, appearances, goals, joined year and club captain are the fork's own meta, with their own editor card and their owndetailTable()on the single. These six are what a club writes ON TOP of that. DELIBERATELY CHOICE-TYPED WHERE IT CAN BE. Frontend\DemoListing::customFieldMeta() fills select / radio / checkbox / taxonomy fields on demo and showroom listings but leaves free text empty on purpose ("fiction dressed as data"), so aninput` reads blank on every design preview. Four of the six are therefore select or checkbox; Previous club and Player sponsor stay free text because no fixed option list could ever be right for them, and a blank row is better than a wrong one.
DemoClub DemoClub.php
Demo CLUB data for the Football fork — everything on a club website that is not a player: the season's results, the fixture list, the league table and the sponsor wall. _football\DemoProfiles turns the seeded listings into a squad; this class seeds the rest of the club. Without it a fresh install had no ppt_match posts, no Standings option and no Sponsors option, so the fixtures, results, form, countdown, league-table and sponsor blocks all fell through to the demo constants in Blocks\_ft\Club\Data — the home page looked finished while the Matches screen was empty and nothing the owner could edit existed yet. Everything here MIRRORS those demo constants on purpose: same club, same ground, same six-team Coastal League, the same opposition names, and a results run whose scorers add up to the goals DemoProfiles gives the squad (16) and to the gf in the club's own table row. A design preview and the live site it becomes therefore tell one story, and the owner can see how every field was filled in before replacing it with their own club's. Called from Tools\SampleData::seed() behind currentFork() === '_football' — never Theme::activeFork(), which memoises at boot (see the note there).
DemoProfiles DemoProfiles.php
Demo PLAYER data for the Football fork — the squad half of a fresh install. Tools\SampleData writes the nineteen curated listings from Content\SampleListings::forNiche('ftsquad'); this class is what turns them into actual PLAYERS. Without it they are ordinary listings with a name and a photo, and every football block ignores them: Blocks\_ft\Club\Data::playerQuery() matches on PlayerSpecs::NUMBER_META / POSITION_META EXISTING, so a listing carrying neither never reaches the squad grid, the formation board or the top-scorer rail — it simply falls back to the hardcoded demo eleven, which is why a live club site looked fully dressed while its Players screen was empty. Resolved by SampleData::seedProfileMeta() as \Ppt\\DemoProfiles from currentFork() — see the note there, and [[newdt-activefork-boot-memo-trap]]: the setup wizard writes the Theme Profile and seeds in the SAME request, so this must never be reached through Theme::activeFork(). Declaring body(), faq(), hours(), specifications() and contact() is the opt-out from the shared business filler. That filler is written for a theme whose listing is a company, and on a player it produced "Tomasz Wierzba is a trusted Goalkeepers based in Bristol, welcoming new and returning customers alike … fair, transparent pricing", a shop spec sheet, a tradesman's FAQ, nine-to-five opening hours and a landline behind a nineteen-year-old's squad profile.
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.
Gallery Gallery.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)
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.
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.
ListingCard ListingCard.php
The canonical listing card — the single, theme-wide way a business/listing is shown as a card. Every directory surface (search results, single-page "related", and the live listing-grid blocks) renders through here, so a listing looks the same everywhere and the demo-vs-live data split lives in ONE place. Data-source agnostic: fromPost() maps a real listing_type record and fromSample() maps a curated demo row into the same normalized shape, which render() draws. The markup is built on the theme design tokens, so the one card automatically adopts each design's colours/fonts. Its CSS is enqueued site-wide (assets/css/listing-card.css) — the card only emits markup. Normalized shape: name, link, img, cat, rating, desc, desc_long, price, city, dist, featured (bool), highlighted (bool), badge (string), id badge is an optional ribbon label (e.g. "New") for callers that need one; when empty it falls back to "Featured" if featured is set.
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.
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.
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.
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().
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.
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_football\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.
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, Comments. 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.
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).
MarkSold MarkSold.php
Classifieds — "Mark as sold" + pick-buyer. Classifieds has no cart/checkout, so unlike the auction fork there is no built-in record of a completed sale. This adds a lightweight one: the listing's owner marks an ad sold and picks the buyer from the members who have messaged them about it. That writes the sale meta (ppt_sold / ppt_sold_buyer / ppt_sold_at) that Account\Feedback::transactionFor() reads to open two-way feedback — so the whole feature only matters where feedback is switched on (control() renders nothing otherwise). Registered only on the classifieds fork (see Theme::$services).
Matches Matches.php
Fixtures & Results engine for the Football fork (_football). A club adds each match once — opponent, home/away, date, kick-off, venue, competition and (once played) the score — and the theme derives everything else: the upcoming fixtures list, the results archive, the recent W-D-L form guide and the next-match countdown. Matches are their own custom post type (ppt_match) so they're separate from the squad (the listing_type player profiles) and from match-report blog posts. This is a registered Service: it only boots under the football profile (Theme::boot fork-gates every Ppt\_football\* class). The block layer (FixturesList / ResultsList / FormGuide / Countdown / MatchdayHero) reads the static query helpers here; nothing else needs to know how a match is stored.
MatchLineup MatchLineup.php
Per-match line-up (the "formations table") for the Football fork (_football). A club picks a formation and assigns 11 squad players to the pitch for a specific Match (ppt_match), plus optional substitutes. Stored as match meta; resolved back into per-slot player rows for the formation-board / tactics blocks. The blocks show a match's real line-up when one is set (single match page, or the next/last fixture on the home page) and fall back to the squad list when none is. Static helper (like PlayerSpecs / MatchTickets): the Match editor renders MatchLineup::editorCard($pid) and AdminMatches::handleSave() calls save($pid).
MatchTickets MatchTickets.php
Match tickets for the Football fork (_football) — the built-in ticketing layer that sits on a Match (ppt_match), NOT on a player listing. A club sets a fixture's ticket status, price(s) and either an external ticketing link or lets the theme sell on-site through the kept single-item checkout (Ppt_football\Checkout, made match-aware). Static helper (like PlayerSpecs): the Matches meta box renders fields($pid) and Matches::save() calls save($pid); the Match single page shows buyBox($pid); the fixtures block shows ticketLink($pid). Checkout reads price()/isBuyable() for a match.
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.
PlayerGallery PlayerGallery.php
Football Theme (_football) — the matchday gallery on a player profile. A player single page is a HEAD TAKEOVER: PlayerHero draws the squad panel in place of the shell's gallery and <h1> (templates/parts/single-hero.php sets $pptHeroDrawn), which is why a profile had no gallery at all. This puts one back at the top of the main column, directly under the hero — and does it without showing the same photograph twice on one page: whatever the hero is already displaying is excluded here. Photographs, best first: 1. The player's own uploads that the hero did NOT show. Live sites only — in preview Media::gallery() hands back the previewed DESIGN's niche pool, which is eight DIFFERENT players' portraits (see the note on PlayerHero::shots()), and a squad's worth of other faces on one man's profile is worse than no gallery. 2. The club's own matchday scenes: the ground, the crowd, match action. Face close-ups of other men (demo/pitchside.webp, demo/player-action.webp) are deliberately NOT in the scene pool. On a profile page a stranger's portrait reads as "this is Callum Reid", and it isn't — the same mismatch PlayerHero::shots() avoids by refusing the niche pool in preview. Wiring mirrors PlayerSpecs / MatchTickets: a plain static helper, NOT a registered Service. templates/parts/single-after-title.php prints PlayerGallery::render($pid) above the squad facts strip.
PlayerHero PlayerHero.php
Football Theme — the player profile hero. Takes over the top of the single-listing page for a player listing: the club's colours behind a cut-out squad photograph, the name set as a programme cover (given name light over surname heavy), the squad chip, and a rank of stat tiles. Everything below it — Overview, the detail table, FAQ, reviews, the sidebar — is the standard listing single, untouched. Drawn from the fields PlayerSpecs already stores and an owner can already edit (shirt number, position, team, captain, DOB, nationality, foot, appearances, goals, joined). Nothing here invents a statistic the club has no way to enter, so the page is as true on a real club's site as it is in the showroom. Re-skins per design: every colour is a design token, so the same hero wears Kickoff's red on paper, Floodlight's near-black and Terrace's brand band.
PlayerSpecs PlayerSpecs.php
Squad profile facts for the Football fork (_football): shirt number, position, which team (first/reserves/youth), captaincy, and a few career facts. This is the field group that turns the primary listing into a player profile — the club's squad is a directory of these. Wiring mirrors _fm\ProjectSpecs / _football\MatchTickets (a plain static helper, NOT a registered Service): the add/edit form prints PlayerSpecs::editorCard($pid); the save is PlayerSpecs::saveFromForm($pid, $in); the card/single call metaStrip($pid) / detailRows($pid). The listing post title is the player name and post_content the bio, so those aren't duplicated here.
ReportHeader ReportHeader.php
Match-report "result header" for the Football fork (_football). Match reports & news are ordinary blog posts (reference feature #6); this adds an optional result header to a post — opponent, the score, competition and an optional link to the ppt_match — so a report can render with a "3 – 1 v Melbrook United" banner and the ft_news block can badge it. A post with no header is just a normal post. Service: a meta box on the post editor + save, self-gated to the football profile. Static getter header($pid) is read by the ft_news block and the single post.
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.
Schema Schema.php
JSON-LD schema for the Football fork (_football) — the theme emits its own structured data (no SEO plugin). Adds, on wp_head, an schema.org Person (athlete) graph on a single player profile and a SportsEvent graph on a single match, alongside the base Organization/BlogPosting from Frontend\Schema. Self-gates to the football profile (only registered under it via Theme::boot's fork-gating, and the single checks guard the post types anyway).
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 price1 / price2 min / max price (numeric, price meta) sort featured | newest | oldest | rating | price_low | price_high | title paged pagination
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.
Sponsors Sponsors.php
Club sponsors for the Football fork (_football) — the data behind the Sponsor Wall block. A club manages its own list of sponsors (name, logo, link, tier) on one themed admin screen; the block reads the static getters here. Self-gates to the football profile. Stored as one option array of rows.
Standings Standings.php
League table (standings) for the Football fork (_football) — the data behind the League Table block. The club manages the table by hand (the reference's "no widget needed, you manage it"): a competition name plus one row per team with P/W/D/L/GF/GA. Points (3·W + D), goal difference, and the sort are computed here; the club's own row is highlighted by matching ClubSettings::name(). MVP: one competition table. (Per-competition multiples can layer on later — the block already takes a competition arg.) Self-gates to the football profile.
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.
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.