diff --git a/.upgradenotes/MDL-84555-2025031404100029.yml b/.upgradenotes/MDL-84555-2025031404100029.yml new file mode 100644 index 00000000000..7be29027c31 --- /dev/null +++ b/.upgradenotes/MDL-84555-2025031404100029.yml @@ -0,0 +1,14 @@ +issueNumber: MDL-84555 +notes: + core_course: + - message: >- + New core_course\output\activity_icon class to render activity icons with + or without purpose color. This output will centralize the way Moodle + renders activity icons + type: improved + core: + - message: >- + The new PHP enum core\output\local\properties\iconsize can be used to limit + the amount of icons sizes an output component can use. The enum has the + same values available in the theme_boost scss. + type: improved diff --git a/admin/tool/componentlibrary/content/moodle/components/activityicons.md b/admin/tool/componentlibrary/content/moodle/components/activityicons.md index 2a8e393dd8c..394f1c75af4 100644 --- a/admin/tool/componentlibrary/content/moodle/components/activityicons.md +++ b/admin/tool/componentlibrary/content/moodle/components/activityicons.md @@ -9,43 +9,100 @@ tags: - Available - '4.0' - Updated -- '4.4' +- '5.0' --- ## Activity icon types Moodle activity icons are single black SVG icons that are stored in `mod/PLUGINNAME/pix/monologo.svg`. -### Minimal activity icons +## Rendering activity icons + +The `core_course\output\activity_icon` class is used to render activity icons. It can be used in several ways depending on the context. Also, there is the `core_course\activity_icon` template that can be included directly from mustache templates. + +### Rendering the activity plugin icon + +The following example shows how to render the default activity icon: + +{{< php >}} +use core_course\output\activity_icon; +$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); + +$icon = activity_icon::from_modname('quiz'); + +echo $renderer->render($icon); +{{< /php >}} + +By default, the activity icon will be rendered colored with the activity purpose color (see below). + +### Rendering the activity icon from a cm_info object + +Specific activity instances can have their own custom icons. For example, the `mod_resource` displays the MIME type icon for the resource. To render the activity icon from a `cm_info` object, use the static constructor `from_cm_info`. The method will return an instance of `activity_icon` with the icon URL set to the custom icon if necessary. + +It is possible to render the activity icon from a `cm_info` object: + +{{< php >}} +use core_course\output\activity_icon; +$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); +$cminfo = get_fast_modinfo($courseid)->get_cm($cmid); + +$icon = activity_icon::from_cm_info($cminfo); + +echo $renderer->render($icon); +{{< /php >}} + +### Rendering the activity icon in dark color + +There are pages like the gradebook where the activity icons must be rendered in black color for accessibility or usability reasons. The `core_course\output\activity_icon` class has a `set_colourize` method to define if the icon must be colorized or not. + +The following example shows how to render the default activity icon in black: + +{{< php >}} +use core_course\output\activity_icon; +$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); + +$icon = activity_icon::from_modname('quiz') + ->set_colourize(false); + +echo $renderer->render($icon); +{{< /php >}} + +### Set the activity icon size When rendered in a page with limited space the icons will be shown in their original design, for example on the course gradebook where activity show in the grade table header. -> NOTE: The icon is using the ```.icon``` CSS class which limits the maximum width and height. It's recommended to define width and height into the SVG. +The `core_course\output\activity_icon` class has a `set_icon_size` method to define the icon size. The method accepts any value from `core\output\local\properties\iconsize` enum. -{{< example >}} -
-
- {{< image "quiz/monologo.svg" "Quiz icon" "icon">}} Multiple choice quiz 1 -
-
-{{< /example >}} +The following example shows how to render the default activity icon with a custom size: -### Coloured activity icons +{{< php >}} +use core_course\output\activity_icon; +use core\output\local\properties\iconsize; +$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); -In places like the course page and the activity chooser icons have a more prominent role and they should be rendered outlined colored against a transparent background. +$icon = activity_icon::from_modname('quiz') + ->set_icon_size(iconsize::SIZE4); -The CSS classes for these icons are ```activityiconcontainer``` wrapper class with the added activity name. And the ```activityicon``` class for the image. See the template ```course/format/templates/local/content/cm/title.mustache``` for more info. +echo $renderer->render($icon); +{{< /php >}} -
-
- {{< image "quiz/monologo.svg" "Quiz icon" "activityicon">}}
-
-
quiz
- -
-
+### Add extra classes to the activity icon -### Activity purposes +The `core_course\output\activity_icon` class has a `set_extra_classes` method to add extra classes to the icon container. + +The following example shows how to render the default activity icon with extra classes: + +{{< php >}} +use core_course\output\activity_icon; +$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); + +$icon = activity_icon::from_modname('quiz') + ->set_extra_classes(['my-extra-class']); + +echo $renderer->render($icon); +{{< /php >}} + +## Activity purposes In the HTML for the example above you might notice the ```assessment``` css class after ```.activityiconcontainer```. This class is the result of assigning a *purpose* to the quiz activity in ```/mod/quiz/lib.php```. @@ -94,16 +151,24 @@ $info = new cached_cm_info(); $info->iconurl = new moodle_url('https://moodle.org/theme/moodleorg/pix/moodle_logo_small.svg'); {{< /php >}} -To get this customised icon, use: +To get this customised icon url, use: {{< php >}} $iconurl = get_fast_modinfo($courseid)->get_cm($cmid)->get_icon_url()->out(false); {{< /php >}} -
-
+And to render the custom icon: + +{{< php >}} +use core_course\output\activity_icon; + +echo $OUTPUT->render(activity_icon::from_cm_info($cminfo)); +{{< /php >}} + +
+
lti icon
-
+ @@ -125,10 +190,10 @@ function h5pactivity_is_branded(): bool { } {{< /php >}} -
-
+
+
{{< image "h5pactivity/monologo.svg" "H5P activity icon" "activityicon">}}
-
+
h5pactivity
@@ -136,64 +201,64 @@ function h5pactivity_is_branded(): bool { ## Examples -
-
+
+
{{< image "quiz/monologo.svg" "Admin icon" "activityicon">}}
-
+
Administration
-
-
+
+
{{< image "quiz/monologo.svg" "Assessment icon" "activityicon">}}
-
+
Assessment
-
-
+
+
{{< image "wiki/monologo.svg" "Collaboration icon" "activityicon">}}
-
+
Collaboration
-
-
+
+
{{< image "choice/monologo.svg" "Communication icon" "activityicon">}}
-
+
Communication
-
-
+
+
{{< image "lesson/monologo.svg" "Interactive content icon" "activityicon">}}
-
+
Interactive content
-
-
+
+
{{< image "book/monologo.svg" "Resource icon" "activityicon">}}
-
+
Resource
-
-
+
+
{{< image "lti/monologo.svg" "Other icon" "activityicon">}}
-
+
Other
diff --git a/backup/util/ui/renderer.php b/backup/util/ui/renderer.php index 59188b0f46c..9feb45ff6a6 100644 --- a/backup/util/ui/renderer.php +++ b/backup/util/ui/renderer.php @@ -22,6 +22,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core_course\output\activity_icon; +use core\output\local\properties\iconsize; + defined('MOODLE_INTERNAL') || die; global $CFG; @@ -203,12 +206,20 @@ class core_backup_renderer extends plugin_renderer_base { $table->data = array(); } $name = get_string('pluginname', $activity->modulename); - $icon = new image_icon('monologo', '', $activity->modulename); - $table->data[] = array( - $this->output->render($icon).$name, + $icon = activity_icon::from_modname($activity->modulename) + ->set_icon_size(iconsize::SIZE4) + ->set_colourize(false); + + $content = $this->output->container( + contents: $this->output->render($icon) . $name, + classes: 'd-flex align-items-center', + ); + + $table->data[] = [ + $content, format_string($activity->title), ($activity->settings[$activitykey.'_userinfo']) ? $yestick : $notick, - ); + ]; } if (!empty($table)) { $html .= $this->backup_detail_pair(get_string('sectionactivities', 'backup'), html_writer::table($table)); diff --git a/blocks/recentlyaccesseditems/classes/external/recentlyaccesseditems_item_exporter.php b/blocks/recentlyaccesseditems/classes/external/recentlyaccesseditems_item_exporter.php index 4836dd6dfc8..e42ef2f0e24 100644 --- a/blocks/recentlyaccesseditems/classes/external/recentlyaccesseditems_item_exporter.php +++ b/blocks/recentlyaccesseditems/classes/external/recentlyaccesseditems_item_exporter.php @@ -21,8 +21,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems\external; -defined('MOODLE_INTERNAL') || die(); +use core_course\output\activity_icon; use renderer_base; use moodle_url; @@ -51,22 +51,18 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter { protected function get_other_values(renderer_base $output) { global $CFG; require_once($CFG->libdir.'/modinfolib.php'); - $iconurl = get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url(); - $iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter'; - $isbranded = component_callback('mod_' . $this->data->modname, 'is_branded') !== null ? : false; + $renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); + $cminfo = get_fast_modinfo($this->data->courseid)->get_cm($this->data->cmid); + $icon = activity_icon::from_cm_info($cminfo); return array( 'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php', array('id' => $this->data->cmid)))->out(false), 'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false), - 'icon' => \html_writer::img( - $iconurl, - get_string('pluginname', $this->data->modname), - ['title' => get_string('pluginname', $this->data->modname), 'class' => "icon $iconclass"] - ), + 'icon' => $renderer->render($icon), 'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER), - 'branded' => $isbranded, + 'branded' => $icon->is_branded(), ); } diff --git a/completion/classes/manager.php b/completion/classes/manager.php index 6055c6e6b68..300143bfd2b 100644 --- a/completion/classes/manager.php +++ b/completion/classes/manager.php @@ -26,13 +26,12 @@ namespace core_completion; use core\context; +use core\output\local\properties\iconsize; +use core_course\output\activity_icon; use stdClass; use context_course; use cm_info; -use tabobject; -use lang_string; use moodle_url; -defined('MOODLE_INTERNAL') || die; /** * Bulk activity completion manager class @@ -105,6 +104,7 @@ class manager { * @return array */ public function get_activities($cmids, $withcompletiondetails = false) { + $output = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); $moduleinfo = get_fast_modinfo($this->courseid); $activities = []; foreach ($cmids as $cmid) { @@ -112,11 +112,16 @@ class manager { if (!$mod->uservisible) { continue; } + + $icon = activity_icon::from_cm_info($mod) + ->set_icon_size(iconsize::SIZE5); + $moduleobject = new stdClass(); $moduleobject->cmid = $cmid; $moduleobject->modname = $mod->get_formatted_name(); $moduleobject->icon = $mod->get_icon_url()->out(); $moduleobject->url = $mod->url; + $moduleobject->activityicon = $icon->export_for_template($output); $moduleobject->canmanage = $withcompletiondetails && self::can_edit_bulk_completion($this->courseid, $mod); // Get activity completion information. diff --git a/course/classes/output/activity_icon.php b/course/classes/output/activity_icon.php new file mode 100644 index 00000000000..6c0690b4de0 --- /dev/null +++ b/course/classes/output/activity_icon.php @@ -0,0 +1,233 @@ +. + +namespace core_course\output; + +use core\component; +use core\output\local\properties\iconsize; +use core\output\renderable; +use core\output\renderer_base; +use core\output\templatable; +use core\url; +use cm_info; + +/** + * Class activity_icon + * + * @package core_course + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_icon implements renderable, templatable { + + /** @var string optional text title */ + protected string $title = ''; + + /** @var string Extra container classes. */ + protected string $extraclasses = ''; + + /** @var bool Determine if the icon must be colored or not. */ + protected bool $colourize = true; + + /** @var iconsize set the icon size */ + protected iconsize $iconsize = iconsize::UNDEFINED; + + /** @var url The icon URL. */ + protected url $iconurl; + + /** @var string The module purpose */ + protected string $purpose; + + /** @var bool is branded */ + protected bool $isbranded; + + /** + * Constructor. + * + * @param string $modname the module name + */ + protected function __construct( + /** @var string the module name */ + protected string $modname, + ) { + $this->isbranded = component_callback('mod_' . $this->modname, 'is_branded', [], false) ? true : false; + $this->purpose = plugin_supports('mod', $this->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER); + } + + /** + * Create an activity icon from a cm_info object. + * + * @param cm_info $cm + * @return self + */ + public static function from_cm_info(cm_info $cm): self { + $result = new self($cm->modname); + $result->iconurl = $cm->get_icon_url(); + return $result; + } + + /** + * Create an activity icon from a module name. + * + * @param string $modname + * @return self + */ + public static function from_modname(string $modname): self { + return new self($modname); + } + + /** + * Set the title. + * + * @param string $title + * @return self + */ + public function set_title(string $title): self { + $this->title = $title; + return $this; + } + + /** + * Set the colourize icon value. + * + * @param bool $colourize + * @return self + */ + public function set_colourize(bool $colourize): self { + $this->colourize = $colourize; + return $this; + } + + /** + * Set the extra classes. + * + * @param string $extraclasses + * @return self + */ + public function set_extra_classes(string $extraclasses): self { + $this->extraclasses = $extraclasses; + return $this; + } + + /** + * Set the icon size. + * + * @param iconsize $iconsize + * @return self + */ + public function set_icon_size(iconsize $iconsize): self { + $this->iconsize = $iconsize; + return $this; + } + + #[\Override] + public function export_for_template(renderer_base $output): array { + if (!isset($this->iconurl)) { + $this->iconurl = $this->get_icon_url($output); + } + $needfiltering = $this->colourize && $this->iconurl->get_param('filtericon'); + $iconclass = $needfiltering ? '' : 'nofilter'; + + $data = [ + 'icon' => $this->iconurl, + 'iconclass' => $iconclass, + 'modname' => $this->modname, + 'pluginname' => get_string('pluginname', 'mod_' . $this->modname), + 'purpose' => $this->purpose, + 'branded' => $this->isbranded, + 'extraclasses' => $this->extraclasses . $this->iconsize->classes(), + ]; + + if (!empty($this->title)) { + $data['title'] = $this->title; + } + + return $data; + } + + /** + * Get the icon URL. + * + * @param renderer_base $output + * @return url + */ + public function get_icon_url(renderer_base $output): url { + $icon = $output->image_url('monologo', $this->modname); + // Legacy activity modules may only have an `icon` icon instead of a `monologo` icon. + $ismonologo = component::has_monologo_icon('mod', $this->modname); + + if ($ismonologo) { + // The filtericon param is used to determine if the icon should be colored or not. + // The name of the param is not colorize to preserve backward compatibility. + $icon->param('filtericon', 1); + } + return $icon; + } + + /** + * Check if the module is branded. + * + * @return bool + */ + public function is_branded(): bool { + return $this->isbranded; + } + + /** + * Get the colourize icon value. + * + * @return bool + */ + public function get_colourize(): bool { + return $this->colourize; + } + + /** + * Get the title text. + * + * @return string + */ + public function get_title(): string { + return $this->title; + } + + /** + * Get the extra classes. + * + * @return string + */ + public function get_extra_classes(): string { + return $this->extraclasses; + } + + /** + * Get the icon size. + * + * @return iconsize + */ + public function get_icon_size(): iconsize { + return $this->iconsize; + } + + /** + * Get the activity purpose. + * + * @return string + */ + public function get_purpose(): string { + return $this->purpose; + } +} diff --git a/course/classes/output/bulk_activity_completion_renderer.php b/course/classes/output/bulk_activity_completion_renderer.php index 90bd2e189a1..a5bb193d870 100644 --- a/course/classes/output/bulk_activity_completion_renderer.php +++ b/course/classes/output/bulk_activity_completion_renderer.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . use core_completion\manager; +use core_course\output\activity_icon; defined('MOODLE_INTERNAL') || die; @@ -32,7 +33,7 @@ class core_course_bulk_activity_completion_renderer extends plugin_renderer_base /** * Render the bulk completion tab. * - * @param Array|stdClass $data the context data to pass to the template. + * @param array|stdClass $data the context data to pass to the template. * @return bool|string */ public function bulkcompletion($data) { @@ -71,6 +72,8 @@ class core_course_bulk_activity_completion_renderer extends plugin_renderer_base $module->open = false; } + $module->activityicon = activity_icon::from_modname($module->name)->export_for_template($this); + $moduleform = manager::get_module_form($module->name, $course); if ($moduleform) { $module->formhtml = $modform->render(); diff --git a/course/format/classes/output/local/overview/overviewpage.php b/course/format/classes/output/local/overview/overviewpage.php index 72f4a8e9fb5..9a43021c1dc 100644 --- a/course/format/classes/output/local/overview/overviewpage.php +++ b/course/format/classes/output/local/overview/overviewpage.php @@ -19,9 +19,8 @@ namespace core_courseformat\output\local\overview; use core\context\course as context_course; use core\output\named_templatable; use core\output\renderable; -use core\output\notification; -use core\plugin_manager; use core\url; +use core_course\output\activity_icon; use core_collator; use stdClass; @@ -155,10 +154,12 @@ class overviewpage implements renderable, named_templatable { * @return string The HTML string for the activity overview icon. */ private function get_activity_overview_icon(\renderer_base $output, string $modname): string { + // Resource is a generic term for all modules with MOD_ARCHETYPE_RESOURCE. + // We group all of them under the mod_page icon. if ($modname === 'resource') { - return $output->pix_icon('monologo', '', 'mod_page', ['class' => 'icon iconsize-medium']); + $modname = 'page'; } - return $output->pix_icon('monologo', '', "mod_$modname", ['class' => 'icon iconsize-medium']); + return $output->render(activity_icon::from_modname($modname)); } /** diff --git a/course/templates/activity_icon.mustache b/course/templates/activity_icon.mustache new file mode 100644 index 00000000000..5929a09de7a --- /dev/null +++ b/course/templates/activity_icon.mustache @@ -0,0 +1,44 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_course/activity_icon + + Displays an activity plugin icon. + + Example context (json): + { + "icon": "../../../pix/help.svg", + "iconclass": "", + "purpose": "content", + "branded": 0, + "pluginname": "File", + "title": "File", + "extraclasses": "mycustomclass" + } +}} +
+ {{#title}}{{#cleanstr}} activityicon, moodle, {{{pluginname}}} {{/cleanstr}}{{/title}} +
diff --git a/course/templates/activityinstance.mustache b/course/templates/activityinstance.mustache index d10c384366d..2eb1950f474 100644 --- a/course/templates/activityinstance.mustache +++ b/course/templates/activityinstance.mustache @@ -36,27 +36,32 @@
- -
-
+
+
{{#completionstatus.icon}} {{{completionstatus.icon}}} {{/completionstatus.icon}} {{^completionstatus.icon}} - +   {{/completionstatus.icon}}
-
+
{{{completionstatus.string}}}
diff --git a/course/templates/bulkactivitycompletion.mustache b/course/templates/bulkactivitycompletion.mustache index 7a5a35a5b7d..6fb832a3428 100644 --- a/course/templates/bulkactivitycompletion.mustache +++ b/course/templates/bulkactivitycompletion.mustache @@ -50,7 +50,7 @@
- +
@@ -64,7 +64,7 @@
- +

{{{name}}}

diff --git a/course/templates/defaultactivitycompletion.mustache b/course/templates/defaultactivitycompletion.mustache index ee17fa6955e..694f1c86f49 100644 --- a/course/templates/defaultactivitycompletion.mustache +++ b/course/templates/defaultactivitycompletion.mustache @@ -48,7 +48,9 @@ {{#canmanage}} {{