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 draft or active.
  • 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, radio and checkbox.
  • An automatic score for radio and checkbox questions.
  • 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

  1. Open /manage/courses/.
  2. Add a course with a caption, categories, a description and a status.
  3. Click the editor action of the course to open /manage/courses/<course>/editor/.
  4. Add chapters and subchapters.
  5. 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

  1. The user opens a quiz on the course page.
  2. onJsonQuizStart adds a row to courses_quiz2user with the status started and returns the questions.
  3. The time limit is the column timeout of the quiz in seconds. It starts at start_datetime.
  4. onJsonQuizFinish saves the answers, the score and the spent time, and sets the status submitted.

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.sql has MySQL statements and PostgreSQL statements in one file, for example '6'::festi_sections_mask and CURRVAL(...).
  • The dump does not create the tables courses_categories, courses2categories and courses2schools. The source reads them.
  • The dump does not create the column courses.status and the columns is_multiple and is_manual_check of course_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.sql has a rule for the method onDisplayCoursesDetails. The plugin class does not have these methods.
  • No file has the rule for /courses/<course>/preview/.
  • The files updates1.sql to updates4.sql do not have the name update.mysql.sql. Thus festi-plugin --mode update does 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 TABLE and no IF NOT EXISTS for the table courses_quiz2user. A second run gives an error at this table.

Other limitations

  • The DGS quiz_results uses the classes App, libs\DateTimeFormatHelper and the tables disciplines and schools_study_groups. These are not in this repository.
  • IQuizzesFacade::getForSchool() and getStructureForSchool() read the table courses2schools of a host project.
  • onDisplayCoursesResults() shows the DGS courses_quiz2user, but it has no URL rule.