PHPackages                             pushword/quiz - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. pushword/quiz

ActiveSymfony-bundle[Utility &amp; Helpers](/categories/utility)

pushword/quiz
=============

Interactive client-side quizzes (QCM) for Pushword: a {{ quiz(...) }} Twig block, an EditorJS editor, anonymous percentile stats and an optional end-of-quiz conversion form.

1.0.0-rc735(1w ago)0219MITPHP

Since Jun 10Pushed 4d agoCompare

[ Source](https://github.com/Pushword/quiz)[ Packagist](https://packagist.org/packages/pushword/quiz)[ Docs](https://pushword.piedweb.com/extension/quiz)[ RSS](/packages/pushword-quiz/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (7)Versions (66)Used By (0)

Pushword Quiz
=============

[](#pushword-quiz)

Interactive, (almost) server-less quizzes (QCM) for [Pushword](https://pushword.piedweb.com).

- **`{% quiz %}{ …json… }{% endquiz %}` block** (recommended) — declare a quiz inline in a page. The JSON is the **raw tag body**, so apostrophes/quotes need no escaping and the payload stays readable/diffable. The legacy `{{ quiz('…json…') }}` function still works (there the JSON is a single-quoted Twig string, so literal `'` must be escaped as `\'`). A malformed quiz degrades gracefully (admins see a detailed error panel, visitors see nothing) instead of 500-ing the page — and a missing media file is skipped, not fatal.
- **EditorJS block** — add/remove questions and answers, flag the correct answer(s), attach an image or a video, write the explanation.
- **`pw:quiz:validate `** — lint quiz blocks in a flat file (or stdin) with precise `{path, message}` violations and a non-zero exit, for an edit→check loop without a server. `pw:quiz:schema` prints the payload's JSON Schema.
- **Progressive enhancement** — the full quiz is rendered server-side as a readable, schema.org-tagged Q&amp;A (great for SEO and no-JS); `quiz.js` turns it into a one-question-at-a-time game with immediate feedback and a score donut.
- **Anonymous percentile** — `POST /quiz/result` stores a score (no PII) and returns "better than X% of participants".
- **Conversion form** — set `cta` to a [`pushword/conversation`](https://pushword.piedweb.com/extension/conversation)form type to show a lead form at the end (pre-filled from a localStorage identity). Optional soft dependency.
- **`POST /api/quiz/validate`** — token-authenticated endpoint (for AI agents) that validates a quiz payload and returns precise `{path, message}` violations. **`GET /api/quiz/schema`** serves the payload's JSON Schema.

Quiz JSON shape
---------------

[](#quiz-json-shape)

```
{
  "title": "Montagnes du monde",
  "difficulty": "Facile",
  "feedback": "immediate",
  "cta": "newsletter",
  "questions": [
    {
      "q": "Quel est le plus haut sommet du monde ?",
      "media": "everest.jpg",
      "alt": "Le mont Everest",
      "answers": [
        { "a": "Mont Blanc" },
        { "a": "Everest", "correct": true },
        { "a": "K2" }
      ],
      "explanation": "Le mont Everest culmine à 8 849 mètres."
    }
  ],
  "results": [
    { "min": 0, "msg": "À retravailler !" },
    { "min": 80, "msg": "Bravo !" }
  ]
}
```

A question may carry an image (`media`) **or** a video (`video`); a video reuses the `media` image as its poster, so both `media` and an `alt` (accessibility) are required for one. The single source of truth for validity is the `Quiz`model + Symfony Validator, shared by the renderer, the editor lint and the API endpoint.

Difficulty levels
-----------------

[](#difficulty-levels)

Add a `levels` array to offer several difficulty levels behind an accessible tab selector. Each entry is a full quiz (`difficulty`, `questions`, `results`, …); the root keeps the shared metadata (`title`, `labels`). The tab label is `label ?? difficulty`, and a level inherits the root's `labels`/`feedback`/ `cta`/`pass` when it omits its own.

```
{
  "title": "Mountains",
  "cta": "newsletter",
  "pass": 50,
  "levels": [
    { "difficulty": "Easy", "questions": [ … ], "results": [ … ] },
    { "difficulty": "Intermediate", "questions": [ … ] },
    { "difficulty": "Hard", "questions": [ … ] }
  ]
}
```

Tabs are freely clickable (WAI-ARIA: arrow keys, roving focus). Passing a level (score ≥ its `pass`, default 50) reveals a *"Next level →"* button to the next tab. Each level keeps its **own** percentile and lead attribution. A quiz without `levels` renders exactly as before. In the EditorJS block, tick *"Multiple difficulty levels"* to edit one sub-quiz per level.

Personality test (`mode: profile`)
----------------------------------

[](#personality-test-mode-profile)

Set `"mode": "profile"` to turn the same block into a personality test ("Which X are you?"). There is no correct answer: each answer carries `weights` toward one or more named `profiles`, and the highest-tallied profile is shown as a result card (title + description + image). Backward compatible — a quiz without `mode`behaves exactly as before.

```
{
  "mode": "profile",
  "title": "Which explorer are you?",
  "profiles": [
    { "key": "sommet", "title": "The Summiteer", "msg": "Higher, always.", "media": "peak.jpg" },
    { "key": "calm",   "title": "The Contemplative", "msg": "The mountain is your refuge." }
  ],
  "questions": [
    {
      "q": "A free weekend, you…",
      "answers": [
        { "a": "climb a peak",    "weights": { "sommet": 2 } },
        { "a": "walk by a lake",  "profile": "calm" }
      ]
    }
  ],
  "cta": "newsletter"
}
```

An answer weighs profiles with a `weights` map, or the `profile: "key"` shorthand (== `{ "key": 1 }`). The validator enforces at least one profile and that every weight references a declared profile `key` (a typo would otherwise vote for nothing). `feedback` is always `end` (no correct answer to reveal), `levels` are not used, and no schema.org/Quiz markup is emitted (there is no accepted answer). `POST /quiz/result` accepts `{ quiz, result }` and returns `{ share }` — "X% got the same profile". Its knowledge-quiz and personality tallies stay separate even under one page slug. In the EditorJS block, tick *"Personality test"* to swap the correct-answer flag for per-answer profile weights and edit the profile cards.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance99

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

65

Last Release

9d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/afce4cf517928a50560237f1410d5957271fd808671b2216687ecf1422adaee0?d=identicon)[Robin D.](/maintainers/Robin%20D.)

---

Top Contributors

[![RobinDev](https://avatars.githubusercontent.com/u/3944894?v=4)](https://github.com/RobinDev "RobinDev (16 commits)")

### Embed Badge

![Health badge](/badges/pushword-quiz/health.svg)

```
[![Health](https://phpackages.com/badges/pushword-quiz/health.svg)](https://phpackages.com/packages/pushword-quiz)
```

###  Alternatives

[aldas/modbus-tcp-client

Modbus TCP protocol client library

213144.7k](/packages/aldas-modbus-tcp-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
