Merge branch 'MDL-77105-master-4' of https://github.com/junpataleta/moodle

This commit is contained in:
Jake Dallimore 2023-03-28 11:42:29 +08:00
commit efac6d72cf
16 changed files with 138 additions and 38 deletions

View File

@ -51,17 +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';
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(
get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url(),
$iconurl,
get_string('pluginname', $this->data->modname),
['title' => get_string('pluginname', $this->data->modname), 'class' => 'icon']
['title' => get_string('pluginname', $this->data->modname), 'class' => "icon $iconclass"]
),
'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER)
'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
);
}

View File

@ -57,7 +57,7 @@
<div class="activityiconcontainer small {{purpose}} courseicon align-self-top align-self-center mx-3 mb-1 mb-sm-0 text-nowrap">
{{#icon}}
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon ">
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}

View File

@ -62,12 +62,15 @@ class event_icon_exporter extends exporter {
$isgroupevent = ($group && !empty($groupid));
$isuserevent = ($user && !empty($userid));
$iconurl = '';
$iconclass = '';
if ($isactivityevent) {
$key = 'monologo';
$component = $coursemodule->get('modname');
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url()->out(false);
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$iconurl = $iconurl->out(false);
if (get_string_manager()->string_exists($event->get_type(), $component)) {
$alttext = get_string($event->get_type(), $component);
} else {
@ -121,6 +124,7 @@ class event_icon_exporter extends exporter {
$data->component = $component;
$data->alttext = $alttext;
$data->iconurl = $iconurl;
$data->iconclass = $iconclass;
parent::__construct($data, $related);
}
@ -136,6 +140,7 @@ class event_icon_exporter extends exporter {
'component' => ['type' => PARAM_TEXT],
'alttext' => ['type' => PARAM_TEXT],
'iconurl' => ['type' => PARAM_TEXT],
'iconclass' => ['type' => PARAM_TEXT],
];
}

View File

@ -26,6 +26,7 @@ namespace core_course\local\repository;
defined('MOODLE_INTERNAL') || die();
use core_component;
use core_course\local\entity\content_item;
use core_course\local\entity\lang_string_title;
use core_course\local\entity\string_title;
@ -195,12 +196,20 @@ class content_item_readonly_repository implements content_item_readonly_reposito
$archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
$purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
$icon = 'monologo';
// Quick check for monologo icons.
// Plugins that don't have monologo icons will be displayed as is and CSS filter will not be applied.
$hasmonologoicons = core_component::has_monologo_icon('mod', $mod->name);
$iconclass = '';
if (!$hasmonologoicons) {
$iconclass = 'nofilter';
}
$contentitem = new content_item(
$mod->id,
$mod->name,
new lang_string_title("modulename", $mod->name),
new \moodle_url('/course/mod.php', ['id' => $course->id, 'add' => $mod->name]),
$OUTPUT->pix_icon('monologo', '', $mod->name, ['class' => 'icon activityicon']),
$OUTPUT->pix_icon($icon, '', $mod->name, ['class' => "activityicon $iconclass"]),
$help,
$archetype,
'mod_' . $mod->name,

View File

@ -102,9 +102,12 @@ class cmname implements named_templatable, renderable {
return [];
}
$iconurl = $mod->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$data = [
'url' => $mod->url,
'icon' => $mod->get_icon_url(),
'icon' => $iconurl,
'iconclass' => $iconclass,
'modname' => $mod->modname,
'textclasses' => $displayoptions['textclasses'] ?? '',
'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),

View File

@ -25,6 +25,7 @@
{
"url": "#",
"icon": "../../../pix/help.svg",
"iconclass": "",
"pluginname": "File",
"textclasses": "",
"purpose": "content",
@ -47,7 +48,7 @@
<div class="activity-instance d-flex flex-column">
<div class="activitytitle media {{textclasses}} modtype_{{modname}} position-relative align-self-start">
<div class="activityiconcontainer {{purpose}} courseicon align-self-start mr-3">
<img src="{{{icon}}}" class="activityicon " alt="{{{modname}}} icon">
<img src="{{{icon}}}" class="activityicon {{iconclass}}" alt="{{{modname}}} icon">
</div>
<div class="media-body align-self-center">
{{#pluginname}}

View File

@ -1337,4 +1337,22 @@ $cache = '.var_export($cache, true).';
public static function get_core_api_names(): array {
return array_keys(self::get_core_apis());
}
/**
* 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");
}
}

View File

@ -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;
}

View File

@ -931,4 +931,19 @@ class component_test extends advanced_testcase {
$this->assertLessThanOrEqual($attributes->allowedlevel2, $attributes->allowedspread, $message);
}
}
/**
* 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'));
}
}

View File

@ -103,6 +103,17 @@ information provided here is intended especially for developers.
Please use the new core_grades_get_enrolled_users_for_selector external function instead.
* The external function core_grades_get_groups_for_search_widget is now deprecated.
Please use the new core_grades_get_groups_for_selector external function instead.
* 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 ===

View File

@ -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;

View File

@ -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;
}

View File

@ -160,8 +160,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;

View File

@ -157,7 +157,9 @@ $iconsizes: map-merge((
background-color: $value;
.activityicon,
.icon {
filter: brightness(0) invert(1);
&:not(.nofilter) {
filter: brightness(0) invert(1);
}
}
}
}

View File

@ -26066,48 +26066,48 @@ 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);
}

View File

@ -26066,48 +26066,48 @@ 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);
}