Skip to main content

Directory Theme class reference

16 min read

Classifieds Theme — Class reference

The 26 classes that make _ct behave differently from the shared Directory core. Generated from the theme source.

← Back to Classifieds Theme features

BuyerProtection BuyerProtection.php​

Classifieds — Buyer Protection fee. The classifieds "Buy now" flow (Ppt_ct\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.

API
enabled()percent()fixed()applies()on()label()
inc/_ct/BuyerProtection.php

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_ct\BuyerProtection), lands on the shared /checkout/ page with ?buy=

``, fills their delivery address, picks a gateway and pays. On the /callback/ return the order completes, the ad is marked sold to the buyer (Ppt_ct\MarkSold), an escrow hold opens on the seller's share (Ppt_ct\Escrow), and buyer + seller + admin are emailed. Note what the hold does and doesn't change: the buyer still pays the FULL amount into the site's own gateway here, exactly as before. The hold governs only when the seller becomes owed their share in the shared payouts ledger — on the two parties confirming the handover, not on the payment clearing. See Ppt_ct\Escrow for the state machine. It reuses the same building blocks the Shop and Auction forks use — the shared gateway registry (Ppt\Admin\Payments) with the 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 CLAS- 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.

API
register()buyUrl()callbackUrl()isClassifiedsRef()itemPrice()isBuyable()needsShipping()totals()handlePay()markPaid()ajaxShippingQuote()renderCheckout()renderCallback()
inc/_ct/Checkout.php

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).

API
register()open()stampType()count()all()renderList()renderForm()
inc/_ct/Comments.php

DemoAccounts DemoAccounts.php​

Classifieds — what the typed DEMO members walk into on top of the shared furnishing (Account\DemoFurnish: favourites, follow, inbox thread, review). the SELLER (AccountType::SOLO) three seeded ads (DemoLogin lends them — the count is raised here from one); the BUYER (AccountType::CLIENT) has favourited them, follows the seller and has an enquiry thread open — the shape a buyer's hub takes before a sale. Nothing else is seeded: a classified sale is a conversation and a "Mark as sold", both of which the shared furnishing already stages. Gated to the classifieds profile by Theme::boot()'s fork-gating (this is Ppt_ct*).

API
register()lendCount()
inc/_ct/DemoAccounts.php

Escrow Escrow.php​

Classifieds — escrow hold on a "Buy now" sale (_ct). Model (chosen 2026-09-11): the same LEDGER shape the Freelancer Marketplace uses (Ppt_fm\Escrow) — NOT held-funds/Stripe-Connect escrow. The buyer pays the full amount into the SITE's gateway at checkout exactly as before; what changes is that the seller's share is no longer owed to them the instant the payment clears. It is held until BOTH parties confirm the handover, and only then accrued to the shared contributor ledger (Ppt\Payouts\Ledger) for the owner to pay out through the existing Payouts workbench. Two-party gate (buyer AND seller confirm): held → seller confirms they dispatched/handed over → handover handover → buyer confirms they received it → released handover → auto-release once the buyer has had N days → released held|handover → buyer reports a problem → frozen (clock stops) frozen → owner records an off-platform refund → refunded The auto-release clock starts at the SELLER's confirmation, not at checkout: until the seller says the item is on its way there is nothing for the buyer to receive, so a timer from payment would release money for an item that never shipped. A hold the seller never confirms simply stays held and shows up in the owner's workbench as stale — deliberately, since that is a seller who took money and did nothing. The contributor share is SNAPSHOTTED at sale time (see open()) so a later change to the site's commission rate never rewrites the economics of an in-flight sale — the same guarantee _fm\Escrow gives an awarded project. What is held is the ex-tax ITEM value only (net = item − coupon discount). Tax, delivery and the Buyer Protection fee are the site's own lines and are never part of the seller's share, so they are excluded from the accrual base. State is order meta (one hold per order; a classified ad sells once). Nothing here touches the off-platform "Mark as sold" path — a seller who arranges a cash sale through Ppt_ct\MarkSold never creates a hold at all.

API
register()scheduleSweep()settings()enabled()autoReleaseDays()saveSettings()open()state()exists()isHolding()sellerId()buyerId()listingId()gross()rate()currency()sellerAmount()commissionAmount()freezeNote()releaseDue()confirmHandover()confirmReceipt()freeze()release()
inc/_ct/Escrow.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/_ct/Expiry.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/_ct/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/_ct/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/_ct/ListingActions.php

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.

API
fromPost()fromSample()render()
inc/_ct/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()socialNetworks()socialValue()socialIcon()hoursDays()hoursValue()normTime()bookingValue()bookingCapacity()bookingBlackout()bookingTz()
inc/_ct/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/_ct/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/_ct/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/_ct/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/_ct/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_ct\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/_ct/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, 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.

API
supported()catalog()config()order()enabled()orderedAmong()sanitize()
inc/_ct/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/_ct/ListingViews.php

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).

API
register()isSold()buyerId()markSold()markUnsold()buyerOptions()ajax()control()
inc/_ct/MarkSold.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/_ct/Media.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/_ct/Reviews.php

Roles Roles.php​

Buyer or Seller: who this member is on the classifieds profile. A classifieds site has two sides. A SELLER posts ads; a BUYER browses and messages. Sign-up asks which in its role step ("browse" / "list") and stores the answer in ppt_signup_role; this bridges that answer onto the type Account\AccountType understands (buy → CLIENT, sell → SOLO) and supplies the words — exactly the Micro Jobs pattern (Ppt_mj\Roles), cloned 2026-09-20 so the home-demo can offer a one-click "Buyer login" and "Seller login" and the hub can show the badge. Gated to the classifieds profile by Theme::boot()'s fork-gating (this is Ppt_ct*).

API
register()profiles()typeForUser()typeCurrent()toType()options()shortLabel()typeOf()isBuyer()hidesSelling()accountBadge()
inc/_ct/Roles.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 price1 / price2 min / max price (numeric, price meta) sort featured | newest | oldest | rating | price_low | price_high | title paged pagination

API
register()shapeQuery()nearCoords()radiusKm()distanceFrom()distanceLabel()radiusClausesMain()radiusClauses()featuredOrderClauses()isListingContext()postType()isMapTakeover()categoryTax()terms()taxQueryFromRequest()listingTaxonomies()hasActiveFilters()matchingIds()termCounts()priceMetaQuery()ratingMetaQuery()activeMetaQuery()sortOptions()template()
inc/_ct/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()demoBookingProvider()template()related()
inc/_ct/SingleListing.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/_ct/TermMeta.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/_ct/Uploads.php