-

+
{{#pluginname}}
diff --git a/lib/classes/component.php b/lib/classes/component.php
index fb6fc9012b0..65e63a88369 100644
--- a/lib/classes/component.php
+++ b/lib/classes/component.php
@@ -1282,4 +1282,22 @@ $cache = '.var_export($cache, true).';
}
return $componentnames;
}
+
+ /**
+ * Checks for the presence of monologo icons within a plugin.
+ *
+ * Only checks monologo icons in PNG and SVG formats as they are
+ * formats that can have transparent background.
+ *
+ * @param string $plugintype The plugin type.
+ * @param string $pluginname The plugin name.
+ * @return bool True if the plugin has a monologo icon
+ */
+ public static function has_monologo_icon(string $plugintype, string $pluginname): bool {
+ $plugindir = core_component::get_plugin_directory($plugintype, $pluginname);
+ if ($plugindir === null) {
+ return false;
+ }
+ return file_exists("$plugindir/pix/monologo.svg") || file_exists("$plugindir/pix/monologo.png");
+ }
}
diff --git a/lib/modinfolib.php b/lib/modinfolib.php
index d0eb3e29b8b..bb5b3bc3543 100644
--- a/lib/modinfolib.php
+++ b/lib/modinfolib.php
@@ -1763,7 +1763,15 @@ class cm_info implements IteratorAggregate {
}
/**
- * @param moodle_core_renderer $output Output render to use, or null for default (global)
+ * Fetch the module's icon URL.
+ *
+ * This function fetches the course module instance's icon URL.
+ * This method adds a `filtericon` parameter in the URL when rendering the monologo version of the course module icon or when
+ * the plugin declares, via its `filtericon` custom data, that the icon needs to be filtered.
+ * This additional information can be used by plugins when rendering the module icon to determine whether to apply
+ * CSS filtering to the icon.
+ *
+ * @param core_renderer $output Output render to use, or null for default (global)
* @return moodle_url Icon URL for a suitable icon to put beside this cm
*/
public function get_icon_url($output = null) {
@@ -1773,6 +1781,7 @@ class cm_info implements IteratorAggregate {
$output = $OUTPUT;
}
+ $ismonologo = false;
if (!empty($this->iconurl)) {
// Support modules setting their own, external, icon image.
$icon = $this->iconurl;
@@ -1792,6 +1801,17 @@ class cm_info implements IteratorAggregate {
}
} else {
$icon = $output->image_url('monologo', $this->modname);
+ // Activity modules may only have an `icon` icon instead of a `monologo` icon.
+ // So we need to determine if the module really has a `monologo` icon.
+ $ismonologo = core_component::has_monologo_icon('mod', $this->modname);
+ }
+
+ // Determine whether the icon will be filtered in the CSS.
+ // This can be controlled by the module by declaring a 'filtericon' custom data.
+ // If the 'filtericon' custom data is not set, icon filtering will be determined whether the module has a `monologo` icon.
+ $filtericon = $this->customdata['filtericon'] ?? $ismonologo;
+ if ($filtericon) {
+ $icon->param('filtericon', 1);
}
return $icon;
}
diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php
index 2d0cc23632c..03ffe440714 100644
--- a/lib/tests/component_test.php
+++ b/lib/tests/component_test.php
@@ -861,4 +861,19 @@ class component_test extends advanced_testcase {
$this->assertContains('tool_usertours', $componentnames);
$this->assertContains('core_favourites', $componentnames);
}
+
+ /**
+ * Test for monologo icons check in plugins.
+ *
+ * @covers core_component::has_monologo_icon
+ * @return void
+ */
+ public function test_has_monologo_icon(): void {
+ // The Forum activity plugin has monologo icons.
+ $this->assertTrue(core_component::has_monologo_icon('mod', 'forum'));
+ // The core H5P subsystem doesn't have monologo icons.
+ $this->assertFalse(core_component::has_monologo_icon('core', 'h5p'));
+ // The function will return false for a non-existent component.
+ $this->assertFalse(core_component::has_monologo_icon('randomcomponent', 'h5p'));
+ }
}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index 36463e05d6e..cba147fd7aa 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -5,6 +5,17 @@ information provided here is intended especially for developers.
* Added 'extrainfo' in the DB options config. Its extra information for the DB driver, e.g. SQL Server,
has additional configuration according to its environment, which the administrator can specify to alter and
override any connection options.
+* New \core_component::has_monologo_icon() that determines whether a plugin has monologo icons. This can be used to
+ determine whether to apply CSS filtering to the plugin's icon when rendering it.
+* \cm_info::get_icon_url() resolves the icon's file type and adds a `filtericon` parameter in the URL when rendering the monologo
+ version of the course module icon or when the plugin declares, via its `filtericon` custom data, that the icon needs to be
+ filtered. This additional information can be used by plugins when rendering the module icon to determine whether to apply
+ CSS filtering to the icon.
+* Activity plugins displaying activity module icons using \cm_info::get_icon_url() can declare the `filtericon` custom data in their
+ `get_coursemodule_info()` callback. If set, this will be used by \cm_info::get_icon_url() to set the icon URL's `filtericon`
+ parameter. This information can be used by the plugins when enclosing the icons in `.activityiconcontainer .icon` or
+ `.activityiconcontainer .activityicon` containers to determine whether CSS filtering should be applied to the icon. If the icon
+ needs to be rendered as is and not whitened out, the `.nofilter` CSS class needs to be applied to the icon.
=== 4.1.2 ===
diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php
index 1ced06aeea0..8c1728fc95b 100644
--- a/mod/lti/locallib.php
+++ b/mod/lti/locallib.php
@@ -2387,7 +2387,15 @@ function lti_get_configured_types($courseid, $sectionreturn = 0) {
$type->help = clean_param($trimmeddescription, PARAM_NOTAGS);
$type->helplink = get_string('modulename_shortcut_link', 'lti');
}
- $type->icon = html_writer::empty_tag('img', ['src' => get_tool_type_icon_url($ltitype), 'alt' => '', 'class' => 'icon']);
+
+ $iconurl = get_tool_type_icon_url($ltitype);
+ $iconclass = '';
+ if ($iconurl !== $OUTPUT->image_url('monologo', 'lti')->out()) {
+ // Do not filter the icon if it is not the default LTI activity icon.
+ $iconclass = 'nofilter';
+ }
+ $type->icon = html_writer::empty_tag('img', ['src' => $iconurl, 'alt' => '', 'class' => "icon $iconclass"]);
+
$type->link = new moodle_url('/course/modedit.php', array('add' => 'lti', 'return' => 0, 'course' => $courseid,
'sr' => $sectionreturn, 'typeid' => $ltitype->id));
$types[] = $type;
diff --git a/mod/url/lib.php b/mod/url/lib.php
index dae393ac800..256c3eb516d 100644
--- a/mod/url/lib.php
+++ b/mod/url/lib.php
@@ -245,6 +245,8 @@ function url_get_coursemodule_info($coursemodule) {
}
$info->customdata['display'] = $display;
+ // The icon will be filtered if it will be the default module icon.
+ $info->customdata['filtericon'] = empty($info->icon);
return $info;
}
diff --git a/theme/boost/classes/output/core_renderer.php b/theme/boost/classes/output/core_renderer.php
index d5123d02749..7f6520b812d 100644
--- a/theme/boost/classes/output/core_renderer.php
+++ b/theme/boost/classes/output/core_renderer.php
@@ -161,8 +161,13 @@ class core_renderer extends \core_renderer {
$heading = format_string($this->page->course->fullname, true, ['context' => $context]);
} else {
$heading = $this->page->cm->get_formatted_name();
- $imagedata = html_writer::img($this->page->cm->get_icon_url()->out(false), '',
- ['class' => 'icon activityicon', 'aria-hidden' => 'true']);
+ $iconurl = $this->page->cm->get_icon_url();
+ $iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
+ $iconattrs = [
+ 'class' => "icon activityicon $iconclass",
+ 'aria-hidden' => 'true'
+ ];
+ $imagedata = html_writer::img($iconurl->out(false), '', $iconattrs);
$purposeclass = plugin_supports('mod', $this->page->activityname, FEATURE_MOD_PURPOSE);
$purposeclass .= ' activityiconcontainer';
$purposeclass .= ' modicon_' . $this->page->activityname;
diff --git a/theme/boost/scss/moodle/icons.scss b/theme/boost/scss/moodle/icons.scss
index 17bf8fe0cae..fa6baf1999c 100644
--- a/theme/boost/scss/moodle/icons.scss
+++ b/theme/boost/scss/moodle/icons.scss
@@ -151,7 +151,9 @@ $iconsizes: map-merge((
background-color: $value;
.activityicon,
.icon {
- filter: brightness(0) invert(1);
+ &:not(.nofilter) {
+ filter: brightness(0) invert(1);
+ }
}
}
}
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index b1b96fab2c3..e7e8fe341f7 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -12506,38 +12506,38 @@ blockquote {
.activityiconcontainer.administration {
background-color: #5d63f6; }
- .activityiconcontainer.administration .activityicon,
- .activityiconcontainer.administration .icon {
+ .activityiconcontainer.administration .activityicon:not(.nofilter),
+ .activityiconcontainer.administration .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.assessment {
background-color: #eb66a2; }
- .activityiconcontainer.assessment .activityicon,
- .activityiconcontainer.assessment .icon {
+ .activityiconcontainer.assessment .activityicon:not(.nofilter),
+ .activityiconcontainer.assessment .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.collaboration {
background-color: #f7634d; }
- .activityiconcontainer.collaboration .activityicon,
- .activityiconcontainer.collaboration .icon {
+ .activityiconcontainer.collaboration .activityicon:not(.nofilter),
+ .activityiconcontainer.collaboration .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.communication {
background-color: #11a676; }
- .activityiconcontainer.communication .activityicon,
- .activityiconcontainer.communication .icon {
+ .activityiconcontainer.communication .activityicon:not(.nofilter),
+ .activityiconcontainer.communication .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.content {
background-color: #399be2; }
- .activityiconcontainer.content .activityicon,
- .activityiconcontainer.content .icon {
+ .activityiconcontainer.content .activityicon:not(.nofilter),
+ .activityiconcontainer.content .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.interface {
background-color: #a378ff; }
- .activityiconcontainer.interface .activityicon,
- .activityiconcontainer.interface .icon {
+ .activityiconcontainer.interface .activityicon:not(.nofilter),
+ .activityiconcontainer.interface .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
:root {
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index 63c38fdc2e2..9cbf8056dba 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -12506,38 +12506,38 @@ blockquote {
.activityiconcontainer.administration {
background-color: #5d63f6; }
- .activityiconcontainer.administration .activityicon,
- .activityiconcontainer.administration .icon {
+ .activityiconcontainer.administration .activityicon:not(.nofilter),
+ .activityiconcontainer.administration .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.assessment {
background-color: #eb66a2; }
- .activityiconcontainer.assessment .activityicon,
- .activityiconcontainer.assessment .icon {
+ .activityiconcontainer.assessment .activityicon:not(.nofilter),
+ .activityiconcontainer.assessment .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.collaboration {
background-color: #f7634d; }
- .activityiconcontainer.collaboration .activityicon,
- .activityiconcontainer.collaboration .icon {
+ .activityiconcontainer.collaboration .activityicon:not(.nofilter),
+ .activityiconcontainer.collaboration .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.communication {
background-color: #11a676; }
- .activityiconcontainer.communication .activityicon,
- .activityiconcontainer.communication .icon {
+ .activityiconcontainer.communication .activityicon:not(.nofilter),
+ .activityiconcontainer.communication .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.content {
background-color: #399be2; }
- .activityiconcontainer.content .activityicon,
- .activityiconcontainer.content .icon {
+ .activityiconcontainer.content .activityicon:not(.nofilter),
+ .activityiconcontainer.content .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
.activityiconcontainer.interface {
background-color: #a378ff; }
- .activityiconcontainer.interface .activityicon,
- .activityiconcontainer.interface .icon {
+ .activityiconcontainer.interface .activityicon:not(.nofilter),
+ .activityiconcontainer.interface .icon:not(.nofilter) {
filter: brightness(0) invert(1); }
:root {