MDL-70795 reportbuilder: add interface for creating reports.

Implement elements for creating/editing reports, along with
new system report for listing and accompanying JS modules for
user interaction.

Create "Users" datasource as proof-of-concept.

Co-authored-By: Paul Holden <paulh@moodle.com>
This commit is contained in:
David Matamoros 2021-07-06 16:10:30 +00:00 committed by Paul Holden
parent 95967d62d5
commit 22d896e063
33 changed files with 1619 additions and 10 deletions

View File

@ -0,0 +1,44 @@
<?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/>.
/**
* Report builder related settings.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
use core_admin\local\externalpage\accesscallback;
use core_reportbuilder\permission;
defined('MOODLE_INTERNAL') || die;
/** @var admin_root $ADMIN */
$ADMIN->add('reports', new admin_category('reportbuilder', new lang_string('reportbuilder', 'core_reportbuilder')));
$ADMIN->add(
'reportbuilder', new accesscallback(
'customreports',
get_string('customreports', 'core_reportbuilder'),
(new moodle_url('/reportbuilder/index.php'))->out(),
static function(accesscallback $accesscallback): bool {
return permission::can_view_reports_list();
}
)
);

View File

@ -28,6 +28,11 @@ $string['coursefullnamewithlink'] = 'Course full name with link';
$string['courseidnumberewithlink'] = 'Course ID number with link';
$string['courseshortnamewithlink'] = 'Course short name with link';
$string['customfieldcolumn'] = '{$a}';
$string['deletereport'] = 'Delete report';
$string['deletereportconfirm'] = 'Are you sure you want to delete the report \'{$a}\' and all associated data?';
$string['editreportdetails'] = 'Edit report details';
$string['editreportname'] = 'Edit report name';
$string['customreports'] = 'Custom reports';
$string['entitycourse'] = 'Course';
$string['entityuser'] = 'User';
$string['errorreportaccess'] = 'You can not view this report';
@ -66,6 +71,9 @@ $string['filtersapplied'] = 'Filters applied';
$string['filtersappliedx'] = 'Filters ({$a})';
$string['filtersreset'] = 'Filters reset';
$string['filterstartswith'] = 'Starts with';
$string['includedefaultsetup'] = 'Include default setup';
$string['includedefaultsetup_help'] = 'Populate report with default layout as defined by the selected source. These include pre-defined columns, filters and conditions.';
$string['newreport'] = 'New report';
$string['privacy:metadata:column'] = 'Report column definitions';
$string['privacy:metadata:column:uniqueidentifier'] = 'Unique identifier of the column';
$string['privacy:metadata:column:usercreated'] = 'The ID of the user who created the column';
@ -79,8 +87,18 @@ $string['privacy:metadata:report'] = 'Report definitions';
$string['privacy:metadata:report:name'] = 'The name of the report';
$string['privacy:metadata:report:usercreated'] = 'The ID of the user who created the report';
$string['privacy:metadata:report:usermodified'] = 'The ID of the user who last modified the report';
$string['reportbuilder'] = 'Report builder';
$string['reportcreated'] = 'Report created';
$string['reportdeleted'] = 'Report deleted';
$string['reportsource'] = 'Report source';
$string['reportsource_help'] = 'The report source defines where the data for the report will come from';
$string['reportupdated'] = 'Report updated';
$string['resetall'] = 'Reset all';
$string['selectareportsource'] = 'Select a report source';
$string['selectcourses'] = 'Select courses';
$string['timeadded'] = 'Time added';
$string['timecreated'] = 'Time created';
$string['timemodified'] = 'Time modified';
$string['userfullnamewithlink'] = 'Full name with link';
$string['userfullnamewithpicture'] = 'Full name with picture';
$string['userfullnamewithpicturelink'] = 'Full name with picture and link';

View File

@ -371,6 +371,9 @@ $string['rating:rate'] = 'Add ratings to items';
$string['rating:view'] = 'View the total rating you received';
$string['rating:viewany'] = 'View total ratings that anyone received';
$string['rating:viewall'] = 'View all raw ratings given by individuals';
$string['reportbuilder:edit'] = 'Create/edit custom reports';
$string['reportbuilder:editall'] = 'Create/edit all custom reports';
$string['reportbuilder:view'] = 'View custom reports';
$string['resetrole'] = 'Reset';
$string['resettingrole'] = 'Resetting role \'{$a}\'';
$string['restore:configure'] = 'Configure restore options';

View File

@ -2627,4 +2627,31 @@ $capabilities = array(
'coursecreator' => CAP_ALLOW,
]
],
// Allow users to view custom reports.
'moodle/reportbuilder:view' => [
'captype' => 'read',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => [
'user' => CAP_ALLOW,
],
],
// Allow users to create/edit their own custom reports.
'moodle/reportbuilder:edit' => [
'captype' => 'write',
'riskbitmap' => RISK_PERSONAL,
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => [
'manager' => CAP_ALLOW,
],
],
// Allow users to create/edit all custom reports.
'moodle/reportbuilder:editall' => [
'captype' => 'write',
'riskbitmap' => RISK_PERSONAL,
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => [],
],
);

View File

@ -2798,6 +2798,12 @@ $functions = array(
'type' => 'write',
'ajax' => true,
],
'core_reportbuilder_reports_delete' => [
'classname' => 'core_reportbuilder\external\reports\delete',
'description' => 'Delete report',
'type' => 'write',
'ajax' => true,
],
);
$services = array(

View File

@ -0,0 +1,2 @@
define ("core_reportbuilder/local/repository/modals",["exports","core_form/modalform","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.createReportModal=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d=function(a,d){var e=2<arguments.length&&arguments[2]!==void 0?arguments[2]:0;return new b.default({modalConfig:{title:d},formClass:"core_reportbuilder\\form\\report",args:{id:e},saveButtonText:(0,c.get_string)("save","moodle"),returnFocus:a})};a.createReportModal=d});
//# sourceMappingURL=modals.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/repository/modals.js"],"names":["createReportModal","triggerElement","modalTitle","reportId","ModalForm","modalConfig","title","formClass","args","id","saveButtonText","returnFocus"],"mappings":"2MAwBA,uDAWO,GAAMA,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAiBC,CAAjB,CAA8C,IAAjBC,CAAAA,CAAiB,wDAAN,CAAM,CAC3E,MAAO,IAAIC,UAAJ,CAAc,CACjBC,WAAW,CAAE,CACTC,KAAK,CAAEJ,CADE,CADI,CAIjBK,SAAS,CAAE,kCAJM,CAKjBC,IAAI,CAAE,CACFC,EAAE,CAAEN,CADF,CALW,CAQjBO,cAAc,CAAE,iBAAU,MAAV,CAAkB,QAAlB,CARC,CASjBC,WAAW,CAAEV,CATI,CAAd,CAWV,CAZM,C","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 * @package core_reportbuilder\n * @copyright 2021 David Matamoros <davidmc@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Open the New Report modal form\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 new ModalForm({\n modalConfig: {\n title: modalTitle,\n },\n formClass: 'core_reportbuilder\\\\form\\\\report',\n args: {\n id: reportId,\n },\n saveButtonText: getString('save', 'moodle'),\n returnFocus: triggerElement,\n });\n};\n"],"file":"modals.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_reportbuilder/local/repository/reports",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.deleteReport=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_reportbuilder_reports_delete",args:{reportid:a}}])[0]};a.deleteReport=c});
//# sourceMappingURL=reports.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/repository/reports.js"],"names":["deleteReport","reportId","Ajax","call","methodname","args","reportid"],"mappings":"gLAwBA,uDAQO,GAAMA,CAAAA,CAAY,CAAG,SAAAC,CAAQ,CAAI,CAMpC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C","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 report AJAX requests\n *\n * @module core_reportbuilder/local/repository/reports\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden <paulh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Delete given report\n *\n * @param {Number} reportId\n * @return {Promise}\n */\nexport const deleteReport = reportId => {\n const request = {\n methodname: 'core_reportbuilder_reports_delete',\n args: {reportid: reportId}\n };\n\n return Ajax.call([request])[0];\n};\n\n"],"file":"reports.min.js"}

View File

@ -1,2 +1,2 @@
define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{systemReport:"[data-region=\"core_reportbuilder/system-report\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]"}};b.forSystemReport=function(a){return"".concat(b.regions.systemReport,"[data-reportid=\"").concat(a,"\"]")};a.default=b;return a.default});
define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{systemReport:"[data-region=\"core_reportbuilder/system-report\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]",reportCreate:"[data-action=\"report-create\"]",reportEdit:"[data-action=\"report-edit\"]",reportDelete:"[data-action=\"report-delete\"]"}};b.forSystemReport=function(a){return"".concat(b.regions.systemReport,"[data-reportid=\"").concat(a,"\"]")};a.default=b;return a.default});
//# sourceMappingURL=selectors.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","systemReport","filterButtonLabel","filtersForm","actions","reportActionPopup","forSystemReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,oDADT,CAELC,iBAAiB,CAAE,uCAFd,CAGLC,WAAW,CAAE,gCAHR,CADK,CAMdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CANK,CAAlB,CAkBAN,CAAS,CAACO,eAAV,CAA4B,SAAAC,CAAQ,kBAAOR,CAAS,CAACC,OAAV,CAAkBC,YAAzB,6BAAwDM,CAAxD,QAApC,C,UAEeR,C","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 * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden <paulh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n systemReport: '[data-region=\"core_reportbuilder/system-report\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n }\n};\n\n/**\n * Selector for given report\n *\n * @method forSystemReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"}
{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","systemReport","filterButtonLabel","filtersForm","actions","reportActionPopup","reportCreate","reportEdit","reportDelete","forSystemReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,oDADT,CAELC,iBAAiB,CAAE,uCAFd,CAGLC,WAAW,CAAE,gCAHR,CADK,CAMdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CAELC,YAAY,CAAE,iCAFT,CAGLC,UAAU,CAAE,+BAHP,CAILC,YAAY,CAAE,iCAJT,CANK,CAAlB,CAqBAT,CAAS,CAACU,eAAV,CAA4B,SAAAC,CAAQ,kBAAOX,CAAS,CAACC,OAAV,CAAkBC,YAAzB,6BAAwDS,CAAxD,QAApC,C,UAEeX,C","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 * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden <paulh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n systemReport: '[data-region=\"core_reportbuilder/system-report\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n reportCreate: '[data-action=\"report-create\"]',\n reportEdit: '[data-action=\"report-edit\"]',\n reportDelete: '[data-action=\"report-delete\"]',\n },\n};\n\n/**\n * Selector for given report\n *\n * @method forSystemReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"}

View File

@ -0,0 +1,2 @@
function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_reportbuilder/reports_list",["exports","core/event_dispatcher","core/notification","core/pending","core/str","core/toast","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/reports","core_reportbuilder/local/repository/modals"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;c=m(c);d=m(d);g=l(g);h=l(h);function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a){return a&&a.__esModule?a:{default:a}}function n(a,b){return s(a)||r(a,b)||p(a,b)||o()}function o(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function p(a,b){if(!a)return;if("string"==typeof a)return q(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return q(a,b)}function q(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function r(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function s(a){if(Array.isArray(a))return a}a.init=function init(){document.addEventListener("click",function(a){var k=a.target.closest(h.actions.reportCreate);if(k){a.preventDefault();var o=(0,j.createReportModal)(a.target,(0,e.get_string)("newreport","core_reportbuilder"));o.addEventListener(o.events.FORM_SUBMITTED,function(a){window.location.href=a.detail});o.show()}var l=a.target.closest(h.actions.reportEdit);if(l){a.preventDefault();var p=(0,j.createReportModal)(a.target,(0,e.get_string)("editreportdetails","core_reportbuilder"),l.dataset.reportId);p.addEventListener(p.events.FORM_SUBMITTED,function(){var d=a.target.closest(h.regions.report);(0,e.get_string)("reportupdated","core_reportbuilder").then(f.add).then(function(){(0,b.dispatchEvent)(g.tableReload,{preservePagination:!0},d)}).catch(c.default.exception)});p.show()}var m=a.target.closest(h.actions.reportDelete);if(m){a.preventDefault();(0,e.get_strings)([{key:"deletereport",component:"core_reportbuilder"},{key:"deletereportconfirm",component:"core_reportbuilder",param:m.dataset.reportName},{key:"delete",component:"moodle"}]).then(function(j){var k=n(j,3),l=k[0],o=k[1],p=k[2];c.default.confirm(l,o,p,null,function(){var j=new d.default("core_reportbuilder/reports:delete"),k=a.target.closest(h.regions.report);(0,i.deleteReport)(m.dataset.reportId).then(function(){return(0,e.get_string)("reportdeleted","core_reportbuilder")}).then(f.add).then(function(){j.resolve();(0,b.dispatchEvent)(g.tableReload,{preservePagination:!0},k)}).catch(c.default.exception)})}).catch(c.default.exception)}})}});
//# sourceMappingURL=reports_list.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,48 @@
// 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/>.
/**
* Module to handle modal form requests
*
* @module core_reportbuilder/local/repository/modals
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import ModalForm from 'core_form/modalform';
import {get_string as getString} from 'core/str';
/**
* Open the New Report modal form
*
* @param {EventTarget} triggerElement
* @param {Promise} modalTitle
* @param {Number} reportId
* @return {ModalForm}
*/
export const createReportModal = (triggerElement, modalTitle, reportId = 0) => {
return new ModalForm({
modalConfig: {
title: modalTitle,
},
formClass: 'core_reportbuilder\\form\\report',
args: {
id: reportId,
},
saveButtonText: getString('save', 'moodle'),
returnFocus: triggerElement,
});
};

View File

@ -0,0 +1,41 @@
// 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/>.
/**
* Module to handle report AJAX requests
*
* @module core_reportbuilder/local/repository/reports
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Delete given report
*
* @param {Number} reportId
* @return {Promise}
*/
export const deleteReport = reportId => {
const request = {
methodname: 'core_reportbuilder_reports_delete',
args: {reportid: reportId}
};
return Ajax.call([request])[0];
};

View File

@ -37,7 +37,10 @@ const SELECTORS = {
},
actions: {
reportActionPopup: '[data-action="report-action-popup"]',
}
reportCreate: '[data-action="report-create"]',
reportEdit: '[data-action="report-edit"]',
reportDelete: '[data-action="report-delete"]',
},
};
/**

View File

@ -0,0 +1,104 @@
// 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/>.
/**
* Report builder reports list management
*
* @module core_reportbuilder/reports_list
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
import {dispatchEvent} from 'core/event_dispatcher';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {get_string as getString, get_strings as getStrings} from 'core/str';
import {add as addToast} from 'core/toast';
import * as reportEvents from 'core_reportbuilder/local/events';
import * as reportSelectors from 'core_reportbuilder/local/selectors';
import {deleteReport} from 'core_reportbuilder/local/repository/reports';
import {createReportModal} from 'core_reportbuilder/local/repository/modals';
/**
* Initialise module
*/
export const init = () => {
document.addEventListener('click', event => {
const reportCreate = event.target.closest(reportSelectors.actions.reportCreate);
if (reportCreate) {
event.preventDefault();
// Redirect user to editing interface for the report after submission.
const reportModal = createReportModal(event.target, getString('newreport', 'core_reportbuilder'));
reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, event => {
window.location.href = event.detail;
});
reportModal.show();
}
const reportEdit = event.target.closest(reportSelectors.actions.reportEdit);
if (reportEdit) {
event.preventDefault();
// Reload current report page after submission.
const reportModal = createReportModal(event.target, getString('editreportdetails', 'core_reportbuilder'),
reportEdit.dataset.reportId);
reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, () => {
const reportElement = event.target.closest(reportSelectors.regions.report);
getString('reportupdated', 'core_reportbuilder')
.then(addToast)
.then(() => {
dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
return;
})
.catch(Notification.exception);
});
reportModal.show();
}
const reportDelete = event.target.closest(reportSelectors.actions.reportDelete);
if (reportDelete) {
event.preventDefault();
getStrings([
{key: 'deletereport', component: 'core_reportbuilder'},
{key: 'deletereportconfirm', component: 'core_reportbuilder', param: reportDelete.dataset.reportName},
{key: 'delete', component: 'moodle'},
]).then(([confirmTitle, confirmText, confirmButton]) => {
Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {
const pendingPromise = new Pending('core_reportbuilder/reports:delete');
const reportElement = event.target.closest(reportSelectors.regions.report);
deleteReport(reportDelete.dataset.reportId)
.then(() => getString('reportdeleted', 'core_reportbuilder'))
.then(addToast)
.then(() => {
pendingPromise.resolve();
dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
return;
})
.catch(Notification.exception);
});
return;
}).catch(Notification.exception);
}
});
};

View File

@ -0,0 +1,43 @@
<?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;
use coding_exception;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\local\models\filter as filter_model;
use core_reportbuilder\local\report\base;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Class datasource
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class datasource extends base {
/**
* Return user friendly name of the datasource
*
* @return string
*/
abstract public static function get_name(): string;
}

View File

@ -0,0 +1,96 @@
<?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\event;
use core\event\base;
use core_reportbuilder\local\models\report;
use moodle_url;
/**
* Report builder custom report created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_created extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportcreated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the custom report with id '$this->objectid'.";
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]);
}
}

View File

@ -0,0 +1,86 @@
<?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\event;
use core\event\base;
use core_reportbuilder\local\models\report;
/**
* Report builder custom report deleted event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_deleted extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportdeleted', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the custom report with id '$this->objectid'.";
}
}

View File

@ -0,0 +1,96 @@
<?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\event;
use core\event\base;
use core_reportbuilder\local\models\report;
use moodle_url;
/**
* Report builder custom report updated event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_updated extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportupdated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the custom report with id '$this->objectid'.";
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]);
}
}

View File

@ -0,0 +1,150 @@
<?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\form;
use context;
use context_system;
use core_reportbuilder\permission;
use moodle_url;
use core_form\dynamic_form;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\report as reporthelper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/formslib.php");
/**
* Report details form
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report extends dynamic_form {
/**
* Return instance of the custom report we are editing, or null when creating a new report
*
* @return datasource|null
*/
protected function get_custom_report(): ?datasource {
if ($reportid = $this->optional_param('id', 0, PARAM_INT)) {
/** @var datasource $customreport */
$customreport = manager::get_report_from_id($reportid);
return $customreport;
}
return null;
}
/**
* Return the context for the form, it should be that of the custom report itself, or system when creating a new report
*
* @return context
*/
public function get_context_for_dynamic_submission(): context {
if ($report = $this->get_custom_report()) {
return $report->get_context();
} else {
return context_system::instance();
}
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
$report = $this->get_custom_report();
if ($report) {
permission::require_can_edit_report($report->get_report_persistent());
} else {
permission::require_can_create_report();
}
}
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('text', 'name', get_string('name'));
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255);
// Allow user to select report source if creating a new report.
if (!$this->get_custom_report()) {
$default = ['' => ['' => get_string('selectareportsource', 'core_reportbuilder')]];
$mform->addElement('selectgroups', 'source', get_string('reportsource', 'core_reportbuilder'),
array_merge($default, manager::get_report_datasources()));
$mform->addRule('source', null, 'required', null, 'client');
$mform->addHelpButton('source', 'reportsource', 'core_reportbuilder');
$mform->addElement('advcheckbox', 'includedefaultsetup', get_string('includedefaultsetup', 'core_reportbuilder'));
$mform->setDefault('includedefaultsetup', 1);
$mform->addHelpButton('includedefaultsetup', 'includedefaultsetup', 'core_reportbuilder');
}
}
/**
* Process the form submission
*
* @return string The URL to advance to upon completion
*/
public function process_dynamic_submission() {
$data = $this->get_data();
if ($data->id) {
$reportpersistent = reporthelper::update_report($data);
} else {
$reportpersistent = reporthelper::create_report($data, (bool)$data->includedefaultsetup);
}
return (new moodle_url('/reportbuilder/edit.php', ['id' => $reportpersistent->get('id')]))->out(false);
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
if ($report = $this->get_custom_report()) {
$this->set_data($report->get_report_persistent()->to_record());
}
}
/**
* URL of the page using this form
*
* @return moodle_url
*/
public function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/reportbuilder/index.php');
}
}

View File

@ -0,0 +1,98 @@
<?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\helpers;
use stdClass;
use invalid_parameter_exception;
use core\persistent;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
use core_reportbuilder\local\models\report as report_model;
/**
* Helper class for manipulating custom reports and their elements (columns, filters, conditions, etc)
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report {
/**
* Create custom report
*
* @param stdClass $data
* @param bool $default If $default is set to true it will populate report with default layout as defined by the selected
* source. These include pre-defined columns, filters and conditions.
* @return report_model
*/
public static function create_report(stdClass $data, bool $default = true): report_model {
// TODO move this properties_definition validation into the persistents, or resolve MDL-71086.
$data = (object) array_merge(array_intersect_key((array) $data, report_model::properties_definition()), [
'type' => datasource::TYPE_CUSTOM_REPORT,
]);
$reportpersistent = manager::create_report_persistent($data);
// Add datasource default columns, filters and conditions to the report.
if ($default) {
$source = $reportpersistent->get('source');
/** @var datasource $datasource */
$datasource = new $source($reportpersistent, []);
}
return $reportpersistent;
}
/**
* Update custom report
*
* @param stdClass $data
* @return report_model
*/
public static function update_report(stdClass $data): report_model {
$report = report_model::get_record(['id' => $data->id, 'type' => datasource::TYPE_CUSTOM_REPORT]);
if ($report === false) {
throw new invalid_parameter_exception('Invalid report');
}
$report->set('name', $data->name)
->update();
return $report;
}
/**
* Delete custom report
*
* @param int $reportid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_report(int $reportid): bool {
$report = report_model::get_record(['id' => $reportid, 'type' => datasource::TYPE_CUSTOM_REPORT]);
if ($report === false) {
throw new invalid_parameter_exception('Invalid report');
}
return $report->delete();
}
}

View File

@ -21,6 +21,9 @@ namespace core_reportbuilder\local\models;
use context;
use context_system;
use core\persistent;
use core_reportbuilder\event\report_created;
use core_reportbuilder\event\report_deleted;
use core_reportbuilder\event\report_updated;
use core_reportbuilder\local\report\base;
/**
@ -91,6 +94,56 @@ class report extends persistent {
];
}
/**
* Trigger report created event when persistent is created
*/
protected function after_create(): void {
if ($this->get('type') === base::TYPE_CUSTOM_REPORT) {
report_created::create_from_object($this)->trigger();
}
}
/**
* Cascade report deletion, first deleting any linked persistents
*/
protected function before_delete(): void {
$reportparams = ['reportid' => $this->get('id')];
// Columns.
foreach (column::get_records($reportparams) as $column) {
$column->delete();
}
// Filters.
foreach (filter::get_records($reportparams) as $filter) {
$filter->delete();
}
}
/**
* Throw report deleted event when persistent is deleted
*
* @param bool $result
*/
protected function after_delete($result): void {
if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) {
return;
}
report_deleted::create_from_object($this)->trigger();
}
/**
* Throw report updated event when persistent is updated
*
* @param bool $result
*/
protected function after_update($result): void {
if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) {
return;
}
report_updated::create_from_object($this)->trigger();
}
/**
* Return report context, used by exporters
*

View File

@ -119,13 +119,6 @@ abstract class base {
*/
abstract protected function initialise(): void;
/**
* Output the report
*
* @return string
*/
abstract public function output(): string;
/**
* Get the report availability. Sub-classes should override this method to declare themselves unavailable, for example if
* they require classes that aren't present due to missing plugin
@ -258,6 +251,21 @@ abstract class base {
$this->entities[$entityname] = $entity->initialise();
}
/**
* Returns the entity added to the report from the given entity name
*
* @param string $name
* @return entity_base
* @throws coding_exception
*/
final protected function get_entity(string $name): entity_base {
if (!array_key_exists($name, $this->entities)) {
throw new coding_exception('Invalid entity name', $name);
}
return $this->entities[$name];
}
/**
* Define a new entity for the report
*
@ -300,6 +308,20 @@ abstract class base {
return $column;
}
/**
* Add given column to the report from an entity
*
* The entity must have already been added to the report before calling this method
*
* @param string $uniqueidentifier
* @return column
*/
final protected function add_column_from_entity(string $uniqueidentifier): column {
[$entityname, $columnname] = explode(':', $uniqueidentifier, 2);
return $this->add_column($this->get_entity($entityname)->get_column($columnname));
}
/**
* Add given columns to the report from one or more entities
*

View File

@ -0,0 +1,276 @@
<?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\systemreports;
use html_writer;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\system_report;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\action;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use core_reportbuilder\output\report_name_editable;
use core_reportbuilder\local\models\report;
use core_reportbuilder\permission;
/**
* Reports list
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reports_list extends system_report {
/**
* The name of our internal report entity
*
* @return string
*/
private function get_report_entity_name(): string {
return 'report';
}
/**
* Initialise the report
*/
protected function initialise(): void {
$this->set_main_table('reportbuilder_report', 'rb');
$this->add_base_condition_simple('rb.type', self::TYPE_CUSTOM_REPORT);
$this->add_base_fields('rb.id, rb.name, rb.source, rb.type, rb.usercreated'); // Necessary for actions/row class.
// Join user entity for "User modified" column.
$entityuser = new user();
$entityuseralias = $entityuser->get_table_alias('user');
$this->add_entity($entityuser
->add_join("JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = rb.usermodified")
);
// Define our internal entity for report elements.
$this->annotate_entity($this->get_report_entity_name(),
new lang_string('customreports', 'core_reportbuilder'));
$this->add_columns();
$this->add_filters();
$this->add_actions();
$this->set_downloadable(false);
}
/**
* Ensure we can view the report
*
* @return bool
*/
protected function can_view(): bool {
return permission::can_view_reports_list();
}
/**
* Dim the table row for invalid datasource
*
* @param stdClass $row
* @return string
*/
public function get_row_class(stdClass $row): string {
return $this->report_source_valid($row->source) ? '' : 'dimmed_text';
}
/**
* Add columns to report
*/
protected function add_columns(): void {
$tablealias = $this->get_main_table_alias();
// Report name column.
$this->add_column((new column(
'name',
new lang_string('name'),
$this->get_report_entity_name()
))
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name, {$tablealias}.id")
->set_is_sortable(true)
->add_callback(function(string $value, stdClass $row) {
global $PAGE;
$reportid = (int) $row->id;
$editable = new report_name_editable($reportid);
return $editable->render($PAGE->get_renderer('core'));
})
);
// Report source column.
$this->add_column((new column(
'source',
new lang_string('reportsource', 'core_reportbuilder'),
$this->get_report_entity_name()
))
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.source")
->set_is_sortable(true)
->add_callback(function(string $value, stdClass $row) {
if (!$this->report_source_valid($value)) {
// Add danger badge if report source is not valid (either it's missing, or has errors).
return html_writer::span(get_string('errorsourceinvalid', 'core_reportbuilder'), 'badge badge-danger');
}
return call_user_func([$value, 'get_name']);
})
);
// Time created column.
$this->add_column((new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_report_entity_name()
))
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timecreated")
->set_is_sortable(true)
->add_callback([format::class, 'userdate'])
);
// Time modified column.
$this->add_column((new column(
'timemodified',
new lang_string('timemodified', 'core_reportbuilder'),
$this->get_report_entity_name()
))
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timemodified")
->set_is_sortable(true)
->add_callback([format::class, 'userdate'])
);
// The user who modified the report.
$this->add_column_from_entity('user:fullname');
// Initial sorting.
$this->set_initial_sort_column('report:timecreated', SORT_DESC);
}
/**
* Add filters to report
*/
protected function add_filters(): void {
$tablealias = $this->get_main_table_alias();
// Name filter.
$this->add_filter((new filter(
text::class,
'name',
new lang_string('name'),
$this->get_report_entity_name(),
"{$tablealias}.name"
)));
// Source filter.
$this->add_filter((new filter(
select::class,
'source',
new lang_string('reportsource', 'core_reportbuilder'),
$this->get_report_entity_name(),
"{$tablealias}.source"
))
->set_options_callback(static function(): array {
return manager::get_report_datasources();
})
);
// Time created filter.
$this->add_filter((new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_report_entity_name(),
"{$tablealias}.timecreated"
))
->set_limited_operators([
date::DATE_ANY,
date::DATE_RANGE,
])
);
}
/**
* Add actions to report
*/
protected function add_actions(): void {
$this->add_action((new action(
new moodle_url('#'),
new pix_icon('t/edit', get_string('editreportdetails', 'core_reportbuilder')),
['data-action' => 'report-edit', 'data-report-id' => ':id']
))
->add_callback(function(stdClass $row): bool {
return $this->report_source_valid($row->source) && permission::can_edit_report($this->get_report_from_row($row));
})
);
$this->add_action((new action(
new moodle_url('#'),
new pix_icon('t/delete', get_string('deletereport', 'core_reportbuilder')),
['data-action' => 'report-delete', 'data-report-id' => ':id', 'data-report-name' => ':name']
))
->add_callback(function(stdClass $row): bool {
// We don't check whether report is valid to ensure editor can always delete them.
return permission::can_edit_report($this->get_report_from_row($row));
})
);
}
/**
* Helper to determine whether given report source is valid (it both exists, and is available)
*
* @param string $source
* @return bool
*/
private function report_source_valid(string $source): bool {
return manager::report_source_exists($source, datasource::class) && manager::report_source_available($source);
}
/**
* Helper to return the report persistent from the row object.
*
* Note that this persistent, for performance reasons, is not complete and only contains id/type/usercreated fields, which
* are needed for the permission methods.
*
* @param stdClass $row
* @return report
*/
private function get_report_from_row(stdClass $row): report {
return new report(0, (object)[
'id' => $row->id,
'type' => $row->type,
'usercreated' => $row->usercreated,
]);
}
}

View File

@ -18,6 +18,8 @@ declare(strict_types=1);
namespace core_reportbuilder;
use core_component;
use core_plugin_manager;
use stdClass;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\report\base;
@ -53,6 +55,19 @@ class manager {
return new $source($report, $parameters);
}
/**
* Return an instance of a report class from the given report ID
*
* @param int $reportid
* @param array $parameters
* @return base
*/
public static function get_report_from_id(int $reportid, array $parameters = []): base {
$report = new report($reportid);
return self::get_report_from_persistent($report, $parameters);
}
/**
* Verify that report source exists and extends appropriate base classes
*
@ -84,4 +99,30 @@ class manager {
public static function create_report_persistent(stdClass $reportdata): report {
return (new report(0, $reportdata))->create();
}
/**
* Return an array of all valid report sources across the site
*
* @return array[][] Indexed by [component => [class => name]]
*/
public static function get_report_datasources(): array {
$sources = array();
$datasources = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\datasource');
foreach ($datasources as $class => $path) {
if (self::report_source_exists($class, datasource::class) && self::report_source_available($class)) {
[$component] = explode('\\', $class);
if ($plugininfo = core_plugin_manager::instance()->get_plugin_info($component)) {
$componentname = $plugininfo->displayname;
} else {
$componentname = get_string('site');
}
$sources[$componentname][$class] = call_user_func([$class, 'get_name']);
}
}
return $sources;
}
}

View File

@ -18,6 +18,7 @@ declare(strict_types=1);
namespace core_reportbuilder\output;
use html_writer;
use plugin_renderer_base;
use core_reportbuilder\table\system_report_table;
@ -56,4 +57,16 @@ class renderer extends plugin_renderer_base {
return $output;
}
/**
* Renders the New report button
*
* @return string
*/
public function render_new_report_button(): string {
return html_writer::tag('button', get_string('newreport', 'core_reportbuilder'), [
'class' => 'btn btn-primary my-auto',
'data-action' => 'report-create',
]);
}
}

View File

@ -0,0 +1,82 @@
<?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\output;
use core_external;
use html_writer;
use moodle_url;
use core\output\inplace_editable;
use core_reportbuilder\permission;
use core_reportbuilder\local\models\report;
/**
* Report name editable component
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_name_editable extends inplace_editable {
/**
* Class constructor
*
* @param int $reportid
* @param report|null $report
*/
public function __construct(int $reportid, ?report $report = null) {
if ($report === null) {
$report = new report($reportid);
}
$editable = permission::can_edit_report($report);
$url = $editable
? new moodle_url('/reportbuilder/edit.php', ['id' => $report->get('id')])
: new moodle_url('/reportbuilder/view.php', ['id' => $report->get('id')]);
$displayvalue = html_writer::link($url, $report->get_formatted_name());
parent::__construct('core_reportbuilder', 'reportname', $report->get('id'), $editable, $displayvalue, $report->get('name'),
get_string('editreportname', 'core_reportbuilder'));
}
/**
* Update report persistent and return self, called from inplace_editable callback
*
* @param int $reportid
* @param string $value
* @return self
*/
public static function update(int $reportid, string $value): self {
$report = new report($reportid);
core_external::validate_context($report->get_context());
permission::require_can_edit_report($report);
$value = clean_param($value, PARAM_TEXT);
if ($value !== '') {
$report
->set('name', $value)
->update();
}
return new self(0, $report);
}
}

View File

@ -0,0 +1,115 @@
<?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;
use context_system;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\report\base;
/**
* Report permission class
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class permission {
/**
* Require given user can view reports list
*
* @param int|null $userid User ID to check, or the current user if omitted
* @throws report_access_exception
*/
public static function require_can_view_reports_list(?int $userid = null): void {
if (!static::can_view_reports_list($userid)) {
throw new report_access_exception();
}
}
/**
* Whether given user can view reports list
*
* @param int|null $userid User ID to check, or the current user if omitted
* @return bool
*/
public static function can_view_reports_list(?int $userid = null): bool {
return has_capability('moodle/reportbuilder:view', context_system::instance(), $userid);
}
/**
* Require given user can edit report
*
* @param report $report
* @param int|null $userid User ID to check, or the current user if omitted
* @return void
* @throws report_access_exception
*/
public static function require_can_edit_report(report $report, ?int $userid = null): void {
if (!static::can_edit_report($report, $userid)) {
throw new report_access_exception('errorreportedit');
}
}
/**
* Whether given user can edit report
*
* @param report $report
* @param int|null $userid User ID to check, or the current user if omitted
* @return bool
*/
public static function can_edit_report(report $report, ?int $userid = null): bool {
global $USER;
// We can only edit custom reports.
if ($report->get('type') !== base::TYPE_CUSTOM_REPORT) {
return false;
}
$userid = $userid ?: (int) $USER->id;
if ($report->get('usercreated') === $userid) {
return has_capability('moodle/reportbuilder:edit', context_system::instance(), $userid);
} else {
return has_capability('moodle/reportbuilder:editall', context_system::instance(), $userid);
}
}
/**
* Whether given user can create a new report
*
* @param int|null $userid User ID to check, or the current user if omitted
* @return bool
*/
public static function can_create_report(?int $userid = null): bool {
$capabilities = ['moodle/reportbuilder:edit', 'moodle/reportbuilder:editall'];
return has_any_capability($capabilities, context_system::instance(), $userid);
}
/**
* Require given user can create a new report
*
* @param int|null $userid User ID to check, or the current user if omitted
* @throws report_access_exception
*/
public static function require_can_create_report(?int $userid = null): void {
if (!static::can_create_report($userid)) {
throw new report_access_exception('errorreportcreate');
}
}
}

53
reportbuilder/index.php Normal file
View File

@ -0,0 +1,53 @@
<?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/>.
/**
* List of custom reports
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
use core_reportbuilder\permission;
use core_reportbuilder\system_report_factory;
use core_reportbuilder\local\systemreports\reports_list;
require_once(__DIR__ . '/../config.php');
require_once("{$CFG->libdir}/adminlib.php");
admin_externalpage_setup('customreports');
$PAGE->requires->js_call_amd('core_reportbuilder/reports_list', 'init');
echo $OUTPUT->header();
echo html_writer::start_div('d-flex justify-content-between mb-2');
echo $OUTPUT->heading(get_string('customreports', 'core_reportbuilder'));
if (permission::can_create_report()) {
/** @var \core_reportbuilder\output\renderer $renderer */
$renderer = $PAGE->get_renderer('core_reportbuilder');
echo $renderer->render_new_report_button();
}
echo html_writer::end_div();
$report = system_report_factory::create(reports_list::class, context_system::instance());
echo $report->output();
echo $OUTPUT->footer();

View File

@ -42,3 +42,23 @@ function core_reportbuilder_output_fragment_filters_form(array $params): string
return $filtersform->render();
}
/**
* Plugin inplace editable implementation
*
* @param string $itemtype
* @param int $itemid
* @param string $newvalue
* @return \core\output\inplace_editable|bool
*/
function core_reportbuilder_inplace_editable($itemtype, $itemid, $newvalue) {
$itemid = (int) $itemid;
switch ($itemtype) {
case 'reportname':
return \core_reportbuilder\output\report_name_editable::update($itemid, $newvalue);
default:
return false;
}
}

View File

@ -0,0 +1,62 @@
<?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_user\reportbuilder\datasource;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\helpers\database;
/**
* Users datasource
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class users extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('users');
}
/**
* Initialise report
*/
protected function initialise(): void {
global $CFG;
$userentity = new user();
$usertablealias = $userentity->get_table_alias('user');
$this->set_main_table('user', $usertablealias);
$userparamguest = database::generate_param_name();
$this->add_base_condition_sql("{$usertablealias}.id != :{$userparamguest} AND {$usertablealias}.deleted = 0", [
$userparamguest => $CFG->siteguest,
]);
// Add all columns from entities to be available in custom reports.
$this->add_entity($userentity);
}
}