Courses Plugin
The Courses plugin manages training courses in the backend. A course has chapters, subchapters, theory items with files, and quizzes with a time limit and an automatic score.
Features
- A list of courses with categories, a description and the status
draftoractive. - A course editor for chapters, subchapters, theory items and quizzes.
- A course page where a user reads the theory and does the quizzes.
- A course preview that shows a quiz and does not start it.
- Quiz questions of three types:
input,radioandcheckbox. - An automatic score for
radioandcheckboxquestions. - Facades that give other plugins access to courses, quizzes and quiz results.
Requirements
| Component | Use in the plugin |
|---|---|
| Jimbo system plugin | Backend area, menu, permission sections and the DGS URLs /festi/<store>/Courses/. |
| Theme package | Festi\Theme\Plugin\DisplayPlugin and Festi\Theme\Stage\StageBuilder. |
| Modules plugin | plugin\Modules\ModulesManager finds the handler that enables quizzes. |
| TinyMce plugin | The course editor loads its JavaScript files. |
Table users |
The DGS of the quiz results shows the user. |
The class loader of the project must find the namespaces Plugins\Courses and plugin\Courses. The file CoursesSections.php uses the second namespace.
Installation
The Festi CLI installs a plugin from its own repository with this command:
./vendor/bin/festi-plugin --mode install --dump ./dumps/ \
--plugin Courses --repository [email protected]:FestiPlugins/php_festi_plugin_courses.git
This command was not run for this page.
The command adds the repository as a git submodule. It copies install/install.mysql.sql into the dumps directory as a migration and applies it.
Read Known limitations of the install folder before you run the command. The install dump is not complete.
What the install folder contains
| File | Content |
|---|---|
install/install.mysql.sql |
The row for festi_plugins, six tables, URL rules, two permission sections and the permissions for the user type admin. |
install/updates1.sql |
A new definition of the table courses_quiz2user and one URL rule. |
install/updates2.sql |
A change of the quiz start rule, the quiz finish rule and the column form_answers. |
install/updates3.sql |
The URL rules of the course editor. |
install/updates4.sql |
The URL rule of the quiz preview. |
init.php loads the custom DGS field SerializeWithCheckboxField. The DGS of the quiz editor uses it as the field type serializeWithCheckbox.
Configuration
A theory item can have files. The DGS field video_file ("Additional Files") saves them on the server. Add the two paths to config.php of the project:
$GLOBALS['config']['paths'] = array(
// ...
'courses_chapters_video' => FS_ROOT.'static/courses/videos/',
);
$GLOBALS['config']['http_paths'] = array(
// ...
'courses_chapters_video' => '/static/courses/videos/'
);
| Key | Description |
|---|---|
paths['courses_chapters_video'] |
The folder on the server where the plugin saves the files. PHP must have write access to it. |
http_paths['courses_chapters_video'] |
The URL of the same folder. The course page uses it for the links to the files. |
The two keys must point to the same folder.
Permission sections
CoursesSections defines two sections:
| Constant | Section | Use |
|---|---|---|
CoursesSections::MANAGE |
manage_courses |
All URL methods of the plugin and the DGS of courses, chapters, items and quizzes. |
CoursesSections::MANAGE_CATEGORIES |
manage_courses_categories |
The DGS of the course categories. |
The course page and the quiz URLs also use the section manage_courses. A user without this section cannot open a course.
URLs
All rules are in the area backend. The annotations of CoursesPlugin declare them.
Management
| URL | Method | Description |
|---|---|---|
/manage/courses/ |
onDisplayBackendCourses |
The list of courses (DGS courses). |
/manage/courses/<course>/editor/ |
onDisplayCourseEditor |
The course editor. |
/manage/courses/<course>/editor/edit/ |
onAjaxFetchCourse |
The edit form of the course. |
/manage/courses/<course>/editor/chapters/create/ |
onJsonCreateChapter |
Creates a chapter or a subchapter. |
/manage/courses/<course>/editor/<chapter>/update/ |
onJsonUpdateChapter |
Changes the caption, the position or the parent of a chapter. |
/manage/courses/<course>/editor/<chapter>/remove/ |
onJsonRemoveChapter |
Removes a chapter, its subchapters, and the items and quizzes of the chapter. |
/manage/courses/<course>/editor/<chapter>/items/create/ |
onJsonCreateItem |
Creates a theory item in a subchapter. |
/manage/courses/<course>/editor/<chapter>/items/<item>/ |
onAjaxFetchChapterItemEditor |
The edit form of a theory item. |
/manage/courses/<course>/editor/<chapter>/items/<item>/remove/ |
onJsonRemoveChapterItems |
Removes a theory item. |
/manage/courses/<course>/editor/<chapter>/quizzes/create/ |
onJsonCreateQuiz |
Creates a quiz in a subchapter. |
/manage/courses/<course>/editor/<chapter>/quizzes/<quiz>/ |
onAjaxFetchChapterQuizEditor |
The edit form of a quiz with its questions. |
/manage/courses/<course>/editor/<chapter>/quizzes/<quiz>/remove/ |
onJsonRemoveChapterQuizzes |
Removes a quiz with its questions. |
Course page
| URL | Method | Description |
|---|---|---|
/courses/<course>/ |
onDisplayCourse |
The course page. |
/courses/<course>/preview/ |
onDisplayCoursePreview |
The course preview. |
/courses/<course>/chapters/<chapter>/items/<item>/ |
onAjaxFetchChapterContent |
The content and the files of a theory item. |
/courses/<course>/chapters/<chapter>/quizzes/<quiz>/ |
onAjaxFetchChapterQuiz |
The start page of a quiz. |
/courses/<course>/chapters/<chapter>/quizzes/<quiz>/preview/ |
onAjaxFetchChapterQuizPreview |
The start page of a quiz in the preview. |
/courses/<course>/chapters/<chapter>/quizzes/<quiz>/start/ |
onJsonQuizStart |
Starts the quiz and returns the questions. |
/courses/<course>/chapters/<chapter>/quizzes/<quiz>/finish/ |
onJsonQuizFinish |
Saves the answers and the score. |
/company/courses_details/<result>/ |
onDisplayDetails |
The answers of one quiz result. |
DGS pages
| URL | DGS |
|---|---|
/festi/course_chapters/Courses/ |
The chapters of a course. |
/festi/courses_categories/Courses/ |
The course categories. |
Usage
Manage courses
- Open
/manage/courses/. - Add a course with a caption, categories, a description and a status.
- Click the editor action of the course to open
/manage/courses/<course>/editor/. - Add chapters and subchapters.
- Add theory items and quizzes to a subchapter.
The plugin creates an item or a quiz only if the course has a subchapter.
Enable quizzes
Quizzes are off by default. The plugin shows quizzes and the action "Add Quiz" only if a module enables them.
To enable quizzes, implement Plugins\Courses\Domain\Listener\ICoursesQuizEnableHandler in a module of the Modules plugin:
<?php
use Plugins\Courses\Domain\Listener\ICoursesQuizEnableHandler;
class ProjectCoursesHandler implements ICoursesQuizEnableHandler
{
public function onCoursesQuizEnableHandler(): bool
{
return true;
}
}
This example was not run for this page. Refer to the Modules plugin page for how a module registers a handler.
Quiz flow
- The user opens a quiz on the course page.
onJsonQuizStartadds a row tocourses_quiz2userwith the statusstartedand returns the questions.- The time limit is the column
timeoutof the quiz in seconds. It starts atstart_datetime. onJsonQuizFinishsaves the answers, the score and the spent time, and sets the statussubmitted.
The class UserQuizValuesObject has the statuses started, in_progress, submitted and approved.
Score
| Question type | Score |
|---|---|
input |
No automatic score. The question needs a manual check. |
radio |
The full score of the question if the answer is the same as the correct answer. |
checkbox |
The score of the question divided by the number of correct options, for each correct option that the user selects. |
Use the plugin from another plugin
<?php
$plugin = Core::getInstance()->getPluginInstance('Courses');
$course = $plugin->getByID($idCourse);
$caption = $course->getCaption();
$courses = $plugin->getFacade();
$quizzes = $plugin->getQuizzesFacade();
$results = $plugin->getQuizResultsFacade();
$chapters = $courses->searchChapters(['id_course' => $idCourse]);
This example was not run for this page.
| Method | Returns | Content |
|---|---|---|
getFacade() |
ICoursesFacade |
Courses, chapters and theory items. |
getQuizzesFacade() |
IQuizzesFacade |
Quizzes, questions and answers. |
getQuizResultsFacade() |
IQuizResultsFacade |
The quiz results of users. |
The interfaces are in Domain/Facades/. getByID() returns a CourseValuesObject and throws NotFoundException if the course is not in the table.
fetchCourseEditor($idCourse) and fetchCoursePreview($idCourse) return the HTML of the editor and of the preview. Use them to show these blocks on a page of a different plugin.
Events
BeforeStartQuizEvent
The plugin dispatches BeforeStartQuizEvent::TYPE through the core when a user starts a quiz that already has the status submitted. A listener can forbid the new start.
<?php
use Plugins\Courses\Domain\Event\BeforeStartQuizEvent;
$this->addEventListener(
BeforeStartQuizEvent::TYPE,
function (BeforeStartQuizEvent &$event) {
$isForbiddenStart = &$event->isForbiddenStart();
$isForbiddenStart = true;
}
);
This example was not run for this page.
| Method | Returns |
|---|---|
getQuiz() |
The quiz as QuizValuesObject. |
getUserQuiz() |
The submitted result of the user as UserQuizValuesObject. |
isForbiddenStart() |
A reference to the flag. The default is false. |
If the flag is true, the plugin does not start the quiz and shows the message that the user already passed it.
CoursesPlugin::EVENT_ON_PREPARE_EDITOR_STORE
The plugin dispatches this event on itself before the course editor loads an edit form. The event target has two keys:
| Key | Value |
|---|---|
storeName |
The DGS name: courses, course_chapter_items or course_chapter_quizzes_editor. |
store |
The Store instance. A listener can change its model. |
Known limitations of the install folder
install/install.mysql.sqlhas MySQL statements and PostgreSQL statements in one file, for example'6'::festi_sections_maskandCURRVAL(...).- The dump does not create the tables
courses_categories,courses2categoriesandcourses2schools. The source reads them. - The dump does not create the column
courses.statusand the columnsis_multipleandis_manual_checkofcourse_chapter_quiz_items. The source reads them. - The dump adds URL rules with fixed identifiers from 82 to 87.
- The dump has a rule for the method
onAjaxQuizStep.install/updates1.sqlhas a rule for the methodonDisplayCoursesDetails. The plugin class does not have these methods. - No file has the rule for
/courses/<course>/preview/. - The files
updates1.sqltoupdates4.sqldo not have the nameupdate.mysql.sql. Thusfesti-plugin --mode updatedoes not apply them. - The dump removes five of its six tables before it creates them. Do not apply it again on a database that has courses.
- The dump has no
DROP TABLEand noIF NOT EXISTSfor the tablecourses_quiz2user. A second run gives an error at this table.
Other limitations
- The DGS
quiz_resultsuses the classesApp,libs\DateTimeFormatHelperand the tablesdisciplinesandschools_study_groups. These are not in this repository. IQuizzesFacade::getForSchool()andgetStructureForSchool()read the tablecourses2schoolsof a host project.onDisplayCoursesResults()shows the DGScourses_quiz2user, but it has no URL rule.