Skip to main content

Directory Theme class reference

23 min read

Stock Photo Theme — Class reference

The 32 classes that make _ph behave differently from the shared Directory core. Generated from the theme source.

← Back to Stock Photo Theme features

AssetFacts AssetFacts.php​

The file facts behind a stock asset (Ppt_ph): resolution, file format, file size, DPI and colour profile — the rows the licence box and the "Technical data" table on the single page read through Ppt_ph\StockMeta. Before this existed NOTHING wrote those metas. StockMeta has always read them real-first-with-a-demo-fallback, so a demo listing looked complete while a live one showed an empty specs table forever, with no editor field to fill it in. Two halves, in this order of authority: 1. DERIVED — read off the actual deliverable (the download file when one is set, else the main photo, else the video: the same precedence Download::protectedFile() uses, so the specs describe the file a buyer actually receives). Width, height and byte size come from WP's own attachment metadata; DPI and colour profile come from EXIF, which is reliable for camera JPEGs and simply absent from a lot of files. 2. TYPED — anything the contributor edits in the "File details" card wins, and is remembered as manual (MANUAL_META) so a later re-sync can't overwrite it. Clearing a box drops the override and hands the row back to detection. sync() is safe to call on every save: it only ever touches keys that are not manual.

API
keys()labels()manualKeys()isManual()sourceAttachment()derive()sync()saveOverrides()countDownload()
inc/_ph/AssetFacts.php

AssetIndex AssetIndex.php​

Search-facet index for stock assets (Ppt_ph). Computes and stores the queryable facets a photo library filters on — orientation, dominant colour, resolution (megapixels) and the licence types the asset offers — so the search page can add meta_query clauses without re-analysing images on every request. Computed once when a listing is saved (ListingEditor → reindex) and backfillable in bulk. Orientation and resolution come from the primary image's stored width/ height (no image library needed); the dominant colour is a best-effort average via Imagick or GD (skipped silently when neither is available — the admin can hide that filter). Licence types come from the resolved pricing tiers. Meta keys (all on the listing post): ph_orientation 'landscape' | 'portrait' | 'square' ph_megapixels float, e.g. 24.0 ph_color a palette key from self::colors() ph_license_type MULTIPLE rows, one per licence type offered (cc0/cc/rf/rm)

API
orientations()colors()resolutions()reindex()reindexAll()nearestColor()licenseTypesFor()
inc/_ph/AssetIndex.php

Checkout Checkout.php​

Stock Photo Theme — single-item licence checkout (Ppt_ph). A buyer clicks "License & download" on an asset with a licence tier selected; that routes here as /checkout/?buy=

&tier=``. This class prices the chosen tier (Ppt_ph\Licensing), shows an order summary + gateway choice, takes payment through the shared gateway machinery, and on success records the entitlement (Ppt_ph\Licenses) so the buyer can download the asset from their account. A sibling to Ppt\Payments\CheckoutFlow and the other forks' checkouts (_sp, _ct) — it reuses the same ppt_gateway_start / ppt_gateway_verify contracts, the shared ppt_orders CPT (Ppt\Admin\Orders), the /checkout/ + /callback/ virtual pages, and the receipt email templates — without touching CheckoutFlow. Order refs are prefixed PHOTO- so Pages::content() routes callbacks here. Digital goods: no shipping, no address, no stock — many buyers can license the same asset. Actual protected file delivery (P0 #1) layers on the entitlement recorded here; until it lands, a completed purchase points the buyer at their downloads.

API
register()buyUrl()callbackUrl()isPhotoRef()basePrice()tier()isBuyable()totals()creditCost()canPayWithCredits()handlePay()markPaid()orderContext()renderCheckout()renderCallback()
inc/_ph/Checkout.php

DemoProfiles DemoProfiles.php​

Stock Photo Theme (_ph) — demo copy for seeded photos. Tools\SampleData resolves Ppt\\DemoProfiles per fork and calls whatever it declares (body / faq / hours / specifications). Declaring a method IS the opt-in, so nothing in the seeder needs to know this class exists — the same contract the two person-profile forks (_da, _es) use. WHY THIS EXISTS The shared filler is written as business/service sales copy, because every theme it was built for really is selling a service. Run through this fork it produced "Misty Pine Forest is a trusted nature based in Manchester, welcoming new and returning customers alike … our experienced team … fair, transparent pricing" as the Overview of a landscape photograph, an FAQ about opening hours and free quotes, a nine-to-five "Business hours" card, and a shop spec sheet in meta nothing on the fork can show. Seen live on a client's site, not just the demo. The copy here describes an image: what is in the frame, how it was shot, what it is good for, and how the licence works. Deterministic in the row index so a reseed rebuilds the same site. A photo has no opening hours and no spec sheet, so those two write NOTHING — the single page then shows neither card.

API
body()faq()hours()specifications()
inc/_ph/DemoProfiles.php

Download Download.php​

Acquire + protected delivery for the Stock Photo Theme (Ppt_ph). Two steps, so a working download URL can never be passed around: 1. ACQUIRE (admin-post ppt_ph_get, the "License & download" button target) — decides by the chosen tier's canonical TYPE, not its caption: · already own a licence for the asset → straight to delivery (re-download); · a FREE type (CC0 / Creative Commons) → record a free licence (for a signed-in visitor) and go to delivery, skipping the paywall; · a PAID type (Royalty-Free / Rights-Managed) not yet owned → the licence checkout (Ppt_ph\Checkout) takes payment first. It never streams a file itself — it redirects to a freshly SIGNED delivery URL. 2. DELIVER (a signed ?ppt_ph_dl=… link, caught on template_redirect) — a short-lived HMAC-signed URL bound to the buyer. Before streaming it re-checks: the signature, the expiry, and — for a paid asset — that the current user is the signed user AND still holds the entitlement (Ppt_ph\Licenses). The raw WordPress attachment URL is never handed out, and an expired/leaked link is inert. Anonymous links are minted only for genuinely free assets. NOTE (infra): this gates the download path; the original still lives at a public wp-content/uploads URL that WordPress serves directly. For airtight protection, store originals outside the web root (or block the uploads dir and serve only via this endpoint) — see STOCK-PHOTO-THEME-FEATURE-GAP.md. The theme never surfaces that raw URL; every download link it emits is signed and expiring.

API
register()actionUrl()signedUrl()demoMessage()isDemoTarget()isDemoDeadEnd()handle()maybeServe()
inc/_ph/Download.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/_ph/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/_ph/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/_ph/Geocoder.php

LicenseBox LicenseBox.php​

The "License & download" sidebar box for a stock-photo single page (Ppt_ph) — the storefront panel of a stock asset: the license tier a buyer picks (Standard vs Extended), the price, the primary download / license button, and the file specs (resolution, format, file size, DPI, downloads). It replaces the generic directory "contact the owner" card for the photography profile. Prices come from the listing price meta (Ppt_ph\SearchPage::PRICE_META, the same key the card + search use). A free asset (no price) shows a single "Free download" button; a priced asset shows the licence tiers resolved by Ppt_ph\Licensing — the contributor's own pricing plans when they set two or more, else the admin-set default ladder (Personal / Commercial / Extended) scaled off the base price. In demo/preview a price-less demo listing gets deterministic sample prices so the box always reads like a real store — mirroring the demo-fallback idiom in Ppt_sp\Product.

API
basePrice()tiers()specs()render()contributorCard()
inc/_ph/LicenseBox.php

Licenses Licenses.php​

Granted licences for the Stock Photo Theme (Ppt_ph). When a buyer completes a licence purchase (Ppt_ph\Checkout), the tier they bought for that asset is recorded here — a per-user ledger stored in user meta. It answers the two questions the rest of the storefront needs: "has this buyer licensed this asset?" (so the download unlocks and the buy button becomes a re-download) and "what has this buyer bought?" (the account area's downloads list). Actual protected file DELIVERY (a signed, expiring URL that only a licence holder can hit) is P0 #1 and layers on top of this — see STOCK-PHOTO-THEME-FEATURE-GAP.md. This class is the entitlement record that gate will check.

API
mine()has()tiersFor()grant()count()renderAccountPanel()
inc/_ph/Licenses.php

LicenseTypes LicenseTypes.php​

Canonical licence types for the Stock Photo Theme (Ppt_ph). A CLOSED set that classifies every licence tier by what it legally IS, independent of whatever the contributor titles it. A tier can be captioned "Commercial", "Big bundle" or anything else, but its type is always one of these ids — so the terms shown to buyers, the badge, the entitlement recorded on purchase, and (later) the search filter are all driven by the real licence model, never by a free-text name. The taxonomy: cc0 — Public Domain / CC0 — free, no attribution, zero restrictions. cc — Creative Commons — free, modular conditions (attribution / NC / SA). rf — Royalty-Free — one-time fee, multi-use, non-exclusive. rm — Rights-Managed — priced per use; specific scope/term, can be exclusive. Extend or relabel via the ppt_ph_license_types filter.

API
all()normalise()has()get()label()badge()summary()isFree()choices()allowed()isAllowed()allowedChoices()freeChoices()
inc/_ph/LicenseTypes.php

Licensing Licensing.php​

Per-image licence tiers for the Stock Photo Theme (Ppt_ph). Resolves the licence tiers a buyer sees on an asset — Personal / Commercial / Extended — from two inputs: 1. The listing's OWN pricing plans (Ppt_ph\ListingPricing::META). ANY priced plan row wins: those ARE the tiers, used verbatim (their names, notes and absolute prices). This is the true per-image override, and it is what the editor's "Custom tiers" mode writes. 2. Otherwise the site's DEFAULT tier ladder — a set of {name, note, multiplier} rows scaled off the asset's base price. This is the editor's "One price" mode: it stores a base price and NO plan rows, which is the only state that reaches this branch on a live asset (that, and a demo listing whose sample price was seeded without plans). The default ladder comes from the Licensing::OPTION option and the ppt_ph_license_tiers_default filter. NOTE: no admin screen writes that option yet — today it is option/filter only. This replaces the earlier hard-coded "base x 4" derivation in LicenseBox: the ladder is now data-driven and per-image editable, while the searchable price meta (lowest plan) is untouched. Cart/checkout wiring of the chosen tier is a separate step (see STOCK-PHOTO-THEME-FEATURE-GAP.md). DO NOT MIRROR THIS FILE INTO inc/_ai/. The AI Photo fork started as a copy of this one, but it is a SEPARATE PRODUCT with its own roadmap — photography decisions (licence ladders, tier pricing, download/demo behaviour) are not AI Photo decisions. Changes here stay here unless someone explicitly asks for the same change in _ai; _ai keeps its own ppt_ai_license_tiers option and ppt_ai_license_tiers_default filter and is free to diverge.

API
modes()mode()defaultTiers()normaliseDefaults()resolve()
inc/_ph/Licensing.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/_ph/ListingActions.php

ListingCard ListingCard.php​

The listing card for the Stock Photo Theme (Ppt_ph). An image-first card: the preview photo dominates, with format + resolution badges riding the image and a price / "Free" chip, and a compact contributor + downloads footer under the title — the look of a stock-photo/asset library (Unsplash / Shutterstock). Forked from Ppt\Directory\ListingCard for the photography profile so the stock-photo grid can diverge from the generic directory card. Every _ph surface (search results, single-page "related", and the live listing-grid blocks) renders through here, so a photo asset looks the same everywhere. Its extra classes (.ppt-lc--photo, .ppt-lc-ph-*) live in assets/css/listing-card.css. Normalized shape (adds format/resolution/downloads/author to the base shape): name, link, img, cat, rating, desc, desc_long, price, city, dist, featured (bool), highlighted (bool), badge (string), id, format, resolution, downloads, author

API
fromPost()fromSample()render()
inc/_ph/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/_ph/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/_ph/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/_ph/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/_ph/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/_ph/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_ph\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/_ph/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/_ph/ListingSections.php

ListingTiles ListingTiles.php​

The tile row at the top of the Stock Photo listing form: Category and Media type. Category used to be a stacked chip-and-button field under the title — a row of chips, a "Choose category" button and a line of hint text, three stacked things for one decision. It now reads as the same square tile the Directory theme uses (Ppt\Directory\ListingTiles) and the Coupon, Software and Real Estate themes before it: icon, label, and either the chips that are set or the word "Select". The Category tile opens the SHARED category picker modal (assets/js/category-picker.js) — searchable, with a Clear All / Continue footer — so there is still exactly one picker implementation across every fork. Media type is the fork-specific second tile, in the slot Directory gives to Business hours. It answers "is this listing a photo or a video?" — the choice that decides which uploader the form shows and which side of the Images/Videos search filter the asset lands on (StockMeta::META_ASSET_TYPE, read by SearchPage::mediaMetaQuery()). It was a segmented Photo/Video toggle buried at the top of the Photos & video card, three cards down the form, so the one decision that changes what the rest of the form asks for was the one you met last. The toggle is not duplicated here: mediaModal() renders the SAME radio group, with the same names and values, inside a dialog that lives in the Photos & video card. The card's own script (inc/_ph/views/listing-form.php) still scopes every query to [data-asset-card], so the pane switching and the "remove the photo before you can switch to video" locks keep working untouched — there is one control, in a new place, not a second source of truth for the same meta. Collections are deliberately NOT a third choice: StockMeta::META_COLLECTION is derived on save from how many assets the listing carries (ListingEditor::syncCollection()), so offering it as a manual pick would put the admin in a fight with the save path.

API
categoryTile()mediaTypes()mediaTile()mediaModal()assets()
inc/_ph/ListingTiles.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/_ph/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/_ph/Media.php

PlanAccess PlanAccess.php​

Membership-plan downloads for the Stock Photo Theme (Ppt_ph) — the "subscription" way to sell a stock library, beside the per-item licences Ppt_ph\Licensing prices. An asset saved in the editor's "Included in plans" pricing mode (Licensing:: MODE_PLANS) is not sold on its own. It names a MINIMUM plan (META_MIN_PLAN) and any member whose plan sits at or above it in the admin's Memberships grid may download it, one from their monthly allowance: ph_downloads a monthly quota row on every plan card (Account\Entitlements); -1 = unlimited, 0 = this plan includes no downloads. RANK IS GRID ORDER. Plans are ranked by where they sit in Memberships::active(), so "Basic and up" means Basic and every enabled plan after it. Price is deliberately not the rank: a yearly Basic can cost more than a monthly Pro. A DOWNLOAD IS A LICENCE. The first plan download of an asset takes one from the allowance and records a Royalty-Free licence in Ppt_ph\Licenses (order ref PLAN-…). From then on Download::handle()'s "already hold a licence" check delivers it — so a re-download never counts again, still works after the membership ends, and the file shows in the member's Downloads tab like any bought licence. OUT OF DOWNLOADS = BLOCKED. No credits top-up: the box says how many are left, when the allowance resets, and links to the plans page.

API
register()rows()available()plans()rank()isPlanAsset()minPlan()minPlanName()minIsTop()label()isStaff()qualifies()resetsAt()state()acquire()plansUrl()message()
inc/_ph/PlanAccess.php

ProtectedStore ProtectedStore.php​

Protected storage for deliverable files (Ppt_ph). Download-slot uploads are written into wp-content/uploads/ppt-protected/ instead of the normal year/month tree, and that folder carries an .htaccess that denies direct web access. The original can therefore only be read from disk by the signed delivery endpoint (Ppt_ph\Download, via readfile() on the absolute path) — never fetched by guessing its URL. Apache honours the .htaccess; on nginx the equivalent must be a server location deny for /wp-content/uploads/ppt-protected/ (documented in STOCK-PHOTO-THEME-FEATURE-GAP.md). Files already stored publicly before this landed are not moved retroactively.

API
baseDir()ensure()uploadDirFilter()isProtected()randomFilename()rememberOriginal()displayName()displayNameForFile()
inc/_ph/ProtectedStore.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/_ph/Reviews.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()mediaTypes()currentMedia()mediaMetaQuery()
inc/_ph/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/_ph/SingleListing.php

StockMeta StockMeta.php​

Stock-photo asset facts (Ppt_ph) — the per-listing details a stock-photo card and single page show that a generic directory listing has no concept of: the file format(s), the pixel resolution, the download count and the contributor. Real meta always wins. When a value isn't set AND we're rendering a demo/preview listing (a ppt-demo post under Preview::isPreview()), a deterministic sample value is returned so the demo grid always reads as a real stock library. Outside demo mode an unset value returns empty/0 — nothing is fabricated on a live site. This mirrors the demo-fallback idiom in Ppt_sp\Product. Meta keys (a listing form / import can populate these): ph_format e.g. "JPG", "RAW + JPG" ph_resolution e.g. "6000 × 4000" ph_downloads integer download count ph_dpi e.g. "300" ph_filesize e.g. "24.6 MB" ph_keywords comma-separated tags

API
format()resolution()downloads()dpi()filesize()profile()assetType()isCollection()author()keywords()keywordLinks()taken()place()modelRelease()propertyRelease()releaseNote()releaseLabel()demoFormat()demoResolution()demoDownloads()demoAssetType()demoIsCollection()demoAuthor()demoFilesize()
inc/_ph/StockMeta.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/_ph/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()downloadExtMap()ajaxUpload()ajaxRemove()ajaxPoster()ajaxPersist()
inc/_ph/Uploads.php