Initial Joomla test questions component

This commit is contained in:
Wojtek
2026-10-14 22:29:10 +02:00
commit 9b093b278d
44 changed files with 964 additions and 0 deletions
+21
View File
@@ -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**.
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<form>
<fieldset name="details">
<field name="id" type="hidden" />
<field name="title" type="text" label="COM_TESTQUESTIONS_CATEGORY_TITLE" required="true" maxlength="150" />
<field name="published" type="list" label="JSTATUS" default="1">
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
</field>
</fieldset>
</form>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<form>
<fieldset name="details" label="COM_TESTQUESTIONS_FIELDSET_DETAILS">
<field name="id" type="hidden" />
<field name="category_id" type="sql" label="COM_TESTQUESTIONS_FIELD_CATEGORY" required="true"
query="SELECT id, title FROM #__testquestions_categories WHERE published = 1 ORDER BY ordering, title"
key_field="id" value_field="title" default="1" />
<field name="time_limit" type="list" label="COM_TESTQUESTIONS_FIELD_TIME_LIMIT" default="60" required="true">
<option value="30">30 s</option>
<option value="45">45 s</option>
<option value="60">60 s</option>
<option value="90">90 s</option>
</field>
<field name="points" type="list" label="COM_TESTQUESTIONS_FIELD_POINTS" default="1" required="true">
<option value="1">1</option><option value="2">2</option><option value="3">3</option>
<option value="4">4</option><option value="5">5</option><option value="6">6</option>
<option value="7">7</option><option value="8">8</option><option value="9">9</option>
</field>
<field name="question_type" type="list" label="COM_TESTQUESTIONS_FIELD_TYPE" default="closed" required="true">
<option value="closed">COM_TESTQUESTIONS_TYPE_CLOSED</option>
<option value="open">COM_TESTQUESTIONS_TYPE_OPEN</option>
</field>
<field name="question" type="editor" label="COM_TESTQUESTIONS_FIELD_QUESTION" description="COM_TESTQUESTIONS_FIELD_QUESTION_DESC" required="true" buttons="true" filter="safehtml" />
<field name="answer_a" type="editor" label="COM_TESTQUESTIONS_FIELD_ANSWER_A" required="false" buttons="true" filter="safehtml" showon="question_type:closed" />
<field name="answer_b" type="editor" label="COM_TESTQUESTIONS_FIELD_ANSWER_B" required="false" buttons="true" filter="safehtml" showon="question_type:closed" />
<field name="answer_c" type="editor" label="COM_TESTQUESTIONS_FIELD_ANSWER_C" required="false" buttons="true" filter="safehtml" showon="question_type:closed" />
<field name="answer_d" type="editor" label="COM_TESTQUESTIONS_FIELD_ANSWER_D" required="false" buttons="true" filter="safehtml" showon="question_type:closed" />
<field name="correct_answer" type="list" label="COM_TESTQUESTIONS_FIELD_CORRECT" required="false" showon="question_type:closed">
<option value="0">COM_TESTQUESTIONS_SELECT_CORRECT</option>
<option value="1">COM_TESTQUESTIONS_ANSWER_A</option>
<option value="2">COM_TESTQUESTIONS_ANSWER_B</option>
<option value="3">COM_TESTQUESTIONS_ANSWER_C</option>
<option value="4">COM_TESTQUESTIONS_ANSWER_D</option>
</field>
<field name="answer_open" type="textarea" label="COM_TESTQUESTIONS_FIELD_OPEN_PATTERN" description="COM_TESTQUESTIONS_FIELD_OPEN_PATTERN_DESC" required="false" rows="4" filter="string" showon="question_type:open" />
<field name="image" type="media" label="COM_TESTQUESTIONS_FIELD_IMAGE" description="COM_TESTQUESTIONS_FIELD_IMAGE_DESC" directory="images/testquestions" preview="tooltip" />
<field name="published" type="list" label="JSTATUS" default="1">
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
</field>
</fieldset>
</form>
@@ -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."
@@ -0,0 +1,2 @@
COM_TESTQUESTIONS="Pytania testowe"
COM_TESTQUESTIONS_DESCRIPTION="Gromadzenie pytań testowych z czterema odpowiedziami i opcjonalnym zdjęciem."
@@ -0,0 +1,27 @@
<?php
defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\Component;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory as ComponentDispatcherFactoryServiceProvider;
use Joomla\CMS\Extension\Service\Provider\MVCFactory as MVCFactoryServiceProvider;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->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));
}
);
}
};
@@ -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);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS `#__testquestions_questions`;
@@ -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;
@@ -0,0 +1 @@
-- Joomla 6 compatibility update: language strings now use Joomla CMS Text API.
@@ -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`;
@@ -0,0 +1 @@
-- Validation update for open and closed question forms.
@@ -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;
@@ -0,0 +1 @@
-- Controller update: cancel returns to the questions list.
@@ -0,0 +1 @@
-- Direct cancel link for the question editor.
@@ -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`;
@@ -0,0 +1 @@
-- Category cancel action returns to the questions list.
@@ -0,0 +1 @@
-- Category filter for the administrator question list.
@@ -0,0 +1,17 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\AdminController;
class CategoriesController extends AdminController
{
protected $text_prefix = 'COM_TESTQUESTIONS';
public function getModel($name = 'Category', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}
@@ -0,0 +1,13 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
class CategoryController extends FormController
{
protected $view_item = 'category';
protected $view_list = 'categories';
}
@@ -0,0 +1,12 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
class DisplayController extends BaseController
{
protected $default_view = 'questions';
}
@@ -0,0 +1,20 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
class QuestionController extends FormController
{
protected $view_item = 'question';
protected $view_list = 'questions';
public function cancel($key = null)
{
$this->setRedirect('index.php?option=com_testquestions&view=questions');
return true;
}
}
@@ -0,0 +1,17 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\AdminController;
class QuestionsController extends AdminController
{
protected $text_prefix = 'COM_TESTQUESTIONS';
public function getModel($name = 'Question', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}
@@ -0,0 +1,20 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ListModel;
class CategoriesModel extends ListModel
{
protected function getListQuery()
{
$db = Factory::getContainer()->get('DatabaseDriver');
return $db->getQuery(true)
->select('*')
->from($db->quoteName('#__testquestions_categories'))
->order($db->quoteName('ordering') . ' ASC, ' . $db->quoteName('title') . ' ASC');
}
}
@@ -0,0 +1,26 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\AdminModel;
class CategoryModel extends AdminModel
{
public function getTable($name = 'Category', $prefix = 'Administrator', $config = [])
{
return $this->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();
}
}
@@ -0,0 +1,50 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Model\AdminModel;
class QuestionModel extends AdminModel
{
public function getTable($name = 'Question', $prefix = 'Administrator', $config = [])
{
return $this->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;
}
}
@@ -0,0 +1,62 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ListModel;
class QuestionsModel extends ListModel
{
protected function populateState($ordering = 'ordering', $direction = 'asc')
{
parent::populateState($ordering, $direction);
$this->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();
}
}
@@ -0,0 +1,22 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Table;
defined('_JEXEC') or die;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
class CategoryTable extends Table
{
public function __construct(DatabaseDriver $db)
{
parent::__construct('#__testquestions_categories', 'id', $db);
}
public function check($updateNulls = false): bool
{
$this->title = trim((string) $this->title);
return $this->title !== '' && parent::check($updateNulls);
}
}
@@ -0,0 +1,60 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Table;
defined('_JEXEC') or die;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
class QuestionTable extends Table
{
public function __construct(DatabaseDriver $db)
{
parent::__construct('#__testquestions_questions', 'id', $db);
}
public function check($updateNulls = false): bool
{
$this->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);
}
}
@@ -0,0 +1,25 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\View\Categories;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected $items;
public function display($tpl = null)
{
$this->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);
}
}
@@ -0,0 +1,27 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\View\Category;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected $form;
protected $item;
public function display($tpl = null)
{
$this->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);
}
}
@@ -0,0 +1,37 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\View\Question;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected $form;
protected $item;
public function display($tpl = null)
{
$this->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'
);
}
}
@@ -0,0 +1,55 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\View\Questions;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
class HtmlView extends BaseHtmlView
{
protected $items;
protected $pagination;
protected $state;
protected $categories;
public function display($tpl = null)
{
$this->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'
);
}
}
@@ -0,0 +1,24 @@
<?php defined('_JEXEC') or die; ?>
<form action="index.php?option=com_testquestions&view=categories" method="post" name="adminForm" id="adminForm">
<table class="table table-striped">
<thead><tr>
<th><?php echo Joomla\CMS\HTML\HTMLHelper::_('grid.checkall'); ?></th>
<th><?php echo Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_CATEGORY_TITLE'); ?></th>
<th><?php echo Joomla\CMS\Language\Text::_('JSTATUS'); ?></th>
<th><?php echo Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_COLUMN_ID'); ?></th>
</tr></thead>
<tbody>
<?php foreach ($this->items as $i => $item) : ?>
<tr>
<td><?php echo Joomla\CMS\HTML\HTMLHelper::_('grid.id', $i, $item->id); ?></td>
<th><a href="index.php?option=com_testquestions&task=category.edit&id=<?php echo (int) $item->id; ?>"><?php echo htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8'); ?></a></th>
<td><?php echo $item->published ? Joomla\CMS\Language\Text::_('JPUBLISHED') : Joomla\CMS\Language\Text::_('JUNPUBLISHED'); ?></td>
<td><?php echo (int) $item->id; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<input type="hidden" name="boxchecked" value="0">
<input type="hidden" name="task" value="">
<?php echo Joomla\CMS\HTML\HTMLHelper::_('form.token'); ?>
</form>
@@ -0,0 +1,13 @@
<?php defined('_JEXEC') or die; ?>
<form action="index.php?option=com_testquestions&layout=edit&id=<?php echo (int) $this->item->id; ?>" method="post" name="adminForm" id="category-form">
<?php echo $this->form->renderField('title'); ?>
<?php echo $this->form->renderField('published'); ?>
<?php echo $this->form->renderField('id'); ?>
<div class="mt-3">
<a class="btn btn-secondary" href="<?php echo Joomla\CMS\Router\Route::_('index.php?option=com_testquestions&view=questions'); ?>">
<?php echo Joomla\CMS\Language\Text::_('JTOOLBAR_CANCEL'); ?>
</a>
</div>
<input type="hidden" name="task" value="category.save">
<?php echo Joomla\CMS\HTML\HTMLHelper::_('form.token'); ?>
</form>
@@ -0,0 +1,33 @@
<?php
defined('_JEXEC') or die;
?>
<form action="<?php echo Joomla\CMS\Router\Route::_('index.php?option=com_testquestions&layout=edit&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="question-form">
<div class="row">
<div class="col-lg-8">
<?php echo $this->form->renderField('question_type'); ?>
<?php echo $this->form->renderField('question'); ?>
<?php echo $this->form->renderField('answer_a'); ?>
<?php echo $this->form->renderField('answer_b'); ?>
<?php echo $this->form->renderField('answer_c'); ?>
<?php echo $this->form->renderField('answer_d'); ?>
<?php echo $this->form->renderField('correct_answer'); ?>
<?php echo $this->form->renderField('answer_open'); ?>
</div>
<div class="col-lg-4">
<?php echo $this->form->renderField('category_id'); ?>
<?php echo $this->form->renderField('time_limit'); ?>
<?php echo $this->form->renderField('points'); ?>
<?php echo $this->form->renderField('image'); ?>
<?php echo $this->form->renderField('published'); ?>
</div>
</div>
<?php echo $this->form->renderField('id'); ?>
<div class="mt-3">
<a class="btn btn-secondary" href="<?php echo Joomla\CMS\Router\Route::_('index.php?option=com_testquestions&view=questions'); ?>">
<?php echo Joomla\CMS\Language\Text::_('JTOOLBAR_CANCEL'); ?>
</a>
</div>
<input type="hidden" name="task" value="question.save">
<?php echo Joomla\CMS\HTML\HTMLHelper::_('form.token'); ?>
</form>
@@ -0,0 +1,73 @@
<?php
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
HTMLHelper::_('behavior.multiselect');
?>
<form action="<?php echo Route::_('index.php?option=com_testquestions&view=questions'); ?>" method="post" name="adminForm" id="adminForm">
<div class="row mb-3">
<div class="col-md-4">
<input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" class="form-control" placeholder="<?php echo $this->escape(\Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_SEARCH_PLACEHOLDER')); ?>">
</div>
<div class="col-md-2">
<select name="filter_published" class="form-select" onchange="this.form.submit();">
<option value=""><?php echo \Joomla\CMS\Language\Text::_('JOPTION_SELECT_PUBLISHED'); ?></option>
<option value="1" <?php echo $this->state->get('filter.published') === '1' ? 'selected' : ''; ?>><?php echo \Joomla\CMS\Language\Text::_('JPUBLISHED'); ?></option>
<option value="0" <?php echo $this->state->get('filter.published') === '0' ? 'selected' : ''; ?>><?php echo \Joomla\CMS\Language\Text::_('JUNPUBLISHED'); ?></option>
</select>
</div>
<div class="col-md-2">
<select name="filter_category_id" class="form-select" onchange="this.form.submit();">
<option value=""><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_FILTER_CATEGORY'); ?></option>
<?php foreach ($this->categories as $category) : ?>
<option value="<?php echo (int) $category->id; ?>" <?php echo (int) $this->state->get('filter.category_id') === (int) $category->id ? 'selected' : ''; ?>>
<?php echo $this->escape($category->title); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary"><?php echo \Joomla\CMS\Language\Text::_('JSEARCH_FILTER_SUBMIT'); ?></button>
<a href="<?php echo Route::_('index.php?option=com_testquestions&view=questions'); ?>" class="btn btn-secondary"><?php echo \Joomla\CMS\Language\Text::_('JSEARCH_FILTER_CLEAR'); ?></a>
</div>
</div>
<table class="table table-striped" id="questionList">
<caption class="visually-hidden"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS'); ?></caption>
<thead>
<tr>
<th scope="col" class="w-1"><?php echo HTMLHelper::_('grid.checkall'); ?></th>
<th scope="col"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_COLUMN_QUESTION'); ?></th>
<th scope="col" class="w-10"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_COLUMN_TYPE'); ?></th>
<th scope="col" class="w-10"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_COLUMN_CORRECT'); ?></th>
<th scope="col" class="w-10"><?php echo \Joomla\CMS\Language\Text::_('JSTATUS'); ?></th>
<th scope="col" class="w-10"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_COLUMN_ID'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) : ?>
<tr>
<td><?php echo HTMLHelper::_('grid.id', $i, $item->id); ?></td>
<th scope="row">
<a href="<?php echo Route::_('index.php?option=com_testquestions&task=question.edit&id=' . (int) $item->id); ?>">
<?php echo $this->escape(strip_tags($item->question)); ?>
</a>
<?php if ($item->image) : ?><span class="icon-image ms-2" title="<?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_HAS_IMAGE'); ?>"></span><?php endif; ?>
</th>
<td><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_TYPE_' . strtoupper($item->question_type)); ?></td>
<td><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_ANSWER_' . chr(64 + (int) $item->correct_answer)); ?></td>
<td><?php echo HTMLHelper::_('jgrid.published', $item->published, $i, 'questions.', true, 'cb'); ?></td>
<td><?php echo (int) $item->id; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php echo $this->pagination->getListFooter(); ?>
<input type="hidden" name="boxchecked" value="0">
<input type="hidden" name="task" value="">
<?php echo HTMLHelper::_('form.token'); ?>
</form>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" method="upgrade">
<name>COM_TESTQUESTIONS</name>
<creationDate>2026-10-14</creationDate>
<author>Komponent</author>
<license>GNU General Public License version 2 or later</license>
<version>1.3.2</version>
<description>COM_TESTQUESTIONS_DESCRIPTION</description>
<element>com_testquestions</element>
<namespace path="src">Komponent\Component\Testquestions</namespace>
<files folder="components/com_testquestions">
<folder>src</folder>
<folder>tmpl</folder>
<folder>language</folder>
</files>
<administration>
<menu link="index.php?option=com_testquestions&amp;view=questions">COM_TESTQUESTIONS</menu>
<files folder="administrator/components/com_testquestions">
<folder>forms</folder>
<folder>services</folder>
<folder>sql</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="administrator/components/com_testquestions/language">
<language tag="en-GB">en-GB/com_testquestions.ini</language>
<language tag="en-GB">en-GB/com_testquestions.sys.ini</language>
</languages>
</administration>
<install>
<sql>
<file driver="mysql" charset="utf8mb4">sql/install.mysql.utf8mb4.sql</file>
</sql>
</install>
<uninstall>
<sql>
<file driver="mysql" charset="utf8mb4">sql/uninstall.mysql.utf8mb4.sql</file>
</sql>
</uninstall>
<update>
<schemas>
<schemapath type="mysql">sql/updates/mysql</schemapath>
</schemas>
</update>
</extension>
@@ -0,0 +1,2 @@
COM_TESTQUESTIONS="Pytania testowe"
COM_TESTQUESTIONS_ENTER_ANSWER="Wpisz odpowiedź"
@@ -0,0 +1,12 @@
<?php
namespace Komponent\Component\Testquestions\Site\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
class DisplayController extends BaseController
{
protected $default_view = 'questions';
}
@@ -0,0 +1,21 @@
<?php
namespace Komponent\Component\Testquestions\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ListModel;
class QuestionsModel extends ListModel
{
protected function getListQuery()
{
$db = Factory::getContainer()->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');
}
}
@@ -0,0 +1,18 @@
<?php
namespace Komponent\Component\Testquestions\Site\View\Questions;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
class HtmlView extends BaseHtmlView
{
protected $items;
public function display($tpl = null)
{
$this->items = $this->get('Items');
return parent::display($tpl);
}
}
@@ -0,0 +1,28 @@
<?php
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
?>
<div class="com-testquestions">
<h1><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS'); ?></h1>
<?php foreach ($this->items as $number => $item) : ?>
<article class="testquestion mb-4">
<h2><?php echo ($number + 1) . '. '; ?><?php echo $item->question; ?></h2>
<?php if ($item->image) : ?>
<img src="<?php echo htmlspecialchars($item->image, ENT_QUOTES, 'UTF-8'); ?>" alt="" loading="lazy">
<?php endif; ?>
<?php if ($item->question_type === 'open') : ?>
<label for="answer-<?php echo (int) $number; ?>"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_ENTER_ANSWER'); ?></label>
<input type="text" id="answer-<?php echo (int) $number; ?>" name="answers[<?php echo (int) $number; ?>]" class="form-control">
<?php else : ?>
<ol type="A">
<li><?php echo $item->answer_a; ?></li>
<li><?php echo $item->answer_b; ?></li>
<li><?php echo $item->answer_c; ?></li>
<li><?php echo $item->answer_d; ?></li>
</ol>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>