WP Manifestindependent plugin directory
manifest / editor / prc-quiz-builder

PRC Quiz Builder

A block-first, interactive quiz system for the PRC platform. Authors compose quizzes using Gutenberg blocks powered by the WordPress Interactivity API. Supports classic quizzes, typology clustering, embeddable usage, and community group results.

by Seth Rubenstein · github.com/pewresearch/prc-quiz-builder · website

3stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/pewresearch/prc-quiz-builder/archive/refs/heads/trunk.zip

Readme

PRC Quiz Builder

Canonical docs: docs/plugins/prc-quiz-builder/

An interactive, block-based quiz system for the PRC platform.

Overview

Quiz Builder provides a custom quiz post type and a suite of Gutenberg blocks backed by the WordPress Interactivity API. Authors compose quizzes in the block editor using a Controller → Pages → Page → Question → Answer hierarchy; the Controller block injects all runtime state into the Interactivity API context and coordinates submission, scoring, and results display on the frontend. The plugin supports three quiz modes (scored quiz, typology/clustering, freeform), three display types (paged, scrollable, and fluid), community group results, and iframe-embeddable views.

Firebase Realtime Database is the persistence layer for archetype (result) records and community groups. WordPress post meta tracks submission analytics.

Dependencies

  • Upstream: prc-platform-core (provides \PRC\Platform\Firebase and the prc_api_endpoints, prc_platform_on_post_init, and prc_iframe_content hooks), prc-research-teams (optional — registers team-prefixed quiz URLs via prc_research_teams_rewrite_config), WP Consent API (wp_add_cookie_info)
  • Downstream: Internal analytics tooling queries the _submissions REST field on the quiz post type; any page embedding a quiz via /embed/ or /iframe/ URLs

Architecture

The plugin bootstraps through includes/class-plugin.php, which loads all dependencies, registers the quiz CPT, sets up rewrite rules, and instantiates each block class. Blocks are loaded from build/ (production) or src/ (local/dev), controlled by wp_get_environment_type().

Archetype lookups are cached in the WordPress object cache (prc_quiz_builder_archetypes group, 1-day TTL) and backed by Firebase. Community groups are stored only in Firebase; Groups::get_group() reads from Firebase.

The Controller block's render_callback is the key server/client bridge: it writes all runtime context (quizId, quizType, displayType, configuredDisplayType, allowSubmissions, groupId, archetype, etc.) into data-wp-context and wires up all Interactivity API directives. configuredDisplayType is an immutable copy of the author's displayType setting; displayType may be rewritten at runtime (e.g. fluid → scrollable on narrow viewports). Core/buttons blocks that carry specific CSS classes (prc-quiz-start-button, prc-quiz-next-page-button, prc-quiz-submit-button, etc.) have data-wp-on--click attributes injected server-side via WP_HTML_Tag_Processor.

Key Files

Path Purpose
prc-quiz-builder.php Plugin entry point; defines PRC_QUIZ_FILE, PRC_QUIZ_DIR, PRC_QUIZ_VERSION constants
includes/class-plugin.php Core orchestrator — loads deps, registers CPT, rewrite rules, query vars, cookies, and all blocks
includes/class-archetypes.php Firebase CRUD for archetype (result hash) records; object-cache layer
includes/class-groups.php Firebase CRUD for community groups
includes/class-rest-api.php REST endpoint registration and handlers; contains the $rest_disabled kill switch
includes/class-analytics.php _report post meta schema and submission counter; exposes _submissions REST field
includes/class-ability-categories.php Registers the quiz WP Abilities category for MCP discovery
includes/class-ability.php WP Abilities API prc-quiz-builder/get-analytics tool (submissions + groups; MCP + REST)
includes/class-cli-report.php WP-CLI wp prc quiz report — ad hoc read/update of _report meta
includes/class-cli-build-audience.php WP-CLI wp prc quiz build-group-owners-audience — thin wrapper around Audience_Service
includes/class-audience-service.php Shared build / list / delete for quiz group-owners audiences (CLI + REST)
includes/class-loader.php Hook registration queue
includes/class-block-supports.php CPT-scoped inserter filtering (allowed_block_types_all), Quiz Builder category, editor-support enqueue
includes/editor-support/ Unregisters quiz core block variations outside the quiz CPT editor
includes/inspector-sidebar-panel/ Block editor plugin: quiz analytics, group analytics, and group-creators audience panel
src/controller/class-controller.php Controller block — server render, Interactivity API context injection, button directive patching
src/controller/view.js Controller Interactivity API store — display-type resolution, submission, page visibility, navigation
src/results/class-results.php Results block server render
src/group-results/ Group results block (view script + create-group action)
src/embeddable/ Embeddable block for cross-site reuse
build/ Compiled JS/CSS/asset manifests for all blocks

Blocks

Block Namespace Role
Controller prc-quiz/controller Root block; owns all Interactivity API state and submission flow
Pages prc-quiz/pages Wrapper for a multi-page quiz
Page prc-quiz/page A single page; contains questions and arbitrary content
Question prc-quiz/question Single-choice, multiple-choice, or thermometer; supports randomization
Answer prc-quiz/answer Answer choice with optional correctness, points, and label
Results prc-quiz/results Container rendered after quiz completion
Result Score prc-quiz/result-score Displays the participant's score
Result Table prc-quiz/result-table Tabular results view; supports demographic breaks
Result Histogram prc-quiz/result-histogram Score distribution histogram
Group Results prc-quiz/group-results Community group aggregate results; required to enable group creation
Progress Bar prc-quiz/progress-bar Linear bar or per-question circles showing completion and outcomes
Embeddable prc-quiz/embeddable Reuse a quiz across other posts; edits propagate to all embeds

Quiz blocks appear in the block inserter only when editing the quiz post type, grouped under the Quiz Builder category (prc-quiz slug). The Quiz embeddable block (prc-quiz/embeddable) remains available on other post types for synced cross-post reuse.

Knowledge quiz features (type: quiz)

These options apply only when the Controller type is quiz (not typology or freeform).

Live feedback and answer lock

Enable Live Feedback on the Controller block. After a participant selects an answer on a single-choice or thermometer question, the UI shows whether the choice is correct or incorrect and locks further changes on that question. Multiple-choice questions stay open so participants can select every required answer before locking.

Live feedback is implemented in src/answer/view.js via context.liveFeedback and state.isQuestionLocked.

Answer correctness tri-state

Knowledge-quiz answers use a tri-state correct attribute:

Value Meaning Toolbar label
true Correct answer Correct Answer
null Neutral / "Not sure" Not Sure
false Incorrect answer Incorrect Answer

The toolbar control lives in src/answer/correct-toggle.js. Freeform quizzes hide the control.

Question outcome

getQuestionOutcome() (src/controller/question-outcome.js) resolves each question to correct, incorrect, unsure, or unanswered:

  • Correct — selected UUIDs exactly match every answer marked correct: true
  • Unsure — only correct: null answers are selected (no wrong or right answers)
  • Incorrect — any other non-empty selection
  • Unanswered — no selection

The Results block exposes a Correct / Incorrect block bit (prc-quiz-builder/question-outcome-label) that prints the active question outcome from context. Labels come from the Quiz Controller (Correct / Incorrect / Not sure outcome label settings) and can be overridden per bit in the bit insert/edit popover. Empty bit fields inherit the quiz-wide defaults.

Any core/paragraph or core/heading that contains this bit receives the class has-prc-quiz-question-outcome and the interactive class is-awaiting-selection while the question is unanswered, so the whole host block stays hidden until the user has a selection.

Progress Circles use the same outcome logic for per-question marks.

Exclusive score buckets

The Controller Score Buckets panel stores a JSON catalog of named, non-overlapping closed ranges ([min, max] inclusive; shared endpoints count as overlap). Validation runs in the editor via validateExclusiveBuckets() (src/controller/score-buckets.js).

At results time, matchScoreBucket() returns the first bucket containing the participant's score. Insert the Matching Score Bucket block bit (prc-quiz-builder/matching-score-bucket) in the Results block to print the matched label.

Progress bar variations

prc-quiz/progress-bar has two block variations:

Variation Class Behavior
Progress Bar (default) (none) Linear fill showing percent of questions answered
Progress Circles is-style-circles One circle per question — check, x, or question mark by outcome

Circles reflect live outcomes when Live Feedback is enabled; otherwise they show answered vs unanswered.

Display Types and Frontend Behavior

The Controller block's displayType attribute accepts paged, scrollable, or fluid. Runtime behavior is implemented in src/controller/view.js and src/page/view.js.

Fluid resolution

A fluid quiz resolves to a concrete display mode by viewport width (782px breakpoint, matching the block editor's mobile preview). Resolution runs on init and re-runs on window resize via actions.applyDisplayType (data-wp-on-async-window--resize on the Controller when displayType is fluid):

Viewport Resolved displayType Notes
< 782px scrollable Next-page button wrappers hidden within the quiz container
>= 782px paged Next-page button wrappers shown; one page at a time

configuredDisplayType retains the original fluid value so client logic can distinguish a fluid quiz that resolved to scrollable from a natively scrollable quiz, and so resize handlers can re-resolve without losing the fluid configuration.

Page visibility (displayPages)

The Pages block binds hidden to !state.displayPages (src/pages/class-pages.php). Visibility rules:

Configuration During quiz Results or group-results URL
paged Pages visible Pages hidden
fluidpaged (desktop) Pages visible Pages hidden
scrollable (native) Pages always visible Pages always visible
fluidscrollable (mobile) Pages visible Pages hidden (matches paged)

Native scrollable quizzes keep pages visible on results URLs so inline, submit-as-you-go results can render below the questions. Fluid quizzes on mobile hide pages when landing on a results URL so users are not dropped at the top of the question stack.

Read the full README on GitHub →