Directory Theme class reference
Freelancer Marketplace — Class reference
The 37 classes that make _fm behave differently from the shared Directory core. Generated from the theme source.
← Back to Freelancer Marketplace features
Bidding Bidding.php
Proposal submission + award — the AJAX write-path and the single-page sidebar UI for the Freelancer Marketplace fork (_fm). Mirrors _at\Bidding's discipline: every write is logged-in, nonce-checked and fully re-validated server-side; the client never sets what it isn't allowed to. The sidebar shows the right thing per viewer: - the client (project owner) → the proposals compare panel + Award buttons - a signed-in freelancer → the "Submit a proposal" box (edit/withdraw if they already have one) - a guest → a sign-in prompt - anyone, once awarded → a closed state
BuyerProtection BuyerProtection.php
Classifieds — Buyer Protection fee. The classifieds "Buy now" flow (Ppt_fm\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_fm\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 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.
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).
DemoAccounts DemoAccounts.php
Freelance Marketplace — what the typed DEMO members walk into on top of the shared furnishing (Account\DemoFurnish: favourites, follow, inbox thread, review). On this fork the LISTER is the client — a project is posted by the person hiring — and the freelancer bids on it. So the lending is turned round: the CLIENT is lent two seeded projects and the FREELANCER none; the freelancer then submits a proposal on each through the fork's own Bidding::place(), so the project page, the client's hub and the freelancer's hub all agree. Proposals are best-effort: a project that is not open, or a site whose free-bid cap the demo freelancer has used, simply gets none. Gated to the freelance profile by Theme::boot()'s fork-gating (this is Ppt_fm*).
DemoKinds DemoKinds.php
Demo listing KINDS for the Freelancer Marketplace fork (_fm) — showroom only. The search page's "Listing type" switch is exclusive: Projects (the default) OR ready-now tasks, never a blend — there is deliberately no "All". On a live site the query does that with the _ppt_fm_kind meta (ListingKind / SearchPage::kindMetaQuery). In a design preview there is no query at all: the results are demo cards (Ppt\Content\SampleListings), which carry no kind — so every tab showed the same cards and the switch looked broken in the showroom. pool() resolves the cards for the chosen tab: 1. The previewed design's OWN cards that already read as that kind — classify() tells them apart: a gig grid ("I will edit your 10-min YouTube video", one fixed price, "2-day delivery") is a ready-now TASK; a brief ("Explainer Video for a Fintech App", £1,500–£3,000, "1–2 weeks") is a PROJECT. 2. Anything still missing comes from that kind's own curated set — ready-now services here in taskRows(), client briefs from the profile's curated niche. The two sets share no photo, no title and no price, so switching tab shows a completely different list rather than the same one reworded. Every task card gets a rebuilt virtual-single link carrying its own title / price / delivery plus k=task, so clicking it opens a fixed-price service page (buy box) rather than a project brief, and the card and the page it opens always agree (Ppt\Frontend\DemoListing). Preview-only: nothing here runs on a live site, where real listings carry a real kind meta and the query does the filtering.
Escrow Escrow.php
Escrow state for an awarded project (Freelancer Marketplace fork, _fm). Model (chosen 2026-09-02): a milestone-release LEDGER over the normal checkout — NOT held-funds/Stripe-Connect escrow. On award the escrow is created (unfunded); the client pays the full amount through checkout (Escrow::markFunded on success); then the client releases milestones one by one, each release accruing the freelancer's share to the shared Payouts ledger (Ppt\Payouts\Ledger), the site keeping the commission. The owner pays freelancers out via the existing Payouts admin. The contributor share (freelancer %) is SNAPSHOTTED at award time so a later change to the site's commission rate never rewrites an in-flight project's economics. Per-project state is post meta (one escrow per project); milestones live in their own table (see _fm\Milestones).
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.
FreelancerCategories FreelancerCategories.php
Freelancer categories (Freelancer Marketplace, _fm). The admin-managed list of disciplines a hireable member can file themselves under — "Video Editing", "Copywriting", "Web Development" — and the thing the dedicated freelancer search (Ppt_fm\FreelancerSearch) filters on. It is a real WordPress taxonomy registered against the user object type, not a bespoke option list, so the admin gets WP's native term editor for free (add / rename / describe / nest / delete, with slugs and search) and each member's choices are ordinary term relationships. Two consequences worth knowing: - WP registers no admin menu for a user taxonomy, so menu() links the native term screen (edit-tags.php) into the PPT admin itself. - Term counts default to counting POSTS, which would always read 0 here, so the taxonomy uses _update_generic_term_count() to count the attached users. The member picks their categories in Member Hub ▸ Profile & settings ▸ Available for hire (Ppt_fm\FreelancerProfile::settingsCard), capped at MAX_PER_USER.
FreelancerProfile FreelancerProfile.php
Freelancer profile fields (Freelancer Marketplace, _fm). Enriches the shared public member profile (Ppt\Account\AuthorProfile, the author archive) so it reads like a hire-me page rather than a blog author archive: a headline, a skills chip row, an hourly/day/project rate, and a "Recent work" grid drawn from the projects the member has been AWARDED (Escrow). The fields are user meta the member edits in place on their own profile (owner-only AJAX); everything is gated to the freelance fork by the view.
FreelancerSearch FreelancerSearch.php
Dedicated freelancer search (Freelancer Marketplace, _fm) — /freelancers/. The listing search (Ppt_fm\SearchPage) searches POSTS: ready-now tasks and project briefs. This searches PEOPLE: the members who have switched on "Available for hire" in Member Hub ▸ Profile & settings. Different object, different query engine (WP_User_Query), so it is its own route and template rather than another layout of the listing search. Self-contained rewrite + render, the same pattern as Frontend\Blog and Account\MembersPage: ?ppt_freelancers=1, prettified to /freelancers/. Filters, all optional and combinable, all GET, all in the top bar (the template has no sidebar — see inc/_fm/templates/freelancers.php): fcat category term id or slug (Ppt_fm\FreelancerCategories) fq keyword — name, headline, skills or bio frating minimum member feedback score, 1–5 (Ppt\Account\Feedback) frate1 / frate2 min / max rate fsort newest | rating | rate_low | rate_high | name The keyword match and the rating/rate ordering both need SQL WP_User_Query can't express (an OR across core columns and meta; a LEFT JOIN so members with no score sort last instead of vanishing), so both are applied in userQuery() on pre_user_query. Everything else rides in the normal args.
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.
Invitations Invitations.php
Direct-hire invitations (Freelancer Marketplace, _fm). A client browsing a freelancer's public profile can "Invite to a project": they pick one of their own OPEN projects, and the freelancer is invited to submit a proposal on it. That's the only new surface — everything after the invitation (proposal → award → escrow → workroom → payout) is the existing loop. This class owns the invitation record, the client-side AJAX, and the freelancer's incoming-invitations list.
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.
ListingKind ListingKind.php
Listing "kind" for the Freelancer Marketplace fork (_fm). Every listing on this fork is one of two things, distinguished by a single meta flag on the shared listing_type post type: - PROJECT (default) — a client posts a brief with a budget; freelancers submit proposals and the client awards one (the bid → escrow → milestone engine). This is "a project people are looking for". - TASK — a member who has opted in to "Available for hire" posts a ready-now service at a FIXED price; a buyer purchases it on the spot through the shared buy-now checkout (Ppt_fm\Checkout). This is "a task that's ready now". Keeping the two on one post type (rather than a second CPT) is deliberate: it lets the search page offer a simple Projects / Ready-now-tasks switch over the same result set. A task also mirrors its fixed price into the searchable price meta (SearchPage::PRICE_META) so price sort/range and the buy engine work unchanged; a project keeps its amount in the dedicated budget meta (ProjectSpecs) and is never buyable.
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_fm\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
Freelancer Marketplace — the "awarded / sold" state store for a listing. Writes the sale meta (ppt_sold / ppt_sold_buyer / ppt_sold_at) that Account\Feedback::transactionFor() reads to open two-way feedback, that Proposals::isOpen() reads to close a project to further bids, and that the ready-now-task Buy Box reads to show its sold state. Unlike the classifieds fork this class came from, there is NO manual owner "mark as awarded" control on this fork, because awarding is not a manual act here — it is a side effect of the real flow. The two callers that write it: - Proposals::award() — the client awards a proposal from the compare panel. Stamps the winner, declines the rest, opens escrow, emails the freelancer, and calls markSold() with the winning freelancer's id. - Checkout — a ready-now task is bought outright. A hand-rolled control would bypass both, leaving a project closed to bids with no awarded proposal and no escrow row. Classifieds keeps its manual control because it has no proposal system to award from.
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.
Milestones Milestones.php
Project milestones for the Freelancer Marketplace fork (_fm). After a project is awarded and funded (see _fm\Escrow), the client breaks the work into milestones and releases them one at a time as the freelancer delivers. Each release accrues the freelancer's snapshotted share of that milestone to the shared Payouts ledger (Ppt\Payouts\Ledger) — the site keeps the commission remainder. The owner then pays freelancers out through the existing Payouts admin. Storage: a custom table (wp_ppt_fm_milestones), one row per milestone. This class is storage + release logic; the AJAX endpoints and UI live in _fm\Bidding.
ProjectSpecs ProjectSpecs.php
Project brief facts for the Freelancer Marketplace fork (_fm): budget, deadline and the skills a client wants. This is the field group that turns a plain listing into a project a client posts and freelancers bid on. Wiring is deliberately thin, mirroring _cd\VehicleSpecs: the add/edit form prints ProjectSpecs::editorCard($pid); the save is ProjectSpecs::saveFromForm($pid, $in); the listing card and single page call ProjectSpecs::metaStrip($pid) / ::skillChips(). Data model — all plain listing meta (no taxonomy): - BUDGET is stored as a min/max pair plus a type ('fixed' | 'range'). A fixed budget keeps only the min; a range keeps both. Kept in dedicated meta (NOT the shared searchable price meta) so a project never triggers the classifieds "Buy now" box — you bid on a project, you don't buy it. A budget search facet is added in the search-filters phase. - DEADLINE is a Y-m-d date; the single/card show a friendly "Due …" label. - SKILLS is a short comma-separated list rendered as chips.
Proposals Proposals.php
Proposal (bid) storage + award logic for the Freelancer Marketplace fork (_fm). A freelancer submits ONE proposal per project — a price, a delivery estimate (in days) and a cover letter. The client (project owner) compares proposals and awards one; awarding marks the project closed (via _fm\MarkSold) and stamps the winning freelancer. Sealed-bid, client-picks-winner — NOT highest-bid-wins, which is the key difference from the auction fork this storage model is copied from (_at\Bids). Storage: a custom table (wp_ppt_fm_proposals), one row per (project, freelancer), upserted so a re-submission edits the existing proposal rather than duplicating it. This class is storage + rules only; the AJAX endpoints and UI live in _fm\Bidding.
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.
Roles Roles.php
Client or Freelancer: who this member is on the freelance profile. A freelance marketplace has two sides. A CLIENT posts projects; a FREELANCER sends proposals. 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 "Client login" and "Freelancer login" and the hub can show the badge. Gated to the freelance profile by Theme::boot()'s fork-gating (this is Ppt_fm*).
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 kind task (ready-now services) | project (briefs — the default) price1 / price2 min / max price; priceband picks a preset range instead fmdays / fmdel delivery time — within N days (services) / window (briefs) fmcountry the listing author's country (ISO code, from their profile) fmrecent 1 = posted within the last recentWindowDays() fm_rating minimum author feedback score 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.
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.
Workroom Workroom.php
The awarded-project workspace (Freelancer Marketplace fork, _fm): once a project is awarded, this replaces the proposals UI on the single page. The client funds the escrow and manages milestones; the awarded freelancer sees the schedule and their earnings. Everyone else sees a short "awarded" note. Escrow state lives in _fm\Escrow (per-project meta); milestones in _fm\Milestones (table). This class owns the AJAX write-path (add / release / delete a milestone) and the rendering. Funding itself is a real gateway charge handled by _fm\Checkout's escrow-funding mode; the "Fund" button here links into it.