Directory Theme class reference
Learning Theme — Class reference
The 34 classes that make _ll behave differently from the shared Directory core. Generated from the theme source.
← Back to Learning Theme features
AdminLessons AdminLessons.php
Themed "Lessons" admin screen for the Learning / LMS Theme (Ppt_ll) — a custom PremiumPress admin page (rendered in the theme's Chrome, like the Courses screen) that lists every lesson grouped by its course on the left, with at-a-glance statistics on the right. Add a lesson, or edit/customise an existing one, through the lesson editor (which carries the Curriculum "Lesson details" fields). It sits in the LEARNING sidebar group directly under Courses (added in Admin\Chrome::sections() when the learning fork is active) and reads the same ppt_lesson data the in-course Curriculum card writes, so the two stay in sync.
BuyBox BuyBox.php
Buy / download box for the Software / Digital Download Theme (Ppt_ll). One button on the single page that does the right thing via Ppt_ll\Download's acquire step: · already own it → re-download (signed link); · free → download now; · paid, not owned → checkout. When the listing has an external/affiliate download URL set, the button links out to that instead (no hosting, no entitlement) — see EXT_URL_META.
Chat Chat.php
Course group chat for the Learning / LMS Theme (Ppt_ll) — a persistent, per-course discussion the enrolled community shares, shown in the classroom's "Discussion" tab (Ppt_ll\Player). Messages live in a custom table; posting and reading are gated to learners who have access to the course (Player::canAccess), so only the classroom can see and use it. The front-end loads the recent history, then polls for new messages so a conversation feels live without a websocket.
Checkout Checkout.php
Software / Digital Download Theme — single-item purchase checkout (Ppt_ll). A buyer clicks "Buy / Download" on a priced product; that routes here as /checkout/?buy=
_ph/_sp/_ct) — same ppt_gateway_start / ppt_gateway_verify contracts, the shared ppt_orders CPT, the /checkout/ + /callback/ virtual pages, and receipt emails. Order refs are prefixed LEARN- so Pages::content() routes callbacks here. Digital goods: no shipping, no address, no stock — many buyers can own the same download. (Single-item first; a multi-item cart layers on later — the sales page's headline "shopping cart" — reusing this order/grant machinery.)
CourseDetails CourseDetails.php
Course detail fields for the Learning / LMS Theme (Ppt_ll) — the "at a glance" facts a learner wants before enrolling (#05): the level, duration and language, plus the "What you'll learn" outcomes and the "Requirements" list. Modelled on the Real Estate fork's property-features (a dedicated per-listing meta set with its own editor card, single-page block and listing-card badges) so the course single page reads like a course, not a generic listing. All rendering lives here so the touch-points in the shared editor form, the single template and the listing card are one-liners: CourseDetails::editorCard($pid), ::single($pid) and ::cardBadges($pid); the save is CourseDetails::saveFromForm().
Curriculum Curriculum.php
Curriculum model for the Learning / LMS Theme (Ppt_ll) — the structure that turns a course (a listing_type post, relabelled "Course" under the learning profile) into an ordered set of sections and lessons. A lesson is its own post (ppt_lesson) so it can carry its own content, video, embed and — later — its own progress/quiz/resource records, while staying tied to one parent course. Lessons are internal: they are never browsed or searched directly (the lesson player renders them, gated on enrolment), so the CPT is non-public with no admin UI of its own — the course editor manages them (a later card) and this class is the single read API the player/progress/certificate work builds on. course (listing_type) └─ section "Getting started" (a label on the lesson, _ppt_section) ├─ lesson (ppt_lesson, menu_order 0) └─ lesson (ppt_lesson, menu_order 1) Ordering: sections keep first-seen order; lessons order by menu_order then id. A lesson with no section falls into an untitled leading group. STATUS: Phase-1 foundation (#01 Curriculum structure) — the data model + read API. Still to come on top of it: the course-editor UI to add/order lessons, the per-lesson content editor (#02), the enrolment-gated player (#03) and progress (#04). Registered as a Service so the CPT exists whenever the learning fork boots.
DemoCommunity DemoCommunity.php
Demo community for the Learning / LMS Theme (Ppt_ll) — sample enrolled members and a seeded group-chat conversation for the demo classroom (Ppt_ll\Player::renderDemo). Zero database: it exists only so a visitor exploring the demo can see who's "enrolled" and try the course group chat. Avatars are drawn from initials + a derived colour, so nothing loads from an external host.
DemoCourses DemoCourses.php
Demo courses for the Learning / LMS Theme (Ppt_ll) — sample, zero-database courses shown while the site is in preview/demo mode (Frontend\Preview::isDemo()), so a visitor evaluating the theme can walk the whole flow: enrol → My Classroom → open a course → work through real, playable lessons — without any content existing yet. Each course carries 7–8 lessons grouped into sections, most of them video (sample clips from Google's public test-video bucket), a couple text. Nothing here touches the database; the demo player (Ppt_ll\Player::renderDemo) renders straight from these arrays, and the My Classroom panel lists them in demo mode.
Download Download.php
Acquire + protected delivery for the Software / Digital Download Theme (Ppt_ll). Two steps, so a working file URL can never be shared: 1. ACQUIRE (admin-post ppt_so_get, the "Download" / "Get" button target): · already own the download → straight to delivery (re-download); · free (no price) → record a free purchase (signed-in) and go to delivery; · paid, not yet owned → the checkout (Ppt_ll\Checkout) takes payment first. It never streams a file itself — it redirects to a freshly SIGNED delivery URL. 2. DELIVER (a signed ?ppt_so_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 download — that the current user is the signed user AND still owns it (Ppt_ll\Purchases). The raw attachment URL is never handed out; an expired/leaked link is inert. Anonymous links are minted only for genuinely free downloads. The deliverable is the dedicated "download file" (Ppt_ll\ProtectedStore, out of the public tree) when set, else the featured image's original — so there's always something to deliver. See also the external-download-URL affiliate path on the single page, which bypasses this entirely.
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_ll\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, 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.
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).
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.
Player Player.php
Lesson player for the Learning / LMS Theme (Ppt_ll) — the student-facing course consumption page (#03). It renders a course's curriculum (sections → lessons) as a sidebar next to the current lesson's content, gated on enrolment. Routing: it hangs off the course's own single URL via a ?lesson=?learn to open the first lesson) query — no rewrite rules, no extra page. On such a request for a single course, this intercepts template_redirect, renders the player inside the theme's header/footer, and exits (so the normal single-course landing, with its Buy box, still lives at the clean permalink). Access is the single question "does this viewer own the course?", answered by Purchases::has() — the exact entitlement the checkout grants on payment (Checkout::renderCallback → Purchases::grant) and the free-acquire path records. The course author and admins always have access; a lesson flagged as a free preview is open to everyone; everything else shows a locked panel that points back to the course page to enrol. STATUS: Phase-1 #03. Progress ticks (#04) are not wired yet — the sidebar leaves room for them. Builds on Curriculum (#01) for the structure and Purchases for the gate.
Progress Progress.php
Lesson progress for the Learning / LMS Theme (Ppt_ll) — #04. Tracks which lessons a learner has completed in each course, the % complete, and where to resume, so the player can show ticks + a progress bar and "Start / Continue" jumps back in. Storage (per user, keyed by course): user-meta ppt_ll_progress = [ courseId => [ completed lessonId, … ] ] user-meta ppt_ll_last = [ courseId => last-viewed lessonId ] (resume hint) Completion is intentionally learner-driven (a "Mark complete" toggle in the player) rather than inferred from a page view, so the signal is deliberate — the same signal certificates (#09), drip (#10), prerequisites (#11) and gamification (#20) build on.
ProtectedStore ProtectedStore.php
Protected storage for deliverable files (Ppt_ll). 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_ll\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.
Purchases Purchases.php
Purchased downloads for the Software / Digital Download Theme (Ppt_ll). When a buyer completes a purchase (Ppt_ll\Checkout) — or grabs a free download — the product they now own is recorded here, a per-user ledger in user meta. It answers "does this buyer own this download?" (so the protected file unlocks and the buy button becomes a re-download) and "what has this buyer bought?" (the account library). Unlike the Stock Photo Theme's licence tiers, a download is owned or not — ownership is product-level, so there's no tier/type here.
RelatedListings RelatedListings.php
"Related listings" — a row of extra listings shown as full listing cards underneath the main single listing (Learning theme: "Related Courses"). Each card links through to that listing. In demo / preview mode it's populated from the previewed design's own on-brand cards (each carrying a virtual demo single URL), so a showroom single page always reads as rich; on a live site it collects other published listings in the same category, and hides itself when there aren't enough to be worthwhile.
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.
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.
Subscriptions Subscriptions.php
LMS course subscriptions (Ppt_ll) — recurring, per-course access with an optional free trial, and a permanent per-account trial ledger so a learner can't recycle the trial by cancelling and re-enrolling. A course becomes a subscription when the admin gives it a billing INTERVAL (month or year); its single base price (Ppt_ll\SearchPage::PRICE_META, via Checkout::basePrice) is the recurring amount, and TRIAL_META (days) sets the free trial. Courses with no interval keep the existing one-time purchase (Ppt_ll\Purchases) untouched. Data model ---------- · Course config (post meta): INTERVAL_META ('month'|'year'), TRIAL_META (int days). · Learner records (user meta SUBS_META): [ courseId => {status, trial_end, period_end, order, started, canceled_at} ]. status ∈ trialing|active|canceled. Access is computed LIVE from the timestamps (liveStatus) — no cron needed to end it: once the relevant end passes the record reads as "expired" and access is gone. · Trial ledger (user meta TRIALS_META): an append-only list of every course the account has EVER trialed. Never cleared — that's the anti-abuse gate. Real recurring billing is done by the site's payment gateway (the PremiumPress Stripe gateway on a live site); this class holds the entitlement bookkeeping the theme gates on and the trial that grants access before any charge.
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.