Merge branch 'MDL-78207-master' of https://github.com/sarjona/moodle
This commit is contained in:
@@ -129,4 +129,4 @@ Feature: Add customised file types
|
||||
And I expand all fieldsets
|
||||
And I set the field "Show type" to "1"
|
||||
And I press "Save and return to course"
|
||||
Then I should see "Froggy file"
|
||||
Then I should see "FROG" in the "div.activitybadge" "css_element"
|
||||
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
use core_course\external\course_summary_exporter;
|
||||
use core_external\external_api;
|
||||
use core_external\external_description;
|
||||
use core_external\external_files;
|
||||
use core_external\external_format_value;
|
||||
use core_external\external_function_parameters;
|
||||
@@ -268,6 +269,10 @@ class core_course_external extends external_api {
|
||||
$module['indent'] = $cm->indent;
|
||||
$module['onclick'] = $cm->onclick;
|
||||
$module['afterlink'] = $cm->afterlink;
|
||||
$activitybadgedata = $cm->get_activitybadge();
|
||||
if (!empty($activitybadgedata)) {
|
||||
$module['activitybadge'] = $activitybadgedata;
|
||||
}
|
||||
$module['customdata'] = json_encode($cm->customdata);
|
||||
$module['completion'] = $cm->completion;
|
||||
$module['downloadcontent'] = $cm->downloadcontent;
|
||||
@@ -461,6 +466,7 @@ class core_course_external extends external_api {
|
||||
'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
|
||||
'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
|
||||
VALUE_OPTIONAL),
|
||||
'activitybadge' => self::get_activitybadge_structure(),
|
||||
'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
|
||||
'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
|
||||
VALUE_OPTIONAL),
|
||||
@@ -533,6 +539,60 @@ class core_course_external extends external_api {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of activitybadge data.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
protected static function get_activitybadge_structure(): external_description {
|
||||
return new external_single_structure(
|
||||
[
|
||||
'badgecontent' => new external_value(
|
||||
PARAM_TEXT,
|
||||
'The content to be displayed in the activity badge',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'badgestyle' => new external_value(
|
||||
PARAM_TEXT,
|
||||
'The style for the activity badge',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'badgeurl' => new external_value(
|
||||
PARAM_URL,
|
||||
'An optional URL to redirect the user when the activity badge is clicked',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'badgeelementid' => new external_value(
|
||||
PARAM_ALPHANUMEXT,
|
||||
'An optional id in case the module wants to add some code for the activity badge',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'badgeextraattributes' => new external_multiple_structure(
|
||||
new external_single_structure(
|
||||
[
|
||||
'name' => new external_value(
|
||||
PARAM_TEXT,
|
||||
'The attribute name',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'value' => new external_value(
|
||||
PARAM_TEXT,
|
||||
'The attribute value',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
],
|
||||
'Each of the attribute names and values',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
'An optional array of extra HTML attributes to add to the badge element',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
],
|
||||
'Activity badge to display near the name',
|
||||
VALUE_OPTIONAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_courseformat\output;
|
||||
|
||||
use cm_info;
|
||||
use core_courseformat\output\local\courseformat_named_templatable;
|
||||
use core\output\named_templatable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Base class to render an activity badge.
|
||||
*
|
||||
* Plugins can extend this class and override some methods to customize the content to be displayed in the activity badge.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2023 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class activitybadge implements named_templatable, \renderable {
|
||||
use courseformat_named_templatable;
|
||||
|
||||
/** @var array Badge defined styles. */
|
||||
public const STYLES = [
|
||||
'none' => 'badge-none',
|
||||
'dark' => 'badge-dark',
|
||||
'danger' => 'badge-danger',
|
||||
'warning' => 'badge-warning',
|
||||
'info' => 'badge-info',
|
||||
];
|
||||
|
||||
/** @var cm_info The course module information. */
|
||||
protected $cminfo = null;
|
||||
|
||||
/** @var string The content to be displayed in the activity badge. */
|
||||
protected $content = null;
|
||||
|
||||
/** @var string The style for the activity badge. */
|
||||
protected $style = self::STYLES['none'];
|
||||
|
||||
/** @var \moodle_url An optional URL to redirect the user when the activity badge is clicked. */
|
||||
protected $url = null;
|
||||
|
||||
/** @var string An optional element id in case the module wants to add some code for the activity badge (events, CSS...). */
|
||||
protected $elementid = null;
|
||||
|
||||
/**
|
||||
* @var array An optional array of extra HTML attributes to add to the badge element (for example, data attributes).
|
||||
* The format for this array is [['name' => 'attr1', 'value' => 'attrval1'], ['name' => 'attr2', 'value' => 'attrval2']].
|
||||
*/
|
||||
protected $extraattributes = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cm_info $cminfo The course module information.
|
||||
*/
|
||||
public function __construct(cm_info $cminfo) {
|
||||
$this->cminfo = $cminfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output typically, the renderer that's calling this function
|
||||
* @return stdClass data context for a mustache template
|
||||
*/
|
||||
final public function export_for_template(renderer_base $output): stdClass {
|
||||
$this->update_content();
|
||||
if (empty($this->content)) {
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
$data = (object)[
|
||||
'badgecontent' => $this->content,
|
||||
'badgestyle' => $this->style,
|
||||
];
|
||||
|
||||
if (!empty($this->url)) {
|
||||
$data->badgeurl = $this->url->out();
|
||||
}
|
||||
|
||||
if (!empty($this->elementid)) {
|
||||
$data->badgeelementid = $this->elementid;
|
||||
}
|
||||
|
||||
if (!empty($this->extraattributes)) {
|
||||
$data->badgeextraattributes = $this->extraattributes;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of activityclass for the given course module, in case it implements it.
|
||||
*
|
||||
* @param cm_info $cminfo
|
||||
* @return self|null An instance of activityclass for the given module or null if the module doesn't implement it.
|
||||
*/
|
||||
final public static function create_instance(cm_info $cminfo): ?self {
|
||||
$classname = '\mod_' . $cminfo->modname . '\output\courseformat\activitybadge';
|
||||
if (!class_exists($classname)) {
|
||||
return null;
|
||||
}
|
||||
return new $classname($cminfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called before exporting the template.
|
||||
*
|
||||
* It should be implemented by any module extending this class and will be in charge of updating any of the class attributes
|
||||
* with the proper information that will be displayed in the activity badge (like the content or the badge style).
|
||||
*/
|
||||
abstract protected function update_content(): void;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ use core_availability\info_module;
|
||||
use core_completion\cm_completion_details;
|
||||
use core_course\output\activity_information;
|
||||
use core_courseformat\base as course_format;
|
||||
use core_courseformat\output\activitybadge;
|
||||
use core_courseformat\output\local\courseformat_named_templatable;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
@@ -186,6 +187,12 @@ class cm implements named_templatable, renderable {
|
||||
);
|
||||
$data->altcontent = (empty($altcontent)) ? false : $altcontent;
|
||||
$data->afterlink = $this->mod->afterlink;
|
||||
|
||||
$activitybadgedata = $this->mod->get_activitybadge($output);
|
||||
if (!empty($activitybadgedata)) {
|
||||
$data->activitybadge = $activitybadgedata;
|
||||
}
|
||||
|
||||
return !empty($data->altcontent);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,23 @@
|
||||
"displayvalue" : "<a class=\"aalink\" href=\"#\"><span class=\"instancename\">Activity example</span></a>"
|
||||
},
|
||||
"hasname": "true",
|
||||
"afterlink": "<span class=\"badge badge-primary\">30 unread messages</span>",
|
||||
"afterlink": "<span class=\"resourcelinkdetails\">24.7 KB · Uploaded 26/05/23, 16:29</span>",
|
||||
"activitybadge": {
|
||||
"badgecontent": "PDF",
|
||||
"badgestyle": "badge-none",
|
||||
"badgeurl": "http://example.org/help",
|
||||
"badgeelementid": "myelementid",
|
||||
"badgeextraattributes": [
|
||||
{
|
||||
"name": "data-el1name",
|
||||
"value": "el1value"
|
||||
},
|
||||
{
|
||||
"name": "data-el2name",
|
||||
"value": "el2value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hasextras": true,
|
||||
"extras": ["<span class=\"badge badge-secondary\">[extras]</span>"],
|
||||
"activityinfo": {
|
||||
@@ -69,11 +85,12 @@
|
||||
{{> core_courseformat/local/content/cm/cmname }}
|
||||
{{/ core_courseformat/local/content/cm/cmname }}
|
||||
{{/cmname}}
|
||||
{{#afterlink}}
|
||||
<div class="afterlink d-flex align-items-center ml-3">
|
||||
{{{afterlink}}}
|
||||
</div>
|
||||
{{/afterlink}}
|
||||
|
||||
{{#activitybadge}}
|
||||
{{$ core_courseformat/local/content/cm/activitybadge }}
|
||||
{{> core_courseformat/local/content/cm/activitybadge }}
|
||||
{{/ core_courseformat/local/content/cm/activitybadge }}
|
||||
{{/activitybadge}}
|
||||
|
||||
{{#activityinfo}}
|
||||
<div class="activity-info mt-1 mt-md-0">
|
||||
@@ -125,3 +142,9 @@
|
||||
{{/ core_courseformat/local/content/cm/availability }}
|
||||
{{/modavailability}}
|
||||
</div>
|
||||
|
||||
{{#afterlink}}
|
||||
<div class="afterlink d-flex align-items-center mt-3">
|
||||
{{{afterlink}}}
|
||||
</div>
|
||||
{{/afterlink}}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{{!
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/local/content/cm/activitybadge
|
||||
|
||||
Container to display activity badge information such as:
|
||||
- Unread messages for forums
|
||||
- File types for resources
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"badgecontent": "PDF",
|
||||
"badgestyle": "badge-none",
|
||||
"badgeurl": "http://example.org/help",
|
||||
"badgeelementid": "myelementid",
|
||||
"badgeextraattributes": [
|
||||
{
|
||||
"name": "data-el1name",
|
||||
"value": "el1value"
|
||||
},
|
||||
{
|
||||
"name": "data-el2name",
|
||||
"value": "el2value"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<div
|
||||
{{#badgeelementid}}id="{{.}}"{{/badgeelementid}}
|
||||
class="m-2 d-flex align-items-center"
|
||||
{{#badgeextraattributes}} {{name}}="{{value}}" {{/badgeextraattributes}}
|
||||
>
|
||||
<div class="activitybadge badge badge-pill {{badgestyle}}">
|
||||
{{#badgeurl}}
|
||||
<a href="{{.}}">{{badgecontent}}</a>
|
||||
{{/badgeurl}}
|
||||
{{^badgeurl}}
|
||||
{{badgecontent}}
|
||||
{{/badgeurl}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_courseformat\output;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Tests for activitybadge class.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2023 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_courseformat\output\activitybadge
|
||||
*/
|
||||
class activitybadge_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the behaviour of create_instance() and export_for_template() attributes.
|
||||
* @runInSeparateProcess
|
||||
*
|
||||
* @covers ::export_for_template
|
||||
* @covers ::create_instance
|
||||
*/
|
||||
public function test_activitybadge_export_for_template() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$data = $this->setup_scenario();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user(['trackforums' => 1]);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
$user->id,
|
||||
$data->course->id,
|
||||
'student'
|
||||
);
|
||||
$this->setUser($user);
|
||||
|
||||
$renderer = $data->renderer;
|
||||
|
||||
// The activitybadge for a file with all options enabled shouldn't be empty.
|
||||
$class = activitybadge::create_instance($data->fileshowtype);
|
||||
$result = $class->export_for_template($renderer);
|
||||
$this->check_activitybadge($result, 'TXT', 'badge-none');
|
||||
|
||||
// The activitybadge for a file with Show type option disabled should be empty.
|
||||
$class = activitybadge::create_instance($data->filehidetype);
|
||||
$result = $class->export_for_template($renderer);
|
||||
$this->check_activitybadge($result);
|
||||
|
||||
// The activitybadge for a forum with unread messages shouldn't be empty.
|
||||
$class = activitybadge::create_instance($data->forumunread);
|
||||
$result = $class->export_for_template($renderer);
|
||||
$this->check_activitybadge($result, '1 unread post', 'badge-dark');
|
||||
|
||||
// The activitybadge for a forum without unread messages should be empty.
|
||||
$class = activitybadge::create_instance($data->forumread);
|
||||
$result = $class->export_for_template($renderer);
|
||||
$this->check_activitybadge($result);
|
||||
|
||||
// The activitybadge for an assignment should be empty.
|
||||
$class = activitybadge::create_instance($data->assign);
|
||||
$this->assertNull($class);
|
||||
|
||||
// The activitybadge for a label should be empty.
|
||||
$class = activitybadge::create_instance($data->label);
|
||||
$this->assertNull($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the default scenario, creating some activities:
|
||||
* - A forum with one unread message from the teacher.
|
||||
* - Another forum without unread messages.
|
||||
* - A file with all the appearance options enabled.
|
||||
* - A file with the "Show type" option disabled.
|
||||
* - An assignment.
|
||||
* - A label.
|
||||
*
|
||||
* @return stdClass the scenario instances.
|
||||
*/
|
||||
private function setup_scenario(): stdClass {
|
||||
global $PAGE;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
|
||||
// Enrol editing teacher to the course.
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
$teacher->id,
|
||||
$course->id,
|
||||
'editingteacher'
|
||||
);
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Create a forum with tracking forced and add a discussion.
|
||||
$record = new stdClass();
|
||||
$record->introformat = FORMAT_HTML;
|
||||
$record->course = $course->id;
|
||||
$record->trackingtype = FORUM_TRACKING_FORCED;
|
||||
$forumread = $this->getDataGenerator()->create_module('forum', $record);
|
||||
$forumunread = $this->getDataGenerator()->create_module('forum', $record);
|
||||
$record = new stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $teacher->id;
|
||||
$record->forum = $forumunread->id;
|
||||
$discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
|
||||
// Create a file with all the options enabled.
|
||||
$record = (object)[
|
||||
'course' => $course->id,
|
||||
'showsize' => 1,
|
||||
'showtype' => 1,
|
||||
'showdate' => 1,
|
||||
];
|
||||
$fileshowtype = self::getDataGenerator()->create_module('resource', $record);
|
||||
|
||||
// Create a file with Show type disabled.
|
||||
$record = (object)[
|
||||
'course' => $course->id,
|
||||
'showsize' => 1,
|
||||
'showtype' => 0,
|
||||
'showdate' => 1,
|
||||
];
|
||||
$filehidetype = self::getDataGenerator()->create_module('resource', $record);
|
||||
|
||||
// Create an assignment and a label.
|
||||
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
||||
$label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
|
||||
|
||||
rebuild_course_cache($course->id, true);
|
||||
$renderer = course_get_format($course->id)->get_renderer($PAGE);
|
||||
$modinfo = get_fast_modinfo($course->id);
|
||||
|
||||
return (object)[
|
||||
'course' => $course,
|
||||
'forumunread' => $modinfo->get_cm($forumunread->cmid),
|
||||
'discussion' => $discussion,
|
||||
'forumread' => $modinfo->get_cm($forumread->cmid),
|
||||
'fileshowtype' => $modinfo->get_cm($fileshowtype->cmid),
|
||||
'filehidetype' => $modinfo->get_cm($filehidetype->cmid),
|
||||
'assign' => $modinfo->get_cm($assign->cmid),
|
||||
'label' => $modinfo->get_cm($label->cmid),
|
||||
'renderer' => $renderer,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if the result of the export_from_template is the expected.
|
||||
*
|
||||
* @param stdClass $result The result of the export_from_template() call.
|
||||
* @param string|null $content The expected activitybadge content.
|
||||
* @param string|null $style The expected activitybadge style.
|
||||
* @param string|null $url The expected activitybadge url.
|
||||
* @param string|null $elementid The expected activitybadge element id.
|
||||
* @param array|null $extra The expected activitybadge extra attributes.
|
||||
*/
|
||||
private function check_activitybadge(
|
||||
stdClass $result,
|
||||
?string $content = null,
|
||||
?string $style = null,
|
||||
?string $url = null,
|
||||
?string $elementid = null,
|
||||
?array $extra = null
|
||||
): void {
|
||||
if (is_null($content)) {
|
||||
$this->assertObjectNotHasAttribute('badgecontent', $result);
|
||||
} else {
|
||||
$this->assertEquals($content, $result->badgecontent);
|
||||
}
|
||||
|
||||
if (is_null($style)) {
|
||||
$this->assertObjectNotHasAttribute('badgestyle', $result);
|
||||
} else {
|
||||
$this->assertEquals($style, $result->badgestyle);
|
||||
}
|
||||
|
||||
if (is_null($url)) {
|
||||
$this->assertObjectNotHasAttribute('badgeurl', $result);
|
||||
} else {
|
||||
$this->assertEquals($url, $result->badgeurl);
|
||||
}
|
||||
|
||||
if (is_null($elementid)) {
|
||||
$this->assertObjectNotHasAttribute('badgeelementid', $result);
|
||||
} else {
|
||||
$this->assertEquals($elementid, $result->badgeelementid);
|
||||
}
|
||||
|
||||
if (is_null($extra)) {
|
||||
$this->assertObjectNotHasAttribute('badgeextraattributes', $result);
|
||||
} else {
|
||||
$this->assertEquals($extra, $result->badgeextraattributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,17 @@ This files describes API changes for course formats
|
||||
|
||||
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
|
||||
|
||||
=== 4.3 ===
|
||||
* New core_courseformat\output\activitybadge class that can be extended by any module to display content near the activity name.
|
||||
The content of the afterlink feature has been moved to the end of the activity card so modules using it should check this new
|
||||
feature which might fit better.
|
||||
Some considerations about the activitybadge feature:
|
||||
- The badge content is always plain text (no HTML).
|
||||
- The badge style can be set (by default is initialized with badge-none, but it can be set by any module).
|
||||
- An optional URL to redirect the user when the badge is clicked.
|
||||
- An optional ID to add the element in case the module wants to add some JS to the badge events.
|
||||
- Optionally, any other extra HTML attributes to the badge element (for example, data attributes).
|
||||
|
||||
=== 4.2 ===
|
||||
* New core_courseformat\base::get_context() to get the course context directly from the format instance.
|
||||
* New core_courseformat\base::delete_module() method. Now format plugins can extend the activity deletion logic
|
||||
|
||||
@@ -1110,6 +1110,15 @@ class externallib_test extends externallib_advanced_testcase {
|
||||
$CFG->forum_allowforcedreadtracking = 1;
|
||||
list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
|
||||
|
||||
// Create a resource with all the appearance options enabled. By default it's a text file and will be added to section 1.
|
||||
$record = (object) [
|
||||
'course' => $course->id,
|
||||
'showsize' => 1,
|
||||
'showtype' => 1,
|
||||
'showdate' => 1,
|
||||
];
|
||||
$resource = self::getDataGenerator()->create_module('resource', $record);
|
||||
|
||||
// We first run the test as admin.
|
||||
$this->setAdminUser();
|
||||
$sections = core_course_external::get_course_contents($course->id, array());
|
||||
@@ -1126,9 +1135,13 @@ class externallib_test extends externallib_advanced_testcase {
|
||||
$this->assertEquals($formattedtext, $module['description']);
|
||||
$this->assertEquals($forumcm->instance, $module['instance']);
|
||||
$this->assertEquals(context_module::instance($forumcm->id)->id, $module['contextid']);
|
||||
$this->assertStringContainsString('1 unread post', $module['afterlink']);
|
||||
$this->assertFalse($module['noviewlink']);
|
||||
$this->assertNotEmpty($module['description']); // Module showdescription is on.
|
||||
// Afterlink for forums has been removed; it has been moved to the new activity badge content.
|
||||
$this->assertEmpty($module['afterlink']);
|
||||
$this->assertEquals('1 unread post', $module['activitybadge']['badgecontent']);
|
||||
$this->assertEquals('badge-dark', $module['activitybadge']['badgestyle']);
|
||||
|
||||
$testexecuted = $testexecuted + 2;
|
||||
} else if ($module['id'] == $labelcm->id and $module['modname'] == 'label') {
|
||||
$cm = $modinfo->cms[$labelcm->id];
|
||||
@@ -1145,6 +1158,12 @@ class externallib_test extends externallib_advanced_testcase {
|
||||
$this->assertFalse($module['noviewlink']);
|
||||
$this->assertArrayNotHasKey('description', $module);
|
||||
$testexecuted = $testexecuted + 1;
|
||||
} else if ($module['instance'] == $resource->id && $module['modname'] == 'resource') {
|
||||
// Resources have both, afterlink for the size and the update date and activitybadge for the file type.
|
||||
$this->assertStringContainsString('32 bytes', $module['afterlink']);
|
||||
$this->assertEquals('TXT', $module['activitybadge']['badgecontent']);
|
||||
$this->assertEquals('badge-none', $module['activitybadge']['badgestyle']);
|
||||
$testexecuted = $testexecuted + 1;
|
||||
}
|
||||
}
|
||||
foreach ($sections[2]['modules'] as $module) {
|
||||
@@ -1157,10 +1176,10 @@ class externallib_test extends externallib_advanced_testcase {
|
||||
$CFG->forum_allowforcedreadtracking = 0; // Recover original value.
|
||||
forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests.
|
||||
|
||||
$this->assertEquals(5, $testexecuted);
|
||||
$this->assertEquals(6, $testexecuted);
|
||||
$this->assertEquals(0, $sections[0]['section']);
|
||||
|
||||
$this->assertCount(6, $sections[0]['modules']);
|
||||
$this->assertCount(7, $sections[0]['modules']);
|
||||
$this->assertCount(1, $sections[1]['modules']);
|
||||
$this->assertCount(1, $sections[2]['modules']);
|
||||
$this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions.
|
||||
|
||||
@@ -3,6 +3,8 @@ information provided here is intended especially for developers.
|
||||
|
||||
=== 4.3 ===
|
||||
* The `core_course_renderer::course_section_cm_completion` method has been removed, and can no longer be used
|
||||
* External function core_course_external::get_course_contents() now returns a new field activitybadge with the data to display
|
||||
the activity badge when the module implements it.
|
||||
|
||||
=== 4.2 ===
|
||||
* course/mod.php now accepts parameter beforemod for adding course modules. It contains the course module id
|
||||
|
||||
@@ -31,8 +31,10 @@ Feature: Add a new custom file type
|
||||
When I add a "File" to section "1" and I fill the form with:
|
||||
| Name | Test file |
|
||||
| Select files | files/tests/fixtures/custom_filetype.mdlr |
|
||||
| Show size | 1 |
|
||||
| Show type | 1 |
|
||||
| Display resource description | 1 |
|
||||
And I am on "Course 1" course homepage
|
||||
Then I should see "Test file"
|
||||
And I should see "Moodle rules" in the "span.resourcelinkdetails" "css_element"
|
||||
And I should see "MDLR" in the "div.activitybadge" "css_element"
|
||||
And I should not see "MDLR" in the "span.resourcelinkdetails" "css_element"
|
||||
|
||||
@@ -32,6 +32,7 @@ if (!defined('MAX_MODINFO_CACHE_SIZE')) {
|
||||
define('MAX_MODINFO_CACHE_SIZE', 10);
|
||||
}
|
||||
|
||||
use core_courseformat\output\activitybadge;
|
||||
|
||||
/**
|
||||
* Information about a course that is cached in the course table 'modinfo' field (and then in
|
||||
@@ -1756,6 +1757,28 @@ class cm_info implements IteratorAggregate {
|
||||
return $this->afterlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activity badge data associated to this course module (if the module supports it).
|
||||
* Modules can use this method to provide additional data to be displayed in the activity badge.
|
||||
*
|
||||
* @param renderer_base $output Output render to use, or null for default (global)
|
||||
* @return stdClass|null The activitybadge data (badgecontent, badgestyle...) or null if the module doesn't implement it.
|
||||
*/
|
||||
public function get_activitybadge(?renderer_base $output = null): ?stdClass {
|
||||
global $OUTPUT;
|
||||
|
||||
$activibybadgeclass = activitybadge::create_instance($this);
|
||||
if (empty($activibybadgeclass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($output)) {
|
||||
$output = $OUTPUT;
|
||||
}
|
||||
|
||||
return $activibybadgeclass->export_for_template($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Will collect view data, if not already obtained.
|
||||
* @return string Extra HTML code to display after editing icons (e.g. more icons)
|
||||
|
||||
@@ -583,6 +583,39 @@ class modinfolib_test extends advanced_testcase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for function cm_info::get_activitybadge().
|
||||
*
|
||||
* @covers \cm_info::get_activitybadge
|
||||
*/
|
||||
public function test_cm_info_get_activitybadge(): void {
|
||||
global $PAGE;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
$resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);
|
||||
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
||||
$label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
|
||||
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$modinfo = get_fast_modinfo($course->id);
|
||||
|
||||
// Forum and resource implements the activitybadge feature.
|
||||
$cminfo = $modinfo->get_cm($forum->cmid);
|
||||
$this->assertNotNull($cminfo->get_activitybadge($renderer));
|
||||
$cminfo = $modinfo->get_cm($resource->cmid);
|
||||
$this->assertNotNull($cminfo->get_activitybadge($renderer));
|
||||
|
||||
// Assign and label don't implement the activitybadge feature (at least for now).
|
||||
$cminfo = $modinfo->get_cm($assign->cmid);
|
||||
$this->assertNull($cminfo->get_activitybadge($renderer));
|
||||
$cminfo = $modinfo->get_cm($label->cmid);
|
||||
$this->assertNull($cminfo->get_activitybadge($renderer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the availability property that has been added to course modules
|
||||
* and sections (just to see that it is correctly saved and accessed).
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_forum\output\courseformat;
|
||||
|
||||
/**
|
||||
* Activity badge forum class, used for rendering unread messages.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2023 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class activitybadge extends \core_courseformat\output\activitybadge {
|
||||
|
||||
/**
|
||||
* This method will be called before exporting the template.
|
||||
*/
|
||||
protected function update_content(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
if (forum_tp_can_track_forums()) {
|
||||
if ($unread = forum_tp_count_forum_unread_posts($this->cminfo, $this->cminfo->get_course())) {
|
||||
if ($unread == 1) {
|
||||
$this->content = get_string('unreadpostsone', 'forum');
|
||||
} else {
|
||||
$this->content = get_string('unreadpostsnumber', 'forum', $unread);
|
||||
}
|
||||
$this->style = self::STYLES['dark'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5487,28 +5487,6 @@ function forum_extend_settings_navigation(settings_navigation $settingsnav, navi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds information about unread messages, that is only required for the course view page (and
|
||||
* similar), to the course-module object.
|
||||
* @param cm_info $cm Course-module object
|
||||
*/
|
||||
function forum_cm_info_view(cm_info $cm) {
|
||||
global $CFG;
|
||||
|
||||
if (forum_tp_can_track_forums()) {
|
||||
if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
|
||||
$out = '<span class="badge badge-secondary">';
|
||||
if ($unread == 1) {
|
||||
$out .= get_string('unreadpostsone', 'forum');
|
||||
} else {
|
||||
$out .= get_string('unreadpostsnumber', 'forum', $unread);
|
||||
}
|
||||
$out .= '</span>';
|
||||
$cm->set_after_link($out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of page types
|
||||
* @param string $pagetype current page type
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_resource\output\courseformat;
|
||||
|
||||
/**
|
||||
* Activity badge resource class, used for displaying the file type.
|
||||
*
|
||||
* @package mod_resource
|
||||
* @copyright 2023 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class activitybadge extends \core_courseformat\output\activitybadge {
|
||||
|
||||
/**
|
||||
* This method will be called before exporting the template.
|
||||
*/
|
||||
protected function update_content(): void {
|
||||
$options = (object) ['displayoptions' => $this->cminfo->customdata['displayoptions']];
|
||||
$this->content = resource_get_optional_filetype($options, $this->cminfo);
|
||||
}
|
||||
}
|
||||
@@ -102,10 +102,10 @@ $string['printintroexplain'] = 'Display resource description below content? Some
|
||||
$string['privacy:metadata'] = 'The File resource plugin does not store any personal data.';
|
||||
$string['resource:addinstance'] = 'Add a new resource';
|
||||
$string['resourcecontent'] = 'Files and subfolders';
|
||||
$string['resourcedetails_sizetype'] = '{$a->size} {$a->type}';
|
||||
$string['resourcedetails_sizedate'] = '{$a->size} {$a->date}';
|
||||
$string['resourcedetails_typedate'] = '{$a->type} {$a->date}';
|
||||
$string['resourcedetails_sizetypedate'] = '{$a->size} {$a->type} {$a->date}';
|
||||
$string['resourcedetails_sizetype'] = '{$a->size} · {$a->type}';
|
||||
$string['resourcedetails_sizedate'] = '{$a->size} · {$a->date}';
|
||||
$string['resourcedetails_typedate'] = '{$a->type} · {$a->date}';
|
||||
$string['resourcedetails_sizetypedate'] = '{$a->size} · {$a->type} · {$a->date}';
|
||||
$string['resource:exportresource'] = 'Export resource';
|
||||
$string['resource:view'] = 'View resource';
|
||||
$string['search:activity'] = 'File';
|
||||
|
||||
@@ -271,7 +271,7 @@ function resource_cm_info_view(cm_info $cm) {
|
||||
require_once($CFG->dirroot . '/mod/resource/locallib.php');
|
||||
|
||||
$resource = (object) ['displayoptions' => $cm->customdata['displayoptions']];
|
||||
$details = resource_get_optional_details($resource, $cm);
|
||||
$details = resource_get_optional_details($resource, $cm, false);
|
||||
if ($details) {
|
||||
$cm->set_after_link(' ' . html_writer::tag('span', $details,
|
||||
array('class' => 'resourcelinkdetails')));
|
||||
|
||||
@@ -291,6 +291,7 @@ function resource_get_file_details($resource, $cm) {
|
||||
if ($mainfile) {
|
||||
$filedetails['type'] = get_mimetype_description($mainfile);
|
||||
$filedetails['mimetype'] = $mainfile->get_mimetype();
|
||||
$filedetails['extension'] = strtoupper(resourcelib_get_extension($mainfile->get_filename()));
|
||||
// Only show type if it is not unknown.
|
||||
if ($filedetails['type'] === get_mimetype_description('document/unknown')) {
|
||||
$filedetails['type'] = '';
|
||||
@@ -328,15 +329,16 @@ function resource_get_file_details($resource, $cm) {
|
||||
*
|
||||
* @param object $resource Resource table row (only property 'displayoptions' is used here)
|
||||
* @param object $cm Course-module table row
|
||||
* @param bool $showtype Whether the file type should be displayed or not (regardless the display option is enabled).
|
||||
* @return string Size and type or empty string if show options are not enabled
|
||||
*/
|
||||
function resource_get_optional_details($resource, $cm) {
|
||||
function resource_get_optional_details($resource, $cm, bool $showtype = true) {
|
||||
global $DB;
|
||||
|
||||
$details = '';
|
||||
|
||||
$options = empty($resource->displayoptions) ? [] : (array) unserialize_array($resource->displayoptions);
|
||||
if (!empty($options['showsize']) || !empty($options['showtype']) || !empty($options['showdate'])) {
|
||||
if (!empty($options['showsize']) || ($showtype && !empty($options['showtype'])) || !empty($options['showdate'])) {
|
||||
if (!array_key_exists('filedetails', $options)) {
|
||||
$filedetails = resource_get_file_details($resource, $cm);
|
||||
} else {
|
||||
@@ -354,9 +356,9 @@ function resource_get_optional_details($resource, $cm) {
|
||||
$infodisplayed += 1;
|
||||
}
|
||||
}
|
||||
if (!empty($options['showtype'])) {
|
||||
if ($showtype && !empty($options['showtype'])) {
|
||||
if (!empty($filedetails['type'])) {
|
||||
$type = $filedetails['type'];
|
||||
$type = $filedetails['extension'];
|
||||
$langstring .= 'type';
|
||||
$infodisplayed += 1;
|
||||
}
|
||||
@@ -385,6 +387,34 @@ function resource_get_optional_details($resource, $cm) {
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets optional file type extension for a resource, depending on resource settings.
|
||||
*
|
||||
* @param object $resource Resource table row (only property 'displayoptions' is used here)
|
||||
* @param object $cm Course-module table row
|
||||
* @return string File extension or null if showtype option is not enabled
|
||||
*/
|
||||
function resource_get_optional_filetype($resource, $cm): ?string {
|
||||
$filetype = null;
|
||||
|
||||
$options = empty($resource->displayoptions) ? [] : (array) unserialize_array($resource->displayoptions);
|
||||
if (empty($options['showtype'])) {
|
||||
// Show type option is disabled; early return null filetype.
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
if (!array_key_exists('filedetails', $options)) {
|
||||
$filedetails = resource_get_file_details($resource, $cm);
|
||||
} else {
|
||||
$filedetails = $options['filedetails'];
|
||||
}
|
||||
if (!empty($filedetails['type'])) {
|
||||
$filetype = $filedetails['extension'];
|
||||
}
|
||||
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource introduction.
|
||||
*
|
||||
|
||||
@@ -44,19 +44,34 @@ Feature: Teacher can specify different display options for the resource
|
||||
| Show upload/modified date | <showdate> |
|
||||
And I press "Save and display"
|
||||
Then I <seesize> see "6 bytes" in the ".resourcedetails" "css_element"
|
||||
And I <seetype> see "Text file" in the ".resourcedetails" "css_element"
|
||||
And I <seetype> see "TXT" in the ".resourcedetails" "css_element"
|
||||
And I <seedate> see "Uploaded" in the ".resourcedetails" "css_element"
|
||||
And I am on "Course 1" course homepage
|
||||
And I <seesize> see "6 bytes" in the ".activity.resource .resourcelinkdetails" "css_element"
|
||||
And I <seetype> see "Text file" in the ".activity.resource .resourcelinkdetails" "css_element"
|
||||
And I <seetype> see "TXT" in the ".activity.resource .activitybadge" "css_element"
|
||||
And I <seedate> see "Uploaded" in the ".activity.resource .resourcelinkdetails" "css_element"
|
||||
|
||||
Examples:
|
||||
| showsize | showtype | showdate | seesize | seetype | seedate |
|
||||
| 1 | 0 | 0 | should | should not | should not |
|
||||
| 0 | 1 | 0 | should not | should | should not |
|
||||
| 0 | 0 | 1 | should not | should not | should |
|
||||
| 1 | 1 | 0 | should | should | should not |
|
||||
| 1 | 0 | 1 | should | should not | should |
|
||||
| 0 | 1 | 1 | should not | should | should |
|
||||
| 1 | 1 | 1 | should | should | should |
|
||||
|
||||
Scenario: Specifying only show type for a file resource
|
||||
When I am on the "Myfile" "resource activity editing" page
|
||||
And I set the following fields to these values:
|
||||
| display | 5 |
|
||||
| Show size | 0 |
|
||||
| Show type | 1 |
|
||||
| Show upload/modified date | 0 |
|
||||
And I press "Save and display"
|
||||
Then I should see "TXT" in the ".resourcedetails" "css_element"
|
||||
Then I should not see "6 bytes" in the ".resourcedetails" "css_element"
|
||||
And I should see "TXT" in the ".resourcedetails" "css_element"
|
||||
And I should not see "Uploaded" in the ".resourcedetails" "css_element"
|
||||
And I am on "Course 1" course homepage
|
||||
And I should see "TXT" in the ".activity.resource .activitybadge" "css_element"
|
||||
And ".activity.resource .resourcelinkdetails" "css_element" should not exist
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
This files describes API changes in the quiz code.
|
||||
This file describes API changes in the resource code.
|
||||
|
||||
=== 4.3 ===
|
||||
|
||||
* Function resource_get_optional_details() has now one new parameter, $showtype, to decide whether the file type should be
|
||||
displayed or not (regardless the display option is enabled).
|
||||
|
||||
=== 4.0 ===
|
||||
|
||||
* Functions resource_print_heading and resource_print_intro have been deprecated in favour for the activity header.
|
||||
* Functions resource_print_heading and resource_print_intro have been deprecated in favour for the activity header.
|
||||
|
||||
Reference in New Issue
Block a user