diff --git a/classes/output/core/course_renderer.php b/classes/output/core/course_renderer.php new file mode 100644 index 00000000000..6ad4903ccbf --- /dev/null +++ b/classes/output/core/course_renderer.php @@ -0,0 +1,251 @@ +. + +namespace theme_boost_union\output\core; + +use html_writer; +use coursecat_helper; +use stdClass; +use core_course_list_element; +use theme_boost_union\util\course; +use moodle_url; + +/** + * Renderers to align Boost Union's course elements to what is expect + * + * @package theme_boost_union + * @copyright 2024 Daniel Neis Araujo {@link https://www.adapta.online} + * @copyright 2022 Willian Mano {@link https://conecti.me} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_renderer extends \core_course_renderer { + + /** + * Renders the list of courses + * + * This is internal function, please use core_course_renderer::courses_list() or another public + * method from outside of the class + * + * If list of courses is specified in $courses; the argument $chelper is only used + * to retrieve display options and attributes, only methods get_show_courses(), + * get_courses_display_option() and get_and_erase_attributes() are called. + * + * @param coursecat_helper $chelper various display options + * @param array $courses the list of courses to display + * @param int|null $totalcount total number of courses (affects display mode if it is AUTO or pagination if applicable), + * defaulted to count($courses) + * @return string + */ + protected function coursecat_courses(coursecat_helper $chelper, $courses, $totalcount = null) { + if (!get_config('theme_boost_union', 'enablecards')) { + return parent::coursecat_courses($chelper, $courses, $totalcount); + } + global $CFG; + if ($totalcount === null) { + $totalcount = count($courses); + } + if (!$totalcount) { + // Courses count is cached during courses retrieval. + return ''; + } + + if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_AUTO) { + // In 'auto' course display mode we analyse if number of courses is more or less than $CFG->courseswithsummarieslimit. + if ($totalcount <= $CFG->courseswithsummarieslimit) { + $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED); + } else { + $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_COLLAPSED); + } + } + + // Prepare content of paging bar if it is needed. + $paginationurl = $chelper->get_courses_display_option('paginationurl'); + $paginationallowall = $chelper->get_courses_display_option('paginationallowall'); + if ($totalcount > count($courses)) { + // There are more results that can fit on one page. + if ($paginationurl) { + // The option paginationurl was specified, display pagingbar. + $perpage = $chelper->get_courses_display_option('limit', $CFG->coursesperpage); + $page = $chelper->get_courses_display_option('offset') / $perpage; + $pagingbar = $this->paging_bar($totalcount, $page, $perpage, + $paginationurl->out(false, ['perpage' => $perpage])); + if ($paginationallowall) { + $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, ['perpage' => 'all']), + get_string('showall', '', $totalcount)), ['class' => 'paging paging-showall']); + } + } else if ($viewmoreurl = $chelper->get_courses_display_option('viewmoreurl')) { + // The option for 'View more' link was specified, display more link. + $viewmoretext = $chelper->get_courses_display_option('viewmoretext', new \lang_string('viewmore')); + $morelink = html_writer::tag( + 'div', + html_writer::link($viewmoreurl, $viewmoretext, ['class' => 'btn btn-secondary']), + ['class' => 'paging paging-morelink'] + ); + } + } else if (($totalcount > $CFG->coursesperpage) && $paginationurl && $paginationallowall) { + // There are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode. + $pagingbar = html_writer::tag('div', + html_writer::link($paginationurl->out(false, ['perpage' => $CFG->coursesperpage]), + get_string('showperpage', '', $CFG->coursesperpage)), ['class' => 'paging paging-showperpage']); + } + + // Display list of courses. + $attributes = $chelper->get_and_erase_attributes('courses'); + $content = html_writer::start_tag('div', $attributes); + + if (!empty($pagingbar)) { + $content .= $pagingbar; + } + + $content .= html_writer::start_tag('div', ['class' => 'card-deck dashboard-card-deck']); + foreach ($courses as $course) { + $content .= $this->coursecat_coursebox($chelper, $course); + } + $content .= html_writer::end_tag('div'); + + if (!empty($pagingbar)) { + $content .= $pagingbar; + } + + if (!empty($morelink)) { + $content .= $morelink; + } + + $content .= html_writer::end_tag('div'); // End courses. + + return $content; + } + + /** + * Displays one course in the list of courses. + * + * This is an internal function, to display an information about just one course + * please use core_course_renderer::course_info_box() + * + * @param coursecat_helper $chelper various display options + * @param core_course_list_element|stdClass $course + * @param string $additionalclasses additional classes to add to the main
tag (usually + * depend on the course position in list - first/last/even/odd) + * @return string + * + * @throws \coding_exception + * @throws \dml_exception + * @throws \moodle_exception + */ + protected function coursecat_coursebox(coursecat_helper $chelper, $course, $additionalclasses = '') { + if (!get_config('theme_boost_union', 'enablecards')) { + return parent::coursecat_coursebox($chelper, $course, $additionalclasses); + } + if (!isset($this->strings->summary)) { + $this->strings->summary = get_string('summary'); + } + + if ($chelper->get_show_courses() <= self::COURSECAT_SHOW_COURSES_COUNT) { + return ''; + } + + if ($course instanceof stdClass) { + $course = new core_course_list_element($course); + } + + return $this->coursecat_coursebox_content($chelper, $course); + } + + /** + * Returns HTML to display course content (summary, course contacts and optionally category name) + * + * This method is called from coursecat_coursebox() and may be re-used in AJAX + * + * @param coursecat_helper $chelper various display options + * @param stdClass|core_course_list_element $course + * + * @return string + * + * @throws \coding_exception + * @throws \dml_exception + * @throws \moodle_exception + */ + protected function coursecat_coursebox_content(coursecat_helper $chelper, $course) { + if (!get_config('theme_boost_union', 'enablecards')) { + return parent::coursecat_coursebox_content($chelper, $course); + } + if ($course instanceof stdClass) { + $course = new core_course_list_element($course); + } + + $courseutil = new course($course); + + $coursecontacts = $courseutil->get_course_contacts(); + + $courseenrolmenticons = $courseutil->get_enrolment_icons(); + $courseenrolmenticons = !empty($courseenrolmenticons) ? $this->render_enrolment_icons($courseenrolmenticons) : false; + + $courseprogress = $courseutil->get_progress(); + $hasprogress = $courseprogress != null; + + $data = [ + 'id' => $course->id, + 'fullname' => $chelper->get_course_formatted_name($course), + 'visible' => $course->visible, + 'image' => $courseutil->get_summary_image(), + 'summary' => $courseutil->get_summary($chelper), + 'category' => $courseutil->get_category(), + 'customfields' => $courseutil->get_custom_fields(), + 'hasprogress' => $hasprogress, + 'progress' => (int) $courseprogress, + 'hasenrolmenticons' => $courseenrolmenticons != false, + 'enrolmenticons' => $courseenrolmenticons, + 'hascontacts' => !empty($coursecontacts), + 'contacts' => $coursecontacts, + 'courseurl' => $this->get_course_url($course->id), + ]; + + return $this->render_from_template('theme_boost_union/coursecard', $data); + } + + /** + * Returns enrolment icons + * + * @param array $icons + * + * @return array + */ + protected function render_enrolment_icons(array $icons): array { + $data = []; + + foreach ($icons as $icon) { + $data[] = $this->render($icon); + } + + return $data; + } + + /** + * Returns the course URL based on some criterias. + * + * @param int $courseid + * + * @return moodle_url + * @throws \moodle_exception + */ + private function get_course_url($courseid) { + if (class_exists('\local_course\output\index')) { + return new moodle_url('/local/course/index.php', ['id' => $courseid]); + } + + return new moodle_url('/course/view.php', ['id' => $courseid]); + } +} diff --git a/classes/util/course.php b/classes/util/course.php new file mode 100644 index 00000000000..7d4814192f7 --- /dev/null +++ b/classes/util/course.php @@ -0,0 +1,165 @@ +. + +namespace theme_boost_union\util; + +use moodle_url; +use core_course_list_element; +use coursecat_helper; +use core_course_category; + +/** + * Course class utility class + * + * @package theme_boost_union + * @copyright 2022 Willian Mano {@link https://conecti.me} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course { + /** + * @var \stdClass $course The course object. + */ + protected $course; + + /** + * Class constructor + * + * @param core_course_list_element $course + * + */ + public function __construct($course) { + $this->course = $course; + } + + /** + * Returns the first course's summary image url + * + * @return string + */ + public function get_summary_image() { + global $CFG, $OUTPUT; + + foreach ($this->course->get_course_overviewfiles() as $file) { + if ($file->is_valid_image()) { + $url = moodle_url::make_file_url("$CFG->wwwroot/pluginfile.php", + '/' . $file->get_contextid() . '/' . $file->get_component() . '/' . + $file->get_filearea() . $file->get_filepath() . $file->get_filename(), !$file->is_valid_image()); + + return $url->out(); + } + } + + return $OUTPUT->get_generated_image_for_id($this->course->id); + } + + /** + * Returns HTML to display course contacts. + * + * @return array + */ + public function get_course_contacts() { + + $contacts = []; + if (get_config('theme_boost_union', 'enableteacherspic') && $this->course->has_course_contacts()) { + $instructors = $this->course->get_course_contacts(); + + foreach ($instructors as $instructor) { + $user = $instructor['user']; + $userutil = new user($user->id); + + $contacts[] = [ + 'id' => $user->id, + 'fullname' => fullname($user), + 'userpicture' => $userutil->get_user_picture(), + 'role' => $instructor['role']->displayname, + ]; + } + } + + return $contacts; + } + + /** + * Returns HTML to display course category name. + * + * @return string + * + * @throws \moodle_exception + */ + public function get_category(): string { + $cat = core_course_category::get($this->course->category, IGNORE_MISSING); + + if (!$cat) { + return ''; + } + + return $cat->get_formatted_name(); + } + + /** + * Returns course summary. + * + * @param coursecat_helper $chelper + */ + public function get_summary(coursecat_helper $chelper): string { + if (get_config('theme_boost_union', 'enablecardssummary') && $this->course->has_summary()) { + return $chelper->get_course_formatted_summary($this->course, + ['overflowdiv' => true, 'noclean' => true, 'para' => false] + ); + } + + return false; + } + + /** + * Returns course custom fields. + * + * @return string + */ + public function get_custom_fields(): string { + if (get_config('theme_boost_union', 'enablecardscfields') && $this->course->has_custom_fields()) { + $handler = \core_course\customfield\course_handler::create(); + + return $handler->display_custom_fields_data($this->course->get_custom_fields()); + } + + return ''; + } + + /** + * Returns HTML to display course enrolment icons. + * + * @return array + */ + public function get_enrolment_icons(): array { + if ($icons = enrol_get_course_info_icons($this->course)) { + return $icons; + } + + return []; + } + + /** + * Get the user progress in the course. + * + * @param null $userid + * + * @return mixed + */ + public function get_progress($userid = null) { + return \core_completion\progress::get_course_progress_percentage(get_course($this->course->id), $userid); + } +} diff --git a/classes/util/user.php b/classes/util/user.php new file mode 100644 index 00000000000..60b7f91d21c --- /dev/null +++ b/classes/util/user.php @@ -0,0 +1,72 @@ +. + +namespace theme_boost_union\util; + +use stdClass; +use user_picture; + +/** + * User class utility class + * + * @package theme_boost_union + * @copyright 2022 Willian Mano {@link https://conecti.me} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user { + /** + * @var \stdClass $user The user object. + */ + protected $user; + + /** + * Class constructor + * + * @param stdClass $user + * + */ + public function __construct($user = null) { + global $USER, $DB; + + if (!is_object($user)) { + $user = $DB->get_record('user', ['id' => $user], '*', MUST_EXIST); + } + + if (!$user) { + $user = $USER; + } + + $this->user = $user; + } + + /** + * Returns the user picture + * + * @param int $imgsize + * + * @return \moodle_url + * @throws \coding_exception + */ + public function get_user_picture($imgsize = 100) { + global $PAGE; + + $userimg = new user_picture($this->user); + + $userimg->size = $imgsize; + + return $userimg->get_url($PAGE)->out(); + } +} diff --git a/lang/en/theme_boost_union.php b/lang/en/theme_boost_union.php index c75e002a02e..527e826c47c 100644 --- a/lang/en/theme_boost_union.php +++ b/lang/en/theme_boost_union.php @@ -250,6 +250,18 @@ // ... ... Setting: Show course completion progress. $string['courseoverviewshowprogresssetting'] = 'Show course completion progress'; $string['courseoverviewshowprogresssetting_desc'] = 'With this setting, you can control whether the course completion progress is visible inside the course overview block or not.'; +// ... ... Setting: Enable cards on course index. +$string['enablecards'] = 'Enable cards on course index'; +$string['enablecardsdesc'] = 'Display courses as cards on site and course index.'; +// ... ... Setting: Enable course summary on course cards. +$string['enablecardssummary'] = 'Enable summary on course cards'; +$string['enablecardssummarydesc'] = 'This setting controls were to hide or display the course summary in the course cards.'; +// ... ... Setting: Enable custom fields on course cards. +$string['enablecardscfields'] = 'Enable custom fields on course cards'; +$string['enablecardscfieldsdesc'] = 'This setting controls were to hide or display the course custom fields in the course cards.'; +// ... ... Setting: Enable teachers pictures on course cards. +$string['enableteacherspic'] = 'Enable teachers pictures'; +$string['enableteacherspicdesc'] = 'This setting controls were to hide or display the teachers\' pictures in the course cards.'; // Settings: Course tab. $string['coursetab'] = 'Course'; diff --git a/scss/boost_union/post.scss b/scss/boost_union/post.scss index 90a1b56a0f3..b5af543e788 100644 --- a/scss/boost_union/post.scss +++ b/scss/boost_union/post.scss @@ -1,3 +1,114 @@ +.pagelayout-frontpage .dashboard-card-deck .dashboard-card, +.pagelayout-coursecategory .dashboard-card-deck .dashboard-card { + width: 20%; +} +.pagelayout-frontpage .dashboard-card, +.pagelayout-coursecategory .dashboard-card { + position: relative; + margin: 1rem; +} +.pagelayout-frontpage .dashboard-card.dimmed .card-img::after, +.pagelayout-coursecategory .dashboard-card.dimmed .card-img::after { + content: ""; + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + top: 0; + right: 0; + left: 0; + bottom: 0; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} +.pagelayout-frontpage .dashboard-card .dashboard-card-footer, +.pagelayout-coursecategory .dashboard-card .dashboard-card-footer { + border-radius: 0.5rem; +} +.pagelayout-frontpage .dashboard-card .card-body, +.pagelayout-coursecategory .dashboard-card .card-body { + padding: 0.8rem; +} +.pagelayout-frontpage .dashboard-card .customfields .customfield, +.pagelayout-coursecategory .dashboard-card .customfields .customfield { + font-size: 80%; + margin-bottom: 0.25rem; +} +.pagelayout-frontpage .dashboard-card .customfields .customfield:last-of-type, +.pagelayout-coursecategory .dashboard-card .customfields .customfield:last-of-type { + margin-bottom: 0; +} +.pagelayout-frontpage .dashboard-card .customfields, +.pagelayout-coursecategory .dashboard-card .customfields { + margin-top: 1.5em; + margin-bottom: 1.5em; +} +.pagelayout-frontpage .dashboard-card .customfields .customfield .customfieldname, +.pagelayout-frontpage .dashboard-card .customfields .customfield .customfieldseparator, +.pagelayout-coursecategory .dashboard-card .customfields .customfield .customfieldname, +.pagelayout-coursecategory .dashboard-card .customfields .customfield .customfieldseparator { + font-weight: 500; +} +.pagelayout-frontpage .dashboard-card .enrolmenticons, +.pagelayout-coursecategory .dashboard-card .enrolmenticons { + position: absolute; + bottom: 8px; + right: 0; +} +.pagelayout-frontpage .dashboard-card .enrolmenticons .enrolmenticon, +.pagelayout-coursecategory .dashboard-card .enrolmenticons .enrolmenticon { + padding: 4px 8px; + background-color: #fff; + color: #0f47ad; + border-radius: 0.5rem; + margin: 0 4px; +} +.pagelayout-frontpage .dashboard-card .enrolmenticons .enrolmenticon .icon, +.pagelayout-coursecategory .dashboard-card .enrolmenticons .enrolmenticon .icon { + margin-right: 0; +} +.pagelayout-frontpage .dashboard-card .course-contacts .contact .info, +.pagelayout-coursecategory .dashboard-card .course-contacts .contact .info { + margin-left: 1em; +} +.pagelayout-frontpage .dashboard-card .info .name, +.pagelayout-coursecategory .dashboard-card .info .name { + margin-bottom: 0; +} +.pagelayout-frontpage .dashboard-card .course-contacts .contact, +.pagelayout-coursecategory .dashboard-card .course-contacts .contact { + display: flex; +} +.pagelayout-frontpage .dashboard-card .course-contacts .contact:not(:first-of-type), +.pagelayout-coursecategory .dashboard-card .course-contacts .contact:not(:first-of-type) { + margin-top: 0.4rem; +} +.pagelayout-frontpage .dashboard-card .course-contacts .contact img, +.pagelayout-coursecategory .dashboard-card .course-contacts .contact img { + width: 36px; + height: 36px; + border: 1px solid #dee2e6; + margin-left: 1em; +} +.pagelayout-frontpage .dashboard-card .course-contacts .contact p.role, +.pagelayout-coursecategory .dashboard-card .course-contacts .contact p.role { + color: #1d2125; + font-size: 80%; +} +.pagelayout-frontpage .dashboard-card .dashboard-card-footer, +.pagelayout-coursecategory .dashboard-card .dashboard-card-footer { + padding: 0 0.8rem 0.8rem 0.8rem; +} +.pagelayout-frontpage .course_category_tree .category .categoryname.aabtn, +.pagelayout-coursecategory .course_category_tree .category .categoryname.aabtn { + font-size: 1.5em; +} + +.pagelayout-frontpage .dashboard-card .course-summary, +.pagelayout-coursecategory .dashboard-card .course-summary { + max-height: 10em; + overflow-y: scroll; + overflow-x: hidden; +} + /*======================================= * Settings: Look -> Site branding ======================================*/ @@ -2583,7 +2694,6 @@ body.dir-rtl { } } - /*======================================= * Supporting third-party plugins ======================================*/ @@ -2674,3 +2784,4 @@ body.dir-rtl { } } } + diff --git a/settings.php b/settings.php index 5ad55d3780f..416f2448d46 100644 --- a/settings.php +++ b/settings.php @@ -735,6 +735,42 @@ $setting->set_updatedcallback('theme_reset_all_caches'); $tab->add($setting); + // Enable cards on course index. + $name = 'theme_boost_union/enablecards'; + $title = get_string('enablecards', 'theme_boost_union'); + $description = get_string('enablecardsdesc', 'theme_boost_union'); + $default = 0; + $choices = [0 => get_string('no'), 1 => get_string('yes')]; + $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); + $tab->add($setting); + + // Enable summary on course cards. + $name = 'theme_boost_union/enablecardssummary'; + $title = get_string('enablecardssummary', 'theme_boost_union'); + $description = get_string('enablecardssummarydesc', 'theme_boost_union'); + $default = 0; + $choices = [0 => get_string('no'), 1 => get_string('yes')]; + $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); + $tab->add($setting); + + // Enable custom fields on course cards. + $name = 'theme_boost_union/enablecardscfields'; + $title = get_string('enablecardscfields', 'theme_boost_union'); + $description = get_string('enablecardscfieldsdesc', 'theme_boost_union'); + $default = 0; + $choices = [0 => get_string('no'), 1 => get_string('yes')]; + $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); + $tab->add($setting); + + // Enable teachers on course cards. + $name = 'theme_boost_union/enableteacherspic'; + $title = get_string('enableteacherspic', 'theme_boost_union'); + $description = get_string('enableteacherspicdesc', 'theme_boost_union'); + $default = 0; + $choices = [0 => get_string('no'), 1 => get_string('yes')]; + $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); + $tab->add($setting); + // Add tab to settings page. $page->add($tab); diff --git a/templates/coursecard.mustache b/templates/coursecard.mustache new file mode 100644 index 00000000000..406be6ef59d --- /dev/null +++ b/templates/coursecard.mustache @@ -0,0 +1,57 @@ +
+ +
+ {{fullname}} + {{#hasenrolmenticons}} +
+ {{#enrolmenticons}} +
+ {{{.}}} +
+ {{/enrolmenticons}} +
+ {{/hasenrolmenticons}} +
+
+
+ + +
+ {{{customfields}}} +
+ +
+ {{{summary}}} +
+
+ {{#hasprogress}} +
+ +
+ {{/hasprogress}} + + {{#hascontacts}} +
+ {{#contacts}} + + {{{fullname}}} +
+

{{{fullname}}}

+

{{{role}}}

+
+
+ {{/contacts}} +
+ {{/hascontacts}} +