diff --git a/amd/build/images_list.min.js b/amd/build/images_list.min.js
new file mode 100644
index 00000000..3516be30
--- /dev/null
+++ b/amd/build/images_list.min.js
@@ -0,0 +1,3 @@
+define("mod_scheduler/images_list",["exports","core/event_dispatcher","core/notification","core/pending","core/prefetch","core/str","core/toast","core_reportbuilder/local/events","core/ajax"],(function(_exports,_event_dispatcher,_notification,_pending,_prefetch,_str,_toast,reportEvents,_ajax){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.deleteImage=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),reportEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(reportEvents),_ajax=_interopRequireDefault(_ajax);const deleteImage=imageId=>{const request={methodname:"mod_scheduler_delete_image",args:{imageid:imageId}};return _ajax.default.call([request])[0]};_exports.deleteImage=deleteImage;_exports.init=()=>{(0,_prefetch.prefetchStrings)("mod_scheduler",["deleteimage","deleteimageconfirm","imagedeleted"]),(0,_prefetch.prefetchStrings)("core",["delete"]),document.addEventListener("click",(event=>{const imageDelete=event.target.closest('[data-action="image-delete"]');if(imageDelete){event.preventDefault();const triggerElement=imageDelete.closest(".dropdown").querySelector(".dropdown-toggle");_notification.default.saveCancelPromise((0,_str.getString)("deleteimage","mod_scheduler"),(0,_str.getString)("deleteimageconfirm","mod_scheduler",imageDelete.dataset.imageName),(0,_str.getString)("delete","core"),{triggerElement:triggerElement}).then((()=>{const pendingPromise=new _pending.default("mod_scheduler/image:delete"),reportElement=event.target.closest('[data-region="core_reportbuilder/report"]');return deleteImage(imageDelete.dataset.imageId).then((()=>(0,_toast.add)((0,_str.getString)("imagedeleted","mod_scheduler")))).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}}))}}));
+
+//# sourceMappingURL=images_list.min.js.map
\ No newline at end of file
diff --git a/amd/build/images_list.min.js.map b/amd/build/images_list.min.js.map
new file mode 100644
index 00000000..164f1f34
--- /dev/null
+++ b/amd/build/images_list.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"images_list.min.js","sources":["../src/images_list.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder images list management\n *\n * @module core_imagebuilder/images_list\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport Ajax from 'core/ajax';\n\n/**\n * Delete given image\n *\n * @param {Number} imageId\n * @return {Promise}\n */\nexport const deleteImage = imageId => {\n const request = {\n methodname: 'mod_scheduler_delete_image',\n args: {imageid: imageId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Initialise module\n */\nexport const init = () => {\n prefetchStrings('mod_scheduler', [\n 'deleteimage',\n 'deleteimageconfirm',\n 'imagedeleted',\n ]);\n\n prefetchStrings('core', [\n 'delete',\n ]);\n\n document.addEventListener('click', event => {\n const imageDelete = event.target.closest('[data-action=\"image-delete\"]');\n if (imageDelete) {\n event.preventDefault();\n\n // Use triggerElement to return focus to the action menu toggle.\n const triggerElement = imageDelete.closest('.dropdown').querySelector('.dropdown-toggle');\n Notification.saveCancelPromise(\n getString('deleteimage', 'mod_scheduler'),\n getString('deleteimageconfirm', 'mod_scheduler', imageDelete.dataset.imageName),\n getString('delete', 'core'),\n {triggerElement}\n ).then(() => {\n const pendingPromise = new Pending('mod_scheduler/image:delete');\n const reportElement = event.target.closest('[data-region=\"core_reportbuilder/report\"]');\n\n return deleteImage(imageDelete.dataset.imageId)\n .then(() => addToast(getString('imagedeleted', 'mod_scheduler')))\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["deleteImage","imageId","request","methodname","args","imageid","Ajax","call","document","addEventListener","event","imageDelete","target","closest","preventDefault","triggerElement","querySelector","saveCancelPromise","dataset","imageName","then","pendingPromise","Pending","reportElement","reportEvents","tableReload","preservePagination","resolve","catch","Notification","exception"],"mappings":"6hDAwCaA,YAAcC,gBACjBC,QAAU,CACZC,WAAY,6BACZC,KAAM,CAACC,QAASJ,iBAGbK,cAAKC,KAAK,CAACL,UAAU,mDAMZ,mCACA,gBAAiB,CAC7B,cACA,qBACA,+CAGY,OAAQ,CACpB,WAGJM,SAASC,iBAAiB,SAASC,cACzBC,YAAcD,MAAME,OAAOC,QAAQ,mCACrCF,YAAa,CACbD,MAAMI,uBAGAC,eAAiBJ,YAAYE,QAAQ,aAAaG,cAAc,0CACzDC,mBACT,kBAAU,cAAe,kBACzB,kBAAU,qBAAsB,gBAAiBN,YAAYO,QAAQC,YACrE,kBAAU,SAAU,QACpB,CAACJ,eAAAA,iBACHK,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,8BAC7BC,cAAgBb,MAAME,OAAOC,QAAQ,oDAEpCb,YAAYW,YAAYO,QAAQjB,SAClCmB,MAAK,KAAM,eAAS,kBAAU,eAAgB,oBAC9CA,MAAK,yCACYI,aAAaC,YAAa,CAACC,oBAAoB,GAAOH,eAC7DF,eAAeM,aAEzBC,MAAMC,sBAAaC,cACzBF,OAAM"}
\ No newline at end of file
diff --git a/amd/src/images_list.js b/amd/src/images_list.js
new file mode 100644
index 00000000..bd1a6594
--- /dev/null
+++ b/amd/src/images_list.js
@@ -0,0 +1,92 @@
+// 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 .
+
+/**
+ * Report builder images list management
+ *
+ * @module core_imagebuilder/images_list
+ * @copyright 2021 David Matamoros
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+"use strict";
+
+import {dispatchEvent} from 'core/event_dispatcher';
+import Notification from 'core/notification';
+import Pending from 'core/pending';
+import {prefetchStrings} from 'core/prefetch';
+import {getString} from 'core/str';
+import {add as addToast} from 'core/toast';
+import * as reportEvents from 'core_reportbuilder/local/events';
+import Ajax from 'core/ajax';
+
+/**
+ * Delete given image
+ *
+ * @param {Number} imageId
+ * @return {Promise}
+ */
+export const deleteImage = imageId => {
+ const request = {
+ methodname: 'mod_scheduler_delete_image',
+ args: {imageid: imageId}
+ };
+
+ return Ajax.call([request])[0];
+};
+
+/**
+ * Initialise module
+ */
+export const init = () => {
+ prefetchStrings('mod_scheduler', [
+ 'deleteimage',
+ 'deleteimageconfirm',
+ 'imagedeleted',
+ ]);
+
+ prefetchStrings('core', [
+ 'delete',
+ ]);
+
+ document.addEventListener('click', event => {
+ const imageDelete = event.target.closest('[data-action="image-delete"]');
+ if (imageDelete) {
+ event.preventDefault();
+
+ // Use triggerElement to return focus to the action menu toggle.
+ const triggerElement = imageDelete.closest('.dropdown').querySelector('.dropdown-toggle');
+ Notification.saveCancelPromise(
+ getString('deleteimage', 'mod_scheduler'),
+ getString('deleteimageconfirm', 'mod_scheduler', imageDelete.dataset.imageName),
+ getString('delete', 'core'),
+ {triggerElement}
+ ).then(() => {
+ const pendingPromise = new Pending('mod_scheduler/image:delete');
+ const reportElement = event.target.closest('[data-region="core_reportbuilder/report"]');
+
+ return deleteImage(imageDelete.dataset.imageId)
+ .then(() => addToast(getString('imagedeleted', 'mod_scheduler')))
+ .then(() => {
+ dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
+ return pendingPromise.resolve();
+ })
+ .catch(Notification.exception);
+ }).catch(() => {
+ return;
+ });
+ }
+ });
+};
diff --git a/classes/external.php b/classes/external.php
index 3c8ab315..47adeea5 100644
--- a/classes/external.php
+++ b/classes/external.php
@@ -28,13 +28,13 @@
require_once("$CFG->libdir/externallib.php");
-use external_api;
-use external_function_parameters;
-use external_value;
-use external_single_structure;
-use external_multiple_structure;
+use core_external\external_api;
+use core_external\external_function_parameters;
+use core_external\external_value;
+use core_external\external_single_structure;
+use core_external\external_multiple_structure;
-use \mod_scheduler\model\scheduler;
+use mod_scheduler\model\scheduler;
/**
* This is the external API for this component.
diff --git a/classes/external/delete_image.php b/classes/external/delete_image.php
new file mode 100644
index 00000000..b86249da
--- /dev/null
+++ b/classes/external/delete_image.php
@@ -0,0 +1,89 @@
+.
+
+/**
+ * This is the external API for this component.
+ *
+ * @package mod_scheduler
+ * @copyright 2022 University of Glasgow
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_scheduler\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_external\external_api;
+use core_external\external_function_parameters;
+use core_external\external_value;
+use core_external\external_single_structure;
+use core_external\external_multiple_structure;
+
+use \mod_scheduler\model\scheduler;
+
+/**
+ * This is the external API for this component.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class delete_image extends external_api {
+
+ /**
+ * Delete image parameters
+ *
+ * @return external_function_parameters
+ */
+ public static function execute_parameters() {
+ return new external_function_parameters([
+ 'imageid' => new external_value(PARAM_INT, 'The image id', VALUE_REQUIRED),
+ ]);
+ }
+
+ public static function execute(int $imageid): bool {
+ global $DB;
+
+ [
+ 'imageid' => $imageid,
+ ] = self::validate_parameters(self::execute_parameters(), [
+ 'imageid' => $imageid,
+ ]);
+
+ $filerecord = $DB->get_record('files', ['id' => $imageid]);
+ if ($filerecord->component !== 'mod_scheduler') {
+ return false;
+ }
+ if ($filerecord->filearea !== 'message') {
+ return false;
+ }
+ $ctx = \context::instance_by_id($filerecord->contextid);
+ if (!has_any_capability(['mod/scheduler:manage', 'mod/scheduler:manageallappointments'], $ctx)) {
+ return false;
+ }
+ $fs = get_file_storage();
+ $file = $fs->get_file($filerecord->contextid, 'mod_scheduler', 'message', $filerecord->itemid, $filerecord->filepath, $filerecord->filename);
+ $file->delete();
+ return true;
+ }
+
+ /**
+ * External method return value
+ *
+ * @return external_value
+ */
+ public static function execute_returns(): external_value {
+ return new external_value(PARAM_BOOL, 'Success');
+ }
+}
diff --git a/classes/local/systemreports/images_list.php b/classes/local/systemreports/images_list.php
new file mode 100644
index 00000000..802157fa
--- /dev/null
+++ b/classes/local/systemreports/images_list.php
@@ -0,0 +1,237 @@
+.
+
+declare(strict_types=1);
+
+namespace mod_scheduler\local\systemreports;
+
+use html_writer;
+use lang_string;
+use moodle_url;
+use pix_icon;
+use stdClass;
+use core_reportbuilder\datasource;
+use core_reportbuilder\manager;
+use core_reportbuilder\system_report;
+use core_reportbuilder\local\entities\user;
+use core_reportbuilder\local\filters\{boolean_select, date, tags, text, select};
+use core_reportbuilder\local\helpers\{audience, custom_fields, format};
+use core_reportbuilder\local\report\{action, column, filter};
+use core_reportbuilder\output\report_name_editable;
+use core_reportbuilder\local\models\report;
+use core_reportbuilder\permission;
+use core_tag\reportbuilder\local\entities\tag;
+use core_tag_tag;
+
+/**
+ * Images list
+ *
+ * @package mod_scheduler
+ * @copyright Daniel Neis Araujo
+ * @copyright Based on reports lists from 2021 David Matamoros
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class images_list extends system_report {
+
+ /**
+ * The name of our internal report entity
+ *
+ * @return string
+ */
+ private function get_report_entity_name(): string {
+ return 'report';
+ }
+
+ /**
+ * Initialise the report
+ */
+ protected function initialise(): void {
+ $this->set_main_table('files', 'f');
+ $report = $this->get_report_persistent();
+ $this->add_base_condition_simple('f.itemid', $report->get('itemid'));
+ $this->add_base_condition_simple('f.filearea', $report->get('area'));
+ $this->add_base_condition_simple('f.component', $report->get('component'));
+ $this->add_base_condition_simple('f.contextid', $report->get('contextid'));
+ $this->add_base_condition_sql('f.filename != "."');
+
+ // Select fields required for actions, permission checks, and row class callbacks.
+ $this->add_base_fields('f.id, f.contenthash, f.filename, f.filearea');
+
+ // Join user entity for "Userid" column.
+ $entityuser = new user();
+ $entityuseralias = $entityuser->get_table_alias('user');
+ $this->add_entity($entityuser
+ ->add_join("JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = f.userid")
+ );
+
+ // Define our internal entity for report elements.
+ $this->annotate_entity($this->get_report_entity_name(),
+ new lang_string('imageslist', 'mod_scheduler'));
+
+ $this->add_columns();
+ $this->add_filters();
+ $this->add_actions();
+
+ $this->set_downloadable(false);
+ }
+
+ /**
+ * Ensure we can view the report
+ *
+ * @return bool
+ */
+ protected function can_view(): bool {
+ // TODO: testar capabilities do modulo
+ return permission::can_view_reports_list();
+ }
+
+ /**
+ * Dim the table row for invalid datasource
+ *
+ * @param stdClass $row
+ * @return string
+ public function get_row_class(stdClass $row): string {
+ return $this->report_source_valid($row->source) ? '' : 'text-muted';
+ }
+ */
+
+ protected function get_file_url($value) {
+ global $CFG;
+ $report = $this->get_report_persistent();
+ return $CFG->wwwroot . '/pluginfile.php/' .
+ $report->get('contextid') . '/mod_scheduler/message/' . $report->get('itemid') . '/' . $value;
+
+ }
+
+ /**
+ * Add columns to report
+ */
+ protected function add_columns(): void {
+ $tablealias = $this->get_main_table_alias();
+
+ // Report name column.
+ $this->add_column((new column(
+ 'filename',
+ new lang_string('file'),
+ $this->get_report_entity_name()
+ ))
+ ->set_type(column::TYPE_TEXT)
+ // We need enough fields to re-create the persistent and pass to the editable component.
+ ->add_fields("{$tablealias}.filename")
+ ->set_is_sortable(true, ["{$tablealias}.filename"])
+ ->add_callback(function(string $value, stdClass $row, $report) {
+ $url = $this->get_file_url($value);
+ return html_writer::link($url, $value);
+ })
+ );
+
+ // Time created column.
+ $this->add_column((new column(
+ 'timecreated',
+ new lang_string('timecreated', 'core_reportbuilder'),
+ $this->get_report_entity_name()
+ ))
+ ->set_type(column::TYPE_TIMESTAMP)
+ ->add_fields("{$tablealias}.timecreated")
+ ->set_is_sortable(true)
+ ->add_callback([format::class, 'userdate'])
+ );
+
+ // The user who modified the report.
+ $this->add_column_from_entity('user:fullnamewithlink')
+ ->set_title(new lang_string('user'));
+
+ // Initial sorting.
+ $this->set_initial_sort_column('report:timecreated', SORT_DESC);
+ }
+
+ /**
+ * Add filters to report
+ */
+ protected function add_filters(): void {
+ $tablealias = $this->get_main_table_alias();
+
+ // Name filter.
+ $this->add_filter((new filter(
+ text::class,
+ 'filename',
+ new lang_string('file'),
+ $this->get_report_entity_name(),
+ "{$tablealias}.filename"
+ )));
+
+ // Time created filter.
+ $this->add_filter((new filter(
+ date::class,
+ 'timecreated',
+ new lang_string('timecreated', 'core_reportbuilder'),
+ $this->get_report_entity_name(),
+ "{$tablealias}.timecreated"
+ ))
+ ->set_limited_operators([
+ date::DATE_ANY,
+ date::DATE_RANGE,
+ date::DATE_BEFORE,
+ date::DATE_LAST,
+ date::DATE_CURRENT,
+ ])
+ );
+
+ // User modified filter.
+ $this->add_filter_from_entity('user:userselect')
+ ->set_header(new lang_string('usermodified', 'reportbuilder'))
+ ->set_is_available(has_capability('moodle/user:viewalldetails', $this->get_context()));
+ }
+
+ /**
+ * Add actions to report
+ */
+ protected function add_actions(): void {
+ // Delete action.
+ $this->add_action((new action(
+ new moodle_url('#'),
+ new pix_icon('t/delete', ''),
+ [
+ 'data-action' => 'image-delete',
+ 'data-image-id' => ':id',
+ 'data-image-name' => ':name',
+ 'class' => 'text-danger',
+ ],
+ false,
+ new lang_string('deleteimage', 'mod_scheduler')
+ ))
+ ->add_callback(function(stdClass $row): bool {
+
+ // Ensure data name attribute is properly formatted.
+ $report = new report(0, $row);
+ $row->name = $report->get_formatted_name();
+
+ // We don't check whether report is valid to ensure editor can always delete them.
+ return permission::can_edit_report($report);
+ })
+ );
+ }
+
+ /**
+ * Helper to determine whether given report source is valid (it both exists, and is available)
+ *
+ * @param string $source
+ * @return bool
+ */
+ private function report_source_valid(string $source): bool {
+ return manager::report_source_exists($source, datasource::class) && manager::report_source_available($source);
+ }
+}
diff --git a/db/services.php b/db/services.php
index 6faa22da..313bcea0 100644
--- a/db/services.php
+++ b/db/services.php
@@ -34,4 +34,13 @@
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
+
+ 'mod_scheduler_delete_image' => array(
+ 'classname' => 'mod_scheduler\external\delete_image',
+ 'classpath' => 'mod/scheduler/classes/external/delete_image',
+ 'methodname' => 'execute',
+ 'description' => 'Delete a given image.',
+ 'type' => 'write',
+ 'ajax' => true,
+ ),
);
diff --git a/lang/en/scheduler.php b/lang/en/scheduler.php
index 3b2b19db..8317d412 100644
--- a/lang/en/scheduler.php
+++ b/lang/en/scheduler.php
@@ -203,6 +203,8 @@
$string['deleteallslots'] = 'Delete all slots';
$string['deleteallunusedslots'] = 'Delete unused slots';
$string['deletecommands'] = 'Delete slots';
+$string['deleteimage'] = 'Delete image';
+$string['deleteimageconfirm'] = 'Are you sure you want to delete the image? It cannot be undone.';
$string['deletemyslots'] = 'Delete all my slots';
$string['deleteselection'] = 'Delete selected slots';
$string['deletetheseslots'] = 'Delete these slots';
@@ -302,12 +304,15 @@
$string['incourse'] = ' in course ';
$string['mixindivgroup'] = 'Mix individual and group bookings';
$string['mixindivgroup_desc'] = 'Where group scheduling is enabled, allow individual bookings as well.';
+$string['imageslist'] = 'Images list';
+$string['imagedeleted'] = 'Image deleted';
$string['introduction'] = 'Introduction';
$string['isnonexclusive'] = 'Non-exclusive';
$string['landscape'] = 'Landscape';
$string['lengthbreakdown'] = 'By slot duration';
$string['limited'] = 'Limited ({$a} left)';
$string['location'] = 'Location';
+$string['manageimages'] = 'Manage images';
$string['markseen'] = 'After you have had an appointment with a student please mark them as "Seen" by clicking the checkbox near to their user picture above.';
$string['markasseennow'] = 'Mark as seen now';
$string['maxgrade'] = 'Take the highest grade';
diff --git a/lib.php b/lib.php
index 14119c92..fc5e15e8 100644
--- a/lib.php
+++ b/lib.php
@@ -719,6 +719,8 @@ function scheduler_pluginfile($course, $cm, $context, $filearea, $args, $forcedo
$permissions->ensure($permissions->can_see_appointment($app));
+ } else if ($filearea === 'message') {
+ // TODO.
} else {
// Unknown file area.
return false;
diff --git a/manageimages.php b/manageimages.php
new file mode 100644
index 00000000..f27b3595
--- /dev/null
+++ b/manageimages.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * Shows a sortable list of appointments
+ *
+ * @package mod_scheduler
+ * @copyright 2015 Henning Bostelmann and others (see README.txt)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use mod_scheduler\local\systemreports\images_list;
+
+$PAGE->set_docs_path('mod/scheduler/manageimages');
+
+$scopecontext = context_course::instance($scheduler->courseid);
+
+// TODO: test capability
+
+$taburl = new moodle_url('/mod/scheduler/view.php',
+ array('id' => $scheduler->cmid, 'what' => 'manageimages'));
+$returnurl = new moodle_url('/mod/scheduler/view.php', array('id' => $scheduler->cmid));
+
+$PAGE->set_url($taburl);
+
+$PAGE->requires->js_call_amd('mod_scheduler/images_list', 'init');
+
+echo $output->header();
+
+// Print top tabs.
+
+echo $output->teacherview_tabs($scheduler, $permissions, $taburl, 'manageimages');
+
+// Find active group in case that group mode is in use.
+$currentgroupid = 0;
+$groupmode = groups_get_activity_groupmode($scheduler->cm);
+if ($groupmode) {
+ $currentgroupid = groups_get_activity_group($scheduler->cm, true);
+
+ echo html_writer::start_div('dropdownmenu');
+ groups_print_activity_menu($scheduler->cm, $taburl);
+ echo html_writer::end_div();
+}
+
+$report = core_reportbuilder\system_report_factory::create(images_list::class, context_module::instance($scheduler->cmid), 'mod_scheduler', 'message', $scheduler->cmid);
+echo $report->output();
+
+echo $output->footer();
diff --git a/message_form.php b/message_form.php
index db55ea83..c66a9391 100644
--- a/message_form.php
+++ b/message_form.php
@@ -92,7 +92,7 @@ protected function definition() {
}
$bodyedit = $mform->addElement('editor', 'body', get_string('messagebody', 'scheduler'),
- array('rows' => 15, 'columns' => 60), array('collapsed' => true));
+ array('rows' => 15, 'columns' => 60), array('collapsed' => true,'maxfiles' => EDITOR_UNLIMITED_FILES));
$mform->setType('body', PARAM_RAW); // Must be PARAM_RAW for rich text editor content.
if (isset($this->_customdata['body'])) {
$bodyedit->setValue(array('text' => $this->_customdata['body']));
diff --git a/renderer.php b/renderer.php
index e5e53f3e..ccd556ac 100644
--- a/renderer.php
+++ b/renderer.php
@@ -370,6 +370,7 @@ public function teacherview_tabs(scheduler $scheduler, scheduler_permissions $pe
$level1[] = $this->teacherview_tab($baseurl, 'datelist', 'datelist');
$level1[] = $statstab;
$level1[] = $this->teacherview_tab($baseurl, 'export', 'export');
+ $level1[] = $this->teacherview_tab($baseurl, 'manageimages', 'manageimages');
return $this->tabtree($level1, $selected, $inactive);
}
diff --git a/teacherview.controller.php b/teacherview.controller.php
index 6cdb092e..70b78739 100644
--- a/teacherview.controller.php
+++ b/teacherview.controller.php
@@ -155,11 +155,17 @@ function scheduler_action_dosendmessage($scheduler, $formdata, $returnurl) {
$data = (object) $formdata;
+ $draftitemid = file_get_submitted_draft_itemid('body');
+ // TODO: link file to notifications instead of course module?
+ $rawmessage = file_save_draft_area_files($draftitemid, $scheduler->context->id,
+ 'mod_scheduler', 'message', $scheduler->cm->id,
+ array('subdirs'=>false, 'maxfiles' => -1), $data->body['text']);
+ $rawmessage = file_rewrite_pluginfile_urls($rawmessage, 'pluginfile.php', $scheduler->context->id, 'mod_scheduler', 'message', $scheduler->cm->id);
+
$recipients = $data->recipient;
if ($data->copytomyself) {
$recipients[$USER->id] = 1;
}
- $rawmessage = $data->body['text'];
$format = $data->body['format'];
$textmessage = format_text_email($rawmessage, $format);
$htmlmessage = null;
diff --git a/templates/action_menu_trigger.mustache b/templates/action_menu_trigger.mustache
index 5d467d8f..c442b035 100644
--- a/templates/action_menu_trigger.mustache
+++ b/templates/action_menu_trigger.mustache
@@ -89,7 +89,7 @@
class="{{triggerextraclasses}} dropdown-toggle icon-no-margin"
id="action-menu-toggle-{{instance}}"
aria-label="{{title}}"
- data-toggle="dropdown"
+ data-bs-toggle="dropdown"
role="{{triggerrole}}"
aria-haspopup="true"
aria-expanded="false"
diff --git a/version.php b/version.php
index 4952256a..2c129227 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
*/
$plugin->component = 'mod_scheduler'; // Full name of the plugin (used for diagnostics).
-$plugin->version = 2024080103; // The current module version (Date: YYYYMMDDXX).
-$plugin->release = '4.x dev'; // Human-friendly version name.
+$plugin->version = 2025091101; // The current module version (Date: YYYYMMDDXX).
+$plugin->release = '5.x dev'; // Human-friendly version name.
$plugin->requires = 2023100905; // Requires Moodle 4.3.
$plugin->maturity = MATURITY_ALPHA; // Development release - not for production use.
diff --git a/view.php b/view.php
index 1edfed23..e4e3c12f 100644
--- a/view.php
+++ b/view.php
@@ -84,6 +84,8 @@
include($CFG->dirroot.'/mod/scheduler/export.php');
} else if ($action == 'datelist') {
include($CFG->dirroot.'/mod/scheduler/datelist.php');
+ } else if ($action == 'manageimages') {
+ include($CFG->dirroot.'/mod/scheduler/manageimages.php');
} else {
include($CFG->dirroot.'/mod/scheduler/teacherview.php');
}