Skip to content

MelisCmsMcqEngine

Data, services, XML snapshots, scoring, and front-office quiz renderer for the MCQ (quiz) system — the backend behind the React MCQ tool. Package melisplatform/melis-cms-mcq-engine.

Purpose

MelisCmsMcqEngine is the data and service layer of the MCQ system: it owns the database tables, the services for questions, answers, MCQs, groups and categories, the XML snapshot that freezes each assembled test, the random-model question picker, answer scoring / passing-rate evaluation, and the front-office quiz renderer. Think MelisEngine ↔ MelisCms, but for quizzes.

The assembled test is stored as mcq_xml so rendering and scoring read a self-contained snapshot, not a live join, ensuring the exact MCQ a user assembled is preserved even if underlying questions are later edited.

No React brick — surfaced through MelisCmsMcq

This engine has no back-office UI of its own — neither legacy nor React. It ships nobrick.manifest.json, no ui-react/ project, no config/react-api.php and noconfig/react.capabilities.php. It is not listed by GET /melis/react-api/react-modules, appears in no React menu, and contributes no capability node.

In /melis-react the MCQ feature lives entirely in the MelisCmsMcq tool (sidebar → MelisCms → MCQ; brick cms-mcq, route /melis-cms/mcq, melisKey meliscmsmcq_tool). That tool's react-api controllers resolve this engine's services and delegate all business logic to them — the same services the legacy tool used. The React → react-apiengine service path simply replaces the legacy jQuery/AJAX → controller → engine service path; no React-specific engine code exists. Access rights and advanced-rights capabilities all live on the MelisCmsMcq node (meliscmsmcq_tool), not here — the engine's rightsDisplay is 'none'.

Enable it

Add to config/melis.module.load.php:

php
return [
    // …
    'MelisCmsMcqEngine',
];

Requires: melisplatform/melis-cms (^5.3), melisplatform/melis-cms-tags (^5.3), laminas/laminas-paginator, PHP ^8.1|^8.3. The module ships dbdeploy: true so its tables are created/updated automatically. Install chain: melis-cms-mcqmelis-cms-mcq-enginemelis-cms + melis-cms-tags.

Key services

All services extend MelisCore's general service and fire *_start / *_end events. Aliases are registered in config/module.config.php and are the exact entry points the MelisCmsMcq React controllers resolve.

Service aliasRoleConsumed by (MelisCmsMcq React controller)
MelisCmsMcqServiceMCQs: save, delete, list, generate/fetch MCQ XML, shuffle order, evaluate answers (scoring).MelisReactApiMcqController
MelisCmsMcqQuestionsServiceQuestions: CRUD, XML generation, basket, random-model picker.MelisReactApiMcqQuestionController
MelisCmsMcqAnswersServiceAnswers: CRUD, correct-answer check, answer count per question.MelisReactApiMcqQuestionController
MelisCmsMcqGroupsServiceMCQ groups: CRUD, assemble groups+questions for an MCQ, deactivate related MCQs.MelisReactApiMcqGroupController
MelisCmsMcqQuestionCategoryServiceQuestion categories (table melis_cms_mcq_question_groups): CRUD.MelisReactApiMcqCategoryController

Selected methods

MelisCmsMcqService

MethodRole
getMcq($mcqId)Returns the assembled MCQ XML; shuffles question order if mcq_random_order is set.
generateMcqXml($mcqProperties, $mcqTrans, $groupsData)Builds the full test XML from groups + questions and stores it in mcq_xml.
evaluateAnswers($mcqXml, $userAnswers, $passingPercentage)Scores an MCQ-type submission; skips Open Ended; returns score + pass/fail.
xmlToArray($mcqXml)Parses the frozen mcq_xml snapshot into an array (used by the React Preview).
getMcqList / saveMcqItem / deleteMcqByIdStandard list/save/delete.

MelisCmsMcqQuestionsService

MethodRole
generateQuestionXml()Builds the per-question XML snapshot on save.
updateMcqQuestionDataXml()Refreshes the question snapshot inside every MCQ that uses it.
getRandomQuestionIds($difficultyId, $categoryId, $tagId, $numberOfQuestions, $excludedIds)Auto-selects N question IDs by difficulty / category / tag.
generateRandomQuestions($userId, $difficultyId, $categoryId, $tags, $numberOfQuestions, $langId, $alreadySelected)Higher-level random picker with exclusion list.
getQuestionsBasketList / processBasketQuestionsManages the per-user question basket (cart).

How the React MCQ tool uses this engine

The React MCQ controllers marshal JSON in/out and delegate all logic to the engine. The main server-side touch-points (from melis-cms-mcq/src/Controller/MelisReactApi*Controller.php):

php
// POST /melis/react-api/mcq/save  → MelisReactApiMcqController::saveAction()
$mcqService = $this->getServiceManager()->get('MelisCmsMcqService');
$mcqXml = $mcqService->generateMcqXml($mcqProperties, $mcqTrans, $groupsData); // engine assembles snapshot

// POST /melis/react-api/mcq/evaluate  → evaluateAction()
$result = $mcqService->evaluateAnswers($xml, $answers, $passing);              // engine scores; React displays

// GET  /melis/react-api/mcq/:id/preview  → previewAction()
$xml    = $mcqService->getMcq($id);            // frozen test XML (shuffled if mcq_random_order)
$parsed = (array) $mcqService->xmlToArray($xml);

// POST /melis/react-api/mcq-questions/random  → MelisReactApiMcqQuestionController::randomAction()
$ids = $questionService->getRandomQuestionIds($difficulty, $category, $tagId, $count, $exclude);

The React Preview renders the parsed mcq_xml directly (getMcq + xmlToArray); it does not call the engine's renderMcqQuestions view helper — that helper is the front-office / website renderer only, and is unchanged by the React back-office.

Front office

ItemRole
renderMcqQuestions view helper (McqQuestionRendererHelper)Converts mcq_xml to an array and renders the quiz through the chosen template. No MelisTemplatingPlugin — the host template decides placement.
Default templateview/templates/default-preview-template.phtml
McqPreviewTemplatesSelect form-element factoryPopulates a select with templates registered under mcq_preview_templates in app.interface.php.

Additional form-element factories the engine registers: McqQuestionCategoriesSelect, McqQuestionsDifficultySelect, QuestionsTypesSelect. The React tool builds its own JSON refs (types, difficulties, categories) rather than rendering these Laminas select elements, but the underlying engine data (types 1=MCQ / 2=Open Ended, difficulties Easy/Medium/Hard, categories) is the same. The engine's app.interface.php still registers the meliscmsmcqengine plugin with datas (mcq_groups.default_group_lists, mcq_preview_templates) — defaults the MCQ tool honours regardless of front-end.

Database tables

Naming note. The back-office "Question categories" map to melis_cms_mcq_question_groups (service MelisCmsMcqQuestionCategoryService). The back-office "MCQ groups" map to melis_cms_mcq_groups (service MelisCmsMcqGroupsService). Two distinct "group" tables — do not conflate them.

TableHolds
melis_cms_mcqAn MCQ/test: mcq_status, mcq_code, mcq_random_order, mcq_xml (assembled test snapshot), mcq_passing_rate, dates/users.
melis_cms_mcq_transMCQ name per language (mcqt_name).
melis_cms_mcq_questionsA question: mcqq_status, mcqq_code, mcqq_type, mcqq_group_id, mcqq_difficulty_id, mcqq_xml (question+answers snapshot).
melis_cms_mcq_questions_transQuestion text, mcqqt_candidate_note, mcqqt_corrector_note per language.
melis_cms_mcq_answersAn answer: mcqa_question_id, mcqa_status, mcqa_correct_answer, mcqa_code.
melis_cms_mcq_answers_transAnswer text per language (mcqat_answer_text).
melis_cms_mcq_question_typesQuestion types: 1 = MCQ, 2 = Open Ended.
melis_cms_mcq_questions_difficulty (+_trans)Difficulties: 1 = Easy, 2 = Medium, 3 = Hard.
melis_cms_mcq_question_groups (+_trans)Question categories (mcqqg_id, mcqqg_code, mcqqgt_name).
melis_cms_mcq_groups (+_trans)MCQ groups (mcqg_id, mcqg_mcq_id, mcqg_code, mcqgt_name), tied to a specific MCQ.
melis_cms_mcq_questions_listLinks questions ↔ an MCQ (mcmql_mcq_id, mcmql_question_id).
melis_cms_mcq_groups_listLinks groups within an MCQ (added by migration).
melis_cms_mcq_questions_tags_listQuestion ↔ tag links (MelisCmsTags).
melis_cms_mcq_basketsPer-user question basket (cart).

Example

php
$sm = $this->getServiceLocator();

// --- Fetch and render a quiz on a website ---
$mcqSvc = $sm->get('MelisCmsMcqService');
$mcqXml = $mcqSvc->getMcq($mcqId); // shuffles if mcq_random_order is set

// In a phtml template:
echo $this->renderMcqQuestions(
    'MelisCmsMcqEngine/default-template',
    $mcqXml,
    $langId
);

// --- Score a submission ---
$result = $mcqSvc->evaluateAnswers($mcqXml, $userAnswers, 50);
// $result['global']  => ['score', 'pass', 'total', 'checked', 'skipped']
// $result['details'] => per-question outcome

// --- Random model: auto-select N questions ---
$qSvc = $sm->get('MelisCmsMcqQuestionsService');
$ids  = $qSvc->getRandomQuestionIds($difficultyId, $categoryId, $tagId, $numberOfQuestions, $excludedIds);

Key files

ConcernPath
Module wiring + service aliasesvendor/melisplatform/melis-cms-mcq-engine/config/module.config.php
Plugin config (default groups, preview templates)vendor/melisplatform/melis-cms-mcq-engine/config/app.interface.php
MCQ service (XML, scoring)vendor/melisplatform/melis-cms-mcq-engine/src/Service/MelisCmsMcqService.php
Questions service (basket, random model)vendor/melisplatform/melis-cms-mcq-engine/src/Service/MelisCmsMcqQuestionsService.php
Answers servicevendor/melisplatform/melis-cms-mcq-engine/src/Service/MelisCmsMcqAnswersService.php
Groups servicevendor/melisplatform/melis-cms-mcq-engine/src/Service/MelisCmsMcqGroupsService.php
Question category servicevendor/melisplatform/melis-cms-mcq-engine/src/Service/MelisCmsMcqQuestionCategoryService.php
Table gatewaysvendor/melisplatform/melis-cms-mcq-engine/src/Model/Tables/
Form-element factoriesvendor/melisplatform/melis-cms-mcq-engine/src/Form/Factory/
View helpervendor/melisplatform/melis-cms-mcq-engine/src/View/Helper/McqQuestionRendererHelper.php
Default front templatevendor/melisplatform/melis-cms-mcq-engine/view/templates/default-preview-template.phtml
DB install + migrationsvendor/melisplatform/melis-cms-mcq-engine/install/dbdeploy/

See also: Module reference · MelisCmsMcq · MelisCms