Skip to main content

SocialProof

8 min read

SocialProof

Shared across every PremiumPress product. 13 classes in inc/SocialProof.

Analytics Analytics.php​

Social Proof — impressions and clicks, rolled up per campaign per day. The browser batches what it showed and what was clicked and posts it with navigator.sendBeacon (Feed::ajaxBeacon); each batch is folded into one row per (campaign, day, page type, device) with an upsert, so the table stays tiny however busy the site is. Days are the SITE's day (current_time), not UTC, so the admin chart lines up with the calendar the owner reads.

API
bump()series()totals()perCampaign()breakdown()spark()purgeCampaign()sweep()
inc/SocialProof/Analytics.php

Backfill Backfill.php​

Social Proof — one-time seed of the events table from what already happened. A site that has been trading for a year installs the feature and should not start from an empty feed. Run once by Admin\Migrations: the last 90 days of completed orders, approved reviews, member registrations and confirmed newsletter subscribers, capped per kind. Everything goes through the same emitters as live activity, so identity, opt-out and escort rules apply.

API
run()
inc/SocialProof/Backfill.php

Campaigns Campaigns.php​

Social Proof — the campaigns model (wp_ppt_sp_campaigns). A campaign is one configured notification: its kind + source, a status, a priority (lower shows first), an optional schedule window and a JSON config holding everything the editor sets — identity mode, template, targeting, design and timing overrides, and the kind-specific blocks (countdown / announce / inline). Blank design/timing values mean "use the global default from Settings ▸ Social proof". The public config the widget receives is built here too (publicConfig), so the admin editor and the front end always read the same shape.

API
globalDefaults()defaultConfig()sanitizeConfig()sanitizeRow()insert()update()get()all()delete()setStatus()reorder()duplicate()flushCache()active()forPage()publicConfig()
inc/SocialProof/Campaigns.php

Emitters Emitters.php​

Social Proof — the emitters: every hook that turns site activity into an event row. The theme has no commerce event bus, so this class listens where the state actually changes: order paid updated_post_meta / added_post_meta on order_status = complete for a ppt_orders post. Every one of the 13 fork Checkout classes (and the admin Orders screen) completes an order by writing exactly that meta, so ONE listener covers every product line with no edit to any checkout. booking made ppt_booking_created (fired by Bookings\Bookings::create()). bid placed ppt_bid_recorded (fired by _at\Bids::record()); proxy auto-raises are skipped — a bot raising itself is not a person acting. listing added core transition_post_status → publish, front-end only (admin and wizard seeding create listings too, and those are not news). review posted core comment_post / transition_comment_status → approved, for review comments carrying a rating. member joined core user_register. subscriber ppt_subscriber_added (direct add AND double-opt-in confirm). Everything goes through Events::add(), which owns the identity rules.

API
register()onOrderMeta()emitOrder()resolveOrderItem()onBooking()onBid()onListingStatus()flushListings()onCommentPosted()onCommentStatus()emitReview()onRegister()emitMember()onSubscriber()emitSubscriber()onPostDeleted()listingCity()
inc/SocialProof/Emitters.php

Events Events.php​

Social Proof — the events store. One row per thing that happened on the site (an order paid, a booking made, a bid, a listing published, a member joined, a subscriber confirmed, a review approved), written ONCE at the moment it happens as a DISPLAY-SAFE SNAPSHOT: first name, initials and city only — never a surname, never an email — plus the item's title, URL and thumbnail. The feed reads these rows straight out; nothing is looked up against the order or the user at display time, so a later edit or deletion can never leak, and the query is a single indexed range scan. Identity rules live in identityFor() and are enforced here, not left to callers: the escort profile and any member who opted out (OptOut) get blank identity columns whatever the emitter passed in.

API
add()identityFor()optedOut()latest()count()total()displayName()hydrateName()purgeUser()purgeListing()sweep()
inc/SocialProof/Events.php

Feed Feed.php​

Social Proof — the two public endpoints the widget talks to. ppt_sp_feed GET/POST c[]=&listing=<id>&type= → { events: {<id>: [...]}, live: {site, listing}, inline: {...}, ts } ppt_sp_beacon POST b=<json array of {c, e, p, d}> (navigator.sendBeacon) → folds impressions / clicks into Analytics Both are nopriv and take NO nonce on purpose: the page that calls them may be served from a full-page cache for a day, and a nonce baked into it would expire while the HTML lives on (the theme's other guest endpoints, e.g. Presence, are short-lived and can afford one). They are read-only / append-only counters, so the protection that matters is input whitelisting + a per-visitor rate limit. The feed call doubles as the anonymous presence heartbeat (Presence::touch).

API
register()ajaxFeed()build()eventsFor()inlineFor()money()ago()ajaxBeacon()
inc/SocialProof/Feed.php

Kinds Kinds.php​

Social Proof — the catalogue of campaign KINDS and their SOURCES. A campaign has a kind (what the visitor sees) and a source (where the data comes from). Toast kinds read the events table; the others are self-contained: sales order | booking | bid | listing "Anna from London just bought …" signup member | subscriber "Sam just joined" review review "Priya left a 5-star review on …" live site | listing "12 people are browsing right now" countdown timer floating widget or sticky top bar announce bar pinned or floating message inline chips "42 sold · 8 viewing · 4.8★" on a listing Which of these a PROFILE gets is decided here (not in the features matrix): a shop has orders but no bids; an auction has bids; a directory has bookings; escort never counts viewers on a person's page. Everything reads through available().

API
all()available()isAvailable()eventKind()inlineSnippets()snippetLabels()verbs()template()label()sourceLabel()isToast()
inc/SocialProof/Kinds.php

OptOut OptOut.php​

Social Proof — a member's right to be left out. "Don't show my activity in site notifications" in the Member Hub (Profile & settings ▸ Privacy). Opting out sets user meta ppt_sp_optout, which Events::identityFor() honours for every FUTURE event, and blanks the identity columns of everything already stored (Events::purgeUser) — counts stay, names and cities go. Deleting the account purges the same way. The hub row itself lives in inc/Account/views/account.php (form data-field="socialproof") and posts through the existing ppt_account_update endpoint; AccountEdit::handle() routes that field here.

API
register()available()isOptedOut()save()set()onDeleteUser()
inc/SocialProof/OptOut.php

Presence Presence.php​

Social Proof — the anonymous "viewing now" counter. Account\Presence rings a MEMBER's avatar while they are active; this is the guest-inclusive count behind "12 people are browsing right now". It sets no cookie: a visitor is a salted HMAC of their IP and user agent, recomputed on every feed poll (the poll IS the heartbeat), and a row per (visitor, page) is kept only for the length of the window. Nothing identifying is stored.

API
window()vhash()touch()siteCount()listingCount()sweep()
inc/SocialProof/Presence.php

Samples Samples.php​

Social Proof — sample events for the design showroom. A demo site has no orders, so in PREVIEW (Blocks\Support\PreviewMode::showSamples: the design showroom, the demo-default preview, the Block Explorer) the widget is fed plausible fake activity so the design shows what the customer will get. The same rows drive the admin editor's live preview. A LIVE site never sees these: with no real events the widget simply shows nothing. Names come from the theme's shared demo-name pool (first names only), items from the site's own published listings when it has any, else a neutral list.

API
cities()firstNames()items()eventsFor()feed()campaigns()
inc/SocialProof/Samples.php

SocialProof SocialProof.php​

Social Proof — the feature root. WiserNotify-style pop-up notifications built INTO the theme: small floating toasts ("Anna from London just bought …", "12 people are browsing", "Sam left a 5-star review"), a countdown timer, an announcement bar and inline "42 sold / 8 viewing" chips, each configured as a CAMPAIGN in PremiumPress ▸ Social Proof. This class is the single switch and the owner of the four tables: wp_ppt_sp_campaigns the admin's campaigns (kind, targeting, design, schedule) wp_ppt_sp_events display-safe snapshots of site activity (the feed's source) wp_ppt_sp_stats daily impression / click roll-up per campaign wp_ppt_sp_presence anonymous "viewing now" heartbeat, cookie-free Every other Ppt\SocialProof* service returns early from register() when available() is false, exactly like the Stories feature — so a profile without the feature, or a site that switched it off, hooks nothing at all. The tables still install (like Notifications\Center) so backfill and cleanup are predictable.

API
register()available()isSensitiveProfile()campaignsTable()eventsTable()statsTable()presenceTable()install()schedule()unschedule()retentionDays()sweep()resetData()
inc/SocialProof/SocialProof.php

Targeting Targeting.php​

Social Proof — where a campaign shows. The rule schema a campaign stores under config.targeting: pages ['all'] or any of home, search, single, checkout, account, page, blog include URL globs the path (+query) must match when the list is not empty exclude URL globs that always hide it devices desktop and/or mobile audience all | guests | members listings optional listing ids (single pages only) The split: the SERVER knows the page (type, listing id, path) and pre-filters on pages + listings, so a campaign that can never show here is not even shipped. The BROWSER knows the device and applies the URL globs, because device is not knowable server-side and the page may be served from a full-page cache, where the same HTML must work for every visitor. audience is decided server-side too (ctx.member), but re-checked in the browser for cached pages.

API
defaults()sanitize()pageContext()matchesPage()globMatch()
inc/SocialProof/Targeting.php

Widget Widget.php​

Social Proof — the front-end widget. Prints ONE page-level config (wp_localize_script) + an empty mount point; the browser does the rest (assets/js/social-proof.js). Nothing here is per-visitor, so the HTML is identical for everyone and safe behind a full-page cache — the visitor's own state (device, what they have seen, daily caps, dismissed bars) lives in their browser and the activity itself comes from Feed. Two modes: live the widget fetches ppt_sp_feed; a site with no events shows nothing. sample PreviewMode::showSamples() — the design showroom / demo-default preview: the feed is inlined from Samples and the JS never calls the server. Never on an editor canvas (Support\EditorCanvas): admin previews and page builders render the page for design, and a toast over it is just in the way. The admin campaign editor is the one exception — it enqueues the same assets itself with preview: true to drive its live preview.

API
register()active()sampleMode()config()i18n()assets()enqueue()mount()topSlot()topSlotFallback()snippet()shortcode()
inc/SocialProof/Widget.php