Merge branch 'MDL-86066' of https://github.com/paulholden/moodle
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
issueNumber: MDL-86066
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >
|
||||
The following methods from the `schedule` helper class have been
|
||||
deprecated, in favour of usage of the new schedule type system:
|
||||
|
||||
|
||||
* `create_schedule`
|
||||
|
||||
* `get_report_empty_options`
|
||||
|
||||
* `send_schedule_message`
|
||||
type: deprecated
|
||||
- message: >-
|
||||
Report schedule types are now extendable by third-party plugins by
|
||||
extending the `core_reportbuilder\local\schedules\base` class in your
|
||||
component namespace: `<component>\reportbuilder\schedule\<type>`
|
||||
type: improved
|
||||
@@ -89,3 +89,6 @@ resources_help,core
|
||||
unabletomessage,core_message
|
||||
notificationimage,core_message
|
||||
hiddensections_help,core
|
||||
privacy:metadata:schedule:message,core_reportbuilder
|
||||
privacy:metadata:schedule:reportempty,core_reportbuilder
|
||||
privacy:metadata:schedule:subject,core_reportbuilder
|
||||
|
||||
@@ -214,13 +214,12 @@ $string['privacy:metadata:report:usercreated'] = 'The ID of the user who created
|
||||
$string['privacy:metadata:report:usermodified'] = 'The ID of the user who last modified the report';
|
||||
$string['privacy:metadata:schedule'] = 'Report schedule definitions';
|
||||
$string['privacy:metadata:schedule:audiences'] = 'The audiences this schedule is for';
|
||||
$string['privacy:metadata:schedule:classname'] = 'The class used by the schedule';
|
||||
$string['privacy:metadata:schedule:configdata'] = 'Configuration data used by the schedule';
|
||||
$string['privacy:metadata:schedule:enabled'] = 'The status of the schedule';
|
||||
$string['privacy:metadata:schedule:format'] = 'The format of the scheduled report';
|
||||
$string['privacy:metadata:schedule:message'] = 'The message of the schedule';
|
||||
$string['privacy:metadata:schedule:name'] = 'The name of the schedule';
|
||||
$string['privacy:metadata:schedule:recurrence'] = 'The recurrence of the schedule';
|
||||
$string['privacy:metadata:schedule:reportempty'] = 'Action to take if scheduled report is empty';
|
||||
$string['privacy:metadata:schedule:subject'] = 'The subject of the schedule';
|
||||
$string['privacy:metadata:schedule:timecreated'] = 'The time when the schedule was created';
|
||||
$string['privacy:metadata:schedule:timemodified'] = 'The time when the schedule was last modified';
|
||||
$string['privacy:metadata:schedule:timescheduled'] = 'The time the schedule will begin';
|
||||
@@ -255,6 +254,8 @@ $string['resetconditions'] = 'Reset conditions';
|
||||
$string['resetconditionsconfirm'] = 'Are you sure you want to reset all conditions for this report?';
|
||||
$string['schedulecreated'] = 'Schedule created';
|
||||
$string['scheduledeleted'] = 'Schedule deleted';
|
||||
$string['scheduleemail'] = 'Schedule an email';
|
||||
$string['scheduleemaildescription'] = 'Set up a recurring email to share this report with your chosen audience';
|
||||
$string['scheduleempty'] = 'If the report is empty';
|
||||
$string['scheduleemptydontsend'] = 'Don\'t send message';
|
||||
$string['scheduleemptysendwithattachment'] = 'Send message with empty report';
|
||||
@@ -302,3 +303,8 @@ $string['filterdateto'] = 'Date to';
|
||||
// Deprecated since Moodle 5.0.
|
||||
$string['privacy:metadata:preference:reportfilter'] = 'Stored report filter values';
|
||||
$string['userpicture'] = 'User picture';
|
||||
|
||||
// Deprecated since Moodle 5.1.
|
||||
$string['privacy:metadata:schedule:message'] = 'The message of the schedule';
|
||||
$string['privacy:metadata:schedule:reportempty'] = 'Action to take if scheduled report is empty';
|
||||
$string['privacy:metadata:schedule:subject'] = 'The subject of the schedule';
|
||||
|
||||
@@ -4669,15 +4669,13 @@
|
||||
<FIELD NAME="reportid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
|
||||
<FIELD NAME="audiences" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="audiences" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="classname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="configdata" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="format" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="subject" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="message" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="messageformat" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="userviewas" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timescheduled" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="recurrence" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="reportempty" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timelastsent" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timenextsend" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usercreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
|
||||
@@ -2125,5 +2125,70 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2025090200.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2025090200.02) {
|
||||
$table = new xmldb_table('reportbuilder_schedule');
|
||||
|
||||
// Conditionally launch add field classname.
|
||||
$field = new xmldb_field('classname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'enabled');
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Conditionally launch add field configdata.
|
||||
$field = new xmldb_field('configdata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'classname');
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Migrate existing data to new structure.
|
||||
$schedules = $DB->get_records('reportbuilder_schedule');
|
||||
foreach ($schedules as $schedule) {
|
||||
$DB->update_record('reportbuilder_schedule', [
|
||||
'id' => $schedule->id,
|
||||
'classname' => core_reportbuilder\reportbuilder\schedule\message::class,
|
||||
'configdata' => json_encode([
|
||||
'subject' => $schedule->subject,
|
||||
'message' => ['text' => $schedule->message, 'format' => $schedule->messageformat],
|
||||
'reportempty' => $schedule->reportempty,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
// Launch change of nullability for field configdata (after migrating data).
|
||||
$field = new xmldb_field('configdata', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'classname');
|
||||
$dbman->change_field_notnull($table, $field);
|
||||
|
||||
// Launch change of nullability for field audiences.
|
||||
$field = new xmldb_field('audiences', XMLDB_TYPE_TEXT, null, null, null, null, null, 'enabled');
|
||||
$dbman->change_field_notnull($table, $field);
|
||||
|
||||
// Conditionally launch drop field subject.
|
||||
$field = new xmldb_field('subject');
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Conditionally launch drop field message.
|
||||
$field = new xmldb_field('message');
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Conditionally launch drop field messageformat.
|
||||
$field = new xmldb_field('messageformat');
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Conditionally launch drop field reportempty.
|
||||
$field = new xmldb_field('reportempty');
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2025090200.02);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ define("core_reportbuilder/local/repository/modals",["exports","core_form/modalf
|
||||
* @module core_reportbuilder/local/repository/modals
|
||||
* @copyright 2021 David Matamoros <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.createScheduleModal=_exports.createReportModal=_exports.createDuplicateReportModal=void 0,_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};const createModalForm=(triggerElement,modalTitle,formClass,formArgs)=>new _modalform.default({modalConfig:{title:modalTitle},formClass:formClass,args:formArgs,saveButtonText:(0,_str.getString)("save","moodle"),returnFocus:triggerElement});_exports.createReportModal=function(triggerElement,modalTitle){let reportId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\report",{id:reportId})};_exports.createDuplicateReportModal=(triggerElement,modalTitle,reportId,reportName)=>createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\duplicate_report",{id:reportId,name:reportName});_exports.createScheduleModal=function(triggerElement,modalTitle,reportId){let scheduleId=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;return createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\schedule",{reportid:reportId,id:scheduleId})}}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.createScheduleModal=_exports.createReportModal=_exports.createDuplicateReportModal=void 0,_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};const createModalForm=(triggerElement,modalTitle,formClass,formArgs)=>new _modalform.default({modalConfig:{title:modalTitle},formClass:formClass,args:formArgs,saveButtonText:(0,_str.getString)("save","moodle"),returnFocus:triggerElement});_exports.createReportModal=function(triggerElement,modalTitle){let reportId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\report",{id:reportId})};_exports.createDuplicateReportModal=(triggerElement,modalTitle,reportId,reportName)=>createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\duplicate_report",{id:reportId,name:reportName});_exports.createScheduleModal=function(triggerElement,modalTitle,reportId){let scheduleId=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,scheduleClass=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"";return createModalForm(triggerElement,modalTitle,"core_reportbuilder\\form\\schedule",{reportid:reportId,id:scheduleId,classname:scheduleClass})}}));
|
||||
|
||||
//# sourceMappingURL=modals.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"modals.min.js","sources":["../../../src/local/repository/modals.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Module to handle modal form requests\n *\n * @module core_reportbuilder/local/repository/modals\n * @copyright 2021 David Matamoros <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {getString} from 'core/str';\n\n/**\n * Return modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {String} formClass\n * @param {Object} formArgs\n * @return {ModalForm}\n */\nconst createModalForm = (triggerElement, modalTitle, formClass, formArgs) => {\n return new ModalForm({\n modalConfig: {\n title: modalTitle,\n },\n formClass: formClass,\n args: formArgs,\n saveButtonText: getString('save', 'moodle'),\n returnFocus: triggerElement,\n });\n};\n\n/**\n * Return report modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @return {ModalForm}\n */\nexport const createReportModal = (triggerElement, modalTitle, reportId = 0) => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\report', {\n id: reportId,\n });\n};\n\n/**\n * Return duplicate report modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @param {String} reportName\n * @return {ModalForm}\n */\nexport const createDuplicateReportModal = (triggerElement, modalTitle, reportId, reportName) => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\duplicate_report', {\n id: reportId,\n name: reportName,\n });\n};\n\n/**\n * Return schedule modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @param {Number} scheduleId\n * @return {ModalForm}\n */\nexport const createScheduleModal = (triggerElement, modalTitle, reportId, scheduleId = 0) => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\schedule', {\n reportid: reportId,\n id: scheduleId,\n });\n};\n"],"names":["createModalForm","triggerElement","modalTitle","formClass","formArgs","ModalForm","modalConfig","title","args","saveButtonText","returnFocus","reportId","id","reportName","name","scheduleId","reportid"],"mappings":";;;;;;;oOAmCMA,gBAAkB,CAACC,eAAgBC,WAAYC,UAAWC,WACrD,IAAIC,mBAAU,CACjBC,YAAa,CACTC,MAAOL,YAEXC,UAAWA,UACXK,KAAMJ,SACNK,gBAAgB,kBAAU,OAAQ,UAClCC,YAAaT,4CAYY,SAACA,eAAgBC,gBAAYS,gEAAW,SAC9DX,gBAAgBC,eAAgBC,WAAY,mCAAoC,CACnFU,GAAID,gDAa8B,CAACV,eAAgBC,WAAYS,SAAUE,aACtEb,gBAAgBC,eAAgBC,WAAY,6CAA8C,CAC7FU,GAAID,SACJG,KAAMD,0CAaqB,SAACZ,eAAgBC,WAAYS,cAAUI,kEAAa,SAC5Ef,gBAAgBC,eAAgBC,WAAY,qCAAsC,CACrFc,SAAUL,SACVC,GAAIG"}
|
||||
{"version":3,"file":"modals.min.js","sources":["../../../src/local/repository/modals.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Module to handle modal form requests\n *\n * @module core_reportbuilder/local/repository/modals\n * @copyright 2021 David Matamoros <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {getString} from 'core/str';\n\n/**\n * Return modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {String} formClass\n * @param {Object} formArgs\n * @return {ModalForm}\n */\nconst createModalForm = (triggerElement, modalTitle, formClass, formArgs) => {\n return new ModalForm({\n modalConfig: {\n title: modalTitle,\n },\n formClass: formClass,\n args: formArgs,\n saveButtonText: getString('save', 'moodle'),\n returnFocus: triggerElement,\n });\n};\n\n/**\n * Return report modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @return {ModalForm}\n */\nexport const createReportModal = (triggerElement, modalTitle, reportId = 0) => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\report', {\n id: reportId,\n });\n};\n\n/**\n * Return duplicate report modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @param {String} reportName\n * @return {ModalForm}\n */\nexport const createDuplicateReportModal = (triggerElement, modalTitle, reportId, reportName) => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\duplicate_report', {\n id: reportId,\n name: reportName,\n });\n};\n\n/**\n * Return schedule modal instance\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @param {Number} scheduleId\n * @param {String} scheduleClass\n * @return {ModalForm}\n */\nexport const createScheduleModal = (triggerElement, modalTitle, reportId, scheduleId = 0, scheduleClass = '') => {\n return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\\\form\\\\schedule', {\n reportid: reportId,\n id: scheduleId,\n classname: scheduleClass,\n });\n};\n"],"names":["createModalForm","triggerElement","modalTitle","formClass","formArgs","ModalForm","modalConfig","title","args","saveButtonText","returnFocus","reportId","id","reportName","name","scheduleId","scheduleClass","reportid","classname"],"mappings":";;;;;;;oOAmCMA,gBAAkB,CAACC,eAAgBC,WAAYC,UAAWC,WACrD,IAAIC,mBAAU,CACjBC,YAAa,CACTC,MAAOL,YAEXC,UAAWA,UACXK,KAAMJ,SACNK,gBAAgB,kBAAU,OAAQ,UAClCC,YAAaT,4CAYY,SAACA,eAAgBC,gBAAYS,gEAAW,SAC9DX,gBAAgBC,eAAgBC,WAAY,mCAAoC,CACnFU,GAAID,gDAa8B,CAACV,eAAgBC,WAAYS,SAAUE,aACtEb,gBAAgBC,eAAgBC,WAAY,6CAA8C,CAC7FU,GAAID,SACJG,KAAMD,0CAcqB,SAACZ,eAAgBC,WAAYS,cAAUI,kEAAa,EAAGC,qEAAgB,UAC/FhB,gBAAgBC,eAAgBC,WAAY,qCAAsC,CACrFe,SAAUN,SACVC,GAAIG,WACJG,UAAWF"}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -82,11 +82,13 @@ export const createDuplicateReportModal = (triggerElement, modalTitle, reportId,
|
||||
* @param {Promise} modalTitle
|
||||
* @param {Number} reportId
|
||||
* @param {Number} scheduleId
|
||||
* @param {String} scheduleClass
|
||||
* @return {ModalForm}
|
||||
*/
|
||||
export const createScheduleModal = (triggerElement, modalTitle, reportId, scheduleId = 0) => {
|
||||
export const createScheduleModal = (triggerElement, modalTitle, reportId, scheduleId = 0, scheduleClass = '') => {
|
||||
return createModalForm(triggerElement, modalTitle, 'core_reportbuilder\\form\\schedule', {
|
||||
reportid: reportId,
|
||||
id: scheduleId,
|
||||
classname: scheduleClass,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -75,7 +75,10 @@ export const init = reportId => {
|
||||
if (scheduleCreate) {
|
||||
event.preventDefault();
|
||||
|
||||
const scheduleModal = createScheduleModal(event.target, getString('newschedule', 'core_reportbuilder'), reportId);
|
||||
const {scheduleClass} = scheduleCreate.dataset;
|
||||
|
||||
const scheduleModal = createScheduleModal(event.target, getString('newschedule', 'core_reportbuilder'), reportId, 0,
|
||||
scheduleClass);
|
||||
scheduleModal.addEventListener(scheduleModal.events.FORM_SUBMITTED, () => {
|
||||
getString('schedulecreated', 'core_reportbuilder')
|
||||
.then(addToast)
|
||||
|
||||
@@ -26,10 +26,10 @@ use core\output\notification;
|
||||
use core_form\dynamic_form;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\permission;
|
||||
use core_reportbuilder\local\helpers\audience;
|
||||
use core_reportbuilder\local\helpers\schedule as helper;
|
||||
use core_reportbuilder\local\helpers\{audience, schedule as helper};
|
||||
use core_reportbuilder\local\models\schedule as model;
|
||||
use core_reportbuilder\local\report\base;
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\local\report\base as report_base;
|
||||
|
||||
/**
|
||||
* Schedule form
|
||||
@@ -41,11 +41,30 @@ use core_reportbuilder\local\report\base;
|
||||
class schedule extends dynamic_form {
|
||||
|
||||
/**
|
||||
* Return instance of the system report using the filter form
|
||||
* Return schedule instance
|
||||
*
|
||||
* @return base
|
||||
*/
|
||||
private function get_report(): base {
|
||||
private function get_schedule(): base {
|
||||
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
|
||||
$scheduleid = $this->optional_param('id', 0, PARAM_INT);
|
||||
|
||||
if ($scheduleid > 0) {
|
||||
$schedule = model::get_record(['id' => $scheduleid, 'reportid' => $reportid], MUST_EXIST);
|
||||
return base::from_persistent($schedule);
|
||||
} else {
|
||||
/** @var base $scheduleclass */
|
||||
$scheduleclass = $this->optional_param('classname', '', PARAM_RAW);
|
||||
return $scheduleclass::instance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return instance of the system report using the filter form
|
||||
*
|
||||
* @return report_base
|
||||
*/
|
||||
private function get_report(): report_base {
|
||||
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
|
||||
return manager::get_report_from_id($reportid);
|
||||
}
|
||||
@@ -83,6 +102,9 @@ class schedule extends dynamic_form {
|
||||
$mform->addElement('hidden', 'id');
|
||||
$mform->setType('id', PARAM_INT);
|
||||
|
||||
$mform->addElement('hidden', 'classname');
|
||||
$mform->setType('id', PARAM_RAW);
|
||||
|
||||
// General fields.
|
||||
$mform->addElement('header', 'headergeneral', get_string('general'));
|
||||
|
||||
@@ -121,63 +143,47 @@ class schedule extends dynamic_form {
|
||||
}
|
||||
|
||||
// Audience fields.
|
||||
$mform->addElement('header', 'headeraudience', get_string('audience', 'core_reportbuilder'));
|
||||
$mform->setExpanded('headeraudience', true);
|
||||
$schedule = $this->get_schedule();
|
||||
if ($schedule->requires_audience()) {
|
||||
$mform->addElement('header', 'headeraudience', get_string('audience', 'core_reportbuilder'));
|
||||
$mform->setExpanded('headeraudience', true);
|
||||
|
||||
$audiences = audience::get_base_records($this->optional_param('reportid', 0, PARAM_INT));
|
||||
if (empty($audiences)) {
|
||||
$notification = new notification(get_string('noaudiences', 'core_reportbuilder'), notification::NOTIFY_INFO, false);
|
||||
$mform->addElement('static', 'noaudiences', '', $OUTPUT->render($notification));
|
||||
}
|
||||
|
||||
$audiencecheckboxes = [];
|
||||
foreach ($audiences as $audience) {
|
||||
$persistent = $audience->get_persistent();
|
||||
|
||||
// Check for a custom name, otherwise fall back to default.
|
||||
if ('' === $audiencelabel = $persistent->get_formatted_heading($context)) {
|
||||
$audiencelabel = get_string('audiencelabel', 'core_reportbuilder', (object) [
|
||||
'name' => $audience->get_name(),
|
||||
'description' => $audience->get_description(),
|
||||
]);
|
||||
$audiences = audience::get_base_records($this->optional_param('reportid', 0, PARAM_INT));
|
||||
if (empty($audiences)) {
|
||||
$notification = new notification(get_string('noaudiences', 'core_reportbuilder'), notification::NOTIFY_INFO, false);
|
||||
$mform->addElement('static', 'noaudiences', '', $OUTPUT->render($notification));
|
||||
}
|
||||
|
||||
$audiencecheckboxes[] = $mform->createElement('checkbox', $persistent->get('id'), $audiencelabel);
|
||||
$audiencecheckboxes = [];
|
||||
foreach ($audiences as $audience) {
|
||||
$persistent = $audience->get_persistent();
|
||||
|
||||
// Check for a custom name, otherwise fall back to default.
|
||||
if ('' === $audiencelabel = $persistent->get_formatted_heading($context)) {
|
||||
$audiencelabel = get_string('audiencelabel', 'core_reportbuilder', (object) [
|
||||
'name' => $audience->get_name(),
|
||||
'description' => $audience->get_description(),
|
||||
]);
|
||||
}
|
||||
|
||||
$audiencecheckboxes[] = $mform->createElement('checkbox', $persistent->get('id'), $audiencelabel);
|
||||
}
|
||||
|
||||
$mform->addElement('group', 'audiences', '', $audiencecheckboxes, html_writer::div('', 'w-100 mb-2'));
|
||||
}
|
||||
|
||||
$mform->addElement('group', 'audiences', '', $audiencecheckboxes, html_writer::div('', 'w-100 mb-2'));
|
||||
|
||||
// Message fields.
|
||||
$mform->addElement('header', 'headermessage', get_string('messagecontent', 'core_reportbuilder'));
|
||||
|
||||
$mform->addElement('text', 'subject', get_string('messagesubject', 'core_reportbuilder'));
|
||||
$mform->setType('subject', PARAM_TEXT);
|
||||
$mform->addRule('subject', null, 'required', null, 'client');
|
||||
$mform->addRule('subject', get_string('maximumchars', '', 255), 'maxlength', 255);
|
||||
|
||||
$mform->addElement('editor', 'message', get_string('messagebody', 'core_reportbuilder'), null, ['autosave' => false]);
|
||||
$mform->setType('message', PARAM_RAW);
|
||||
$mform->addRule('message', null, 'required', null, 'client');
|
||||
|
||||
// Advanced.
|
||||
$mform->addElement('header', 'headeradvanced', get_string('advanced'));
|
||||
|
||||
$mform->addElement('select', 'reportempty', get_string('scheduleempty', 'core_reportbuilder'),
|
||||
helper::get_report_empty_options());
|
||||
$mform->setType('reportempty', PARAM_INT);
|
||||
// Load schedule type form definition.
|
||||
$schedule->definition($mform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load form data if we are editing an existing schedule
|
||||
*/
|
||||
public function set_data_for_dynamic_submission(): void {
|
||||
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
|
||||
$scheduleid = $this->optional_param('id', 0, PARAM_INT);
|
||||
$schedule = $this->get_schedule();
|
||||
|
||||
if ($scheduleid > 0) {
|
||||
$schedule = model::get_record(['id' => $scheduleid, 'reportid' => $reportid]);
|
||||
|
||||
$data = (array) $schedule->to_record();
|
||||
if ($schedule->get_persistent()->get('id') > 0) {
|
||||
$data = (array) $schedule->get_persistent()->to_record();
|
||||
|
||||
// Pre-process some of the form fields.
|
||||
if (!in_array($data['userviewas'], [model::REPORT_VIEWAS_CREATOR, model::REPORT_VIEWAS_RECIPIENT])) {
|
||||
@@ -185,17 +191,18 @@ class schedule extends dynamic_form {
|
||||
$data['userviewas'] = model::REPORT_VIEWAS_USER;
|
||||
}
|
||||
|
||||
$audiences = json_decode($data['audiences']);
|
||||
$data['audiences'] = array_fill_keys($audiences, 1);
|
||||
if ($schedule->requires_audience()) {
|
||||
$audiences = (array) json_decode((string) $data['audiences']);
|
||||
$data['audiences'] = array_fill_keys($audiences, 1);
|
||||
}
|
||||
|
||||
$data['message'] = [
|
||||
'text' => $data['message'],
|
||||
'format' => $data['messageformat'],
|
||||
];
|
||||
// Load schedule type form definition data.
|
||||
$data['configdata'] = $schedule->get_configdata();
|
||||
|
||||
$this->set_data($data);
|
||||
} else {
|
||||
$this->set_data(['reportid' => $reportid]);
|
||||
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
|
||||
$this->set_data(['reportid' => $reportid, 'classname' => $schedule::class]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,11 +227,13 @@ class schedule extends dynamic_form {
|
||||
$errors['user'] = get_string('required');
|
||||
}
|
||||
|
||||
if (empty($data['audiences'])) {
|
||||
// Load schedule type form validation.
|
||||
$schedule = $this->get_schedule();
|
||||
if ($schedule->requires_audience() && empty($data['audiences'])) {
|
||||
$errors['audiences'] = get_string('required');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
return array_merge($errors, $schedule->validate($data, $files));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,13 +247,21 @@ class schedule extends dynamic_form {
|
||||
$data->userviewas = (int) $data->user;
|
||||
}
|
||||
|
||||
$data->audiences = json_encode(array_keys($data->audiences));
|
||||
['text' => $data->message, 'format' => $data->messageformat] = $data->message;
|
||||
$schedule = $this->get_schedule();
|
||||
|
||||
if ($data->id) {
|
||||
if ($schedule->requires_audience()) {
|
||||
$data->audiences = json_encode(array_keys($data->audiences));
|
||||
} else {
|
||||
$data->audiences = null;
|
||||
}
|
||||
|
||||
$data->configdata = json_encode((array) $data->configdata);
|
||||
|
||||
if ($schedule->get_persistent()->get('id') > 0) {
|
||||
helper::update_schedule($data);
|
||||
} else {
|
||||
helper::create_schedule($data);
|
||||
unset($data->id);
|
||||
$schedule::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ class audience {
|
||||
public static function get_audiences_for_report_schedules(int $reportid): array {
|
||||
global $DB;
|
||||
|
||||
$audiences = $DB->get_fieldset_select(schedule::TABLE, 'audiences', 'reportid = ?', [$reportid]);
|
||||
$audiences = $DB->get_fieldset_select(schedule::TABLE, 'audiences', 'reportid = ? AND audiences IS NOT NULL', [$reportid]);
|
||||
|
||||
// Reduce JSON encoded audience data of each schedule to an array of audience IDs.
|
||||
$audienceids = array_reduce($audiences, static function(array $carry, string $audience): array {
|
||||
|
||||
@@ -171,7 +171,7 @@ class report {
|
||||
// Map new audience ids with the old ones.
|
||||
$audiences = array_map(
|
||||
fn($audienceid) => $audiencemap[$audienceid] ?? 0,
|
||||
(array) json_decode($schedulerecord->audiences),
|
||||
(array) json_decode((string) $schedulerecord->audiences),
|
||||
);
|
||||
|
||||
(new schedule(0, $schedulerecord))
|
||||
|
||||
@@ -19,7 +19,7 @@ declare(strict_types=1);
|
||||
namespace core_reportbuilder\local\helpers;
|
||||
|
||||
use context_user;
|
||||
use core\{clock, di};
|
||||
use core\{component, clock, di};
|
||||
use core\exception\{coding_exception, invalid_parameter_exception};
|
||||
use core_user;
|
||||
use stdClass;
|
||||
@@ -29,6 +29,7 @@ use core\message\message;
|
||||
use core\plugininfo\dataformat;
|
||||
use core_reportbuilder\local\models\audience as audience_model;
|
||||
use core_reportbuilder\local\models\schedule as model;
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\table\custom_report_table_view;
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,26 @@ use core_reportbuilder\table\custom_report_table_view;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class schedule {
|
||||
/**
|
||||
* Determine whether given class is a valid schedule type
|
||||
*
|
||||
* @param string $scheduleclass
|
||||
* @return bool
|
||||
*/
|
||||
public static function valid(string $scheduleclass): bool {
|
||||
return class_exists($scheduleclass) && is_subclass_of($scheduleclass, base::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all available/valid schedule types
|
||||
*
|
||||
* @return base[]
|
||||
*/
|
||||
public static function get_schedules(): array {
|
||||
$classes = component::get_component_classes_in_namespace(null, 'reportbuilder\\schedule');
|
||||
|
||||
return array_filter(array_keys($classes), [static::class, 'valid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create report schedule, calculate when it should be next sent
|
||||
@@ -46,18 +67,18 @@ class schedule {
|
||||
* @param stdClass $data
|
||||
* @param int|null $timenow Deprecated since Moodle 4.5 - please use {@see clock} dependency injection
|
||||
* @return model
|
||||
*
|
||||
* @deprecated since Moodle 5.1 - please do not use this function any more, {@see base::create}
|
||||
*/
|
||||
#[\core\attribute\deprecated(base::class . '::create', since: '5.1', mdl: 'MDL-86066')]
|
||||
public static function create_schedule(stdClass $data, ?int $timenow = null): model {
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
|
||||
if ($timenow !== null) {
|
||||
debugging('Passing $timenow is deprecated, please use \core\clock dependency injection', DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$data->name = trim($data->name);
|
||||
|
||||
$schedule = (new model(0, $data));
|
||||
$schedule->set('timenextsend', self::calculate_next_send_time($schedule));
|
||||
|
||||
return $schedule->create();
|
||||
return base::create($data)->get_persistent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +134,7 @@ class schedule {
|
||||
public static function get_schedule_report_users(model $schedule): array {
|
||||
global $DB;
|
||||
|
||||
$audienceids = (array) json_decode($schedule->get('audiences'));
|
||||
$audienceids = (array) json_decode((string) $schedule->get('audiences'));
|
||||
|
||||
// Retrieve all selected audience records for the schedule.
|
||||
[$audienceselect, $audienceparams] = $DB->get_in_or_equal($audienceids, SQL_PARAMS_NAMED, 'aid', true, null);
|
||||
@@ -317,8 +338,13 @@ class schedule {
|
||||
* @param stdClass $user
|
||||
* @param stored_file $attachment
|
||||
* @return bool
|
||||
*
|
||||
* @deprecated since Moodle 5.1 - please do not use this function any more
|
||||
*/
|
||||
#[\core\attribute\deprecated(reason: 'It is no longer used', since: '5.1', mdl: 'MDL-86066')]
|
||||
public static function send_schedule_message(model $schedule, stdClass $user, stored_file $attachment): bool {
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
|
||||
$message = new message();
|
||||
$message->component = 'moodle';
|
||||
$message->name = 'reportbuilderschedule';
|
||||
@@ -402,12 +428,17 @@ class schedule {
|
||||
* Return list of options for when report is empty
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @deprecated since Moodle 5.1 - please do not use this function any more
|
||||
*/
|
||||
#[\core\attribute\deprecated(reason: 'It is no longer used', since: '5.1', mdl: 'MDL-86066')]
|
||||
public static function get_report_empty_options(): array {
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
|
||||
return [
|
||||
model::REPORT_EMPTY_SEND_EMPTY => get_string('scheduleemptysendwithattachment', 'core_reportbuilder'),
|
||||
model::REPORT_EMPTY_SEND_WITHOUT => get_string('scheduleemptysendwithoutattachment', 'core_reportbuilder'),
|
||||
model::REPORT_EMPTY_DONT_SEND => get_string('scheduleemptydontsend', 'core_reportbuilder'),
|
||||
0 => get_string('scheduleemptysendwithattachment', 'core_reportbuilder'),
|
||||
1 => get_string('scheduleemptysendwithoutattachment', 'core_reportbuilder'),
|
||||
2 => get_string('scheduleemptydontsend', 'core_reportbuilder'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,15 +67,6 @@ class schedule extends persistent {
|
||||
/** @var int Annual recurrence */
|
||||
public const RECURRENCE_ANNUALLY = 5;
|
||||
|
||||
/** @var int Send schedule with empty report */
|
||||
public const REPORT_EMPTY_SEND_EMPTY = 0;
|
||||
|
||||
/** @var int Send schedule without report */
|
||||
public const REPORT_EMPTY_SEND_WITHOUT = 1;
|
||||
|
||||
/** @var int Don't send schedule if report is empty */
|
||||
public const REPORT_EMPTY_DONT_SEND = 2;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
@@ -95,27 +86,19 @@ class schedule extends persistent {
|
||||
],
|
||||
'audiences' => [
|
||||
'type' => PARAM_RAW,
|
||||
'default' => '[]',
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => null,
|
||||
],
|
||||
'classname' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'configdata' => [
|
||||
'type' => PARAM_RAW,
|
||||
'default' => '{}',
|
||||
],
|
||||
'format' => [
|
||||
'type' => PARAM_PLUGIN,
|
||||
],
|
||||
'subject' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'message' => [
|
||||
'type' => PARAM_CLEANHTML,
|
||||
],
|
||||
'messageformat' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => FORMAT_HTML,
|
||||
'choices' => [
|
||||
FORMAT_MOODLE,
|
||||
FORMAT_HTML,
|
||||
FORMAT_PLAIN,
|
||||
FORMAT_MARKDOWN,
|
||||
],
|
||||
],
|
||||
'userviewas' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => self::REPORT_VIEWAS_CREATOR,
|
||||
@@ -136,15 +119,6 @@ class schedule extends persistent {
|
||||
self::RECURRENCE_ANNUALLY,
|
||||
],
|
||||
],
|
||||
'reportempty' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => self::REPORT_EMPTY_SEND_EMPTY,
|
||||
'choices' => [
|
||||
self::REPORT_EMPTY_SEND_EMPTY,
|
||||
self::REPORT_EMPTY_SEND_WITHOUT,
|
||||
self::REPORT_EMPTY_DONT_SEND,
|
||||
],
|
||||
],
|
||||
'timelastsent' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 0,
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_reportbuilder\local\schedules;
|
||||
|
||||
use core_reportbuilder\local\helpers\schedule as helper;
|
||||
use core_reportbuilder\local\models\schedule;
|
||||
use MoodleQuickForm;
|
||||
use progress_trace;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Schedule base class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2025 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
/**
|
||||
* Private constructor, please use {@see instance} or {@see from_persistent} methods instead
|
||||
*
|
||||
* @param schedule $schedule The persistent object associated with this schedule
|
||||
*/
|
||||
private function __construct(
|
||||
/** @var schedule The persistent object associated with this schedule */
|
||||
private schedule $schedule,
|
||||
) {
|
||||
// Nothing to see here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Load instance of schedule type
|
||||
*
|
||||
* @param int $id
|
||||
* @param stdClass|null $record
|
||||
* @return self|null
|
||||
*/
|
||||
final public static function instance(int $id = 0, ?stdClass $record = null): ?self {
|
||||
$schedule = new schedule($id, $record);
|
||||
return static::from_persistent($schedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load instance of schedule type from persistent
|
||||
*
|
||||
* @param schedule $schedule
|
||||
* @return self|null
|
||||
*/
|
||||
final public static function from_persistent(schedule $schedule): ?self {
|
||||
// Ensure schedule class is always populated.
|
||||
if (!$classname = $schedule->get('classname')) {
|
||||
$classname = get_called_class();
|
||||
$schedule->set('classname', $classname);
|
||||
}
|
||||
|
||||
if (!helper::valid($classname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $classname($schedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of schedule type from record
|
||||
*
|
||||
* @param stdClass $record
|
||||
* @return self
|
||||
*/
|
||||
final public static function create(stdClass $record): self {
|
||||
$schedule = new schedule(0, $record);
|
||||
|
||||
$schedule->set_many([
|
||||
'name' => trim($schedule->get('name')),
|
||||
'timenextsend' => helper::calculate_next_send_time($schedule),
|
||||
]);
|
||||
|
||||
$instance = self::from_persistent($schedule);
|
||||
$instance->get_persistent()->create();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the schedule
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_name(): string;
|
||||
|
||||
/**
|
||||
* Description of the schedule
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_description(): string;
|
||||
|
||||
/**
|
||||
* If the current user is able to add this schedule type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function user_can_add(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the schedule requires audience configuration
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function requires_audience(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule specific form definition elements
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
abstract public function definition(MoodleQuickForm $mform): void;
|
||||
|
||||
/**
|
||||
* Validate schedule specific form data
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array
|
||||
*/
|
||||
public function validate(array $data, array $files): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the schedule. If {@see requires_audience}, then a list of audience user records will be passed as parameter
|
||||
*
|
||||
* Will be called via cron as part of the {@see \core_reportbuilder\task\send_schedule} task
|
||||
*
|
||||
* @param stdClass[] $users
|
||||
* @param progress_trace $trace
|
||||
*/
|
||||
abstract public function execute(array $users, progress_trace $trace): void;
|
||||
|
||||
/**
|
||||
* Return schedule persistent
|
||||
*
|
||||
* @return schedule
|
||||
*/
|
||||
final public function get_persistent(): schedule {
|
||||
return $this->schedule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decoded schedule config
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_configdata(): array {
|
||||
return json_decode($this->schedule->get('configdata'), true);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ use core_reportbuilder\local\filters\{boolean_select, date, text};
|
||||
use core_reportbuilder\local\helpers\format;
|
||||
use core_reportbuilder\local\models\{report, schedule};
|
||||
use core_reportbuilder\local\report\{action, column, filter};
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\output\schedule_name_editable;
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,7 @@ class report_schedules extends system_report {
|
||||
$this->add_base_condition_simple('sc.reportid', $this->get_parameter('reportid', 0, PARAM_INT));
|
||||
|
||||
// Select fields required for actions, permission checks, and row class callbacks.
|
||||
$this->add_base_fields('sc.id, sc.name, sc.enabled, rb.contextid');
|
||||
$this->add_base_fields('sc.id, sc.name, sc.enabled, sc.classname, rb.contextid');
|
||||
|
||||
// Join user entity for "User modified" column.
|
||||
$entityuser = new user();
|
||||
@@ -310,31 +311,38 @@ class report_schedules extends system_report {
|
||||
*/
|
||||
protected function add_actions(): void {
|
||||
// Edit action.
|
||||
$this->add_action(new action(
|
||||
$this->add_action((new action(
|
||||
new moodle_url('#'),
|
||||
new pix_icon('t/edit', ''),
|
||||
['data-action' => 'schedule-edit', 'data-schedule-id' => ':id'],
|
||||
false,
|
||||
new lang_string('editscheduledetails', 'core_reportbuilder')
|
||||
));
|
||||
))
|
||||
->add_callback(static function (stdClass $row): bool {
|
||||
$instance = base::instance(0, $row);
|
||||
return $instance !== null && $instance->user_can_add();
|
||||
}));
|
||||
|
||||
// Send now action.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('#'),
|
||||
new pix_icon('t/email', ''),
|
||||
new pix_icon('t/play', ''),
|
||||
['data-action' => 'schedule-send', 'data-schedule-id' => ':id', 'data-schedule-name' => ':name'],
|
||||
false,
|
||||
new lang_string('sendschedule', 'core_reportbuilder')
|
||||
))
|
||||
->add_callback(function(stdClass $row): bool {
|
||||
$instance = base::instance(0, $row);
|
||||
if ($instance === null || !$instance->user_can_add()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure data name attribute is properly formatted.
|
||||
$row->name = (new schedule(0, $row))->get_formatted_name(
|
||||
$row->name = $instance->get_persistent()->get_formatted_name(
|
||||
context::instance_by_id($row->contextid));
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
// Delete action.
|
||||
$this->add_action((new action(
|
||||
@@ -350,13 +358,16 @@ class report_schedules extends system_report {
|
||||
new lang_string('deleteschedule', 'core_reportbuilder')
|
||||
))
|
||||
->add_callback(function(stdClass $row): bool {
|
||||
$instance = base::instance(0, $row);
|
||||
if ($instance !== null && !$instance->user_can_add()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure data name attribute is properly formatted.
|
||||
$row->name = (new schedule(0, $row))->get_formatted_name(
|
||||
context::instance_by_id($row->contextid));
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,14 @@ declare(strict_types=1);
|
||||
namespace core_reportbuilder\output\dynamictabs;
|
||||
|
||||
use context_system;
|
||||
use renderer_base;
|
||||
use core\output\{choicelist, renderer_base};
|
||||
use core\output\local\dropdown\{dialog, status};
|
||||
use core\output\dynamic_tabs\base;
|
||||
use core_reportbuilder\permission;
|
||||
use core_reportbuilder\system_report_factory;
|
||||
use core_reportbuilder\local\helpers\schedule;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\systemreports\report_schedules;
|
||||
use core_reportbuilder\output\report_action;
|
||||
|
||||
/**
|
||||
* Schedules dynamic tab
|
||||
@@ -45,12 +46,38 @@ class schedules extends base {
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
$report = system_report_factory::create(report_schedules::class, context_system::instance(), '', '', 0,
|
||||
['reportid' => $this->data['reportid']]);
|
||||
$report->set_report_action(new report_action(
|
||||
|
||||
// Schedule type menu.
|
||||
$choicelist = new choicelist();
|
||||
$choicelist->set_allow_empty(true);
|
||||
|
||||
// Include those schedule types the user can add.
|
||||
foreach (schedule::get_schedules() as $schedule) {
|
||||
$instance = $schedule::instance();
|
||||
if ($instance->user_can_add()) {
|
||||
$choicelist->add_option(
|
||||
$instance::class,
|
||||
$instance->get_name(),
|
||||
[
|
||||
'description' => $instance->get_description(),
|
||||
'extras' => ['data-action' => 'schedule-create', 'data-schedule-class' => $instance::class],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$dialog = new status(
|
||||
get_string('newschedule', 'core_reportbuilder'),
|
||||
['class' => 'btn btn-primary ms-auto', 'data-action' => 'schedule-create'],
|
||||
));
|
||||
$choicelist,
|
||||
[
|
||||
'classes' => 'd-flex justify-content-end mb-2',
|
||||
'buttonclasses' => 'btn btn-primary dropdown-toggle',
|
||||
],
|
||||
);
|
||||
$dialog->set_dialog_width(dialog::WIDTH['small']);
|
||||
|
||||
return [
|
||||
'menu' => $dialog->export_for_template($output),
|
||||
'reportid' => $this->data['reportid'],
|
||||
'report' => $report->output(),
|
||||
];
|
||||
|
||||
@@ -30,6 +30,7 @@ use core_privacy\local\request\userlist;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\local\helpers\schedule as schedule_helper;
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\local\models\{audience, column, filter, report, schedule, user_filter};
|
||||
|
||||
/**
|
||||
@@ -95,13 +96,12 @@ class provider implements
|
||||
'name' => 'privacy:metadata:schedule:name',
|
||||
'enabled' => 'privacy:metadata:schedule:enabled',
|
||||
'audiences' => 'privacy:metadata:schedule:audiences',
|
||||
'classname' => 'privacy:metadata:schedule:classname',
|
||||
'configdata' => 'privacy:metadata:schedule:configdata',
|
||||
'format' => 'privacy:metadata:schedule:format',
|
||||
'subject' => 'privacy:metadata:schedule:subject',
|
||||
'message' => 'privacy:metadata:schedule:message',
|
||||
'userviewas' => 'privacy:metadata:schedule:userviewas',
|
||||
'timescheduled' => 'privacy:metadata:schedule:timescheduled',
|
||||
'recurrence' => 'privacy:metadata:schedule:recurrence',
|
||||
'reportempty' => 'privacy:metadata:schedule:reportempty',
|
||||
'usercreated' => 'privacy:metadata:schedule:usercreated',
|
||||
'usermodified' => 'privacy:metadata:schedule:usermodified',
|
||||
'timecreated' => 'privacy:metadata:schedule:timecreated',
|
||||
@@ -367,31 +367,38 @@ class provider implements
|
||||
$formatoptions = schedule_helper::get_format_options();
|
||||
$recurrenceoptions = schedule_helper::get_recurrence_options();
|
||||
$viewasoptions = schedule_helper::get_viewas_options();
|
||||
$reportemptyoptions = schedule_helper::get_report_empty_options();
|
||||
|
||||
$scheduledata = array_map(static function(schedule $schedule) use (
|
||||
$context, $formatoptions, $recurrenceoptions, $viewasoptions, $reportemptyoptions): stdClass {
|
||||
$scheduledata = array_map(
|
||||
static function (schedule $schedule) use (
|
||||
$context,
|
||||
$formatoptions,
|
||||
$recurrenceoptions,
|
||||
$viewasoptions,
|
||||
): stdClass {
|
||||
// Show the schedule name, if it exists.
|
||||
$scheduleinstance = base::from_persistent($schedule);
|
||||
|
||||
// The "User view as" property will be either creator, recipient or a specific userid.
|
||||
$userviewas = $schedule->get('userviewas');
|
||||
// The "User view as" property will be either creator, recipient or a specific userid.
|
||||
$userviewas = $schedule->get('userviewas');
|
||||
|
||||
return (object) [
|
||||
'name' => $schedule->get_formatted_name($context),
|
||||
'enabled' => transform::yesno($schedule->get('enabled')),
|
||||
'format' => $formatoptions[$schedule->get('format')],
|
||||
'timescheduled' => transform::datetime($schedule->get('timescheduled')),
|
||||
'recurrence' => $recurrenceoptions[$schedule->get('recurrence')],
|
||||
'userviewas' => $viewasoptions[$userviewas] ?? transform::user($userviewas),
|
||||
'audiences' => $schedule->get('audiences'),
|
||||
'subject' => $schedule->get('subject'),
|
||||
'message' => format_text($schedule->get('message'), $schedule->get('messageformat'), ['context' => $context]),
|
||||
'reportempty' => $reportemptyoptions[$schedule->get('reportempty')],
|
||||
'usercreated' => transform::user($schedule->get('usercreated')),
|
||||
'usermodified' => transform::user($schedule->get('usermodified')),
|
||||
'timecreated' => transform::datetime($schedule->get('timecreated')),
|
||||
'timemodified' => transform::datetime($schedule->get('timemodified')),
|
||||
];
|
||||
}, $schedules);
|
||||
return (object) [
|
||||
'name' => $schedule->get_formatted_name($context),
|
||||
'enabled' => transform::yesno($schedule->get('enabled')),
|
||||
'classname' => $scheduleinstance?->get_name() ?? $schedule->get('classname'),
|
||||
'configdata' => $schedule->get('configdata'),
|
||||
'format' => $formatoptions[$schedule->get('format')],
|
||||
'timescheduled' => transform::datetime($schedule->get('timescheduled')),
|
||||
'recurrence' => $recurrenceoptions[$schedule->get('recurrence')],
|
||||
'userviewas' => $viewasoptions[$userviewas] ?? transform::user($userviewas),
|
||||
'audiences' => $schedule->get('audiences'),
|
||||
'usercreated' => transform::user($schedule->get('usercreated')),
|
||||
'usermodified' => transform::user($schedule->get('usermodified')),
|
||||
'timecreated' => transform::datetime($schedule->get('timecreated')),
|
||||
'timemodified' => transform::datetime($schedule->get('timemodified')),
|
||||
];
|
||||
},
|
||||
$schedules,
|
||||
);
|
||||
|
||||
writer::with_context($context)->export_related_data($subcontext, 'schedules', (object) ['data' => $scheduledata]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_reportbuilder\reportbuilder\schedule;
|
||||
|
||||
use core\exception\moodle_exception;
|
||||
use core\user;
|
||||
use core_reportbuilder\local\helpers\{report, schedule as helper};
|
||||
use core_reportbuilder\local\models\schedule;
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use MoodleQuickForm;
|
||||
use progress_trace;
|
||||
use stdClass;
|
||||
use stored_file;
|
||||
|
||||
/**
|
||||
* Message schedule class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2025 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class message extends base {
|
||||
/** @var int Send schedule with empty report */
|
||||
public const REPORT_EMPTY_SEND_EMPTY = 0;
|
||||
|
||||
/** @var int Send schedule without report */
|
||||
public const REPORT_EMPTY_SEND_WITHOUT = 1;
|
||||
|
||||
/** @var int Don't send schedule if report is empty */
|
||||
public const REPORT_EMPTY_DONT_SEND = 2;
|
||||
|
||||
#[\Override]
|
||||
public function get_name(): string {
|
||||
return get_string('scheduleemail', 'core_reportbuilder');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_description(): string {
|
||||
return get_string('scheduleemaildescription', 'core_reportbuilder');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function definition(MoodleQuickForm $mform): void {
|
||||
// Message fields.
|
||||
$mform->addElement('header', 'headermessage', get_string('messagecontent', 'core_reportbuilder'));
|
||||
|
||||
$mform->addElement('text', 'configdata[subject]', get_string('messagesubject', 'core_reportbuilder'));
|
||||
$mform->setType('configdata[subject]', PARAM_TEXT);
|
||||
$mform->addRule('configdata[subject]', null, 'required', null, 'client');
|
||||
$mform->addRule('configdata[subject]', get_string('maximumchars', '', 255), 'maxlength', 255);
|
||||
|
||||
$mform->addElement('editor', 'configdata[message]', get_string('messagebody', 'core_reportbuilder'), null, [
|
||||
'autosave' => false,
|
||||
]);
|
||||
$mform->setType('configdata[message]', PARAM_RAW);
|
||||
$mform->addRule('configdata[message]', null, 'required', null, 'client');
|
||||
|
||||
// Advanced.
|
||||
$mform->addElement('header', 'headeradvanced', get_string('advanced'));
|
||||
|
||||
$mform->addElement('select', 'configdata[reportempty]', get_string('scheduleempty', 'core_reportbuilder'), [
|
||||
static::REPORT_EMPTY_SEND_EMPTY => get_string('scheduleemptysendwithattachment', 'core_reportbuilder'),
|
||||
static::REPORT_EMPTY_SEND_WITHOUT => get_string('scheduleemptysendwithoutattachment', 'core_reportbuilder'),
|
||||
static::REPORT_EMPTY_DONT_SEND => get_string('scheduleemptydontsend', 'core_reportbuilder'),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function execute(array $users, progress_trace $trace): void {
|
||||
$scheduleattachment = null;
|
||||
|
||||
if (count($users) > 0) {
|
||||
$schedule = $this->get_persistent();
|
||||
|
||||
$scheduleuserviewas = $schedule->get('userviewas');
|
||||
$schedulereportempty = $this->get_configdata()['reportempty'] ?? static::REPORT_EMPTY_SEND_EMPTY;
|
||||
|
||||
// Handle schedule configuration as to who the report should be viewed as.
|
||||
if ($scheduleuserviewas === schedule::REPORT_VIEWAS_CREATOR) {
|
||||
$scheduleattachment = helper::get_schedule_report_file($schedule);
|
||||
} else if ($scheduleuserviewas !== schedule::REPORT_VIEWAS_RECIPIENT) {
|
||||
// Get the user to view the schedule report as, ensure it's an active account.
|
||||
try {
|
||||
$scheduleviewas = user::get_user($scheduleuserviewas, '*', MUST_EXIST);
|
||||
user::require_active_user($scheduleviewas);
|
||||
} catch (moodle_exception $exception) {
|
||||
$trace->output('Invalid schedule view as user: ' . $exception->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
\core\cron::setup_user($scheduleviewas);
|
||||
$scheduleattachment = helper::get_schedule_report_file($schedule);
|
||||
}
|
||||
|
||||
// Apply special handling if report is empty (default is to send it anyway).
|
||||
if (
|
||||
$schedulereportempty === static::REPORT_EMPTY_DONT_SEND &&
|
||||
$scheduleattachment !== null &&
|
||||
report::get_report_row_count($schedule->get('reportid')) === 0
|
||||
) {
|
||||
$trace->output('Empty report, skipping', 1);
|
||||
} else {
|
||||
// Now iterate over recipient users, send the report to each.
|
||||
foreach ($users as $user) {
|
||||
$trace->output('Sending to: ' . fullname($user, true), 1);
|
||||
|
||||
// If we already created the attachment, send that. Otherwise generate per recipient.
|
||||
if ($scheduleattachment !== null) {
|
||||
$this->send_message($user, $scheduleattachment);
|
||||
} else {
|
||||
\core\cron::setup_user($user);
|
||||
|
||||
if (
|
||||
$schedulereportempty === static::REPORT_EMPTY_DONT_SEND &&
|
||||
report::get_report_row_count($schedule->get('reportid')) === 0
|
||||
) {
|
||||
$trace->output('Empty report, skipping', 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
$recipientattachment = helper::get_schedule_report_file($schedule);
|
||||
$this->send_message($user, $recipientattachment);
|
||||
$recipientattachment->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($scheduleattachment !== null) {
|
||||
$scheduleattachment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send schedule message to user
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @param stored_file $attachment
|
||||
* @return bool
|
||||
*/
|
||||
private function send_message(stdClass $user, stored_file $attachment): bool {
|
||||
$config = $this->get_configdata();
|
||||
|
||||
$message = new \core\message\message();
|
||||
$message->component = 'moodle';
|
||||
$message->name = 'reportbuilderschedule';
|
||||
$message->courseid = SITEID;
|
||||
$message->userfrom = user::get_noreply_user();
|
||||
$message->userto = $user;
|
||||
$message->subject = $config['subject'];
|
||||
$message->fullmessage = $config['message']['text'];
|
||||
$message->fullmessageformat = $config['message']['format'];
|
||||
$message->fullmessagehtml = $message->fullmessage;
|
||||
$message->smallmessage = $message->fullmessage;
|
||||
|
||||
// Attach report to outgoing message.
|
||||
$message->attachment = $attachment;
|
||||
$message->attachname = $attachment->get_filename();
|
||||
|
||||
return (bool) message_send($message);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ namespace core_reportbuilder\task;
|
||||
use core\{clock, di};
|
||||
use core\task\adhoc_task;
|
||||
use core_user;
|
||||
use core_reportbuilder\local\helpers\{report, schedule as helper};
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\local\helpers\schedule as helper;
|
||||
use core_reportbuilder\local\models\schedule;
|
||||
use moodle_exception;
|
||||
|
||||
@@ -62,14 +63,13 @@ class send_schedule extends adhoc_task {
|
||||
}
|
||||
|
||||
$schedule = schedule::get_record(['id' => $scheduleid, 'reportid' => $reportid]);
|
||||
if ($schedule === false) {
|
||||
if ($schedule === false || !$instance = base::from_persistent($schedule)) {
|
||||
$this->log('Invalid schedule', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log_start('Sending schedule: ' . $schedule->get_formatted_name());
|
||||
$this->log_start('Sending schedule: ' . $schedule->get_formatted_name() . ' (' . $instance->get_name() . ')');
|
||||
|
||||
$scheduleattachment = null;
|
||||
$originaluser = $USER;
|
||||
|
||||
// Get the schedule creator, ensure it's an active account.
|
||||
@@ -84,71 +84,19 @@ class send_schedule extends adhoc_task {
|
||||
// Switch to schedule creator, and retrieve list of recipient users.
|
||||
\core\cron::setup_user($schedulecreator);
|
||||
|
||||
$users = helper::get_schedule_report_users($schedule);
|
||||
if (count($users) > 0) {
|
||||
|
||||
$scheduleuserviewas = $schedule->get('userviewas');
|
||||
$schedulereportempty = $schedule->get('reportempty');
|
||||
|
||||
// Handle schedule configuration as to who the report should be viewed as.
|
||||
if ($scheduleuserviewas === schedule::REPORT_VIEWAS_CREATOR) {
|
||||
$scheduleattachment = helper::get_schedule_report_file($schedule);
|
||||
} else if ($scheduleuserviewas !== schedule::REPORT_VIEWAS_RECIPIENT) {
|
||||
|
||||
// Get the user to view the schedule report as, ensure it's an active account.
|
||||
try {
|
||||
$scheduleviewas = core_user::get_user($scheduleuserviewas, '*', MUST_EXIST);
|
||||
core_user::require_active_user($scheduleviewas);
|
||||
} catch (moodle_exception $exception) {
|
||||
$this->log('Invalid schedule view as user: ' . $exception->getMessage(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
\core\cron::setup_user($scheduleviewas);
|
||||
$scheduleattachment = helper::get_schedule_report_file($schedule);
|
||||
}
|
||||
|
||||
// Apply special handling if report is empty (default is to send it anyway).
|
||||
if ($schedulereportempty === schedule::REPORT_EMPTY_DONT_SEND && $scheduleattachment !== null &&
|
||||
report::get_report_row_count($schedule->get('reportid')) === 0) {
|
||||
|
||||
$this->log('Empty report, skipping');
|
||||
} else {
|
||||
|
||||
// Now iterate over recipient users, send the report to each.
|
||||
foreach ($users as $user) {
|
||||
$this->log('Sending to: ' . fullname($user, true));
|
||||
|
||||
// If we already created the attachment, send that. Otherwise generate per recipient.
|
||||
if ($scheduleattachment !== null) {
|
||||
helper::send_schedule_message($schedule, $user, $scheduleattachment);
|
||||
} else {
|
||||
\core\cron::setup_user($user);
|
||||
|
||||
if ($schedulereportempty === schedule::REPORT_EMPTY_DONT_SEND &&
|
||||
report::get_report_row_count($schedule->get('reportid')) === 0) {
|
||||
|
||||
$this->log('Empty report, skipping', 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
$recipientattachment = helper::get_schedule_report_file($schedule);
|
||||
helper::send_schedule_message($schedule, $user, $recipientattachment);
|
||||
$recipientattachment->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
$users = [];
|
||||
if ($instance->requires_audience()) {
|
||||
$users = helper::get_schedule_report_users($schedule);
|
||||
}
|
||||
|
||||
// Execute schedule type.
|
||||
$instance->execute($users, $this->get_trace());
|
||||
|
||||
// Finish, clean up (set persistent property manually to avoid updating it's user/time modified data).
|
||||
$DB->set_field($schedule::TABLE, 'timelastsent', di::get(clock::class)->time(), [
|
||||
'id' => $schedule->get('id'),
|
||||
]);
|
||||
|
||||
if ($scheduleattachment !== null) {
|
||||
$scheduleattachment->delete();
|
||||
}
|
||||
|
||||
$this->log_finish('Sending schedule complete');
|
||||
|
||||
// Restore cron user to original state.
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"menu": {},
|
||||
"report": "The report content",
|
||||
"reportid": 10
|
||||
}
|
||||
}}
|
||||
<h2 class="visually-hidden">{{#str}} schedules, core_reportbuilder {{/str}}</h2>
|
||||
<div class="reportbuilder-schedules-container">
|
||||
{{#menu}}
|
||||
{{> core/local/dropdown/status}}
|
||||
{{/menu}}
|
||||
{{{ report }}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ Feature: Configure access to reports based on intended audience
|
||||
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
|
||||
And I click on the "Schedules" dynamic tab
|
||||
And I press "New schedule"
|
||||
And I click on "Schedule an email" "link" in the ".dropdown" "css_element"
|
||||
And I set the following fields in the "New schedule" "dialogue" to these values:
|
||||
| Name | My schedule |
|
||||
| Starting from | ##tomorrow 11:00## |
|
||||
|
||||
@@ -30,6 +30,7 @@ Feature: Manage custom report schedules
|
||||
And I press "Save changes"
|
||||
When I click on the "Schedules" dynamic tab
|
||||
And I press "New schedule"
|
||||
And I click on "Schedule an email" "link" in the ".dropdown" "css_element"
|
||||
And I set the following fields in the "New schedule" "dialogue" to these values:
|
||||
| Name | My schedule |
|
||||
| Starting from | ##tomorrow 11:00## |
|
||||
@@ -52,6 +53,7 @@ Feature: Manage custom report schedules
|
||||
And I set the field "Rename audience 'All users'" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
|
||||
When I click on the "Schedules" dynamic tab
|
||||
And I press "New schedule"
|
||||
And I click on "Schedule an email" "link" in the ".dropdown" "css_element"
|
||||
Then I should see "English" in the "New schedule" "dialogue"
|
||||
And I should not see "Spanish" in the "New schedule" "dialogue"
|
||||
And I click on "Cancel" "button" in the "New schedule" "dialogue"
|
||||
@@ -147,11 +149,22 @@ Feature: Manage custom report schedules
|
||||
| Name | My updated schedule |
|
||||
| Starting from | ##tomorrow 11:00## |
|
||||
| All users: All site users | 1 |
|
||||
| Subject | Tell me how to win your heart |
|
||||
| Body | For I haven't got a clue |
|
||||
| If the report is empty | Don't send message |
|
||||
And I click on "Save" "button" in the "Edit schedule details" "dialogue"
|
||||
Then I should see "Schedule updated"
|
||||
And the following should exist in the "Report schedules" table:
|
||||
| Name | Time last sent | Time next send | Modified by |
|
||||
| My updated schedule | Never | ##tomorrow 11:00##%A, %d %B %Y, %H:%M## | Admin User |
|
||||
And I press "Edit schedule details" action in the "My updated schedule" report row
|
||||
And the following fields in the "Edit schedule details" "dialogue" match these values:
|
||||
| Name | My updated schedule |
|
||||
# | Starting from | ##tomorrow 11:00## | Nope, can't: MDL-86255.
|
||||
| All users: All site users | 1 |
|
||||
| Subject | Tell me how to win your heart |
|
||||
| Body | For I haven't got a clue |
|
||||
| If the report is empty | Don't send message |
|
||||
|
||||
Scenario: Send report schedule
|
||||
Given the following "core_reportbuilder > Schedules" exist:
|
||||
|
||||
@@ -18,13 +18,10 @@ declare(strict_types=1);
|
||||
|
||||
use core\{clock, di};
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\local\helpers\report as helper;
|
||||
use core_reportbuilder\local\helpers\schedule as schedule_helper;
|
||||
use core_reportbuilder\local\models\column;
|
||||
use core_reportbuilder\local\models\filter;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\models\schedule;
|
||||
use core_reportbuilder\local\audiences\base as audience_base;
|
||||
use core_reportbuilder\local\helpers\report as helper;
|
||||
use core_reportbuilder\local\models\{column, filter, report, schedule};
|
||||
use core_reportbuilder\local\schedules\base as schedule_base;
|
||||
|
||||
/**
|
||||
* Report builder test generator
|
||||
@@ -177,10 +174,9 @@ class core_reportbuilder_generator extends component_generator_base {
|
||||
|
||||
// Default to all users if not specified, for convenience.
|
||||
/** @var audience_base $classname */
|
||||
$classname = $record['classname'] ??
|
||||
\core_reportbuilder\reportbuilder\audience\allusers::class;
|
||||
$classname = $record['classname'] ?? \core_reportbuilder\reportbuilder\audience\allusers::class;
|
||||
|
||||
return ($classname)::create($record['reportid'], $record['configdata']);
|
||||
return $classname::create($record['reportid'], $record['configdata']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,16 +201,14 @@ class core_reportbuilder_generator extends component_generator_base {
|
||||
if (!array_key_exists('format', $record)) {
|
||||
$record['format'] = 'csv';
|
||||
}
|
||||
if (!array_key_exists('subject', $record)) {
|
||||
$record['subject'] = $record['name'] . ' subject';
|
||||
}
|
||||
if (!array_key_exists('message', $record)) {
|
||||
$record['message'] = $record['name'] . ' message';
|
||||
}
|
||||
if (!array_key_exists('timescheduled', $record)) {
|
||||
$record['timescheduled'] = usergetmidnight(di::get(clock::class)->time() + DAYSECS);
|
||||
}
|
||||
|
||||
return schedule_helper::create_schedule((object) $record);
|
||||
// Default to message schedule if not specified, for convenience.
|
||||
/** @var schedule_base $classname */
|
||||
$classname = $record['classname'] ?? \core_reportbuilder\reportbuilder\schedule\message::class;
|
||||
|
||||
return $classname::create((object) $record)->get_persistent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ use core\exception\{coding_exception, invalid_parameter_exception};
|
||||
use core_cohort\reportbuilder\audience\cohortmember;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder\local\models\schedule as model;
|
||||
use core_reportbuilder\local\schedules\base;
|
||||
use core_reportbuilder\reportbuilder\audience\manual;
|
||||
use core_reportbuilder\reportbuilder\schedule\message;
|
||||
use core_user\reportbuilder\datasource\users;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,39 @@ final class schedule_test extends advanced_testcase {
|
||||
$this->clock = $this->mock_clock_with_frozen(1622847600);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_valid}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function valid_provider(): array {
|
||||
return [
|
||||
[message::class, true],
|
||||
[base::class, false],
|
||||
[\core\url::class, false],
|
||||
['doesntexist', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validity of given classname as a schedule type
|
||||
*
|
||||
* @param string $classname
|
||||
* @param bool $expected
|
||||
*
|
||||
* @dataProvider valid_provider
|
||||
*/
|
||||
public function test_valid(string $classname, bool $expected): void {
|
||||
$this->assertEquals($expected, schedule::valid($classname));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting list of available schedules
|
||||
*/
|
||||
public function test_get_schedules(): void {
|
||||
$this->assertContains(message::class, schedule::get_schedules());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create schedule
|
||||
*/
|
||||
@@ -64,17 +99,19 @@ final class schedule_test extends advanced_testcase {
|
||||
$schedule = schedule::create_schedule((object) [
|
||||
'name' => 'My schedule',
|
||||
'reportid' => $report->get('id'),
|
||||
'classname' => message::class,
|
||||
'configdata' => json_encode(['subject' => 'Hello', 'message' => 'Hola']),
|
||||
'format' => 'csv',
|
||||
'subject' => 'Hello',
|
||||
'message' => 'Hola',
|
||||
'timescheduled' => $timescheduled,
|
||||
]);
|
||||
|
||||
$this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
|
||||
|
||||
$this->assertEquals('My schedule', $schedule->get('name'));
|
||||
$this->assertEquals($report->get('id'), $schedule->get('reportid'));
|
||||
$this->assertEquals(message::class, $schedule->get('classname'));
|
||||
$this->assertEquals('{"subject":"Hello","message":"Hola"}', $schedule->get('configdata'));
|
||||
$this->assertEquals('csv', $schedule->get('format'));
|
||||
$this->assertEquals('Hello', $schedule->get('subject'));
|
||||
$this->assertEquals('Hola', $schedule->get('message'));
|
||||
$this->assertEquals($timescheduled, $schedule->get('timescheduled'));
|
||||
$this->assertEquals($timescheduled, $schedule->get('timenextsend'));
|
||||
}
|
||||
@@ -263,7 +300,7 @@ final class schedule_test extends advanced_testcase {
|
||||
|
||||
// There is only one row in the report (the only user on the site).
|
||||
$count = schedule::get_schedule_report_count($schedule);
|
||||
$this->assertDebuggingCalled();
|
||||
$this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
|
||||
$this->assertEquals(1, $count);
|
||||
}
|
||||
|
||||
|
||||
@@ -261,14 +261,13 @@ final class provider_test extends provider_testcase {
|
||||
|
||||
$this->assertEquals($schedule->get_formatted_name(), $scheduledata->name);
|
||||
$this->assertEquals('Yes', $scheduledata->enabled);
|
||||
$this->assertEquals('Schedule an email', $scheduledata->classname);
|
||||
$this->assertEquals('{}', $scheduledata->configdata);
|
||||
$this->assertEquals('Comma separated values (.csv)', $scheduledata->format);
|
||||
$this->assertNotEmpty($scheduledata->timescheduled);
|
||||
$this->assertEquals('None', $scheduledata->recurrence);
|
||||
$this->assertEquals('Schedule creator', $scheduledata->userviewas);
|
||||
$this->assertEquals(json_encode([$audiencepersistent->get('id')]), $scheduledata->audiences);
|
||||
$this->assertEquals($schedule->get('subject'), $scheduledata->subject);
|
||||
$this->assertEquals(format_text($schedule->get('message'), $schedule->get('messageformat')), $scheduledata->message);
|
||||
$this->assertEquals('Send message with empty report', $scheduledata->reportempty);
|
||||
$this->assertEquals($user->id, $scheduledata->usercreated);
|
||||
$this->assertEquals($user->id, $scheduledata->usermodified);
|
||||
$this->assertNotEmpty($scheduledata->timecreated);
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_reportbuilder\reportbuilder\schedule;
|
||||
|
||||
use advanced_testcase;
|
||||
use core_collator;
|
||||
use core_user;
|
||||
use core_reportbuilder\local\filters\user;
|
||||
use core_reportbuilder\local\models\schedule;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\reportbuilder\audience\manual;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder\task\send_schedule;
|
||||
use core_notes\reportbuilder\datasource\notes;
|
||||
use core_user\reportbuilder\datasource\users;
|
||||
|
||||
/**
|
||||
* Unit tests for message schedule class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\reportbuilder\schedule\message
|
||||
* @copyright 2025 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class message_test extends advanced_testcase {
|
||||
/**
|
||||
* Data provider for {@see test_execute_viewas_user}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function execute_report_viewas_user_provider(): array {
|
||||
return [
|
||||
'View report as schedule creator' => [schedule::REPORT_VIEWAS_CREATOR, null, 'admin', 'admin'],
|
||||
'View report as schedule recipient' => [schedule::REPORT_VIEWAS_RECIPIENT, null, 'userone', 'usertwo'],
|
||||
'View report as specific user' => [null, 'userone', 'userone', 'userone'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test executing task for a schedule with differing "View as user" configuration
|
||||
*
|
||||
* @param int|null $viewasuser
|
||||
* @param string|null $viewasusername
|
||||
* @param string $useronesees
|
||||
* @param string $usertwosees
|
||||
*
|
||||
* @dataProvider execute_report_viewas_user_provider
|
||||
*/
|
||||
public function test_execute_report_viewas_user(
|
||||
?int $viewasuser,
|
||||
?string $viewasusername,
|
||||
string $useronesees,
|
||||
string $usertwosees
|
||||
): void {
|
||||
$this->preventResetByRollback();
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userone = $this->getDataGenerator()->create_user([
|
||||
'username' => 'userone',
|
||||
'email' => '[email protected]',
|
||||
'firstname' => 'Zoe',
|
||||
'lastname' => 'Zebra',
|
||||
]);
|
||||
$usertwo = $this->getDataGenerator()->create_user([
|
||||
'username' => 'usertwo',
|
||||
'email' => '[email protected]',
|
||||
'firstname' => 'Henrietta',
|
||||
'lastname' => 'Hamster',
|
||||
]);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create a report, with a single column and condition that the current user only sees themselves.
|
||||
$report = $generator->create_report(['name' => 'Myself', 'source' => users::class, 'default' => false]);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
|
||||
|
||||
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:userselect']);
|
||||
manager::get_report_from_persistent($report)
|
||||
->set_condition_values(['user:userselect_operator' => user::USER_CURRENT]);
|
||||
|
||||
// Add audience/schedule for our two test users.
|
||||
$audience = $generator->create_audience([
|
||||
'reportid' => $report->get('id'),
|
||||
'classname' => manual::class,
|
||||
'configdata' => [
|
||||
'users' => [$userone->id, $usertwo->id],
|
||||
],
|
||||
]);
|
||||
|
||||
// If "View as user" isn't specified, it should be the ID of the given "View as username".
|
||||
if ($viewasuser === null) {
|
||||
$viewasuser = core_user::get_user_by_username($viewasusername, '*', null, MUST_EXIST)->id;
|
||||
}
|
||||
$schedule = $generator->create_schedule([
|
||||
'reportid' => $report->get('id'),
|
||||
'name' => 'My schedule',
|
||||
'userviewas' => $viewasuser,
|
||||
'audiences' => json_encode([$audience->get_persistent()->get('id')]),
|
||||
'configdata' => json_encode(['subject' => 'Hi', 'message' => ['text' => 'Hi', 'format' => 1]]),
|
||||
]);
|
||||
|
||||
// Send the schedule, catch emails in sink (noting the users are sorted alphabetically).
|
||||
$sink = $this->redirectEmails();
|
||||
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule \(Schedule an email\)\n" .
|
||||
" Sending to: " . fullname($usertwo) . "\n" .
|
||||
" Sending to: " . fullname($userone) . "\n" .
|
||||
"Sending schedule complete\n/");
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
|
||||
$messages = $sink->get_messages();
|
||||
$this->assertCount(2, $messages);
|
||||
|
||||
$sink->close();
|
||||
|
||||
// Ensure caught messages are consistently ordered by recipient email prior to assertions.
|
||||
core_collator::asort_objects_by_property($messages, 'to');
|
||||
$messages = array_values($messages);
|
||||
|
||||
$messageoneattachment = self::extract_message_attachment($messages[0]->body);
|
||||
$this->assertEquals($userone->email, $messages[0]->to);
|
||||
$this->assertStringEndsWith("Username\n{$useronesees}\n", $messageoneattachment);
|
||||
|
||||
$messagetwoattachment = self::extract_message_attachment($messages[1]->body);
|
||||
$this->assertEquals($usertwo->email, $messages[1]->to);
|
||||
$this->assertStringEndsWith("Username\n{$usertwosees}\n", $messagetwoattachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test executing for a schedule that is configured to not send empty reports
|
||||
*/
|
||||
public function test_execute_report_empty(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create a report that won't return any data.
|
||||
$report = $generator->create_report(['name' => 'Notes', 'source' => notes::class]);
|
||||
|
||||
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
|
||||
$schedule = $generator->create_schedule([
|
||||
'reportid' => $report->get('id'),
|
||||
'name' => 'My schedule',
|
||||
'audiences' => json_encode([$audience->get_persistent()->get('id')]),
|
||||
'configdata' => json_encode(['reportempty' => message::REPORT_EMPTY_DONT_SEND]),
|
||||
]);
|
||||
|
||||
$this->expectOutputString("Sending schedule: My schedule (Schedule an email)\n" .
|
||||
" Empty report, skipping\n" .
|
||||
"Sending schedule complete\n");
|
||||
|
||||
// Execute via task.
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a multi-part message in MIME format, return the base64 encoded attachment contained within
|
||||
*
|
||||
* @param string $messagebody
|
||||
* @return string
|
||||
*/
|
||||
private static function extract_message_attachment(string $messagebody): string {
|
||||
$mimepart = preg_split('/Content-Disposition: attachment; filename="My schedule.csv"\s+/m', $messagebody);
|
||||
|
||||
// Extract the base64 encoded content after the "Content-Disposition" header.
|
||||
preg_match_all('/^([A-Z0-9\/\+=]+)\s/im', $mimepart[1], $matches);
|
||||
|
||||
return base64_decode(implode($matches[0]));
|
||||
}
|
||||
}
|
||||
@@ -38,114 +38,6 @@ use core_user\reportbuilder\datasource\users;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class send_schedule_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_execute_viewas_user}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function execute_report_viewas_user_provider(): array {
|
||||
return [
|
||||
'View report as schedule creator' => [schedule::REPORT_VIEWAS_CREATOR, null, 'admin', 'admin'],
|
||||
'View report as schedule recipient' => [schedule::REPORT_VIEWAS_RECIPIENT, null, 'userone', 'usertwo'],
|
||||
'View report as specific user' => [null, 'userone', 'userone', 'userone'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test executing task for a schedule with differing "View as user" configuration
|
||||
*
|
||||
* @param int|null $viewasuser
|
||||
* @param string|null $viewasusername
|
||||
* @param string $useronesees
|
||||
* @param string $usertwosees
|
||||
*
|
||||
* @dataProvider execute_report_viewas_user_provider
|
||||
*/
|
||||
public function test_execute_report_viewas_user(
|
||||
?int $viewasuser,
|
||||
?string $viewasusername,
|
||||
string $useronesees,
|
||||
string $usertwosees
|
||||
): void {
|
||||
$this->preventResetByRollback();
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userone = $this->getDataGenerator()->create_user([
|
||||
'username' => 'userone',
|
||||
'email' => '[email protected]',
|
||||
'firstname' => 'Zoe',
|
||||
'lastname' => 'Zebra',
|
||||
]);
|
||||
$usertwo = $this->getDataGenerator()->create_user([
|
||||
'username' => 'usertwo',
|
||||
'email' => '[email protected]',
|
||||
'firstname' => 'Henrietta',
|
||||
'lastname' => 'Hamster',
|
||||
]);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create a report, with a single column and condition that the current user only sees themselves.
|
||||
$report = $generator->create_report(['name' => 'Myself', 'source' => users::class, 'default' => false]);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
|
||||
|
||||
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:userselect']);
|
||||
manager::get_report_from_persistent($report)
|
||||
->set_condition_values(['user:userselect_operator' => user::USER_CURRENT]);
|
||||
|
||||
// Add audience/schedule for our two test users.
|
||||
$audience = $generator->create_audience([
|
||||
'reportid' => $report->get('id'),
|
||||
'classname' => manual::class,
|
||||
'configdata' => [
|
||||
'users' => [$userone->id, $usertwo->id],
|
||||
],
|
||||
]);
|
||||
|
||||
// If "View as user" isn't specified, it should be the ID of the given "View as username".
|
||||
if ($viewasuser === null) {
|
||||
$viewasuser = core_user::get_user_by_username($viewasusername, '*', null, MUST_EXIST)->id;
|
||||
}
|
||||
$schedule = $generator->create_schedule([
|
||||
'reportid' => $report->get('id'),
|
||||
'name' => 'My schedule',
|
||||
'userviewas' => $viewasuser,
|
||||
'audiences' => json_encode([$audience->get_persistent()->get('id')]),
|
||||
]);
|
||||
|
||||
// Send the schedule, catch emails in sink (noting the users are sorted alphabetically).
|
||||
$sink = $this->redirectEmails();
|
||||
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule\n" .
|
||||
" Sending to: " . fullname($usertwo) . "\n" .
|
||||
" Sending to: " . fullname($userone) . "\n" .
|
||||
"Sending schedule complete\n/"
|
||||
);
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
|
||||
$messages = $sink->get_messages();
|
||||
$this->assertCount(2, $messages);
|
||||
|
||||
$sink->close();
|
||||
|
||||
// Ensure caught messages are consistently ordered by recipient email prior to assertions.
|
||||
core_collator::asort_objects_by_property($messages, 'to');
|
||||
$messages = array_values($messages);
|
||||
|
||||
$messageoneattachment = self::extract_message_attachment($messages[0]->body);
|
||||
$this->assertEquals($userone->email, $messages[0]->to);
|
||||
$this->assertStringEndsWith("Username\n{$useronesees}\n", $messageoneattachment);
|
||||
|
||||
$messagetwoattachment = self::extract_message_attachment($messages[1]->body);
|
||||
$this->assertEquals($usertwo->email, $messages[1]->to);
|
||||
$this->assertStringEndsWith("Username\n{$usertwosees}\n", $messagetwoattachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test executing task where the schedule "View as user" is an inactive account
|
||||
*/
|
||||
@@ -166,44 +58,8 @@ final class send_schedule_test extends advanced_testcase {
|
||||
'audiences' => json_encode([$audience->get_persistent()->get('id')]),
|
||||
]);
|
||||
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule\nInvalid schedule view as user: Invalid user/");
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test executing task for a schedule that is configured to not send empty reports
|
||||
*/
|
||||
public function test_execute_report_empty(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create a report that won't return any data.
|
||||
$report = $generator->create_report(['name' => 'Myself', 'source' => users::class, 'default' => false]);
|
||||
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
|
||||
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
|
||||
|
||||
manager::get_report_from_persistent($report)->set_condition_values([
|
||||
'user:username_operator' => text::IS_EQUAL_TO,
|
||||
'user:username_value' => 'baconlettucetomato',
|
||||
]);
|
||||
|
||||
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
|
||||
$schedule = $generator->create_schedule([
|
||||
'reportid' => $report->get('id'),
|
||||
'name' => 'My schedule',
|
||||
'audiences' => json_encode([$audience->get_persistent()->get('id')]),
|
||||
'reportempty' => schedule::REPORT_EMPTY_DONT_SEND,
|
||||
]);
|
||||
|
||||
$this->expectOutputString("Sending schedule: My schedule\n" .
|
||||
" Empty report, skipping\n" .
|
||||
"Sending schedule complete\n");
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule \(Schedule an email\)\n" .
|
||||
"Invalid schedule view as user: Invalid user/");
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
@@ -222,7 +78,7 @@ final class send_schedule_test extends advanced_testcase {
|
||||
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
|
||||
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
|
||||
|
||||
$this->expectOutputString("Sending schedule: My schedule\n" .
|
||||
$this->expectOutputString("Sending schedule: My schedule (Schedule an email)\n" .
|
||||
"Sending schedule complete\n");
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
@@ -241,7 +97,8 @@ final class send_schedule_test extends advanced_testcase {
|
||||
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
|
||||
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'usercreated' => 42]);
|
||||
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule\nInvalid schedule creator: Invalid user/");
|
||||
$this->expectOutputRegex("/^Sending schedule: My schedule \(Schedule an email\)\n" .
|
||||
"Invalid schedule creator: Invalid user/");
|
||||
$sendschedule = new send_schedule();
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]);
|
||||
$sendschedule->execute();
|
||||
@@ -263,19 +120,4 @@ final class send_schedule_test extends advanced_testcase {
|
||||
$sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => 42]);
|
||||
$sendschedule->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a multi-part message in MIME format, return the base64 encoded attachment contained within
|
||||
*
|
||||
* @param string $messagebody
|
||||
* @return string
|
||||
*/
|
||||
private static function extract_message_attachment(string $messagebody): string {
|
||||
$mimepart = preg_split('/Content-Disposition: attachment; filename="My schedule.csv"\s+/m', $messagebody);
|
||||
|
||||
// Extract the base64 encoded content after the "Content-Disposition" header.
|
||||
preg_match_all('/^([A-Z0-9\/\+=]+)\s/im', $mimepart[1], $matches);
|
||||
|
||||
return base64_decode(implode($matches[0]));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2025090200.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2025090200.02; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '5.1dev+ (Build: 20250902)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user