commit 9b093b278d3e4bd111eca538442de0492af49045 Author: Wojtek Date: Wed Oct 14 22:29:10 2026 +0200 Initial Joomla test questions component diff --git a/README.md b/README.md new file mode 100644 index 0000000..39812e6 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# com_testquestions + +Szkielet komponentu Joomla 6 do gromadzenia pytań testowych. + +## Funkcje + +- pytanie i dokładnie cztery odpowiedzi, +- pytania zamknięte oraz otwarte z wzorcem poprawnej odpowiedzi, +- edytor formatowania treści pytania i odpowiedzi zamkniętych, +- czas odpowiedzi, punktacja 1–9 oraz zarządzalne kategorie pytań, +- filtrowanie listy pytań po kategorii, +- wskazanie poprawnej odpowiedzi, +- opcjonalne zdjęcie wybierane z Media Managera, +- publikowanie, filtrowanie i usuwanie pytań w panelu administratora, +- tabela `#__testquestions_questions` tworzona podczas instalacji. + +## Instalacja + +W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.3.2). + +Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**. diff --git a/administrator/components/com_testquestions/forms/category.xml b/administrator/components/com_testquestions/forms/category.xml new file mode 100644 index 0000000..93cf5bc --- /dev/null +++ b/administrator/components/com_testquestions/forms/category.xml @@ -0,0 +1,11 @@ + +
+
+ + + + + + +
+
diff --git a/administrator/components/com_testquestions/forms/question.xml b/administrator/components/com_testquestions/forms/question.xml new file mode 100644 index 0000000..679465d --- /dev/null +++ b/administrator/components/com_testquestions/forms/question.xml @@ -0,0 +1,42 @@ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini new file mode 100644 index 0000000..158306d --- /dev/null +++ b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini @@ -0,0 +1,42 @@ +COM_TESTQUESTIONS="Pytania testowe" +COM_TESTQUESTIONS_QUESTIONS="Pytania" +COM_TESTQUESTIONS_NEW_QUESTION="Nowe pytanie" +COM_TESTQUESTIONS_EDIT_QUESTION="Edytuj pytanie" +COM_TESTQUESTIONS_FIELDSET_DETAILS="Treść pytania i odpowiedzi" +COM_TESTQUESTIONS_FIELD_TYPE="Typ pytania" +COM_TESTQUESTIONS_FIELD_CATEGORY="Kategoria" +COM_TESTQUESTIONS_FIELD_TIME_LIMIT="Czas na odpowiedź" +COM_TESTQUESTIONS_FIELD_POINTS="Liczba punktów" +COM_TESTQUESTIONS_MANAGE_CATEGORIES="Kategorie" +COM_TESTQUESTIONS_FILTER_CATEGORY="Wszystkie kategorie" +COM_TESTQUESTIONS_CATEGORIES="Kategorie pytań" +COM_TESTQUESTIONS_CATEGORY_TITLE="Nazwa kategorii" +COM_TESTQUESTIONS_TYPE_CLOSED="Zamknięte — wybór odpowiedzi" +COM_TESTQUESTIONS_TYPE_OPEN="Otwarte — wpisywana odpowiedź" +COM_TESTQUESTIONS_FIELD_QUESTION="Pytanie" +COM_TESTQUESTIONS_FIELD_QUESTION_DESC="Wpisz treść pytania." +COM_TESTQUESTIONS_FIELD_ANSWER_A="Odpowiedź A" +COM_TESTQUESTIONS_FIELD_ANSWER_B="Odpowiedź B" +COM_TESTQUESTIONS_FIELD_ANSWER_C="Odpowiedź C" +COM_TESTQUESTIONS_FIELD_ANSWER_D="Odpowiedź D" +COM_TESTQUESTIONS_FIELD_OPEN_PATTERN="Wzorzec poprawnej odpowiedzi" +COM_TESTQUESTIONS_FIELD_OPEN_PATTERN_DESC="Wpisz poprawną odpowiedź lub kilka wariantów oddzielonych znakiem |. Wielkość liter nie będzie rozróżniana." +COM_TESTQUESTIONS_FIELD_CORRECT="Poprawna odpowiedź" +COM_TESTQUESTIONS_SELECT_CORRECT="-- wybierz poprawną odpowiedź --" +COM_TESTQUESTIONS_ANSWER_A="A" +COM_TESTQUESTIONS_ANSWER_B="B" +COM_TESTQUESTIONS_ANSWER_C="C" +COM_TESTQUESTIONS_ANSWER_D="D" +COM_TESTQUESTIONS_FIELD_IMAGE="Zdjęcie" +COM_TESTQUESTIONS_FIELD_IMAGE_DESC="Opcjonalnie wybierz zdjęcie z Media Managera." +COM_TESTQUESTIONS_COLUMN_QUESTION="Pytanie" +COM_TESTQUESTIONS_COLUMN_TYPE="Typ" +COM_TESTQUESTIONS_COLUMN_CORRECT="Poprawna" +COM_TESTQUESTIONS_COLUMN_ID="ID" +COM_TESTQUESTIONS_SEARCH_PLACEHOLDER="Szukaj w pytaniach..." +COM_TESTQUESTIONS_HAS_IMAGE="Ma zdjęcie" +COM_TESTQUESTIONS_ERROR_CORRECT_ANSWER="Wybierz poprawną odpowiedź." +COM_TESTQUESTIONS_ERROR_QUESTION_TYPE="Wybierz poprawny typ pytania." +COM_TESTQUESTIONS_ERROR_OPEN_PATTERN="Dla pytania otwartego wpisz wzorzec poprawnej odpowiedzi." +COM_TESTQUESTIONS_ERROR_ALL_ANSWERS="Dla pytania zamkniętego uzupełnij wszystkie cztery odpowiedzi." +COM_TESTQUESTIONS_ERROR_META="Wybierz poprawny czas i liczbę punktów." diff --git a/administrator/components/com_testquestions/language/en-GB/com_testquestions.sys.ini b/administrator/components/com_testquestions/language/en-GB/com_testquestions.sys.ini new file mode 100644 index 0000000..1a2096b --- /dev/null +++ b/administrator/components/com_testquestions/language/en-GB/com_testquestions.sys.ini @@ -0,0 +1,2 @@ +COM_TESTQUESTIONS="Pytania testowe" +COM_TESTQUESTIONS_DESCRIPTION="Gromadzenie pytań testowych z czterema odpowiedziami i opcjonalnym zdjęciem." diff --git a/administrator/components/com_testquestions/services/provider.php b/administrator/components/com_testquestions/services/provider.php new file mode 100644 index 0000000..b65ad72 --- /dev/null +++ b/administrator/components/com_testquestions/services/provider.php @@ -0,0 +1,27 @@ +registerServiceProvider(new MVCFactoryServiceProvider('Komponent\\Component\\Testquestions')); + $container->registerServiceProvider(new ComponentDispatcherFactoryServiceProvider('Komponent\\Component\\Testquestions')); + $container->set( + ComponentInterface::class, + function (Container $container) { + return new Component($container->get(ComponentDispatcherFactoryInterface::class)); + } + ); + } +}; diff --git a/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql b/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql new file mode 100644 index 0000000..157b70a --- /dev/null +++ b/administrator/components/com_testquestions/sql/install.mysql.utf8mb4.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS `#__testquestions_questions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `category_id` int unsigned NOT NULL DEFAULT 0, + `time_limit` smallint unsigned NOT NULL DEFAULT 60, + `points` tinyint unsigned NOT NULL DEFAULT 1, + `question_type` varchar(10) NOT NULL DEFAULT 'closed', + `question` text NOT NULL, + `answer_a` text NOT NULL, + `answer_b` text NOT NULL, + `answer_c` text NOT NULL, + `answer_d` text NOT NULL, + `answer_open` text NULL, + `correct_answer` tinyint unsigned NOT NULL DEFAULT 0, + `image` varchar(1024) NOT NULL DEFAULT '', + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + `created` datetime NOT NULL, + `created_by` int unsigned NOT NULL DEFAULT 0, + `modified` datetime NULL DEFAULT NULL, + `modified_by` int unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_published` (`published`), + KEY `idx_ordering` (`ordering`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `#__testquestions_categories` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(150) NOT NULL, + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_title` (`title`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; + +INSERT IGNORE INTO `#__testquestions_categories` (`id`, `title`, `published`, `ordering`) +VALUES (1, 'Bez kategorii', 1, 0); diff --git a/administrator/components/com_testquestions/sql/uninstall.mysql.utf8mb4.sql b/administrator/components/com_testquestions/sql/uninstall.mysql.utf8mb4.sql new file mode 100644 index 0000000..25bb53c --- /dev/null +++ b/administrator/components/com_testquestions/sql/uninstall.mysql.utf8mb4.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `#__testquestions_questions`; diff --git a/administrator/components/com_testquestions/sql/updates/mysql/.keep b/administrator/components/com_testquestions/sql/updates/mysql/.keep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/.keep @@ -0,0 +1 @@ + diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.0.2.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.0.2.sql new file mode 100644 index 0000000..4de91ff --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.0.2.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS `#__testquestions_questions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `question` text NOT NULL, + `answer_a` varchar(500) NOT NULL, + `answer_b` varchar(500) NOT NULL, + `answer_c` varchar(500) NOT NULL, + `answer_d` varchar(500) NOT NULL, + `correct_answer` tinyint unsigned NOT NULL DEFAULT 0, + `image` varchar(1024) NOT NULL DEFAULT '', + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + `created` datetime NOT NULL, + `created_by` int unsigned NOT NULL DEFAULT 0, + `modified` datetime NULL DEFAULT NULL, + `modified_by` int unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_published` (`published`), + 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.0.3.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.0.3.sql new file mode 100644 index 0000000..4f8bf97 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.0.3.sql @@ -0,0 +1 @@ +-- Joomla 6 compatibility update: language strings now use Joomla CMS Text API. diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.1.0.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.1.0.sql new file mode 100644 index 0000000..295cd85 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.1.0.sql @@ -0,0 +1,3 @@ +ALTER TABLE `#__testquestions_questions` + ADD COLUMN `question_type` varchar(10) NOT NULL DEFAULT 'closed' AFTER `id`, + ADD COLUMN `answer_open` text NULL AFTER `answer_d`; diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.1.1.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.1.1.sql new file mode 100644 index 0000000..1dd3690 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.1.1.sql @@ -0,0 +1 @@ +-- Validation update for open and closed question forms. diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.2.0.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.2.0.sql new file mode 100644 index 0000000..7e465bd --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.2.0.sql @@ -0,0 +1,5 @@ +ALTER TABLE `#__testquestions_questions` + MODIFY COLUMN `answer_a` text NOT NULL, + MODIFY COLUMN `answer_b` text NOT NULL, + MODIFY COLUMN `answer_c` text NOT NULL, + MODIFY COLUMN `answer_d` text NOT NULL; diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.2.1.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.2.1.sql new file mode 100644 index 0000000..38758bf --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.2.1.sql @@ -0,0 +1 @@ +-- Controller update: cancel returns to the questions list. diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.2.2.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.2.2.sql new file mode 100644 index 0000000..4009d0b --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.2.2.sql @@ -0,0 +1 @@ +-- Direct cancel link for the question editor. diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.3.0.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.3.0.sql new file mode 100644 index 0000000..5da8396 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.3.0.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS `#__testquestions_categories` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(150) NOT NULL, + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_title` (`title`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; + +INSERT IGNORE INTO `#__testquestions_categories` (`id`, `title`, `published`, `ordering`) +VALUES (1, 'Bez kategorii', 1, 0); + +ALTER TABLE `#__testquestions_questions` + ADD COLUMN `category_id` int unsigned NOT NULL DEFAULT 1 AFTER `id`, + ADD COLUMN `time_limit` smallint unsigned NOT NULL DEFAULT 60 AFTER `category_id`, + ADD COLUMN `points` tinyint unsigned NOT NULL DEFAULT 1 AFTER `time_limit`; diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.3.1.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.3.1.sql new file mode 100644 index 0000000..11867ae --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.3.1.sql @@ -0,0 +1 @@ +-- Category cancel action returns to the questions list. diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.3.2.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.3.2.sql new file mode 100644 index 0000000..a4e0529 --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.3.2.sql @@ -0,0 +1 @@ +-- Category filter for the administrator question list. diff --git a/administrator/components/com_testquestions/src/Controller/CategoriesController.php b/administrator/components/com_testquestions/src/Controller/CategoriesController.php new file mode 100644 index 0000000..4fb85b7 --- /dev/null +++ b/administrator/components/com_testquestions/src/Controller/CategoriesController.php @@ -0,0 +1,17 @@ + true]) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/administrator/components/com_testquestions/src/Controller/CategoryController.php b/administrator/components/com_testquestions/src/Controller/CategoryController.php new file mode 100644 index 0000000..957cb11 --- /dev/null +++ b/administrator/components/com_testquestions/src/Controller/CategoryController.php @@ -0,0 +1,13 @@ +setRedirect('index.php?option=com_testquestions&view=questions'); + + return true; + } +} diff --git a/administrator/components/com_testquestions/src/Controller/QuestionsController.php b/administrator/components/com_testquestions/src/Controller/QuestionsController.php new file mode 100644 index 0000000..d1be671 --- /dev/null +++ b/administrator/components/com_testquestions/src/Controller/QuestionsController.php @@ -0,0 +1,17 @@ + true]) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/administrator/components/com_testquestions/src/Model/CategoriesModel.php b/administrator/components/com_testquestions/src/Model/CategoriesModel.php new file mode 100644 index 0000000..453f701 --- /dev/null +++ b/administrator/components/com_testquestions/src/Model/CategoriesModel.php @@ -0,0 +1,20 @@ +get('DatabaseDriver'); + return $db->getQuery(true) + ->select('*') + ->from($db->quoteName('#__testquestions_categories')) + ->order($db->quoteName('ordering') . ' ASC, ' . $db->quoteName('title') . ' ASC'); + } +} diff --git a/administrator/components/com_testquestions/src/Model/CategoryModel.php b/administrator/components/com_testquestions/src/Model/CategoryModel.php new file mode 100644 index 0000000..58729c6 --- /dev/null +++ b/administrator/components/com_testquestions/src/Model/CategoryModel.php @@ -0,0 +1,26 @@ +getMVCFactory()->createTable($name, $prefix, $config); + } + + public function getForm($data = [], $loadData = true) + { + return $this->loadForm('com_testquestions.category', 'category', ['control' => 'jform', 'load_data' => $loadData]); + } + + protected function loadFormData() + { + return Factory::getApplication()->getUserState('com_testquestions.edit.category.data', []) ?: $this->getItem(); + } +} diff --git a/administrator/components/com_testquestions/src/Model/QuestionModel.php b/administrator/components/com_testquestions/src/Model/QuestionModel.php new file mode 100644 index 0000000..c3012df --- /dev/null +++ b/administrator/components/com_testquestions/src/Model/QuestionModel.php @@ -0,0 +1,50 @@ +getMVCFactory()->createTable($name, $prefix, $config); + } + + public function getForm($data = [], $loadData = true) + { + $form = $this->loadForm('com_testquestions.question', 'question', ['control' => 'jform', 'load_data' => $loadData]); + + if (!$form) { + return false; + } + + return $form; + } + + protected function loadFormData() + { + $data = Factory::getApplication()->getUserState('com_testquestions.edit.question.data', []); + + if (!$data) { + $data = $this->getItem(); + } + + return $data; + } + + protected function prepareTable($table) + { + if (empty($table->id)) { + $table->created = Factory::getDate()->toSql(); + $table->created_by = Factory::getApplication()->getIdentity()->id; + } + + $table->modified = Factory::getDate()->toSql(); + $table->modified_by = Factory::getApplication()->getIdentity()->id; + } +} diff --git a/administrator/components/com_testquestions/src/Model/QuestionsModel.php b/administrator/components/com_testquestions/src/Model/QuestionsModel.php new file mode 100644 index 0000000..1a96e3c --- /dev/null +++ b/administrator/components/com_testquestions/src/Model/QuestionsModel.php @@ -0,0 +1,62 @@ +setState('filter.search', $this->getUserStateFromRequest( + $this->context . '.filter.search', 'filter_search', '', 'string' + )); + $this->setState('filter.published', $this->getUserStateFromRequest( + $this->context . '.filter.published', 'filter_published', '', 'string' + )); + $this->setState('filter.category_id', $this->getUserStateFromRequest( + $this->context . '.filter.category_id', 'filter_category_id', '', 'int' + )); + } + + protected function getListQuery() + { + $db = Factory::getContainer()->get('DatabaseDriver'); + $query = $db->getQuery(true) + ->select('q.*, c.title AS category_title') + ->from($db->quoteName('#__testquestions_questions', 'q')) + ->leftJoin($db->quoteName('#__testquestions_categories', 'c') . ' ON c.id = q.category_id'); + $search = trim((string) $this->getState('filter.search')); + $published = $this->getState('filter.published'); + + if ($search !== '') { + $search = '%' . $db->escape($search, true) . '%'; + $query->where($db->quoteName('q.question') . ' LIKE ' . $db->quote($search, false)); + } + if ($published !== '' && $published !== null) { + $query->where($db->quoteName('q.published') . ' = ' . (int) $published); + } + if ((int) $this->getState('filter.category_id') > 0) { + $query->where('q.category_id = ' . (int) $this->getState('filter.category_id')); + } + + return $query->order($db->quoteName('q.' . $this->getState('list.ordering', 'ordering')) . ' ' + . $db->escape($this->getState('list.direction', 'ASC'))); + } + + public function getCategories() + { + $db = Factory::getContainer()->get('DatabaseDriver'); + return $db->setQuery( + $db->getQuery(true) + ->select(['id', 'title']) + ->from($db->quoteName('#__testquestions_categories')) + ->where($db->quoteName('published') . ' = 1') + ->order($db->quoteName('ordering') . ' ASC, ' . $db->quoteName('title') . ' ASC') + )->loadObjectList(); + } +} diff --git a/administrator/components/com_testquestions/src/Table/CategoryTable.php b/administrator/components/com_testquestions/src/Table/CategoryTable.php new file mode 100644 index 0000000..945798b --- /dev/null +++ b/administrator/components/com_testquestions/src/Table/CategoryTable.php @@ -0,0 +1,22 @@ +title = trim((string) $this->title); + return $this->title !== '' && parent::check($updateNulls); + } +} diff --git a/administrator/components/com_testquestions/src/Table/QuestionTable.php b/administrator/components/com_testquestions/src/Table/QuestionTable.php new file mode 100644 index 0000000..21f20e1 --- /dev/null +++ b/administrator/components/com_testquestions/src/Table/QuestionTable.php @@ -0,0 +1,60 @@ +category_id = (int) $this->category_id ?: 1; + $this->time_limit = (int) $this->time_limit; + $this->points = (int) $this->points; + + if (!in_array($this->time_limit, [30, 45, 60, 90], true) || $this->points < 1 || $this->points > 9) { + $this->setError('COM_TESTQUESTIONS_ERROR_META'); + return false; + } + + $this->question_type = $this->question_type ?: 'closed'; + + if (!in_array($this->question_type, ['closed', 'open'], true)) { + $this->setError('COM_TESTQUESTIONS_ERROR_QUESTION_TYPE'); + return false; + } + + if ($this->question_type === 'open') { + if (trim((string) $this->answer_open) === '') { + $this->setError('COM_TESTQUESTIONS_ERROR_OPEN_PATTERN'); + return false; + } + + $this->correct_answer = 0; + } else { + foreach (['answer_a', 'answer_b', 'answer_c', 'answer_d'] as $answer) { + if (trim((string) $this->$answer) === '') { + $this->setError('COM_TESTQUESTIONS_ERROR_ALL_ANSWERS'); + return false; + } + } + } + + $this->correct_answer = (int) $this->correct_answer; + + if ($this->question_type === 'closed' && ($this->correct_answer < 1 || $this->correct_answer > 4)) { + $this->setError('COM_TESTQUESTIONS_ERROR_CORRECT_ANSWER'); + return false; + } + + return parent::check($updateNulls); + } +} diff --git a/administrator/components/com_testquestions/src/View/Categories/HtmlView.php b/administrator/components/com_testquestions/src/View/Categories/HtmlView.php new file mode 100644 index 0000000..68a68f1 --- /dev/null +++ b/administrator/components/com_testquestions/src/View/Categories/HtmlView.php @@ -0,0 +1,25 @@ +items = $this->get('Items'); + ToolbarHelper::title(Text::_('COM_TESTQUESTIONS_CATEGORIES')); + ToolbarHelper::addNew('category.add', 'JTOOLBAR_NEW'); + ToolbarHelper::editList('category.edit', 'JTOOLBAR_EDIT'); + ToolbarHelper::deleteList('', 'categories.delete', 'JTOOLBAR_DELETE'); + ToolbarHelper::link('index.php?option=com_testquestions&view=questions', 'COM_TESTQUESTIONS', 'back'); + return parent::display($tpl); + } +} diff --git a/administrator/components/com_testquestions/src/View/Category/HtmlView.php b/administrator/components/com_testquestions/src/View/Category/HtmlView.php new file mode 100644 index 0000000..b85d8f6 --- /dev/null +++ b/administrator/components/com_testquestions/src/View/Category/HtmlView.php @@ -0,0 +1,27 @@ +form = $this->get('Form'); + $this->item = $this->get('Item'); + ToolbarHelper::save('category.save', 'JTOOLBAR_SAVE'); + ToolbarHelper::link( + 'index.php?option=com_testquestions&view=questions', + 'JTOOLBAR_CANCEL', + 'cancel' + ); + return parent::display($tpl); + } +} diff --git a/administrator/components/com_testquestions/src/View/Question/HtmlView.php b/administrator/components/com_testquestions/src/View/Question/HtmlView.php new file mode 100644 index 0000000..0503426 --- /dev/null +++ b/administrator/components/com_testquestions/src/View/Question/HtmlView.php @@ -0,0 +1,37 @@ +form = $this->get('Form'); + $this->item = $this->get('Item'); + $this->addToolbar(); + return parent::display($tpl); + } + + protected function addToolbar() + { + $this->getDocument()->setTitle($this->item->id + ? \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_EDIT_QUESTION') + : \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_NEW_QUESTION')); + ToolbarHelper::apply('question.apply', 'JTOOLBAR_APPLY'); + ToolbarHelper::save('question.save', 'JTOOLBAR_SAVE'); + ToolbarHelper::link( + 'index.php?option=com_testquestions&view=questions', + 'JTOOLBAR_CANCEL', + 'cancel' + ); + } +} diff --git a/administrator/components/com_testquestions/src/View/Questions/HtmlView.php b/administrator/components/com_testquestions/src/View/Questions/HtmlView.php new file mode 100644 index 0000000..3d25e59 --- /dev/null +++ b/administrator/components/com_testquestions/src/View/Questions/HtmlView.php @@ -0,0 +1,55 @@ +items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->state = $this->get('State'); + $this->categories = $this->get('Categories'); + + if (count($errors = $this->get('Errors'))) { + throw new \RuntimeException(implode("\n", $errors)); + } + + $this->addToolbar(); + return parent::display($tpl); + } + + protected function addToolbar() + { + $user = $this->getCurrentUser(); + $this->getDocument()->setTitle(Text::_('COM_TESTQUESTIONS')); + + if ($user->authorise('core.create', 'com_testquestions')) { + ToolbarHelper::addNew('question.add', 'JTOOLBAR_NEW'); + } + if ($user->authorise('core.edit', 'com_testquestions')) { + ToolbarHelper::editList('question.edit', 'JTOOLBAR_EDIT'); + } + if ($user->authorise('core.delete', 'com_testquestions')) { + ToolbarHelper::deleteList('', 'questions.delete', 'JTOOLBAR_DELETE'); + } + + ToolbarHelper::link( + 'index.php?option=com_testquestions&view=categories', + 'COM_TESTQUESTIONS_MANAGE_CATEGORIES', + 'list' + ); + } +} diff --git a/administrator/components/com_testquestions/tmpl/categories/default.php b/administrator/components/com_testquestions/tmpl/categories/default.php new file mode 100644 index 0000000..d034837 --- /dev/null +++ b/administrator/components/com_testquestions/tmpl/categories/default.php @@ -0,0 +1,24 @@ + +
+ + + + + + + + + items as $i => $item) : ?> + + + + + + + + +
id); ?>title, ENT_QUOTES, 'UTF-8'); ?>published ? Joomla\CMS\Language\Text::_('JPUBLISHED') : Joomla\CMS\Language\Text::_('JUNPUBLISHED'); ?>id; ?>
+ + + +
diff --git a/administrator/components/com_testquestions/tmpl/category/edit.php b/administrator/components/com_testquestions/tmpl/category/edit.php new file mode 100644 index 0000000..ed445f6 --- /dev/null +++ b/administrator/components/com_testquestions/tmpl/category/edit.php @@ -0,0 +1,13 @@ + +
+ form->renderField('title'); ?> + form->renderField('published'); ?> + form->renderField('id'); ?> +
+ + + +
+ + +
diff --git a/administrator/components/com_testquestions/tmpl/question/edit.php b/administrator/components/com_testquestions/tmpl/question/edit.php new file mode 100644 index 0000000..b502389 --- /dev/null +++ b/administrator/components/com_testquestions/tmpl/question/edit.php @@ -0,0 +1,33 @@ + +
+
+
+ form->renderField('question_type'); ?> + form->renderField('question'); ?> + form->renderField('answer_a'); ?> + form->renderField('answer_b'); ?> + form->renderField('answer_c'); ?> + form->renderField('answer_d'); ?> + form->renderField('correct_answer'); ?> + form->renderField('answer_open'); ?> +
+
+ form->renderField('category_id'); ?> + form->renderField('time_limit'); ?> + form->renderField('points'); ?> + form->renderField('image'); ?> + form->renderField('published'); ?> +
+
+ form->renderField('id'); ?> +
+ + + +
+ + +
diff --git a/administrator/components/com_testquestions/tmpl/questions/default.php b/administrator/components/com_testquestions/tmpl/questions/default.php new file mode 100644 index 0000000..dc07454 --- /dev/null +++ b/administrator/components/com_testquestions/tmpl/questions/default.php @@ -0,0 +1,73 @@ + +
+
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + items as $i => $item) : ?> + + + + + + + + + + +
id); ?> + + escape(strip_tags($item->question)); ?> + + image) : ?> + question_type)); ?>correct_answer)); ?>published, $i, 'questions.', true, 'cb'); ?>id; ?>
+ + pagination->getListFooter(); ?> + + + +
diff --git a/com_testquestions.xml b/com_testquestions.xml new file mode 100644 index 0000000..71da755 --- /dev/null +++ b/com_testquestions.xml @@ -0,0 +1,45 @@ + + + COM_TESTQUESTIONS + 2026-10-14 + Komponent + GNU General Public License version 2 or later + 1.3.2 + COM_TESTQUESTIONS_DESCRIPTION + com_testquestions + Komponent\Component\Testquestions + + src + tmpl + language + + + COM_TESTQUESTIONS + + forms + services + sql + src + tmpl + + + en-GB/com_testquestions.ini + en-GB/com_testquestions.sys.ini + + + + + sql/install.mysql.utf8mb4.sql + + + + + sql/uninstall.mysql.utf8mb4.sql + + + + + sql/updates/mysql + + + diff --git a/components/com_testquestions/language/en-GB/com_testquestions.ini b/components/com_testquestions/language/en-GB/com_testquestions.ini new file mode 100644 index 0000000..2132320 --- /dev/null +++ b/components/com_testquestions/language/en-GB/com_testquestions.ini @@ -0,0 +1,2 @@ +COM_TESTQUESTIONS="Pytania testowe" +COM_TESTQUESTIONS_ENTER_ANSWER="Wpisz odpowiedź" diff --git a/components/com_testquestions/src/Controller/DisplayController.php b/components/com_testquestions/src/Controller/DisplayController.php new file mode 100644 index 0000000..5800747 --- /dev/null +++ b/components/com_testquestions/src/Controller/DisplayController.php @@ -0,0 +1,12 @@ +get('DatabaseDriver'); + return $db->getQuery(true) + ->select(['question_type', 'question', 'answer_a', 'answer_b', 'answer_c', 'answer_d', 'answer_open', 'correct_answer', 'image']) + ->from($db->quoteName('#__testquestions_questions')) + ->where($db->quoteName('published') . ' = 1') + ->order($db->quoteName('ordering') . ' ASC, ' . $db->quoteName('id') . ' ASC'); + } +} diff --git a/components/com_testquestions/src/View/Questions/HtmlView.php b/components/com_testquestions/src/View/Questions/HtmlView.php new file mode 100644 index 0000000..32a1124 --- /dev/null +++ b/components/com_testquestions/src/View/Questions/HtmlView.php @@ -0,0 +1,18 @@ +items = $this->get('Items'); + return parent::display($tpl); + } +} diff --git a/components/com_testquestions/tmpl/questions/default.php b/components/com_testquestions/tmpl/questions/default.php new file mode 100644 index 0000000..968a692 --- /dev/null +++ b/components/com_testquestions/tmpl/questions/default.php @@ -0,0 +1,28 @@ + +
+

+ items as $number => $item) : ?> +
+

question; ?>

+ image) : ?> + + + question_type === 'open') : ?> + + + +
    +
  1. answer_a; ?>
  2. +
  3. answer_b; ?>
  4. +
  5. answer_c; ?>
  6. +
  7. answer_d; ?>
  8. +
+ +
+ +