=== Neutrope Quiz Engine ===
Contributors: neutropeinc
Tags: quiz, exam, test, questions, e-learning
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.9.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Create certification quizzes and practice tests in WordPress with question banks, shortcodes, instant scoring, and explanations.

== Description ==

**Neutrope Quiz Engine** is a flexible quiz engine for WordPress, designed for
certification study sites, e-learning, and media sites that want to embed
interactive quizzes. Build a library of questions and one or more test sets in
the WordPress admin, then drop a shortcode anywhere — on pages, posts, or
custom content — to render the quiz. Scoring happens entirely on the client,
so quizzes work on cached / static pages and do not write to the database.

= Core features =

* Questions and test sets managed as WordPress custom post types
* Practice mode (per-question feedback) and mock mode (graded at the end)
* Step-by-step or full-list display
* Question shuffling, choice shuffling, configurable choice labels (A/B/C/D,
  カナ, numeric, custom)
* Pass thresholds (percentage or number of correct answers) and per-set time limit with countdown
* Question banks (practice / mock / 過去問 etc.) for grouped delivery
* Per-field accuracy breakdown on the results screen
* CSV import with dry-run, upsert and automatic term creation
* Optional question and explanation images with alternative text
* Optional per-choice images, including choices made of a picture alone
* Optional image credits, shown under the image, with the explanation, or
  nowhere in the quiz, as the site chooses
* Images held as media library attachments, at a size the test set picks
* Unscored essay prompts with browser-only responses and self-review rubrics
* Optional choice-question group minimums by field or canonical position
* Configurable call-to-action link per test set on the result screen

= Shortcodes =

* `[ntqengine_quiz set="<ID>"]` — renders the test set with the given ID.

Example: `[ntqengine_quiz set="123"]`

= No external services =

All processing happens inside your WordPress install. The plugin makes no
outbound HTTP requests, loads no remote scripts or styles, and stores no
quiz-result records in the database.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`, or install through the
   WordPress *Plugins* screen.
2. Activate **Neutrope Quiz Engine** through the *Plugins* menu in WordPress.
3. Under the *問題* menu, add some questions. Under the *テストセット* menu,
   create a test set and link it to the relevant *資格種別* taxonomy term.
4. Embed the test set anywhere with `[ntqengine_quiz set="<ID>"]`.

CSV import is available under *問題 → CSVインポート* (dry-run + upsert).

== Frequently Asked Questions ==

= In what order are questions shown when shuffling is off? =

By the external ID (`ql_external_id`), compared without regard to case. Where
two IDs are identical apart from their digits, the digits are compared as
numbers, so `item-2` comes before `item-10` even without zero padding. IDs
that differ in their text are compared character by character, as before.
Questions whose IDs compare equal are ordered by post ID, and questions
without an external ID come last. Canonical question positions — which group
minimums can be keyed to — follow the same order.

= Does the plugin store quiz results in the database? =

No. Scoring is computed entirely on the client and is not persisted. Pages
can be safely cached because no per-user state is written on the server.

= What is removed on uninstall? =

Only the CSV-import temporary transients and the
`wp-content/uploads/ntqengine-imports/` working directory. Questions, test
sets, taxonomy terms and post meta are deliberately preserved so production
data is never lost on uninstall.

= Is the plugin translation-ready? =

Yes. All user-facing strings (PHP and JS) are routed through the
`neutrope-quiz-engine` text domain. A `languages/neutrope-quiz-engine.pot` template is
included. JS strings are delivered via `wp_localize_script`, with Japanese
fallbacks compiled into the script so display never breaks when translations
are missing.

= Does it work with cache plugins? =

Yes. Output is purely HTML plus a JSON-in-`<script>` payload; nothing depends
on per-request server state. Page caches (WP Super Cache, W3 Total Cache,
LiteSpeed, etc.) are safe.

= Which shortcode should I use? =

Use `[ntqengine_quiz set="<ID>"]`. It is the only shortcode the plugin
registers. You can place several instances on the same page; each one renders
and scores independently.

= How do I add question or explanation images? =

Use the question editor's image URL and alternative-text fields, or the
optional CSV columns `question_image_url`, `question_image_alt`,
`explanation_image_url`, and `explanation_image_alt`. Existing CSV files do
not need these columns. Sites can enforce media policies before JSON output
with the `ntqengine_question_data` filter.

= How do I put a picture on a choice? =

Each choice in the question editor has its own image URL and
alternative-text fields, or you can use the optional CSV columns
`choice_1_image_url` / `choice_1_image_alt` through
`choice_4_image_url` / `choice_4_image_alt`. A choice may hold a
picture, text, or both; leave its text empty for a picture-only choice,
and give that picture alternative text, because the alternative text is
what the result screen and the review sheet record as the chosen answer.
Existing CSV files do not need these columns, and a question with no
choice image produces exactly the quiz data it produced before.

= Can an image come from the media library? =

Yes. Every image field — question, explanation and each choice — has an
attachment ID field beside its URL field, or the matching optional CSV
columns (`question_image_id`, `explanation_image_id`,
`choice_1_image_id` … `choice_4_image_id`). An attachment ID is used in
place of that field's URL, at the size the test set picks, so moving or
renaming the file does not break the question. A question with no
attachment ID keeps using its URL exactly as before.

**An attachment that cannot be shown does not fall back to the URL.**
When the attachment is missing, is not an image, or is not publicly
viewable — WordPress decides that, so an image attached to a draft is
not public while an unattached one is — the slot is left empty and named
in the question's `images_unavailable`, so the site can drop the whole
question with `ntqengine_skip_question` rather than ask it without the
picture it depends on. Falling back would quietly serve an image the
site has taken down.

Alternative text and credits are never read from the attachment. A line
written for the media library usually names the subject, which in a quiz
can be the answer, so the question's own fields are the only source and
nothing is generated. The editor shows the attachment's alternative text
and caption as a hint to copy from deliberately.

A multilingual site can return the translated attachment from the
`ntqengine_attachment_id` filter; the plugin does no translation of its
own. `ntqengine_attachment_image_size` overrides the size per image.

= Can a site keep a question out of a quiz? =

Yes. Return true from the `ntqengine_skip_question` filter and that
question is left out of the attempt: out of the question count, the
scoring, the per-field accuracy, the group minimums and the canonical
positions. It is meant for a site that refuses an image on rights
grounds and does not want the question asked without the picture it
depends on. The questions that remain are numbered from one, the way an
unpublished question already renumbers them, and a set whose questions
are all dropped shows a notice instead of an empty quiz. When a set asks
for more questions than are left, it asks the ones that remain; the
settings' `question_count_target` and `question_count_actual` show the
difference, and `skipped_questions` says how many the site dropped.

`ntqengine_question_data` is separate and unchanged: it keeps the
question whenever it is handed anything that is not an array, so a
filter that forgets to return cannot start dropping questions.

= How do I credit an image? =

Each image field — question, explanation and each choice — has a credit
text field and a credit link field, or the matching optional CSV columns
(`question_image_credit`, `question_image_credit_url`,
`explanation_image_credit`, `explanation_image_credit_url`,
`choice_1_image_credit` … `choice_4_image_credit_url`). Put the text you
want displayed in the credit field; the plugin builds the link itself and
escapes both, and the link opens in a new tab so an answer in progress is
not lost. A link with no text shows nothing, because the text is what gets
displayed.

Each test set chooses where its credits appear: under the image, with the
explanation after the question is answered (for when a photographer name
or a source page would give the answer away), or nowhere in the quiz, so
the site can list them in a standing section of its own. The plugin never
invents a credit from a file name, a domain or the alternative text, and a
question with no credit renders exactly as it did before.

Whether a licence requires a credit, and what it has to say, is for the
site to decide. `Neutrope\QuizEngine\Credits::for_set( $set_id )` returns
every credit a set would show, for building that standing section, and the
`ntqengine_set_credits` filter can adjust the list.

= What does the score pass mode mean? =

The stored `score` mode compares the threshold with the number of correct
answers; every question counts as one. It does not calculate weighted points
or convert the result to a 100-point scale. Updating does not change any saved
threshold or verdict. Administrators can audit affected sets without writing
to the database with `wp ntqengine doctor pass-modes --format=json`. Each
row says which shape its saved threshold has: `ok`, `perfect_required`
(the threshold equals the question count, so only a perfect result
passes — the shape a threshold meant as "60 points out of 100" ends up
with), `unreachable` (the threshold is above the question count, so the
set cannot be passed), `no_threshold` (zero, so everyone passes) or
`count_dynamic` (the set asks every question in its bank, so the two
cannot be compared here). These are advisories about intent, not errors;
the settings screen shows the same warning while you edit a set.

== Screenshots ==

1. Quiz display on the front end (practice mode, step-by-step).
2. Result screen with score, accuracy, pass / fail verdict and per-field
   breakdown.
3. Question admin: choices, correct answer, explanation and difficulty.
4. Test set admin: mode, question count, time limit, pass mode, CTA and
   display options.
5. CSV import: dry-run report with per-row create / update / skip / error
   counts.

== Changelog ==

= 0.9.0 =
* Let a question point at an image by media library attachment ID,
  beside the existing URL fields, in the editor and in six optional CSV
  columns. Each test set picks the size its attachment-backed images are
  served at.
* Leave a question without an attachment ID exactly as it was.
* **Never fall back to the stored URL when an attachment cannot be
  shown.** The slot is left empty and named in `images_unavailable`, so
  the site can drop the question with `ntqengine_skip_question`.
* Judge whether an attachment may be shown the way WordPress does, so an
  image attached to a draft is not public while an unattached one is.
* Never read alternative text or credits from the attachment; show them
  in the editor as a hint instead.
* Add `ntqengine_attachment_id` and `ntqengine_attachment_image_size`.

= 0.8.2 =
* Add the `ntqengine_skip_question` filter, so a site can keep one
  question out of an attempt — for a picture refused on rights grounds,
  rather than asking the question without it and marking it wrong.
* Leave `ntqengine_question_data` as it was: anything that is not an
  array still keeps the question, so an existing filter cannot start
  dropping questions on upgrade.
* Report the number of dropped questions in the quiz data, only when a
  site dropped one.

= 0.8.1 =
* Say which saved correct-count thresholds look like a points total that
  was entered by mistake. `wp ntqengine doctor pass-modes` now labels
  each set, and the test set screen warns while the set is being edited
  when the threshold equals or exceeds the question count.
* No change to how any verdict is decided, and no setting is rewritten.

= 0.8.0 =
* Add an optional credit to every image — question, explanation and each
  choice — as a text field and a separate link field, so the anchor is
  built by the plugin and both are escaped on output.
* Let each test set choose where its credits appear: under the image, with
  the explanation after answering, or nowhere in the quiz.
* Add twelve optional CSV columns for the credits. Columns that are absent
  from an uploaded CSV are left untouched on update.
* Keep credits out of the recorded answers: they describe the image, not
  the attempt.
* Add `Credits::for_set()` and the `ntqengine_set_credits` filter for
  building a standing credits section outside the quiz.
* Keep the quiz data and the rendered markup identical when no credit is
  entered.

= 0.7.0 =
* Allow a picture on each choice, kept in its own meta fields rather
  than inside the choice text, so the recorded answer and the review
  sheet stay free of markup. A choice may be a picture alone.
* Add the per-choice image URL and alternative-text fields to the
  question editor and eight optional CSV columns
  (`choice_1_image_url` … `choice_4_image_alt`). Columns that are
  absent from an uploaded CSV are left untouched on update.
* Fall back to a picture-only choice's alternative text wherever a
  choice is named as text: the result screen, the review sheet, the
  completion event, and the button's accessible name.
* Keep the quiz data identical when no choice carries a picture, so
  sites that do not use the feature see no change.

= 0.6.0 =
* Compare external IDs so that digits are read as numbers when that is the
  only difference between two IDs, so `item-2` is shown before `item-10`
  without zero padding. IDs that differ in their text keep the order they had,
  and IDs that differ only in case or in padding still fall back to the
  previous comparison and the post-ID tiebreak.
* Document the unshuffled question order, which was not described anywhere.

**Order change.** Sites whose external IDs end in unpadded numbers will see
those questions in a different order: by value instead of character by
character. This is the intended fix. If a test set keys group minimums to
canonical question positions, the questions at those positions change with the
order, so review those sets after updating. Zero-padded IDs are unaffected.
Positions recorded in earlier attempts refer to the earlier order. No schema
or content migration is required.

= 0.5.0 =
* Add unscored essay prompts with browser-only responses and optional
  self-review rubrics. Essay responses are not included in automatic scoring,
  pass/fail verdicts, completion events, local history or Pro persistence.
* Add optional choice-question group minimums by field or canonical question
  position, using either correct-count or correct-percentage thresholds in
  addition to the existing overall condition.
* Sign the exact server-rendered choice-question selection so Pro can validate
  question IDs and canonical positions without trusting submitted positions.
* Exclude essays from signed scoring selections, including questions converted
  from choice format while legacy choice metadata remains.

= 0.4.9 =
* Keep hand-created questions without `ql_external_id` eligible when question
  shuffling is disabled, with deterministic ordering for missing or duplicate IDs.
* Add official question/explanation image URL and alt-text fields to the editor,
  CSV importer and public payload, plus a pre-JSON site-policy filter.
* Keep legacy CSV imports compatible when image columns are absent; all four
  image columns are optional, and empty-value overwrites apply only to columns
  actually present in the uploaded CSV.
* Render explanation images after explanation text in step, list and review views.
* Clarify that legacy `score` pass mode means number of correct answers and add
  a read-only WP-CLI audit; saved values and verdict logic remain unchanged.
* Keep deterministic release builds compatible with both LF and CRLF source
  checkouts while validating version metadata.
* Accept legacy `ql_choices` arrays with numeric 0-based or 1-based keys when
  canonical `choice_N` keys are absent, while keeping canonical keys preferred.
* Normalize and reject invalid questions before applying each test set's
  question-count limit, preventing random quizzes from returning too few
  questions when the valid pool is large enough.
* Include the plugin version in frontend CSS and JavaScript cache keys while
  retaining file-change cache busting during development.

= 0.4.8 =
* Improve timed quiz bar positioning when a site uses a sticky or fixed header.
* Add automatic sticky header offset detection while preserving manual CSS
  override support.
* Keep the quiz timer sticky within the quiz container without covering the
  site header.

= 0.4.7 =
* Hardens the quiz shortcode `set` attribute to accept only positive integer
  IDs, including values wrapped in paired straight or smart quotes.
* Rejects missing, malformed and out-of-range set values with a safe notice,
  without querying quiz posts or emitting a quiz JSON payload.
* Adds a read-only WP-CLI identifier doctor for detecting legacy residue after
  schema 2 migrations.
* Adds HTTP, DOM, JSON and real-browser release gates for migrated quizzes,
  including question integrity, asset loading and browser/PHP error checks.
* Keeps timed-quiz controls visible while scrolling on desktop and mobile,
  including the correct offset below the WordPress admin bar and isolation
  between multiple quizzes on one page.

= 0.4.6 =
* Automatically migrates pre-0.4.5 question and test-set post types,
  taxonomies and executable legacy shortcodes to the `ntqengine` identifiers.
* Preserves post IDs, term IDs, relationships and every `ql_*` / `ql_set_*`
  post-meta value.
* Stores an immutable pre-migration snapshot, runs database changes in one
  transaction and verifies that no executable legacy identifiers remain.
* Uses a database lock to prevent concurrent migrations and stops module
  startup with an administrator notice if a conflict or database error is found.

= 0.4.5 =
* Prefix compliance update for WordPress.org: renamed the shortcode, custom
  post types, taxonomies, script/style handle, JavaScript object, plugin
  constants and the CSV-import transient/admin identifiers to the unique
  `ntqengine` prefix.
* The shortcode is now `[ntqengine_quiz set="<ID>"]`. The previous
  `[nqe_quiz]`, `[ql_quiz]` and `[wpqe_quiz]` aliases have been removed.
* Existing content created with earlier versions needs to be migrated to the
  new post types and taxonomies before upgrading.

= 0.4.4 =
* Add browser-local anonymous quiz summary support for future Study Desk integrations when Pro integration data is available.
* Keep quiz summary storage client-side and scoped to the visitor's browser.

= 0.4.3 =
* Pass completed quiz result payloads to the Pro integration bridge when available.
* Preserve existing quiz scoring, retry, and scroll behavior.

= 0.4.2 =
* Fix scroll position after viewing quiz results and retrying a quiz.
* Preserve the quiz completed JavaScript event hook for Pro integrations.

= 0.4.1 =
* Added a `nqe:quiz:completed` JavaScript event, dispatched on the quiz root
  element (`.skl-quiz`, with `bubbles: true`) when scoring completes. This is a
  purely additive extension point for add-ons such as Neutrope Quiz Engine Pro;
  it does not change existing shortcodes, markup, CSS, scoring logic, the result
  display, or any backward-compatibility identifiers. Scoring still happens
  entirely on the client and no data is written to the database by the free
  plugin.

= 0.4.0 =
* Initial public release.
* Question and test-set custom post types with practice and mock modes,
  step / list display, question and choice shuffling, configurable choice
  labels, pass thresholds, countdown timer, question banks, per-field
  accuracy breakdown, and a configurable per-set CTA on the result screen.
* CSV importer with dry-run, upsert, automatic term creation and
  CSRF-hardened upload handling.
* Shortcode for embedding a test set on any page or post.
* Frontend strings routed through `wp_localize_script` with Japanese
  fallbacks (translation-ready via the `neutrope-quiz-engine` text domain).
* No outbound HTTP, no external assets, no per-user database writes.

== Upgrade Notice ==

= 0.9.0 =
Adds optional media library attachment IDs for question, explanation and choice images, with a per-set image size. Questions that use image URLs are unchanged. An attachment that cannot be shown leaves its slot empty rather than falling back to the URL, and says so, so a site can drop the question. No schema or content migration is required.

= 0.8.2 =
Adds a filter for keeping one question out of an attempt, for sites that refuse an image on rights grounds. Nothing changes for a site that does not use it, and the existing question-data filter behaves exactly as before.

= 0.8.1 =
Points out saved correct-count thresholds that look like a points total entered by mistake, in the doctor command and on the test set screen. Nothing about scoring, verdicts or saved settings changes.

= 0.8.0 =
Adds optional credits for question, explanation and choice images, in the editor and in twelve new optional CSV columns, with a per-set choice of where they appear. Credits are never generated automatically and never added to recorded answers. A question without a credit renders exactly as before, and no schema or content migration is required.

= 0.7.0 =
Adds optional per-choice images, including picture-only choices, in the question editor and in eight new optional CSV columns. Give every choice picture alternative text, because that text is what the result screen and the review sheet record. Questions without choice images are unchanged, and no schema or content migration is required.

= 0.6.0 =
Unshuffled question order now reads digits in external IDs as numbers, so IDs numbered without zero padding are shown in the order their numbers imply. Zero-padded IDs and IDs whose text differs are unaffected. If a test set keys group minimums to canonical question positions, review those sets after updating. No schema or content migration is required.

= 0.5.0 =
Adds unscored essay prompts and optional group minimums for automatically scored choice questions. Essays remain outside automatic and official grading. No schema or content migration is required.

= 0.4.9 =
Fixes question selection, adds optional question/explanation images and clarifies correct-count thresholds. No schema or content migration is required; image fields are opt-in and existing verdict logic is unchanged.

= 0.4.8 =
Improves the timed quiz bar position on sites with sticky or fixed headers.

= 0.4.7 =
Hardens shortcode set parsing and adds rendering safety checks without changing schema 2. Invalid set values now show a safe notice instead of producing an empty quiz payload.

= 0.4.6 =
Safely repairs the 0.4.5 breaking identifier change. Sites upgrading from 0.4.4 or earlier are migrated automatically; take a full database backup before updating.

= 0.4.5 =
Breaking change: the shortcode, post types, taxonomies, handles and constants now use the unique `ntqengine` prefix, and `[nqe_quiz]` / `[ql_quiz]` / `[wpqe_quiz]` are removed. Use `[ntqengine_quiz set="<ID>"]` and migrate existing content before upgrading.

= 0.4.4 =
Adds browser-local anonymous quiz summary support for future Study Desk integrations when Pro integration data is available.

= 0.4.3 =
Improves the quiz-completed JavaScript event payload for future Pro integrations.

= 0.4.2 =
Fixes the page scroll position after viewing results and retrying a quiz. No breaking changes.

= 0.4.1 =
Adds the `nqe:quiz:completed` JavaScript extension event. No breaking changes.

= 0.4.0 =
Initial release of Neutrope Quiz Engine.
