From 8cc1862178a36849feeb0d5f9ded73161c1fe2a6 Mon Sep 17 00:00:00 2001 From: Wojtek Date: Fri, 16 Oct 2026 10:12:26 +0200 Subject: [PATCH] Add multi-variant test sets --- README.md | 2 +- .../com_testquestions/forms/set.xml | 6 ++ .../language/en-GB/com_testquestions.ini | 8 ++ .../sql/install.mysql.utf8mb4.sql | 3 + .../sql/updates/mysql/1.34.0.sql | 7 ++ .../src/Controller/SetController.php | 6 ++ .../com_testquestions/src/Model/SetModel.php | 80 ++++++++++++++++++- .../com_testquestions/src/Model/SetsModel.php | 3 +- .../src/View/Set/HtmlView.php | 2 + .../com_testquestions/tmpl/set/edit.php | 27 +++++++ .../com_testquestions/tmpl/sets/default.php | 4 +- com_testquestions.xml | 2 +- .../com_testquestions/src/Model/SetModel.php | 33 +++++++- 13 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 administrator/components/com_testquestions/sql/updates/mysql/1.34.0.sql diff --git a/README.md b/README.md index 644552c..f855067 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,6 @@ Szkielet komponentu Joomla 6 do gromadzenia pytań testowych. ## Instalacja -W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.33.0). +W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.34.0). Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**. diff --git a/administrator/components/com_testquestions/forms/set.xml b/administrator/components/com_testquestions/forms/set.xml index affc801..3375c80 100644 --- a/administrator/components/com_testquestions/forms/set.xml +++ b/administrator/components/com_testquestions/forms/set.xml @@ -2,7 +2,13 @@
+ + + + + + diff --git a/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini index d1f00ec..32a40df 100644 --- a/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini +++ b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini @@ -49,6 +49,14 @@ COM_TESTQUESTIONS_SETS="Zestawy pytań" COM_TESTQUESTIONS_NEW_SET="Nowy zestaw pytań" COM_TESTQUESTIONS_EDIT_SET="Edytuj zestaw pytań" COM_TESTQUESTIONS_SET_TITLE="Nazwa zestawu" +COM_TESTQUESTIONS_SET_TYPE="Rodzaj zestawu" +COM_TESTQUESTIONS_SET_TYPE_ORDINARY="Zwykły zestaw" +COM_TESTQUESTIONS_SET_TYPE_MULTI="Zestaw wielowariantowy" +COM_TESTQUESTIONS_SET_TYPE_VARIANT="Wariant zestawu" +COM_TESTQUESTIONS_SET_VARIANTS="Warianty zestawu" +COM_TESTQUESTIONS_SET_ADD_VARIANT="Dodaj wariant" +COM_TESTQUESTIONS_SET_PARENT="Zestaw nadrzędny" +COM_TESTQUESTIONS_SET_NO_VARIANTS="Brak wariantów" COM_TESTQUESTIONS_SET_DESCRIPTION="Opis zestawu" COM_TESTQUESTIONS_SET_QUESTIONS="Pytania w zestawie" COM_TESTQUESTIONS_SET_QUESTION_COUNT="Liczba pytań" diff --git a/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql b/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql index 2e29b11..7fc339d 100644 --- a/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql +++ b/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql @@ -40,6 +40,8 @@ VALUES (1, 'Bez kategorii', 1, 0); CREATE TABLE IF NOT EXISTS `#__testquestions_sets` ( `id` int unsigned NOT NULL AUTO_INCREMENT, + `parent_id` int unsigned NOT NULL DEFAULT 0, + `set_type` varchar(12) NOT NULL DEFAULT 'ordinary', `subject_id` int unsigned NOT NULL DEFAULT 0, `title` varchar(255) NOT NULL, `duration` smallint unsigned NOT NULL DEFAULT 0, @@ -54,6 +56,7 @@ CREATE TABLE IF NOT EXISTS `#__testquestions_sets` ( `modified_by` int unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_published` (`published`), + KEY `idx_parent_id` (`parent_id`), KEY `idx_ordering` (`ordering`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.34.0.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.34.0.sql new file mode 100644 index 0000000..153dbc7 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.34.0.sql @@ -0,0 +1,7 @@ +ALTER TABLE `#__testquestions_sets` + ADD COLUMN `parent_id` int unsigned NOT NULL DEFAULT 0 AFTER `id`, + ADD COLUMN `set_type` varchar(12) NOT NULL DEFAULT 'ordinary' AFTER `parent_id`; + +UPDATE `#__testquestions_sets` +SET `set_type` = 'ordinary' +WHERE `set_type` = '' OR `set_type` IS NULL; diff --git a/administrator/components/com_testquestions/src/Controller/SetController.php b/administrator/components/com_testquestions/src/Controller/SetController.php index b075807..696c1c0 100644 --- a/administrator/components/com_testquestions/src/Controller/SetController.php +++ b/administrator/components/com_testquestions/src/Controller/SetController.php @@ -11,6 +11,12 @@ class SetController extends FormController protected $view_item = 'set'; protected $view_list = 'sets'; + public function addvariant(): void + { + $parentId = $this->input->getInt('parent_id'); + $this->setRedirect('index.php?option=com_testquestions&view=set&layout=edit&parent_id=' . $parentId); + } + public function cancel($key = null) { $this->setRedirect('index.php?option=com_testquestions&view=sets'); diff --git a/administrator/components/com_testquestions/src/Model/SetModel.php b/administrator/components/com_testquestions/src/Model/SetModel.php index 8d23a12..6b31019 100644 --- a/administrator/components/com_testquestions/src/Model/SetModel.php +++ b/administrator/components/com_testquestions/src/Model/SetModel.php @@ -25,6 +25,14 @@ class SetModel extends AdminModel if ($item && !empty($item->id)) { $db = Factory::getContainer()->get('DatabaseDriver'); + if ((int) $item->parent_id > 0) { + $item->parent_title = (string) $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('title')) + ->from($db->quoteName('#__testquestions_sets')) + ->where($db->quoteName('id') . ' = ' . (int) $item->parent_id) + )->loadResult(); + } $item->question_ids = $db->setQuery( $db->getQuery(true) ->select($db->quoteName('question_id')) @@ -41,7 +49,32 @@ class SetModel extends AdminModel protected function loadFormData() { - return Factory::getApplication()->getUserState('com_testquestions.edit.set.data', []) ?: $this->getItem(); + $data = Factory::getApplication()->getUserState('com_testquestions.edit.set.data', []) ?: $this->getItem(); + $parentId = Factory::getApplication()->getInput()->getInt('parent_id'); + + $hasId = is_array($data) ? !empty($data['id']) : !empty($data->id); + + if ($parentId > 0 && !$hasId) { + $parent = $this->getItem($parentId); + $variantCount = count($this->getVariants($parentId)); + + if ($parent) { + $data = [ + 'parent_id' => $parentId, + 'set_type' => 'variant', + 'title' => $parent->title . ' — wariant ' . ($variantCount + 1), + 'subject_id' => $parent->subject_id, + 'duration' => $parent->duration, + 'test_content' => $parent->test_content, + 'image' => $parent->image, + 'description' => $parent->description, + 'published' => $parent->published, + 'question_ids' => [], + ]; + } + } + + return $data; } public function getQuestions() @@ -69,12 +102,41 @@ class SetModel extends AdminModel )->loadObjectList(); } + public function getVariants($parentId = null) + { + $parentId = (int) ($parentId ?: Factory::getApplication()->getInput()->getInt('id')); + + if ($parentId < 1) { + return []; + } + + $db = Factory::getContainer()->get('DatabaseDriver'); + + return $db->setQuery( + $db->getQuery(true) + ->select(['id', 'title', 'published']) + ->from($db->quoteName('#__testquestions_sets')) + ->where($db->quoteName('parent_id') . ' = ' . $parentId) + ->order($db->quoteName('ordering') . ' ASC, ' . $db->quoteName('id') . ' ASC') + )->loadObjectList(); + } + public function save($data) { $formData = Factory::getApplication()->getInput()->post->get('jform', [], 'array'); $questionIds = array_values(array_unique(array_filter(array_map('intval', (array) ($formData['question_ids'] ?? $data['question_ids'] ?? []))))); unset($data['question_ids']); + $parentId = (int) ($formData['parent_id'] ?? $data['parent_id'] ?? 0); + $parentId = max(0, $parentId); + $data['parent_id'] = $parentId; + + if ($parentId > 0) { + $data['set_type'] = 'variant'; + } elseif (!in_array(($data['set_type'] ?? 'ordinary'), ['ordinary', 'multi'], true)) { + $data['set_type'] = 'ordinary'; + } + if (!parent::save($data)) { return false; } @@ -111,12 +173,28 @@ class SetModel extends AdminModel $db = Factory::getContainer()->get('DatabaseDriver'); $ids = array_map('intval', (array) $pks); + if ($ids) { + $childIds = $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__testquestions_sets')) + ->where($db->quoteName('parent_id') . ' IN (' . implode(',', $ids) . ')') + )->loadColumn(); + $ids = array_values(array_unique(array_merge($ids, array_map('intval', $childIds)))); + } + if ($ids) { $db->setQuery( $db->getQuery(true) ->delete($db->quoteName('#__testquestions_set_questions')) ->where($db->quoteName('set_id') . ' IN (' . implode(',', $ids) . ')') )->execute(); + + $db->setQuery( + $db->getQuery(true) + ->delete($db->quoteName('#__testquestions_sets')) + ->where($db->quoteName('id') . ' IN (' . implode(',', $ids) . ')') + )->execute(); } return true; diff --git a/administrator/components/com_testquestions/src/Model/SetsModel.php b/administrator/components/com_testquestions/src/Model/SetsModel.php index a324496..9bc2da6 100644 --- a/administrator/components/com_testquestions/src/Model/SetsModel.php +++ b/administrator/components/com_testquestions/src/Model/SetsModel.php @@ -21,9 +21,10 @@ class SetsModel extends ListModel { $db = Factory::getContainer()->get('DatabaseDriver'); $query = $db->getQuery(true) - ->select(['s.*', 'sub.title AS subject_title', 'COUNT(sq.question_id) AS question_count']) + ->select(['s.*', 'sub.title AS subject_title', 'p.title AS parent_title', 'COUNT(sq.question_id) AS question_count']) ->from($db->quoteName('#__testquestions_sets', 's')) ->leftJoin($db->quoteName('#__testquestions_subjects', 'sub') . ' ON sub.id = s.subject_id') + ->leftJoin($db->quoteName('#__testquestions_sets', 'p') . ' ON p.id = s.parent_id') ->leftJoin($db->quoteName('#__testquestions_set_questions', 'sq') . ' ON sq.set_id = s.id') ->group('s.id'); diff --git a/administrator/components/com_testquestions/src/View/Set/HtmlView.php b/administrator/components/com_testquestions/src/View/Set/HtmlView.php index 4b740c0..a23a02c 100644 --- a/administrator/components/com_testquestions/src/View/Set/HtmlView.php +++ b/administrator/components/com_testquestions/src/View/Set/HtmlView.php @@ -14,6 +14,7 @@ class HtmlView extends BaseHtmlView protected $item; protected $questions; protected $categories; + protected $variants; public function display($tpl = null) { @@ -21,6 +22,7 @@ class HtmlView extends BaseHtmlView $this->item = $this->get('Item'); $this->questions = $this->get('Questions'); $this->categories = $this->get('Categories'); + $this->variants = $this->item && (int) $this->item->id > 0 ? $this->get('Variants') : []; if (count($errors = $this->get('Errors'))) { throw new \RuntimeException(implode("\n", $errors)); diff --git a/administrator/components/com_testquestions/tmpl/set/edit.php b/administrator/components/com_testquestions/tmpl/set/edit.php index 5ac909b..1c110a3 100644 --- a/administrator/components/com_testquestions/tmpl/set/edit.php +++ b/administrator/components/com_testquestions/tmpl/set/edit.php @@ -9,6 +9,13 @@ HTMLHelper::_('behavior.formvalidator'); ?>
+ form->renderField('parent_id'); ?> + item->parent_id > 0) : ?> +
: escape($this->item->parent_title ?? ''); ?>
+ + + form->renderField('set_type'); ?> + form->renderField('title'); ?> form->renderField('subject_id'); ?> form->renderField('duration'); ?> @@ -17,6 +24,26 @@ HTMLHelper::_('behavior.formvalidator'); form->renderField('image'); ?> form->renderField('description'); ?> form->renderField('published'); ?> + item->id > 0 && (string) $this->item->set_type === 'multi') : ?> +
+ + variants) : ?> +
    + variants as $variant) : ?> +
  • + escape($variant->title); ?> + published ? \Joomla\CMS\Language\Text::_('JPUBLISHED') : \Joomla\CMS\Language\Text::_('JUNPUBLISHED'); ?> +
  • + +
+ +

+ + + + +
+
diff --git a/administrator/components/com_testquestions/tmpl/sets/default.php b/administrator/components/com_testquestions/tmpl/sets/default.php index da884bb..5dc8b0b 100644 --- a/administrator/components/com_testquestions/tmpl/sets/default.php +++ b/administrator/components/com_testquestions/tmpl/sets/default.php @@ -38,6 +38,7 @@ HTMLHelper::_('behavior.multiselect'); + @@ -48,7 +49,8 @@ HTMLHelper::_('behavior.multiselect'); items as $i => $item) : ?> id); ?> - escape($item->title); ?> + parent_id > 0 ? '↳ ' : ''; ?>escape($item->title); ?>parent_id > 0 && $item->parent_title) : ?> (escape($item->parent_title); ?>) + escape($item->set_type === 'multi' ? \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_SET_TYPE_MULTI') : ($item->set_type === 'variant' ? \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_SET_TYPE_VARIANT') : \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_SET_TYPE_ORDINARY'))); ?> escape($item->subject_title ?: '—'); ?> question_count; ?> published, $i, 'sets.', true, 'cb'); ?> diff --git a/com_testquestions.xml b/com_testquestions.xml index 76d873e..45ba0db 100644 --- a/com_testquestions.xml +++ b/com_testquestions.xml @@ -4,7 +4,7 @@ 2026-10-14 Komponent GNU General Public License version 2 or later - 1.33.0 + 1.34.0 COM_TESTQUESTIONS_DESCRIPTION com_testquestions Komponent\Component\Testquestions diff --git a/components/com_testquestions/src/Model/SetModel.php b/components/com_testquestions/src/Model/SetModel.php index 76305dc..24fdbf2 100644 --- a/components/com_testquestions/src/Model/SetModel.php +++ b/components/com_testquestions/src/Model/SetModel.php @@ -9,6 +9,8 @@ use Joomla\CMS\MVC\Model\BaseDatabaseModel; class SetModel extends BaseDatabaseModel { + protected $resolvedSetId; + protected function getSetId(): int { $application = Factory::getApplication(); @@ -27,20 +29,46 @@ class SetModel extends BaseDatabaseModel return null; } - return $db->setQuery( + $set = $db->setQuery( $db->getQuery(true) ->select(['s.*', 'sub.title AS subject_title']) ->from($db->quoteName('#__testquestions_sets', 's')) ->leftJoin($db->quoteName('#__testquestions_subjects', 'sub') . ' ON sub.id = s.subject_id') ->where('s.id = ' . $setId) + ->where('s.parent_id = 0') ->where('s.published = 1') )->loadObject(); + + if (!$set) { + return null; + } + + $this->resolvedSetId = $setId; + + if ($set->set_type === 'multi') { + $variantId = (int) $db->setQuery( + $db->getQuery(true) + ->select('id') + ->from($db->quoteName('#__testquestions_sets')) + ->where('parent_id = ' . $setId) + ->where('published = 1') + ->order('RAND()') + ->setLimit(1) + )->loadResult(); + + $this->resolvedSetId = $variantId; + } + + return $set; } public function getItems() { $db = Factory::getContainer()->get('DatabaseDriver'); - $setId = $this->getSetId(); + if ($this->resolvedSetId === null) { + $this->getSet(); + } + $setId = (int) $this->resolvedSetId; if ($setId < 1) { return []; @@ -67,6 +95,7 @@ class SetModel extends BaseDatabaseModel ->from($db->quoteName('#__testquestions_sets', 's')) ->leftJoin($db->quoteName('#__testquestions_subjects', 'sub') . ' ON sub.id = s.subject_id') ->where('s.published = 1') + ->where('s.parent_id = 0') ->order('sub.title ASC, s.title ASC') )->loadObjectList(); }