Compare commits

..
87 Commits
Author SHA1 Message Date
Wojtek ba9e236e83 Randomize test answers consistently per attempt 2026-10-16 21:27:22 +02:00
Wojtek 8b28a5fd44 Show images in saved test details 2026-10-16 21:23:43 +02:00
Wojtek c0c70396a5 Use question times when test duration is unset 2026-10-16 21:17:33 +02:00
Wojtek 344c1b42a2 Add bulk selection for filtered set questions 2026-10-16 21:08:28 +02:00
Wojtek 9af456989a Add question ID filter to set editor 2026-10-16 20:54:05 +02:00
Wojtek 887b27f30b Add preview toolbar navigation for sets 2026-10-16 20:50:54 +02:00
Wojtek c9076d4a22 Add read-only preview for question sets 2026-10-16 20:48:32 +02:00
Wojtek 606612784c Add read-only test question preview 2026-10-16 20:44:44 +02:00
Wojtek ef5410689d Resolve exam images relative to Joomla root 2026-10-16 20:38:15 +02:00
Wojtek b774f0d94d Sync variant publication with parent set 2026-10-16 19:55:00 +02:00
Wojtek a788c8c00e Add action to unpublish all test sets 2026-10-16 19:52:09 +02:00
Wojtek bf05c9515c Split AI token counters by provider 2026-10-16 19:48:45 +02:00
Wojtek 08220dc6e8 Add aggregate AI tokens and move column 2026-10-16 19:46:10 +02:00
Wojtek 42b5006574 Show AI token usage per saved test 2026-10-16 19:43:15 +02:00
Wojtek 8693720b78 Replace AI balance with usage counters 2026-10-16 19:36:42 +02:00
Wojtek 37d8c6040c Show remaining API credits in AI settings 2026-10-16 15:12:33 +02:00
Wojtek 7ed00f95e6 Add GPT-6 Luna model option 2026-10-16 13:22:18 +02:00
Wojtek 57065d4ca1 Add AI model selection per provider 2026-10-16 13:16:35 +02:00
Wojtek 7d9efd64f4 Show actual AI token usage and cost 2026-10-16 13:08:59 +02:00
Wojtek 6daca5be59 Configure separate OpenAI and Grok keys 2026-10-16 13:04:34 +02:00
Wojtek dc6b5ad3e8 Hide correction fields for closed questions 2026-10-16 12:47:24 +02:00
Wojtek aa12305e65 Award partial points from AI assessment 2026-10-16 12:43:22 +02:00
Wojtek af98d0d1d4 Evaluate all failed open answers with AI 2026-10-16 12:39:53 +02:00
Wojtek acfdf21488 Save AI settings through component page 2026-10-16 12:31:50 +02:00
Wojtek 3efb937469 Use Joomla password field for AI key 2026-10-16 12:28:50 +02:00
Wojtek bb1dc728d1 Diagnose empty AI API key configuration 2026-10-16 12:27:23 +02:00
Wojtek ed2c6b663b Normalize AI provider validation 2026-10-16 12:22:38 +02:00
Wojtek 03728ce49f Add AI key visibility toggle 2026-10-16 12:20:49 +02:00
Wojtek a7637aedcc Widen AI API key field 2026-10-16 12:17:12 +02:00
Wojtek 7f4f86080a Prefer stored AI configuration values 2026-10-16 12:14:39 +02:00
Wojtek 9e940c5710 Read saved AI component parameters 2026-10-16 12:07:29 +02:00
Wojtek 62befb825e Fix component AI configuration form 2026-10-16 11:56:56 +02:00
Wojtek 804c8f5467 Add AI evaluation for incorrect open answers 2026-10-16 11:53:23 +02:00
Wojtek 53dd9658e8 Order multi-variant questions without frontend randomization 2026-10-16 11:33:58 +02:00
Wojtek 5e12e8838d Add ordinary set ordering and random frontend questions 2026-10-16 11:27:37 +02:00
Wojtek 96e8a4a0f5 Inherit duration from parent set 2026-10-16 11:15:13 +02:00
Wojtek 3ee0b22d75 Remove apply button from set form 2026-10-16 10:35:23 +02:00
Wojtek 59335cbc52 Use explicit set save toolbar actions 2026-10-16 10:32:20 +02:00
Wojtek 5abf29cebb Fix variant save and close redirect 2026-10-16 10:29:52 +02:00
Wojtek defdfcb32f Return save and close from variant to parent 2026-10-16 10:28:31 +02:00
Wojtek 0f1bd3e9fe Return variant exit to parent set 2026-10-16 10:26:37 +02:00
Wojtek 8abee59a49 Remove questions from parent multi-variant sets 2026-10-16 10:21:52 +02:00
Wojtek 2e3bbe6fc6 Hide variants from main set list 2026-10-16 10:20:00 +02:00
Wojtek 8cc1862178 Add multi-variant test sets 2026-10-16 10:12:26 +02:00
Wojtek 6ff21ef245 Calculate grade from corrected percentage 2026-10-15 21:49:12 +02:00
Wojtek 8049b63f58 Stack result filters vertically 2026-10-15 21:46:19 +02:00
Wojtek 33783399f9 Use all sets in result filter 2026-10-15 21:43:24 +02:00
Wojtek 3fd488e19e Add result list date and test filters 2026-10-15 21:41:34 +02:00
Wojtek 301f322017 Show percentages for original and corrected points 2026-10-15 21:39:34 +02:00
Wojtek 987373b565 Set result toolbar labels 2026-10-15 21:37:06 +02:00
Wojtek 96bdb73450 Fix save and close toolbar button 2026-10-15 21:34:41 +02:00
Wojtek eac29077fc Separate save and save-and-close actions 2026-10-15 21:33:19 +02:00
Wojtek 8446e4a84a Move result save action to toolbar 2026-10-15 21:32:10 +02:00
Wojtek 1d0b6d7798 Move result save button to top 2026-10-15 21:29:31 +02:00
Wojtek 6c23252eef Remove correct answer detail label 2026-10-15 21:27:58 +02:00
Wojtek 1e14949574 Show maximum awarded points 2026-10-15 21:24:51 +02:00
Wojtek 5e259cd5c7 Hide correction fields for correct answers 2026-10-15 21:21:38 +02:00
Wojtek 5721370ea1 Include correction points in final score 2026-10-15 21:15:00 +02:00
Wojtek 1df6c6dfc3 Track corrected result points 2026-10-15 21:11:30 +02:00
Wojtek f3efb81af4 Add per-question result assessment fields 2026-10-15 21:07:44 +02:00
Wojtek 2af8f7c878 Highlight extended-time participant row 2026-10-15 21:04:19 +02:00
Wojtek dfc38e7eb0 Track test execution time 2026-10-15 20:52:15 +02:00
Wojtek 06d6cc2027 Highlight extended-time results 2026-10-15 20:49:45 +02:00
Wojtek e52d31a783 Add saved test result preview 2026-10-15 20:45:08 +02:00
Wojtek ceb6a6c6ef Persist test results 2026-10-15 20:43:52 +02:00
Wojtek 3eabeebf6b Warn with timer colors near timeout 2026-10-15 19:50:39 +02:00
Wojtek 83e70dd02f Refine fixed timer spacing and frame 2026-10-15 19:43:35 +02:00
Wojtek c9b39f795a Fix test timer to viewport corner 2026-10-15 19:41:31 +02:00
Wojtek 84aa7e637b Add automatic test countdown timer 2026-10-15 18:37:32 +02:00
Wojtek fe64990b1b Label submitted open answer 2026-10-15 18:26:11 +02:00
Wojtek 41ec027e42 Hide test controls after completion 2026-10-15 18:22:33 +02:00
Wojtek 463160c0c7 Add two-step set test start form 2026-10-15 18:12:53 +02:00
Wojtek 54c4c56469 Match set frontend to exam view 2026-10-15 18:05:11 +02:00
Wojtek b2ea519ff5 Add frontend test view for question sets 2026-10-15 18:01:09 +02:00
Wojtek c50e79164a Remove correct answer column entirely 2026-10-15 17:55:28 +02:00
Wojtek 68650492d3 Remove question type column 2026-10-15 17:51:04 +02:00
Wojtek 32cae2ffc3 Hide redundant correct answer column 2026-10-15 17:49:18 +02:00
Wojtek 03a9ea64ed Align answer preview and mark correct options 2026-10-15 17:46:40 +02:00
Wojtek 53d39ada2c Use minute slider for test duration 2026-10-15 17:36:32 +02:00
Wojtek 0c3ae353a5 Use seconds slider for test duration 2026-10-15 17:32:33 +02:00
Wojtek 197a03a2d0 Place correctness marker after answer 2026-10-15 17:31:00 +02:00
Wojtek 18574741be Keep correct answer marker inline 2026-10-15 17:28:55 +02:00
Wojtek baa00d37be Adjust test content editor and answer preview 2026-10-15 17:26:39 +02:00
Wojtek 4492097b5a Render test set fields in form 2026-10-15 17:24:26 +02:00
Wojtek bb96cb3116 Add test duration content and image to sets 2026-10-15 17:21:30 +02:00
Wojtek 70f9a0ac4b Separate selected and available set questions 2026-10-15 17:14:58 +02:00
Wojtek e762b207bb Add subject filter to question sets 2026-10-15 17:11:06 +02:00
130 changed files with 2859 additions and 43 deletions
+8 -1
View File
@@ -11,6 +11,12 @@ Szkielet komponentu Joomla 6 do gromadzenia pytań testowych.
- filtrowanie listy pytań po kategorii, - filtrowanie listy pytań po kategorii,
- przełącznik widoku listy: same pytania albo pytania z odpowiedziami, - przełącznik widoku listy: same pytania albo pytania z odpowiedziami,
- frontendowy widok pytań filtrowany po symbolu arkusza egzaminacyjnego, - frontendowy widok pytań filtrowany po symbolu arkusza egzaminacyjnego,
- frontendowy widok testu na podstawie wybranego zestawu pytań,
- dwuetapowe rozpoczęcie testu z loginem użytkownika i możliwością przedłużenia czasu,
- zegar odliczający czas testu i automatyczne zakończenie po jego upływie,
- zapisywanie wyników testów i zaznaczonych odpowiedzi w bazie danych,
- podgląd zapisanych testów w panelu administratora,
- zapisywanie i wyświetlanie czasu wykonania testu,
- pozycję menu „Arkusz egzaminacyjny” z opcjonalnym symbolem arkusza, - pozycję menu „Arkusz egzaminacyjny” z opcjonalnym symbolem arkusza,
- listę wyboru dostępnych symboli arkuszy na stronie klienckiej, - listę wyboru dostępnych symboli arkuszy na stronie klienckiej,
- możliwość rozwiązania arkusza i wyświetlenia wyniku oraz odpowiedzi poprawnych i zaznaczonych, - możliwość rozwiązania arkusza i wyświetlenia wyniku oraz odpowiedzi poprawnych i zaznaczonych,
@@ -19,10 +25,11 @@ Szkielet komponentu Joomla 6 do gromadzenia pytań testowych.
- opcjonalne zdjęcie wybierane z Media Managera, - opcjonalne zdjęcie wybierane z Media Managera,
- publikowanie, filtrowanie i usuwanie pytań w panelu administratora, - publikowanie, filtrowanie i usuwanie pytań w panelu administratora,
- tworzenie zestawów pytań w panelu administratora i przypisywanie do nich pytań, - tworzenie zestawów pytań w panelu administratora i przypisywanie do nich pytań,
- czas trwania w minutach (suwak), treść i obraz zestawu testowego,
- tabela `#__testquestions_questions` tworzona podczas instalacji. - tabela `#__testquestions_questions` tworzona podczas instalacji.
## Instalacja ## Instalacja
W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.10.5). W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.43.22).
Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**. Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**.
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<config>
<fields name="params">
<fieldset name="basic" label="COM_TESTQUESTIONS_AI_SETTINGS">
<field name="ai_provider" type="list" label="COM_TESTQUESTIONS_AI_PROVIDER">
<option value="openai">OpenAI</option>
<option value="grok">Grok</option>
</field>
<field name="ai_api_key" type="aiapikey" addfieldprefix="Komponent\Component\Testquestions\Administrator\Field" label="COM_TESTQUESTIONS_AI_API_KEY" filter="raw" autocomplete="off" size="120" class="w-100" />
</fieldset>
</fields>
</config>
@@ -2,10 +2,19 @@
<form> <form>
<fieldset name="details"> <fieldset name="details">
<field name="id" type="hidden" /> <field name="id" type="hidden" />
<field name="parent_id" type="hidden" default="0" />
<field name="title" type="text" label="COM_TESTQUESTIONS_SET_TITLE" required="true" maxlength="255" /> <field name="title" type="text" label="COM_TESTQUESTIONS_SET_TITLE" required="true" maxlength="255" />
<field name="set_type" type="list" label="COM_TESTQUESTIONS_SET_TYPE" default="ordinary" required="true">
<option value="ordinary">COM_TESTQUESTIONS_SET_TYPE_ORDINARY</option>
<option value="multi">COM_TESTQUESTIONS_SET_TYPE_MULTI</option>
<option value="variant">COM_TESTQUESTIONS_SET_TYPE_VARIANT</option>
</field>
<field name="subject_id" type="sql" label="COM_TESTQUESTIONS_SUBJECT" required="true" query="SELECT id, title FROM #__testquestions_subjects WHERE published = 1 ORDER BY title ASC" key_field="id" value_field="title" translate_query="false"> <field name="subject_id" type="sql" label="COM_TESTQUESTIONS_SUBJECT" required="true" query="SELECT id, title FROM #__testquestions_subjects WHERE published = 1 ORDER BY title ASC" key_field="id" value_field="title" translate_query="false">
<option value="0">COM_TESTQUESTIONS_SELECT_SUBJECT</option> <option value="0">COM_TESTQUESTIONS_SELECT_SUBJECT</option>
</field> </field>
<field name="duration" type="range" label="COM_TESTQUESTIONS_SET_DURATION" description="COM_TESTQUESTIONS_SET_DURATION_DESC" min="0" max="120" step="1" default="0" filter="integer" />
<field name="test_content" type="editor" label="COM_TESTQUESTIONS_SET_TEST_CONTENT" buttons="true" filter="safehtml" height="150" />
<field name="image" type="media" label="COM_TESTQUESTIONS_SET_IMAGE" directory="images/testquestions" preview="tooltip" />
<field name="description" type="textarea" label="COM_TESTQUESTIONS_SET_DESCRIPTION" rows="4" filter="raw" /> <field name="description" type="textarea" label="COM_TESTQUESTIONS_SET_DESCRIPTION" rows="4" filter="raw" />
<field name="published" type="list" label="JSTATUS" default="1"> <field name="published" type="list" label="JSTATUS" default="1">
<option value="1">JPUBLISHED</option> <option value="1">JPUBLISHED</option>
@@ -46,20 +46,109 @@ COM_TESTQUESTIONS_ERROR_OPEN_PATTERN="Dla pytania otwartego wpisz wzorzec popraw
COM_TESTQUESTIONS_ERROR_ALL_ANSWERS="Dla pytania zamkniętego uzupełnij wszystkie cztery 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." COM_TESTQUESTIONS_ERROR_META="Wybierz poprawny czas i liczbę punktów."
COM_TESTQUESTIONS_SETS="Zestawy pytań" COM_TESTQUESTIONS_SETS="Zestawy pytań"
COM_TESTQUESTIONS_SETS_UNPUBLISH_ALL="Wyłącz wszystkie zestawy"
COM_TESTQUESTIONS_SETS_UNPUBLISH_ALL_CONFIRM="Czy na pewno wyłączyć wszystkie zestawy testów, w tym wszystkie warianty?"
COM_TESTQUESTIONS_SETS_UNPUBLISHED_ALL="Wyłączono %s zestawów."
COM_TESTQUESTIONS_NEW_SET="Nowy zestaw pytań" COM_TESTQUESTIONS_NEW_SET="Nowy zestaw pytań"
COM_TESTQUESTIONS_EDIT_SET="Edytuj zestaw pytań" COM_TESTQUESTIONS_EDIT_SET="Edytuj zestaw pytań"
COM_TESTQUESTIONS_SET_TITLE="Nazwa zestawu" 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_MULTI_QUESTIONS_INFO="Pytania są dodawane w poszczególnych wariantach zestawu."
COM_TESTQUESTIONS_SET_REORDER_INFO="Przeciągnij pytania, aby zmienić ich kolejność."
COM_TESTQUESTIONS_SET_SAVE="Zapisz"
COM_TESTQUESTIONS_SET_SAVE_AND_CLOSE="Zapisz i zamknij"
COM_TESTQUESTIONS_SET_VARIANT_DURATION_INFO="Czas testu jest pobierany z zestawu nadrzędnego."
COM_TESTQUESTIONS_SET_VARIANT_STATUS_INHERITED="Status publikacji wariantu jest taki sam jak status zestawu nadrzędnego i zmienia się razem z nim."
COM_TESTQUESTIONS_AI_SETTINGS="Ustawienia AI"
COM_TESTQUESTIONS_AI_PROVIDER="Dostawca AI"
COM_TESTQUESTIONS_AI_API_KEY="Klucz API"
COM_TESTQUESTIONS_AI_OPENAI_KEY="Klucz API OpenAI"
COM_TESTQUESTIONS_AI_GROK_KEY="Klucz API Grok"
COM_TESTQUESTIONS_AI_OPENAI_MODEL="Model OpenAI"
COM_TESTQUESTIONS_AI_GROK_MODEL="Model Grok"
COM_TESTQUESTIONS_AI_MODEL_INVALID="Wybierz prawidłowy model dla każdego dostawcy AI."
COM_TESTQUESTIONS_AI_TOKEN_USAGE="Wykorzystanie tokenów API"
COM_TESTQUESTIONS_AI_TOKEN_USAGE_INFO="Suma użycia zapisanego przy ocenach AI we wszystkich wynikach testów. Powtórne oceny są zliczane, jeśli zostały wykonane po tej aktualizacji."
COM_TESTQUESTIONS_AI_USAGE_CALLS="Liczba ocen"
COM_TESTQUESTIONS_AI_USAGE_INPUT="Tokeny wejściowe"
COM_TESTQUESTIONS_AI_USAGE_OUTPUT="Tokeny wyjściowe"
COM_TESTQUESTIONS_AI_USAGE_TOTAL="Razem tokenów"
COM_TESTQUESTIONS_AI_USAGE_COST="Koszt zapisany"
COM_TESTQUESTIONS_PREVIEW_SELECTED="Podgląd zaznaczonych"
COM_TESTQUESTIONS_PREVIEW_TITLE="Podgląd pytań testowych"
COM_TESTQUESTIONS_PREVIEW_READONLY="Tryb podglądu — odpowiedzi są nieaktywne, test nie ma limitu czasu ani przycisku zakończenia."
COM_TESTQUESTIONS_PREVIEW_EMPTY="Nie znaleziono pytań do wyświetlenia."
COM_TESTQUESTIONS_PREVIEW_SELECT_QUESTIONS="Zaznacz pytania, które chcesz podejrzeć."
COM_TESTQUESTIONS_PREVIEW_BACK="Wróć do listy pytań"
COM_TESTQUESTIONS_PREVIEW_SET="Podgląd zestawu"
COM_TESTQUESTIONS_AI_SETTINGS_MENU="Konfiguracja AI"
COM_TESTQUESTIONS_AI_SETTINGS_SAVED="Ustawienia AI zapisano."
COM_TESTQUESTIONS_AI_SAVE_FAILED="Nie udało się zapisać ustawień AI."
COM_TESTQUESTIONS_AI_PROVIDER_INVALID="Wybierz prawidłowego dostawcę AI."
COM_TESTQUESTIONS_AI_EVALUATE_ALL="AI — oceń wszystkie niezaliczone pytania otwarte (%s)"
COM_TESTQUESTIONS_AI_BULK_DONE="AI oceniło %s pytań."
COM_TESTQUESTIONS_AI_BULK_PARTIAL="AI oceniło %s pytań; nie udało się ocenić %s. Błąd: %s"
COM_TESTQUESTIONS_AI_NO_OPEN_QUESTIONS="Brak niezaliczonych pytań otwartych do oceny AI."
COM_TESTQUESTIONS_AI_EVALUATE="AI"
COM_TESTQUESTIONS_AI_RESULT="Ocena AI"
COM_TESTQUESTIONS_AI_NOT_CONFIGURED="Skonfiguruj dostawcę i klucz API w ustawieniach komponentu."
COM_TESTQUESTIONS_AI_ERROR="Nie udało się uzyskać odpowiedzi AI: %s"
COM_TESTQUESTIONS_SET_DESCRIPTION="Opis zestawu" COM_TESTQUESTIONS_SET_DESCRIPTION="Opis zestawu"
COM_TESTQUESTIONS_SET_QUESTIONS="Pytania w zestawie" COM_TESTQUESTIONS_SET_QUESTIONS="Pytania w zestawie"
COM_TESTQUESTIONS_SET_QUESTION_COUNT="Liczba pytań" COM_TESTQUESTIONS_SET_QUESTION_COUNT="Liczba pytań"
COM_TESTQUESTIONS_SET_SEARCH="Szukaj zestawów..." COM_TESTQUESTIONS_SET_SEARCH="Szukaj zestawów..."
COM_TESTQUESTIONS_SET_CATEGORY_FILTER="Filtruj pytania według kategorii" COM_TESTQUESTIONS_SET_CATEGORY_FILTER="Filtruj pytania według kategorii"
COM_TESTQUESTIONS_SET_CHOOSE_CATEGORY="Wybierz kategorię"
COM_TESTQUESTIONS_SET_ID_FILTER="Filtruj pytania według ID"
COM_TESTQUESTIONS_SET_ID_FILTER_HINT="Wpisz ID, np. 12, 34, 56"
COM_TESTQUESTIONS_SET_SELECT_ALL_VISIBLE="Zaznacz wszystkie widoczne pytania"
COM_TESTQUESTIONS_SET_ALL_CATEGORIES="Wszystkie kategorie" COM_TESTQUESTIONS_SET_ALL_CATEGORIES="Wszystkie kategorie"
COM_TESTQUESTIONS_UNCATEGORIZED="Bez kategorii" COM_TESTQUESTIONS_UNCATEGORIZED="Bez kategorii"
COM_TESTQUESTIONS_SET_SHOW_DETAILS="Pokaż odpowiedzi" COM_TESTQUESTIONS_SET_SHOW_DETAILS="Pokaż odpowiedzi"
COM_TESTQUESTIONS_SUBJECTS="Przedmioty" COM_TESTQUESTIONS_SUBJECTS="Przedmioty"
COM_TESTQUESTIONS_SET_DURATION="Czas trwania testu (minuty)"
COM_TESTQUESTIONS_SET_DURATION_DESC="Ustaw czas trwania testu w minutach za pomocą suwaka."
COM_TESTQUESTIONS_SET_TEST_CONTENT="Treść testu"
COM_TESTQUESTIONS_SET_IMAGE="Obraz testu"
COM_TESTQUESTIONS_RESULTS="Zapisane testy"
COM_TESTQUESTIONS_RESULT_PREVIEW="Podgląd zapisanego testu"
COM_TESTQUESTIONS_RESULT_NOT_FOUND="Nie znaleziono zapisanego testu."
COM_TESTQUESTIONS_RESULT_PARTICIPANT="Użytkownik"
COM_TESTQUESTIONS_RESULT_AI_TOKENS="Tokeny AI"
COM_TESTQUESTIONS_RESULTS_AI_TOKENS_TOTAL="Łączne wykorzystanie tokenów w wynikach"
COM_TESTQUESTIONS_RESULT_SCORE="Wynik"
COM_TESTQUESTIONS_RESULT_POINTS_BEFORE_AFTER="Punkty przed / po korekcie"
COM_TESTQUESTIONS_RESULT_GRADE="Ocena"
COM_TESTQUESTIONS_RESULT_DATE="Data zakończenia"
COM_TESTQUESTIONS_RESULT_ANSWERS="Odpowiedzi"
COM_TESTQUESTIONS_RESULT_EXECUTION_TIME="Czas wykonania"
COM_TESTQUESTIONS_RESULT_AWARDED_POINTS="Uznane punkty (maksymalnie %s)"
COM_TESTQUESTIONS_RESULT_COMMENT="Komentarz"
COM_TESTQUESTIONS_RESULT_SAVED="Dane wyniku zostały zapisane."
COM_TESTQUESTIONS_RESULT_SAVE="Zapisz"
COM_TESTQUESTIONS_RESULT_SAVE_AND_CLOSE="Zapisz i zamknij"
COM_TESTQUESTIONS_RESULTS_SEARCH="Szukaj po użytkowniku lub zestawie..."
COM_TESTQUESTIONS_RESULTS_TEST_NAME="Nazwa testu"
COM_TESTQUESTIONS_RESULTS_ALL_TESTS="Wszystkie zestawy"
COM_TESTQUESTIONS_RESULTS_DATE_FROM="Data od"
COM_TESTQUESTIONS_RESULTS_DATE_TO="Data do"
COM_TESTQUESTIONS_YOUR_ANSWER="Twoja odpowiedź"
COM_TESTQUESTIONS_SELECTED_ANSWER="Zaznaczona odpowiedź"
COM_TESTQUESTIONS_ANSWER_CORRECT="Poprawna odpowiedź"
COM_TESTQUESTIONS_ANSWER_INCORRECT="Niepoprawna odpowiedź"
COM_TESTQUESTIONS_CORRECT_PATTERN="Wzorzec poprawnej odpowiedzi"
COM_TESTQUESTIONS_SUBJECT="Przedmiot" COM_TESTQUESTIONS_SUBJECT="Przedmiot"
COM_TESTQUESTIONS_SUBJECT_TITLE="Nazwa przedmiotu" COM_TESTQUESTIONS_SUBJECT_TITLE="Nazwa przedmiotu"
COM_TESTQUESTIONS_NEW_SUBJECT="Nowy przedmiot" COM_TESTQUESTIONS_NEW_SUBJECT="Nowy przedmiot"
COM_TESTQUESTIONS_EDIT_SUBJECT="Edytuj przedmiot" COM_TESTQUESTIONS_EDIT_SUBJECT="Edytuj przedmiot"
COM_TESTQUESTIONS_SELECT_SUBJECT="-- wybierz przedmiot --" COM_TESTQUESTIONS_SELECT_SUBJECT="-- wybierz przedmiot --"
COM_TESTQUESTIONS_FILTER_SUBJECT="Wszystkie przedmioty"
COM_TESTQUESTIONS_SET_SELECTED_QUESTIONS="Wybrane pytania"
COM_TESTQUESTIONS_SET_AVAILABLE_QUESTIONS="Pytania do wyboru"
@@ -40,8 +40,13 @@ VALUES (1, 'Bez kategorii', 1, 0);
CREATE TABLE IF NOT EXISTS `#__testquestions_sets` ( CREATE TABLE IF NOT EXISTS `#__testquestions_sets` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `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, `subject_id` int unsigned NOT NULL DEFAULT 0,
`title` varchar(255) NOT NULL, `title` varchar(255) NOT NULL,
`duration` smallint unsigned NOT NULL DEFAULT 0,
`test_content` text NULL,
`image` varchar(1024) NOT NULL DEFAULT '',
`description` text NULL, `description` text NULL,
`published` tinyint NOT NULL DEFAULT 1, `published` tinyint NOT NULL DEFAULT 1,
`ordering` int NOT NULL DEFAULT 0, `ordering` int NOT NULL DEFAULT 0,
@@ -51,6 +56,7 @@ CREATE TABLE IF NOT EXISTS `#__testquestions_sets` (
`modified_by` int unsigned NOT NULL DEFAULT 0, `modified_by` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `idx_published` (`published`), KEY `idx_published` (`published`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_ordering` (`ordering`) KEY `idx_ordering` (`ordering`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
@@ -70,3 +76,24 @@ CREATE TABLE IF NOT EXISTS `#__testquestions_set_questions` (
PRIMARY KEY (`set_id`, `question_id`), PRIMARY KEY (`set_id`, `question_id`),
KEY `idx_question_id` (`question_id`) KEY `idx_question_id` (`question_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__testquestions_results` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`set_id` int unsigned NOT NULL,
`participant_name` varchar(255) NOT NULL DEFAULT '',
`started` datetime NULL DEFAULT NULL,
`execution_time` int unsigned NOT NULL DEFAULT 0,
`extra_time` smallint unsigned NOT NULL DEFAULT 0,
`correct_count` smallint unsigned NOT NULL DEFAULT 0,
`total_count` smallint unsigned NOT NULL DEFAULT 0,
`percentage` decimal(5,2) NOT NULL DEFAULT 0.00,
`points_total` int unsigned NOT NULL DEFAULT 0,
`points_before` int unsigned NOT NULL DEFAULT 0,
`points_after` int unsigned NOT NULL DEFAULT 0,
`answers` longtext NULL,
`created` datetime NOT NULL,
`created_by` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_set_id` (`set_id`),
KEY `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
@@ -1,4 +1,5 @@
DROP TABLE IF EXISTS `#__testquestions_set_questions`; DROP TABLE IF EXISTS `#__testquestions_set_questions`;
DROP TABLE IF EXISTS `#__testquestions_sets`; DROP TABLE IF EXISTS `#__testquestions_sets`;
DROP TABLE IF EXISTS `#__testquestions_subjects`; DROP TABLE IF EXISTS `#__testquestions_subjects`;
DROP TABLE IF EXISTS `#__testquestions_results`;
DROP TABLE IF EXISTS `#__testquestions_questions`; DROP TABLE IF EXISTS `#__testquestions_questions`;
@@ -0,0 +1 @@
-- No database changes required for version 1.10.6.
@@ -0,0 +1 @@
-- No database changes required for version 1.11.0.
@@ -0,0 +1,4 @@
ALTER TABLE `#__testquestions_sets`
ADD COLUMN `duration` smallint unsigned NOT NULL DEFAULT 0 AFTER `title`,
ADD COLUMN `test_content` text NULL AFTER `duration`,
ADD COLUMN `image` varchar(1024) NOT NULL DEFAULT '' AFTER `test_content`;
@@ -0,0 +1 @@
-- No database changes required for version 1.12.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.12.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.12.3.
@@ -0,0 +1 @@
-- No database changes required for version 1.12.4.
@@ -0,0 +1,3 @@
UPDATE `#__testquestions_sets`
SET `duration` = `duration` * 60
WHERE `duration` > 0;
@@ -0,0 +1,3 @@
UPDATE `#__testquestions_sets`
SET `duration` = ROUND(`duration` / 60)
WHERE `duration` > 0;
@@ -0,0 +1 @@
-- No database changes required for version 1.13.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.13.3.
@@ -0,0 +1 @@
-- No database changes required for version 1.13.4.
@@ -0,0 +1 @@
-- No database changes required for version 1.13.5.
@@ -0,0 +1 @@
-- No database changes required for version 1.14.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.14.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.15.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.15.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.15.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.16.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.16.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.16.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.16.3.
@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `#__testquestions_results` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`set_id` int unsigned NOT NULL,
`participant_name` varchar(255) NOT NULL DEFAULT '',
`extra_time` smallint unsigned NOT NULL DEFAULT 0,
`correct_count` smallint unsigned NOT NULL DEFAULT 0,
`total_count` smallint unsigned NOT NULL DEFAULT 0,
`percentage` decimal(5,2) NOT NULL DEFAULT 0.00,
`answers` longtext NULL,
`created` datetime NOT NULL,
`created_by` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_set_id` (`set_id`),
KEY `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1 @@
-- No database changes required for version 1.18.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.18.1.
@@ -0,0 +1,3 @@
ALTER TABLE `#__testquestions_results`
ADD COLUMN `started` datetime NULL DEFAULT NULL AFTER `participant_name`,
ADD COLUMN `execution_time` int unsigned NOT NULL DEFAULT 0 AFTER `started`;
@@ -0,0 +1 @@
-- No database changes required for version 1.19.1.
@@ -0,0 +1 @@
-- Per-question awarded points and comments are stored in the existing answers JSON column.
@@ -0,0 +1,8 @@
ALTER TABLE `#__testquestions_results`
ADD COLUMN `points_before` int unsigned NOT NULL DEFAULT 0 AFTER `percentage`,
ADD COLUMN `points_after` int unsigned NOT NULL DEFAULT 0 AFTER `points_before`;
UPDATE `#__testquestions_results`
SET `points_before` = `correct_count`,
`points_after` = `correct_count`
WHERE `points_before` = 0 AND `points_after` = 0;
@@ -0,0 +1 @@
-- Corrected points are calculated as automatic points plus manually awarded correction points.
@@ -0,0 +1 @@
-- No database changes required for version 1.23.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.24.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.25.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.26.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.27.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.28.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.28.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.28.2.
@@ -0,0 +1,6 @@
ALTER TABLE `#__testquestions_results`
ADD COLUMN `points_total` int unsigned NOT NULL DEFAULT 0 AFTER `percentage`;
UPDATE `#__testquestions_results`
SET `points_total` = `total_count`
WHERE `points_total` = 0;
@@ -0,0 +1 @@
-- No database changes required for version 1.30.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.31.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.32.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.33.0.
@@ -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;
@@ -0,0 +1 @@
-- No database changes required for version 1.35.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.36.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.37.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.37.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.37.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.37.3.
@@ -0,0 +1 @@
-- No database changes required for version 1.38.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.39.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.40.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.41.0.
@@ -0,0 +1 @@
-- AI feedback is stored in the existing answers JSON column.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.2.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.3.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.4.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.5.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.6.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.7.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.8.
@@ -0,0 +1 @@
-- No database changes required for version 1.42.9.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.0.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.1.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.10.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.11.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.12.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.13.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.14.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.15.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.16.
@@ -0,0 +1 @@
-- Wydanie 1.43.17: zmieniono przyciski paska narzędzi podglądu zestawu.
@@ -0,0 +1 @@
-- Wydanie 1.43.18: dodano filtrowanie dostępnych pytań po ID podczas edycji zestawu.
@@ -0,0 +1 @@
-- Wydanie 1.43.19: usprawniono filtrowanie i zaznaczanie pytań w edycji zestawu.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.2.
@@ -0,0 +1 @@
-- Wydanie 1.43.20: czas testu bez ustawionego limitu zestawu jest sumowany z czasów pytań.
@@ -0,0 +1 @@
-- Wydanie 1.43.21: naprawiono wyświetlanie obrazów w szczegółach rozwiązanego testu.
@@ -0,0 +1 @@
-- Wydanie 1.43.22: kolejność odpowiedzi zamkniętych jest losowana i zapisywana dla podejścia.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.3.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.4.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.5.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.6.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.7.
@@ -0,0 +1,5 @@
UPDATE `#__extensions`
SET `params` = JSON_REMOVE(`params`, '$.ai_grok_team_id', '$.ai_grok_management_key')
WHERE `type` = 'component'
AND `element` = 'com_testquestions'
AND JSON_VALID(`params`);
@@ -0,0 +1 @@
-- No database changes required for version 1.43.9.
@@ -2,6 +2,9 @@ CREATE TABLE IF NOT EXISTS `#__testquestions_sets` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `id` int unsigned NOT NULL AUTO_INCREMENT,
`subject_id` int unsigned NOT NULL DEFAULT 0, `subject_id` int unsigned NOT NULL DEFAULT 0,
`title` varchar(255) NOT NULL, `title` varchar(255) NOT NULL,
`duration` smallint unsigned NOT NULL DEFAULT 0,
`test_content` text NULL,
`image` varchar(1024) NOT NULL DEFAULT '',
`description` text NULL, `description` text NULL,
`published` tinyint NOT NULL DEFAULT 1, `published` tinyint NOT NULL DEFAULT 1,
`ordering` int NOT NULL DEFAULT 0, `ordering` int NOT NULL DEFAULT 0,
@@ -0,0 +1,103 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Komponent\Component\Testquestions\Administrator\Service\AiService;
class AiController extends BaseController
{
public function save(): void
{
$this->saveSettings();
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=ai', false),
Text::_('COM_TESTQUESTIONS_AI_SETTINGS_SAVED')
);
}
private function saveSettings(): array
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$user = Factory::getApplication()->getIdentity();
if (!$user->authorise('core.admin', 'com_testquestions') && !$user->authorise('core.options', 'com_testquestions')) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$input = Factory::getApplication()->getInput();
$provider = strtolower($input->post->getCmd('ai_provider', ''));
if (!in_array($provider, ['openai', 'grok'], true)) {
throw new \RuntimeException(Text::_('COM_TESTQUESTIONS_AI_PROVIDER_INVALID'));
}
$models = AiService::availableModels();
$selectedModels = [];
foreach (['openai', 'grok'] as $modelProvider) {
$model = $input->post->getCmd('ai_' . $modelProvider . '_model', '');
if (!isset($models[$modelProvider][$model])) {
throw new \RuntimeException(Text::_('COM_TESTQUESTIONS_AI_MODEL_INVALID'));
}
$selectedModels[$modelProvider] = $model;
}
$db = Factory::getContainer()->get('DatabaseDriver');
$row = $db->setQuery(
$db->getQuery(true)
->select([$db->quoteName('extension_id'), $db->quoteName('params')])
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_testquestions'))
)->loadObject();
if (!$row) {
throw new \RuntimeException(Text::_('COM_TESTQUESTIONS_AI_SAVE_FAILED'));
}
$settings = json_decode((string) $row->params, true);
$settings = is_array($settings) ? $settings : [];
$legacyKey = trim((string) ($settings['ai_api_key'] ?? ''));
$settings['ai_provider'] = $provider;
foreach ($selectedModels as $modelProvider => $model) {
$settings['ai_' . $modelProvider . '_model'] = $model;
}
foreach (['openai', 'grok'] as $keyProvider) {
$fieldName = 'ai_' . $keyProvider . '_api_key';
$apiKey = trim((string) $input->post->get($fieldName, '', 'RAW'));
if ($apiKey !== '') {
$settings[$fieldName] = $apiKey;
}
if ($keyProvider === $provider && empty($settings[$fieldName]) && $legacyKey !== '') {
$settings[$fieldName] = $legacyKey;
}
}
unset($settings['ai_api_key']);
unset($settings['ai_grok_team_id'], $settings['ai_grok_management_key']);
$extension = (object) [
'extension_id' => (int) $row->extension_id,
'params' => json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
];
if (!$db->updateObject('#__extensions', $extension, 'extension_id')) {
throw new \RuntimeException(Text::_('COM_TESTQUESTIONS_AI_SAVE_FAILED'));
}
return $settings;
}
public function cancel(): void
{
$this->setRedirect(Route::_('index.php?option=com_testquestions&view=questions', false));
}
}
@@ -4,7 +4,11 @@ namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController; use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
class QuestionsController extends AdminController class QuestionsController extends AdminController
{ {
@@ -14,4 +18,28 @@ class QuestionsController extends AdminController
{ {
return parent::getModel($name, $prefix, $config); return parent::getModel($name, $prefix, $config);
} }
public function preview(): void
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$user = Factory::getApplication()->getIdentity();
if (!$user->authorise('core.edit', 'com_testquestions') && !$user->authorise('core.manage', 'com_testquestions')) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$ids = array_values(array_unique(array_filter(array_map('intval', (array) $this->input->post->get('cid', [], 'array')))));
if (!$ids) {
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=questions', false),
Text::_('COM_TESTQUESTIONS_PREVIEW_SELECT_QUESTIONS'),
'warning'
);
return;
}
$this->setRedirect(Route::_('index.php?option=com_testquestions&view=preview&ids=' . implode(',', $ids), false));
}
} }
@@ -0,0 +1,123 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
class ResultController extends BaseController
{
public function save(): void
{
$this->saveAssessment();
$id = Factory::getApplication()->getInput()->post->getInt('id');
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=result&id=' . $id, false),
Text::_('COM_TESTQUESTIONS_RESULT_SAVED')
);
}
public function save2close(): void
{
$this->saveAssessment();
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=results', false),
Text::_('COM_TESTQUESTIONS_RESULT_SAVED')
);
}
public function ai(): void
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$input = Factory::getApplication()->getInput();
$id = $input->post->getInt('id');
$questionId = $input->post->getInt('ai_question_id');
$message = Text::_('COM_TESTQUESTIONS_AI_RESULT');
$type = 'message';
try {
$this->getModel('Result')->evaluateWithAi($id, $questionId);
} catch (\Throwable $error) {
$errorMessage = $error->getMessage();
$message = str_starts_with($errorMessage, 'AI_KEY_EMPTY|')
? 'Diagnostyka konfiguracji AI (1.42.8): ' . substr($errorMessage, strlen('AI_KEY_EMPTY|'))
: ($errorMessage === 'AI_NOT_CONFIGURED'
? Text::_('COM_TESTQUESTIONS_AI_NOT_CONFIGURED')
: Text::sprintf('COM_TESTQUESTIONS_AI_ERROR', $errorMessage));
$type = 'error';
}
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=result&id=' . $id, false),
$message,
$type
);
}
public function aiAll(): void
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$input = Factory::getApplication()->getInput();
$id = $input->post->getInt('id');
$message = '';
$type = 'message';
try {
$summary = $this->getModel('Result')->evaluateAllWithAi($id);
if ($summary['failed'] > 0) {
$message = Text::sprintf(
'COM_TESTQUESTIONS_AI_BULK_PARTIAL',
$summary['evaluated'],
$summary['failed'],
$summary['first_error']
);
$type = 'warning';
} else {
$message = Text::sprintf('COM_TESTQUESTIONS_AI_BULK_DONE', $summary['evaluated']);
}
} catch (\Throwable $error) {
$errorMessage = $error->getMessage();
$message = $errorMessage === 'AI_NO_OPEN_QUESTIONS'
? Text::_('COM_TESTQUESTIONS_AI_NO_OPEN_QUESTIONS')
: ($errorMessage === 'AI_NOT_CONFIGURED'
? Text::_('COM_TESTQUESTIONS_AI_NOT_CONFIGURED')
: Text::sprintf('COM_TESTQUESTIONS_AI_ERROR', $errorMessage));
$type = 'error';
}
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=result&id=' . $id, false),
$message,
$type
);
}
private function saveAssessment(): void
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$input = Factory::getApplication()->getInput();
$id = $input->post->getInt('id');
$points = (array) $input->post->get('awarded_points', [], 'array');
$comments = (array) $input->post->get('comments', [], 'array');
$model = $this->getModel('Result');
if (!$model->saveAssessment($id, $points, $comments)) {
throw new \RuntimeException(Text::_('COM_TESTQUESTIONS_RESULT_NOT_FOUND'));
}
}
}
@@ -11,8 +11,89 @@ class SetController extends FormController
protected $view_item = 'set'; protected $view_item = 'set';
protected $view_list = 'sets'; 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 save2close($key = null, $urlVar = null)
{
$formData = (array) $this->input->post->get('jform', [], 'array');
$parentId = (int) ($formData['parent_id'] ?? 0);
$itemId = (int) ($formData['id'] ?? $this->input->getInt('id'));
$result = parent::save2close($key, $urlVar);
if ($result) {
if ($itemId > 0) {
$item = $this->getModel()->getItem($itemId);
$parentId = (int) ($item->parent_id ?? $parentId);
}
if ($parentId > 0) {
$this->setRedirect('index.php?option=com_testquestions&task=set.edit&id=' . $parentId);
}
}
return $result;
}
public function save($key = null, $urlVar = null)
{
$formData = (array) $this->input->post->get('jform', [], 'array');
$result = parent::save($key, $urlVar);
$itemId = (int) ($formData['id'] ?? $this->input->getInt('id'));
if ($result && $itemId > 0) {
$this->setRedirect('index.php?option=com_testquestions&task=set.edit&id=' . $itemId);
}
return $result;
}
public function saveandclose($key = null, $urlVar = null)
{
$formData = (array) $this->input->post->get('jform', [], 'array');
$parentId = (int) ($formData['parent_id'] ?? 0);
$result = parent::save($key, $urlVar);
$itemId = (int) ($formData['id'] ?? $this->input->getInt('id'));
if ($result && $itemId > 0 && $parentId < 1) {
$item = $this->getModel()->getItem($itemId);
$parentId = (int) ($item->parent_id ?? 0);
}
if ($result) {
$this->setRedirect(
$parentId > 0
? 'index.php?option=com_testquestions&task=set.edit&id=' . $parentId
: 'index.php?option=com_testquestions&view=sets'
);
}
return $result;
}
public function cancel($key = null) public function cancel($key = null)
{ {
$parentId = $this->input->getInt('parent_id');
$formData = (array) $this->input->post->get('jform', [], 'array');
$parentId = (int) ($formData['parent_id'] ?? $parentId);
if ($parentId < 1) {
$itemId = $this->input->getInt('id');
if ($itemId > 0) {
$item = $this->getModel()->getItem($itemId);
$parentId = (int) ($item->parent_id ?? 0);
}
}
if ($parentId > 0) {
$this->setRedirect('index.php?option=com_testquestions&task=set.edit&id=' . $parentId);
return true;
}
$this->setRedirect('index.php?option=com_testquestions&view=sets'); $this->setRedirect('index.php?option=com_testquestions&view=sets');
return true; return true;
@@ -5,11 +5,40 @@ namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\AdminController; use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
class SetsController extends AdminController class SetsController extends AdminController
{ {
protected $text_prefix = 'COM_TESTQUESTIONS'; protected $text_prefix = 'COM_TESTQUESTIONS';
public function unpublishall(): void
{
if (!Session::checkToken()) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$user = Factory::getApplication()->getIdentity();
if (!$user->authorise('core.edit.state', 'com_testquestions') && !$user->authorise('core.edit', 'com_testquestions')) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$db = Factory::getContainer()->get('DatabaseDriver');
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__testquestions_sets'))
->set($db->quoteName('published') . ' = 0')
->where($db->quoteName('published') . ' = 1')
)->execute();
$this->setRedirect(
Route::_('index.php?option=com_testquestions&view=sets', false),
Text::sprintf('COM_TESTQUESTIONS_SETS_UNPUBLISHED_ALL', $db->getAffectedRows())
);
}
public function getModel($name = 'Set', $prefix = 'Administrator', $config = ['ignore_request' => true]) public function getModel($name = 'Set', $prefix = 'Administrator', $config = ['ignore_request' => true])
{ {
return parent::getModel($name, $prefix, $config); return parent::getModel($name, $prefix, $config);
@@ -0,0 +1,35 @@
<?php
namespace Komponent\Component\Testquestions\Administrator\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\Field\PasswordField;
class AiapikeyField extends PasswordField
{
protected $type = 'Aiapikey';
protected function getInput(): string
{
$id = htmlspecialchars($this->id, ENT_QUOTES, 'UTF-8');
$length = mb_strlen((string) $this->value);
return '<div class="input-group">'
. parent::getInput()
. '<button type="button" class="btn btn-outline-secondary" data-ai-key-toggle="' . $id . '">Pokaż</button>'
. '</div>'
. '<small class="form-text text-muted">Klucz: ' . (int) $length . ' znaków.</small>'
. '<script>
document.addEventListener("click", function (event) {
var button = event.target.closest("[data-ai-key-toggle]");
if (!button) return;
var input = document.getElementById(button.getAttribute("data-ai-key-toggle"));
if (!input) return;
var visible = input.type === "text";
input.type = visible ? "password" : "text";
button.textContent = visible ? "Pokaż" : "Ukryj";
});
</script>';
}
}

Some files were not shown because too many files have changed in this diff Show More