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/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/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/lib/classes/output/local/properties/iconsize.php b/lib/classes/output/local/properties/iconsize.php new file mode 100644 index 00000000000..5a08aefa77b --- /dev/null +++ b/lib/classes/output/local/properties/iconsize.php @@ -0,0 +1,46 @@ +. + +namespace core\output\local\properties; + +/** + * Icon sizes property enum. + * + * @package core + * @copyright 2024 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +enum iconsize: string { + case UNDEFINED = ''; + case SIZE0 = 'icon-size-0'; + case SIZE1 = 'icon-size-1'; + case SIZE2 = 'icon-size-2'; + case SIZE3 = 'icon-size-3'; + case SIZE4 = 'icon-size-4'; + case SIZE5 = 'icon-size-5'; + case SIZE6 = 'icon-size-6'; + case SIZE7 = 'icon-size-7'; + + + /** + * Returns the CSS classes for the property based on its type. + * + * @return string The CSS classes. + */ + public function classes(): string { + return ' ' . $this->value; + } +} diff --git a/theme/boost/scss/moodle/icons.scss b/theme/boost/scss/moodle/icons.scss index 067f7b628d7..8ed70c49358 100644 --- a/theme/boost/scss/moodle/icons.scss +++ b/theme/boost/scss/moodle/icons.scss @@ -195,13 +195,10 @@ img.icon { // Revisit these styles when MDL-78284 lands with new icons. // Icon container will be removed and icons will be used directly. .activityiconcontainer { - width: $activity-iconcontainer-width; - height: $activity-iconcontainer-height; display: inline-flex; justify-content: center; align-items: center; border-radius: 4px; - padding: 0.7rem; .activityicon, .icon { margin: 0; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index b65ab40c5b9..c9d858bcd01 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -28838,13 +28838,10 @@ img.icon { } .activityiconcontainer { - width: 52px; - height: 52px; display: inline-flex; justify-content: center; align-items: center; border-radius: 4px; - padding: 0.7rem; } .activityiconcontainer .activityicon, .activityiconcontainer .icon { diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index fddb47c17df..d01ce8baa9a 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -28838,13 +28838,10 @@ img.icon { } .activityiconcontainer { - width: 52px; - height: 52px; display: inline-flex; justify-content: center; align-items: center; border-radius: 4px; - padding: 0.7rem; } .activityiconcontainer .activityicon, .activityiconcontainer .icon {