Merge branch 'MDL-70794' of git://github.com/paulholden/moodle
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'reportbuilder', language 'en'
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['actions'] = 'Actions';
|
||||
$string['apply'] = 'Apply';
|
||||
$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['entitycourse'] = 'Course';
|
||||
$string['entityuser'] = 'User';
|
||||
$string['errorreportaccess'] = 'You can not view this report';
|
||||
$string['errorsourceinvalid'] = 'Could not find valid report source';
|
||||
$string['errorsourceunavailable'] = 'Report source is not available';
|
||||
$string['filtercontains'] = 'Contains';
|
||||
$string['filterdatefrom'] = 'Date from';
|
||||
$string['filterdateto'] = 'Date to';
|
||||
$string['filterdoesnotcontain'] = 'Does not contain';
|
||||
$string['filterendswith'] = 'Ends with';
|
||||
$string['filterequalorgreaterthan'] = 'Greater than or equal';
|
||||
$string['filterequalorlessthan'] = 'Less than or equal';
|
||||
$string['filterfieldoperator'] = '{$a} operator';
|
||||
$string['filterfieldvalue'] = '{$a} value';
|
||||
$string['filtergreaterthan'] = 'Greater than';
|
||||
$string['filterinvalid'] = 'Invalid filter';
|
||||
$string['filterisanyvalue'] = 'Is any value';
|
||||
$string['filterisempty'] = 'Is empty';
|
||||
$string['filterisequalto'] = 'Is equal to';
|
||||
$string['filterisnotempty'] = 'Is not empty';
|
||||
$string['filterisnotequalto'] = 'Is not equal to';
|
||||
$string['filterlessthan'] = 'Less than';
|
||||
$string['filterrange'] = 'Range';
|
||||
$string['filtersapplied'] = 'Filters applied';
|
||||
$string['filtersreset'] = 'Filters reset';
|
||||
$string['filterstartswith'] = 'Starts with';
|
||||
$string['privacy:metadata:preference:reportfilter'] = 'Stored report filter values';
|
||||
$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['resetall'] = 'Reset all';
|
||||
$string['selectcourses'] = 'Select courses';
|
||||
$string['userfullnamewithlink'] = 'Full name with link';
|
||||
$string['userfullnamewithpicture'] = 'Full name with picture';
|
||||
$string['userfullnamewithpicturelink'] = 'Full name with picture and link';
|
||||
$string['userpicture'] = 'User picture';
|
||||
@@ -104,6 +104,7 @@
|
||||
"privacy": "privacy",
|
||||
"question": "question",
|
||||
"rating": "rating",
|
||||
"reportbuilder": "reportbuilder",
|
||||
"repository": "repository",
|
||||
"rss": "rss",
|
||||
"role": "admin\/roles",
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20210422" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20210728" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -4376,5 +4376,26 @@
|
||||
<INDEX NAME="userid-issuerid-scopehash" UNIQUE="true" FIELDS="userid, issuerid, scopehash"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="reportbuilder_report" COMMENT="Table to represent a report">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="source" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="type" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="area" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usercreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="usercreated" TYPE="foreign" FIELDS="usercreated" REFTABLE="user" REFFIELDS="id"/>
|
||||
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
|
||||
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
||||
@@ -2773,6 +2773,12 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_reportbuilder_filters_reset' => [
|
||||
'classname' => 'core_reportbuilder\external\filters\reset',
|
||||
'description' => 'Reset filters for given report',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
);
|
||||
|
||||
$services = array(
|
||||
|
||||
@@ -2675,5 +2675,37 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2021060900.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2021072800.01) {
|
||||
// Define table reportbuilder_report to be created.
|
||||
$table = new xmldb_table('reportbuilder_report');
|
||||
|
||||
// Adding fields to table reportbuilder_report.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('source', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('type', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('area', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table reportbuilder_report.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
|
||||
$table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
|
||||
$table->add_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
|
||||
|
||||
// Conditionally launch create table for reportbuilder_report.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2021072800.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class component_test extends advanced_testcase {
|
||||
* this is defined here to annoy devs that try to add more without any thinking,
|
||||
* always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
*/
|
||||
const SUBSYSTEMCOUNT = 73;
|
||||
const SUBSYSTEMCOUNT = 74;
|
||||
|
||||
public function setUp(): void {
|
||||
$psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
|
||||
|
||||
@@ -202,6 +202,9 @@
|
||||
<testsuite name="core_contentbank_testsuite">
|
||||
<directory suffix="_test.php">contentbank/tests</directory>
|
||||
</testsuite>
|
||||
<testsuite name="core_reportbuilder_testsuite">
|
||||
<directory suffix="_test.php">reportbuilder/tests</directory>
|
||||
</testsuite>
|
||||
|
||||
<!--Plugin suites: use admin/tool/phpunit/cli/util.php to build phpunit.xml from
|
||||
phpunit.xml.dist with up-to-date list of plugins in current install-->
|
||||
|
||||
+2
@@ -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/filters",["exports","core/event_dispatcher","core/notification","core/pending","core/str","core/toast","core_form/dynamicform","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/filters"],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=m(g);h=l(h);i=l(i);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}}var n=function(a){var k=document.querySelector(i.forSystemReport(a)),l=k.querySelector(i.regions.filtersForm),m=new g.default(l,"\\core_reportbuilder\\form\\filter");m.addEventListener(m.events.FORM_SUBMITTED,function(a){a.preventDefault();(0,b.dispatchEvent)(h.tableReload,{},k);(0,e.get_string)("filtersapplied","core_reportbuilder").then(f.add).catch(c.default.exception)});m.addEventListener(m.events.NOSUBMIT_BUTTON_PRESSED,function(b){b.preventDefault();var g=new d.default("core_reportbuilder/filters:reset");(0,j.reset)(a).then(function(){return(0,e.get_string)("filtersreset","core_reportbuilder")}).then(f.add).then(function(){g.resolve();window.location.reload()}).catch(c.default.exception)});document.querySelector("#region-main").style.overflowX="visible"};a.init=n});
|
||||
//# sourceMappingURL=filters.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../src/filters.js"],"names":["init","reportId","reportElement","document","querySelector","reportSelectors","forSystemReport","filterFormContainer","regions","filtersForm","filterForm","DynamicForm","addEventListener","events","FORM_SUBMITTED","event","preventDefault","reportEvents","tableReload","then","addToast","catch","Notification","exception","NOSUBMIT_BUTTON_PRESSED","pendingPromise","Pending","resolve","window","location","reload","style","overflowX"],"mappings":"8pBAyBA,OACA,OAGA,OACA,OACA,O,ylBAQO,GAAMA,CAAAA,CAAI,CAAG,SAAAC,CAAQ,CAAI,IACtBC,CAAAA,CAAa,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,eAAhB,CAAgCL,CAAhC,CAAvB,CADM,CAEtBM,CAAmB,CAAGL,CAAa,CAACE,aAAd,CAA4BC,CAAe,CAACG,OAAhB,CAAwBC,WAApD,CAFA,CAGtBC,CAAU,CAAG,GAAIC,UAAJ,CAAgBJ,CAAhB,CAAqC,oCAArC,CAHS,CAM5BG,CAAU,CAACE,gBAAX,CAA4BF,CAAU,CAACG,MAAX,CAAkBC,cAA9C,CAA8D,SAAAC,CAAK,CAAI,CACnEA,CAAK,CAACC,cAAN,GAGA,oBAAcC,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChB,CAA5C,EAEA,iBAAU,gBAAV,CAA4B,oBAA5B,EACKiB,IADL,CACUC,KADV,EAEKC,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CATD,EAYAb,CAAU,CAACE,gBAAX,CAA4BF,CAAU,CAACG,MAAX,CAAkBW,uBAA9C,CAAuE,SAAAT,CAAK,CAAI,CAC5EA,CAAK,CAACC,cAAN,GAEA,GAAMS,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,kCAAZ,CAAvB,CAEA,YAAazB,CAAb,EACKkB,IADL,CACU,iBAAM,iBAAU,cAAV,CAA0B,oBAA1B,CAAN,CADV,EAEKA,IAFL,CAEUC,KAFV,EAGKD,IAHL,CAGU,UAAM,CACRM,CAAc,CAACE,OAAf,GACAC,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EAEH,CAPL,EAQKT,KARL,CAQWC,UAAaC,SARxB,CASH,CAdD,EAiBApB,QAAQ,CAACC,aAAT,CAAuB,cAAvB,EAAuC2B,KAAvC,CAA6CC,SAA7C,CAAyD,SAC5D,CApCM,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 filter management\n *\n * @module core_reportbuilder/filters\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport DynamicForm from 'core_form/dynamicform';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {reset as resetFilters} from 'core_reportbuilder/local/repository/filters';\n\n/**\n * Initialise module\n *\n * @param {Number} reportId\n */\nexport const init = reportId => {\n const reportElement = document.querySelector(reportSelectors.forSystemReport(reportId));\n const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm);\n const filterForm = new DynamicForm(filterFormContainer, '\\\\core_reportbuilder\\\\form\\\\filter');\n\n // Submit report filters.\n filterForm.addEventListener(filterForm.events.FORM_SUBMITTED, event => {\n event.preventDefault();\n\n // After the form has been submitted, we should trigger report table reload.\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n\n getString('filtersapplied', 'core_reportbuilder')\n .then(addToast)\n .catch(Notification.exception);\n });\n\n // Reset report filters.\n filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED, event => {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/filters:reset');\n\n resetFilters(reportId)\n .then(() => getString('filtersreset', 'core_reportbuilder'))\n .then(addToast)\n .then(() => {\n pendingPromise.resolve();\n window.location.reload();\n return;\n })\n .catch(Notification.exception);\n });\n\n // Modify \"region-main\" overflow for big filter forms.\n document.querySelector('#region-main').style.overflowX = \"visible\";\n};\n"],"file":"filters.min.js"}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
define ("core_reportbuilder/local/events",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={tableReload:"core_reportbuilder_table_reload"};return a.default});
|
||||
//# sourceMappingURL=events.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../src/local/events.js"],"names":["tableReload"],"mappings":"2JAwBe,CAGXA,WAAW,CAAE,iCAHF,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 events\n *\n * @module core_reportbuilder/local/events\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n // Trigger table reloading, passing optional `preservePagination` details argument can be used to\n // define whether current pagination should be preserved (default is false, i.e. reload first page.)\n tableReload: 'core_reportbuilder_table_reload',\n};\n"],"file":"events.min.js"}
|
||||
@@ -0,0 +1,2 @@
|
||||
define ("core_reportbuilder/local/repository/filters",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.reset=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_reportbuilder_filters_reset",args:{reportid:a}}])[0]};a.reset=c});
|
||||
//# sourceMappingURL=filters.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/local/repository/filters.js"],"names":["reset","reportId","Ajax","call","methodname","args","reportid"],"mappings":"yKAwBA,uDAQO,GAAMA,CAAAA,CAAK,CAAG,SAAAC,CAAQ,CAAI,CAM7B,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,kCADA,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 filter AJAX requests\n *\n * @module core_reportbuilder/local/repository/filters\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Reset all filters for given report\n *\n * @param {Number} reportId\n * @return {Promise}\n */\nexport const reset = reportId => {\n const request = {\n methodname: 'core_reportbuilder_filters_reset',\n args: {reportid: reportId}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"filters.min.js"}
|
||||
@@ -0,0 +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\"]",filtersForm:"[data-region=\"filters-form\"]"}};b.forSystemReport=function(a){return"".concat(b.regions.systemReport,"[data-reportid=\"").concat(a,"\"]")};a.default=b;return a.default});
|
||||
//# sourceMappingURL=selectors.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","systemReport","filtersForm","forSystemReport","reportId"],"mappings":"oJAwBA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,oDADT,CAELC,WAAW,CAAE,gCAFR,CADK,CAAlB,CAOAH,CAAS,CAACI,eAAV,CAA4B,SAAAC,CAAQ,kBAAOL,CAAS,CAACC,OAAV,CAAkBC,YAAzB,6BAAwDG,CAAxD,QAApC,C,UAEeL,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 * @package core_reportbuilder\n * @copyright 2021 Paul Holden <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n regions: {\n systemReport: '[data-region=\"core_reportbuilder/system-report\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n },\n};\n\nSELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"}
|
||||
+2
@@ -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/report",["exports","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_table/dynamic","core_table/local/dynamic/selectors"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=g(b);c=g(c);e=g(e);function f(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;f=function(){return a};return a}function g(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=f();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 g=d?Object.getOwnPropertyDescriptor(a,e):null;if(g&&(g.get||g.set)){Object.defineProperty(c,e,g)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}var j=function(a){document.addEventListener(b.tableReload,function(){var b=i(regeneratorRuntime.mark(function b(f){var g,h,i,j;return regeneratorRuntime.wrap(function(b){while(1){switch(b.prev=b.next){case 0:h=f.target.closest(c.forSystemReport(a));if(!(null===h)){b.next=3;break}return b.abrupt("return");case 3:i=h.querySelector(e.main.region);j=(null===(g=f.detail)||void 0===g?void 0:g.preservePagination)?null:1;b.next=7;return(0,d.setPageNumber)(i,j,!1).then(d.refreshTableContent);case 7:case"end":return b.stop();}}},b)}));return function(){return b.apply(this,arguments)}}())};a.init=j});
|
||||
//# sourceMappingURL=report.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../src/report.js"],"names":["init","reportId","document","addEventListener","reportEvents","tableReload","event","triggerElement","target","closest","reportSelectors","forSystemReport","tableRoot","querySelector","tableSelectors","main","region","pageNumber","detail","preservePagination","then","refreshTableContent"],"mappings":"ojBAwBA,OACA,OAEA,O,q2BAOO,GAAMA,CAAAA,CAAI,CAAG,SAAAC,CAAQ,CAAI,CAE5BC,QAAQ,CAACC,gBAAT,CAA0BC,CAAY,CAACC,WAAvC,4CAAoD,WAAMC,CAAN,+FAC1CC,CAD0C,CACzBD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACC,eAAhB,CAAgCV,CAAhC,CAArB,CADyB,MAEzB,IAAnB,GAAAM,CAF4C,mDAM1CK,CAN0C,CAM9BL,CAAc,CAACM,aAAf,CAA6BC,CAAc,CAACC,IAAf,CAAoBC,MAAjD,CAN8B,CAO1CC,CAP0C,CAO7B,WAAAX,CAAK,CAACY,MAAN,uBAAcC,kBAAd,EAAmC,IAAnC,CAA0C,CAPb,gBAS1C,oBAAcP,CAAd,CAAyBK,CAAzB,KACDG,IADC,CACIC,qBADJ,CAT0C,yCAApD,wDAYH,CAdM,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 report management\n *\n * @module core_reportbuilder/report\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {setPageNumber, refreshTableContent} from 'core_table/dynamic';\nimport * as tableSelectors from 'core_table/local/dynamic/selectors';\n\n/**\n * Initialise module\n *\n * @param {Number} reportId\n */\nexport const init = reportId => {\n // Listen for the table reload event.\n document.addEventListener(reportEvents.tableReload, async(event) => {\n const triggerElement = event.target.closest(reportSelectors.forSystemReport(reportId));\n if (triggerElement === null) {\n return;\n }\n\n const tableRoot = triggerElement.querySelector(tableSelectors.main.region);\n const pageNumber = event.detail?.preservePagination ? null : 1;\n\n await setPageNumber(tableRoot, pageNumber, false)\n .then(refreshTableContent);\n });\n};\n"],"file":"report.min.js"}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 filter management
|
||||
*
|
||||
* @module core_reportbuilder/filters
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {dispatchEvent} from 'core/event_dispatcher';
|
||||
import Notification from 'core/notification';
|
||||
import Pending from 'core/pending';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import {add as addToast} from 'core/toast';
|
||||
import DynamicForm from 'core_form/dynamicform';
|
||||
import * as reportEvents from 'core_reportbuilder/local/events';
|
||||
import * as reportSelectors from 'core_reportbuilder/local/selectors';
|
||||
import {reset as resetFilters} from 'core_reportbuilder/local/repository/filters';
|
||||
|
||||
/**
|
||||
* Initialise module
|
||||
*
|
||||
* @param {Number} reportId
|
||||
*/
|
||||
export const init = reportId => {
|
||||
const reportElement = document.querySelector(reportSelectors.forSystemReport(reportId));
|
||||
const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm);
|
||||
const filterForm = new DynamicForm(filterFormContainer, '\\core_reportbuilder\\form\\filter');
|
||||
|
||||
// Submit report filters.
|
||||
filterForm.addEventListener(filterForm.events.FORM_SUBMITTED, event => {
|
||||
event.preventDefault();
|
||||
|
||||
// After the form has been submitted, we should trigger report table reload.
|
||||
dispatchEvent(reportEvents.tableReload, {}, reportElement);
|
||||
|
||||
getString('filtersapplied', 'core_reportbuilder')
|
||||
.then(addToast)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
|
||||
// Reset report filters.
|
||||
filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED, event => {
|
||||
event.preventDefault();
|
||||
|
||||
const pendingPromise = new Pending('core_reportbuilder/filters:reset');
|
||||
|
||||
resetFilters(reportId)
|
||||
.then(() => getString('filtersreset', 'core_reportbuilder'))
|
||||
.then(addToast)
|
||||
.then(() => {
|
||||
pendingPromise.resolve();
|
||||
window.location.reload();
|
||||
return;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
|
||||
// Modify "region-main" overflow for big filter forms.
|
||||
document.querySelector('#region-main').style.overflowX = "visible";
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 events
|
||||
*
|
||||
* @module core_reportbuilder/local/events
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Trigger table reloading, passing optional `preservePagination` details argument can be used to
|
||||
// define whether current pagination should be preserved (default is false, i.e. reload first page.)
|
||||
tableReload: 'core_reportbuilder_table_reload',
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 filter AJAX requests
|
||||
*
|
||||
* @module core_reportbuilder/local/repository/filters
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Reset all filters for given report
|
||||
*
|
||||
* @param {Number} reportId
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const reset = reportId => {
|
||||
const request = {
|
||||
methodname: 'core_reportbuilder_filters_reset',
|
||||
args: {reportid: reportId}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 selectors
|
||||
*
|
||||
* @module core_reportbuilder/local/selectors
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
const SELECTORS = {
|
||||
regions: {
|
||||
systemReport: '[data-region="core_reportbuilder/system-report"]',
|
||||
filtersForm: '[data-region="filters-form"]',
|
||||
},
|
||||
};
|
||||
|
||||
SELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid="${reportId}"]`;
|
||||
|
||||
export default SELECTORS;
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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 report management
|
||||
*
|
||||
* @module core_reportbuilder/report
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import * as reportEvents from 'core_reportbuilder/local/events';
|
||||
import * as reportSelectors from 'core_reportbuilder/local/selectors';
|
||||
import {setPageNumber, refreshTableContent} from 'core_table/dynamic';
|
||||
import * as tableSelectors from 'core_table/local/dynamic/selectors';
|
||||
|
||||
/**
|
||||
* Initialise module
|
||||
*
|
||||
* @param {Number} reportId
|
||||
*/
|
||||
export const init = reportId => {
|
||||
// Listen for the table reload event.
|
||||
document.addEventListener(reportEvents.tableReload, async(event) => {
|
||||
const triggerElement = event.target.closest(reportSelectors.forSystemReport(reportId));
|
||||
if (triggerElement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tableRoot = triggerElement.querySelector(tableSelectors.main.region);
|
||||
const pageNumber = event.detail?.preservePagination ? null : 1;
|
||||
|
||||
await setPageNumber(tableRoot, pageNumber, false)
|
||||
.then(refreshTableContent);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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\external\filters;
|
||||
|
||||
use context_system;
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_value;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once("{$CFG->libdir}/externallib.php");
|
||||
|
||||
/**
|
||||
* External method for resetting report filters
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class reset extends external_api {
|
||||
|
||||
/**
|
||||
* External method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'reportid' => new external_value(PARAM_INT, 'Report ID'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External method execution
|
||||
*
|
||||
* @param int $reportid
|
||||
* @return bool
|
||||
*/
|
||||
public static function execute(int $reportid): bool {
|
||||
[
|
||||
'reportid' => $reportid,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'reportid' => $reportid,
|
||||
]);
|
||||
|
||||
$context = context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
return user_filter_manager::reset_all($reportid);
|
||||
}
|
||||
|
||||
/**
|
||||
* External method return value
|
||||
*
|
||||
* @return external_value
|
||||
*/
|
||||
public static function execute_returns(): external_value {
|
||||
return new external_value(PARAM_BOOL, 'Success');
|
||||
}
|
||||
}
|
||||
@@ -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\external;
|
||||
|
||||
use core\external\persistent_exporter;
|
||||
use core_reportbuilder\form\filter;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
use core_reportbuilder\table\system_report_table;
|
||||
use core_reportbuilder\table\system_report_table_filterset;
|
||||
use core_reportbuilder\table\system_report_table_parameters_filter;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Report exporter class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_exporter extends persistent_exporter {
|
||||
|
||||
/**
|
||||
* Return the name of the class we are exporting
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function define_class(): string {
|
||||
return report::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of objects that are related to the persistent
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related(): array {
|
||||
return [
|
||||
'source' => base::class,
|
||||
'parameters' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of additional properties used only for display
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties(): array {
|
||||
return [
|
||||
'table' => ['type' => PARAM_RAW],
|
||||
'parameters' => ['type' => PARAM_RAW],
|
||||
'filterspresent' => ['type' => PARAM_BOOL],
|
||||
'filtersform' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional values to inject while exporting
|
||||
*
|
||||
* @uses \core_reportbuilder\output\renderer::render_system_report_table()
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return array
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output): array {
|
||||
/** @var base $source */
|
||||
$source = $this->related['source'];
|
||||
|
||||
/** @var array $parameters */
|
||||
$parameters = $this->related['parameters'];
|
||||
|
||||
$filterset = new system_report_table_filterset();
|
||||
$filterset->add_filter(new system_report_table_parameters_filter('parameters', null, [$parameters]));
|
||||
|
||||
$table = system_report_table::create($this->persistent->get('id'), (array) json_decode($parameters, true));
|
||||
$table->set_filterset($filterset);
|
||||
|
||||
// Generate filters form if report contains any filters.
|
||||
$filterspresent = !empty($source->get_filters());
|
||||
if ($filterspresent) {
|
||||
$filtersform = new filter(null, null, 'post', '', [], true, [
|
||||
'reportid' => $this->persistent->get('id'),
|
||||
'parameters' => $parameters,
|
||||
]);
|
||||
$filtersform->set_data_for_dynamic_submission();
|
||||
}
|
||||
|
||||
return [
|
||||
'table' => $output->render($table),
|
||||
'parameters' => $this->related['parameters'],
|
||||
'filterspresent' => $filterspresent,
|
||||
'filtersform' => $filterspresent ? $filtersform->render() : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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 moodle_url;
|
||||
use core_form\dynamic_form;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\system_report;
|
||||
use core_reportbuilder\local\models\report;
|
||||
|
||||
/**
|
||||
* Dynamic filter form
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter extends dynamic_form {
|
||||
|
||||
/**
|
||||
* Return instance of the system report using the filter form
|
||||
*
|
||||
* @return system_report
|
||||
*/
|
||||
private function get_system_report(): system_report {
|
||||
$report = new report($this->optional_param('reportid', 0, PARAM_INT));
|
||||
$parameters = (array) json_decode($this->optional_param('parameters', '', PARAM_RAW));
|
||||
|
||||
/** @var system_report $systemreport */
|
||||
$systemreport = manager::get_report_from_persistent($report, $parameters);
|
||||
|
||||
return $systemreport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the context for the form, it should be that of the system report itself
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
protected function get_context_for_dynamic_submission(): context {
|
||||
return ($this->get_system_report())->get_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$this->get_system_report()->require_can_view();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form submission
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process_dynamic_submission() {
|
||||
$values = $this->get_data();
|
||||
|
||||
// Remove some unneeded fields.
|
||||
unset($values->reportid, $values->parameters);
|
||||
|
||||
return $this->get_system_report()->set_filter_values((array) $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load in existing data as form defaults
|
||||
*/
|
||||
public function set_data_for_dynamic_submission(): void {
|
||||
$defaults = [
|
||||
'reportid' => $this->optional_param('reportid', 0, PARAM_INT),
|
||||
'parameters' => $this->optional_param('parameters', 0, PARAM_RAW),
|
||||
];
|
||||
|
||||
$this->set_data(array_merge($defaults, $this->get_system_report()->get_filter_values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* URL of the page using this form
|
||||
*
|
||||
* @return moodle_url
|
||||
*/
|
||||
protected function get_page_url_for_dynamic_submission(): moodle_url {
|
||||
return new moodle_url('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter form definition. It should provide necessary field itself, then allow all report filters to add their own elements
|
||||
*/
|
||||
protected function definition() {
|
||||
global $OUTPUT;
|
||||
|
||||
$mform = $this->_form;
|
||||
|
||||
$mform->addElement('hidden', 'reportid');
|
||||
$mform->setType('reportid', PARAM_INT);
|
||||
|
||||
$mform->addElement('hidden', 'parameters');
|
||||
$mform->setType('parameters', PARAM_RAW);
|
||||
|
||||
// Allow each filter instance to add itself to this form, wrapping each inside custom header/footer template.
|
||||
foreach ($this->get_system_report()->get_filter_instances() as $filterinstance) {
|
||||
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/filters/header', [
|
||||
'name' => $filterinstance->get_header(),
|
||||
]));
|
||||
|
||||
$filterinstance->setup_form($mform);
|
||||
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/filters/footer', []));
|
||||
}
|
||||
|
||||
$this->set_display_vertical();
|
||||
|
||||
// We'll add a second submit button to the form that will be used to reset current report filters.
|
||||
$mform->registerNoSubmitButton('resetfilters');
|
||||
|
||||
$buttons = [];
|
||||
$buttons[] = $mform->createElement('submit', 'submitbutton', get_string('apply', 'core_reportbuilder'));
|
||||
$buttons[] = $mform->createElement('submit', 'resetfilters', get_string('resetall', 'core_reportbuilder'),
|
||||
null, null, ['customclassoverride' => 'btn-link']);
|
||||
|
||||
$mform->addGroup($buttons, 'buttonar', '', [' '], false);
|
||||
$mform->closeHeaderBefore('buttonar');
|
||||
|
||||
$mform->disable_form_change_checker();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?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\entities;
|
||||
|
||||
use coding_exception;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Base class for all report entities
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2019 Marina Glancy <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
|
||||
/** @var string $entityname Internal reference to name of entity */
|
||||
private $entityname = '';
|
||||
|
||||
/** @var lang_string $entitytitle Used as a title for the entity in reports */
|
||||
private $entitytitle = null;
|
||||
|
||||
/** @var array $tablealiases Database tables that this entity uses and their default aliases */
|
||||
private $tablealiases = [];
|
||||
|
||||
/** @var string[] $joins List of SQL joins for the entity */
|
||||
private $joins = [];
|
||||
|
||||
/** @var column[] $columns List of columns for the entity */
|
||||
private $columns = [];
|
||||
|
||||
/** @var filter[] $filters List of filters for the entity */
|
||||
private $filters = [];
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses and their default aliases
|
||||
*
|
||||
* Must be overridden by the entity to list all database tables that it expects to be present in the main
|
||||
* SQL or in JOINs added to this entity
|
||||
*
|
||||
* @return string[] Array of $tablename => $alias
|
||||
*/
|
||||
abstract protected function get_default_table_aliases(): array;
|
||||
|
||||
/**
|
||||
* The default title for this entity
|
||||
*
|
||||
* @return lang_string
|
||||
*/
|
||||
abstract protected function get_default_entity_title(): lang_string;
|
||||
|
||||
/**
|
||||
* Initialise the entity, called automatically when it is added to a report
|
||||
*
|
||||
* This is where entity defines all its columns and filters by calling:
|
||||
* - {@see add_column}
|
||||
* - {@see add_filter}
|
||||
* - etc
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
abstract public function initialise(): self;
|
||||
|
||||
/**
|
||||
* The default machine-readable name for this entity that will be used in the internal names of the columns/filters
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_default_entity_name(): string {
|
||||
$namespace = explode('\\', get_called_class());
|
||||
|
||||
return end($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entity name
|
||||
*
|
||||
* @param string $entityname
|
||||
* @return self
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final public function set_entity_name(string $entityname): self {
|
||||
if ($entityname === '' || $entityname !== clean_param($entityname, PARAM_ALPHANUMEXT)) {
|
||||
throw new coding_exception('Entity name must be comprised of alphanumeric character, underscore or dash');
|
||||
}
|
||||
|
||||
$this->entityname = $entityname;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return entity name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function get_entity_name(): string {
|
||||
return $this->entityname ?: $this->get_default_entity_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entity title
|
||||
*
|
||||
* @param lang_string $title
|
||||
* @return self
|
||||
*/
|
||||
final public function set_entity_title(lang_string $title): self {
|
||||
$this->entitytitle = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity title
|
||||
*
|
||||
* @return lang_string
|
||||
*/
|
||||
final public function get_entity_title(): lang_string {
|
||||
return $this->entitytitle ?? $this->get_default_entity_title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default alias for given database table used in entity queries
|
||||
*
|
||||
* @param string $tablename
|
||||
* @param string $alias
|
||||
* @return self
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final public function set_table_alias(string $tablename, string $alias): self {
|
||||
if (!array_key_exists($tablename, $this->get_default_table_aliases())) {
|
||||
throw new coding_exception('Invalid table name', $tablename);
|
||||
}
|
||||
|
||||
$this->tablealiases[$tablename] = $alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an alias used in the queries for a given table
|
||||
*
|
||||
* @param string $tablename
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final public function get_table_alias(string $tablename): string {
|
||||
$defaulttablealiases = $this->get_default_table_aliases();
|
||||
if (!array_key_exists($tablename, $defaulttablealiases)) {
|
||||
throw new coding_exception('Invalid table name', $tablename);
|
||||
}
|
||||
|
||||
return $this->tablealiases[$tablename] ?? $defaulttablealiases[$tablename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join clause required for this entity to join to existing tables/entities
|
||||
*
|
||||
* @param string $join
|
||||
* @return self
|
||||
*/
|
||||
final public function add_join(string $join): self {
|
||||
$this->joins[trim($join)] = trim($join);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple join clauses required for this entity {@see add_join}
|
||||
*
|
||||
* @param string[] $joins
|
||||
* @return self
|
||||
*/
|
||||
final public function add_joins(array $joins): self {
|
||||
foreach ($joins as $join) {
|
||||
$this->add_join($join);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return entity joins
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
final protected function get_joins(): array {
|
||||
return array_values($this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column to the entity
|
||||
*
|
||||
* @param column $column
|
||||
* @return self
|
||||
*/
|
||||
final protected function add_column(column $column): self {
|
||||
$this->columns[$column->get_name()] = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entity columns
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
final public function get_columns(): array {
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an entity column
|
||||
*
|
||||
* @param string $name
|
||||
* @return column
|
||||
* @throws coding_exception For invalid column name
|
||||
*/
|
||||
final public function get_column(string $name): column {
|
||||
if (!array_key_exists($name, $this->columns)) {
|
||||
throw new coding_exception('Invalid column name', $name);
|
||||
}
|
||||
|
||||
return $this->columns[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to the entity
|
||||
*
|
||||
* @param filter $filter
|
||||
* @return $this
|
||||
*/
|
||||
final protected function add_filter(filter $filter): self {
|
||||
$this->filters[$filter->get_name()] = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entity filters
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
final public function get_filters(): array {
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an entity filter
|
||||
*
|
||||
* @param string $name
|
||||
* @return filter
|
||||
* @throws coding_exception For invalid filter name
|
||||
*/
|
||||
final public function get_filter(string $name): filter {
|
||||
if (!array_key_exists($name, $this->filters)) {
|
||||
throw new coding_exception('Invalid filter name', $name);
|
||||
}
|
||||
|
||||
return $this->filters[$name];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
<?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\entities;
|
||||
|
||||
use context_course;
|
||||
use context_helper;
|
||||
use core_course_category;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\course_selector;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\helpers\custom_fields;
|
||||
use core_reportbuilder\local\helpers\format;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use html_writer;
|
||||
use lang_string;
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
|
||||
/**
|
||||
* Course entity class implementation
|
||||
*
|
||||
* This entity defines all the course columns and filters to be used in any report.
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Sara Arjona <[email protected]> based on Marina Glancy code.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course extends base {
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses and their default aliases.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_table_aliases(): array {
|
||||
return [
|
||||
'course' => 'c',
|
||||
'context' => 'cctx',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The default machine-readable name for this entity that will be used in the internal names of the columns/filters.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_default_entity_name(): string {
|
||||
return 'course';
|
||||
}
|
||||
|
||||
/**
|
||||
* The default title for this entity in the list of columns/filters in the report builder.
|
||||
*
|
||||
* @return lang_string
|
||||
*/
|
||||
protected function get_default_entity_title(): lang_string {
|
||||
return new lang_string('entitycourse', 'core_reportbuilder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom fields helper
|
||||
*
|
||||
* @return custom_fields
|
||||
*/
|
||||
protected function get_custom_fields(): custom_fields {
|
||||
$customfields = new custom_fields($this->get_table_alias('course') . '.id', $this->get_entity_name(),
|
||||
'core_course', 'course');
|
||||
$customfields->add_joins($this->get_joins());
|
||||
return $customfields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the entity, adding all course and custom course fields
|
||||
*
|
||||
* @return base
|
||||
*/
|
||||
public function initialise(): base {
|
||||
$customfields = $this->get_custom_fields();
|
||||
|
||||
$columns = array_merge($this->get_all_columns(), $customfields->get_columns());
|
||||
foreach ($columns as $column) {
|
||||
$this->add_column($column);
|
||||
}
|
||||
|
||||
$filters = array_merge($this->get_all_filters(), $customfields->get_filters());
|
||||
foreach ($filters as $filter) {
|
||||
$this->add_filter($filter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Course fields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_course_fields(): array {
|
||||
return [
|
||||
'fullname' => new lang_string('fullnamecourse'),
|
||||
'shortname' => new lang_string('shortnamecourse'),
|
||||
'category' => new lang_string('coursecategory'),
|
||||
'idnumber' => new lang_string('idnumbercourse'),
|
||||
'summary' => new lang_string('coursesummary'),
|
||||
'format' => new lang_string('format'),
|
||||
'startdate' => new lang_string('startdate'),
|
||||
'enddate' => new lang_string('enddate'),
|
||||
'visible' => new lang_string('coursevisibility'),
|
||||
'groupmode' => new lang_string('groupmode', 'group'),
|
||||
'groupmodeforce' => new lang_string('groupmodeforce', 'group'),
|
||||
'lang' => new lang_string('forcelanguage'),
|
||||
'calendartype' => new lang_string('forcecalendartype', 'calendar'),
|
||||
'theme' => new lang_string('forcetheme'),
|
||||
'enablecompletion' => new lang_string('enablecompletion', 'completion'),
|
||||
'downloadcontent' => new lang_string('downloadcoursecontent', 'course'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this field is sortable
|
||||
*
|
||||
* @param string $fieldname
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_sortable(string $fieldname): bool {
|
||||
// Some columns can't be sorted, like longtext or images.
|
||||
$nonsortable = [
|
||||
'summary',
|
||||
];
|
||||
|
||||
return !in_array($fieldname, $nonsortable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return appropriate column type for given user field
|
||||
*
|
||||
* @param string $coursefield
|
||||
* @return int
|
||||
*/
|
||||
protected function get_course_field_type(string $coursefield): int {
|
||||
switch ($coursefield) {
|
||||
case 'downloadcontent':
|
||||
case 'enablecompletion':
|
||||
case 'groupmodeforce':
|
||||
case 'visible':
|
||||
$fieldtype = column::TYPE_BOOLEAN;
|
||||
break;
|
||||
case 'startdate':
|
||||
case 'enddate':
|
||||
$fieldtype = column::TYPE_TIMESTAMP;
|
||||
break;
|
||||
case 'summary':
|
||||
$fieldtype = column::TYPE_LONGTEXT;
|
||||
break;
|
||||
case 'category':
|
||||
case 'groupmode':
|
||||
$fieldtype = column::TYPE_INTEGER;
|
||||
break;
|
||||
case 'calendartype':
|
||||
case 'idnumber':
|
||||
case 'format':
|
||||
case 'fullname':
|
||||
case 'lang':
|
||||
case 'shortname':
|
||||
case 'theme':
|
||||
default:
|
||||
$fieldtype = column::TYPE_TEXT;
|
||||
break;
|
||||
}
|
||||
|
||||
return $fieldtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all available columns.
|
||||
*
|
||||
* These are all the columns available to use in any report that uses this entity.
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
protected function get_all_columns(): array {
|
||||
$columns = [];
|
||||
$coursefields = $this->get_course_fields();
|
||||
$tablealias = $this->get_table_alias('course');
|
||||
$contexttablealias = $this->get_table_alias('context');
|
||||
|
||||
// Columns course full name with link, course short name with link and course id with link.
|
||||
$fields = [
|
||||
'coursefullnamewithlink' => 'fullname',
|
||||
'courseshortnamewithlink' => 'shortname',
|
||||
'courseidnumberewithlink' => 'idnumber',
|
||||
];
|
||||
foreach ($fields as $key => $field) {
|
||||
$columns[] = (new column(
|
||||
$key,
|
||||
new lang_string($key, 'core_reportbuilder'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$tablealias}.{$field} as $key, {$tablealias}.id")
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function(?string $value, stdClass $row): string {
|
||||
if ($value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return html_writer::link(course_get_url($row->id), $value);
|
||||
});
|
||||
}
|
||||
|
||||
foreach ($coursefields as $coursefield => $coursefieldlang) {
|
||||
$column = (new column(
|
||||
$coursefield,
|
||||
$coursefieldlang,
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type($this->get_course_field_type($coursefield))
|
||||
->add_field("$tablealias.$coursefield")
|
||||
->add_callback([$this, 'format'], $coursefield)
|
||||
->set_is_sortable($this->is_sortable($coursefield));
|
||||
|
||||
// Join on the context table so that we can use it for formatting these columns later.
|
||||
if ($coursefield === 'summary' || $coursefield === 'shortname' || $coursefield === 'fullname') {
|
||||
$join = "LEFT JOIN {context} {$contexttablealias}
|
||||
ON {$contexttablealias}.contextlevel = " . CONTEXT_COURSE . "
|
||||
AND {$contexttablealias}.instanceid = {$tablealias}.id";
|
||||
|
||||
$column->add_join($join)
|
||||
->add_field("{$tablealias}.id", 'courseid')
|
||||
->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias));
|
||||
}
|
||||
|
||||
$columns[] = $column;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all available filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_all_filters(): array {
|
||||
global $DB;
|
||||
|
||||
$filters = [];
|
||||
$tablealias = $this->get_table_alias('course');
|
||||
|
||||
$fields = $this->get_course_fields();
|
||||
foreach ($fields as $field => $name) {
|
||||
// Filtering isn't supported for LONGTEXT fields on Oracle.
|
||||
if ($this->get_course_field_type($field) === column::TYPE_LONGTEXT &&
|
||||
$DB->get_dbfamily() === 'oracle') {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$optionscallback = [static::class, 'get_options_for_' . $field];
|
||||
if (is_callable($optionscallback)) {
|
||||
$filterclass = select::class;
|
||||
} else if ($this->get_course_field_type($field) === column::TYPE_BOOLEAN) {
|
||||
$filterclass = boolean_select::class;
|
||||
} else if ($this->get_course_field_type($field) === column::TYPE_TIMESTAMP) {
|
||||
$filterclass = date::class;
|
||||
} else {
|
||||
$filterclass = text::class;
|
||||
}
|
||||
|
||||
$filter = (new filter(
|
||||
$filterclass,
|
||||
$field,
|
||||
$name,
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.$field"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Populate filter options by callback, if available.
|
||||
if (is_callable($optionscallback)) {
|
||||
$filter->set_options_callback($optionscallback);
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
// We add our own custom course selector filter.
|
||||
$filters[] = (new filter(
|
||||
course_selector::class,
|
||||
'courseselector',
|
||||
new lang_string('courses'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.id"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of options if the filter supports it
|
||||
*
|
||||
* @param string $fieldname
|
||||
* @return null|array
|
||||
*/
|
||||
protected function get_options_for(string $fieldname): ?array {
|
||||
static $cached = [];
|
||||
if (!array_key_exists($fieldname, $cached)) {
|
||||
$callable = [static::class, 'get_options_for_' . $fieldname];
|
||||
if (is_callable($callable)) {
|
||||
$cached[$fieldname] = $callable();
|
||||
} else {
|
||||
$cached[$fieldname] = null;
|
||||
}
|
||||
}
|
||||
return $cached[$fieldname];
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field groupmode.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_groupmode(): array {
|
||||
return [
|
||||
NOGROUPS => get_string('groupsnone', 'group'),
|
||||
SEPARATEGROUPS => get_string('groupsseparate', 'group'),
|
||||
VISIBLEGROUPS => get_string('groupsvisible', 'group'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field category.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_category(): array {
|
||||
return core_course_category::make_categories_list('moodle/category:viewcourselist');
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_format(): array {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
|
||||
$options = [];
|
||||
|
||||
$courseformats = get_sorted_course_formats(true);
|
||||
foreach ($courseformats as $courseformat) {
|
||||
$options[$courseformat] = get_string('pluginname', "format_{$courseformat}");
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field theme.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_theme(): array {
|
||||
$options = [];
|
||||
|
||||
$themeobjects = get_list_of_themes();
|
||||
foreach ($themeobjects as $key => $theme) {
|
||||
if (empty($theme->hidefromselector)) {
|
||||
$options[$key] = get_string('pluginname', "theme_{$theme->name}");
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field lang.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_lang(): array {
|
||||
return get_string_manager()->get_list_of_translations();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options_for_calendartype(): array {
|
||||
return \core_calendar\type_factory::get_list_of_calendar_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the course field for display.
|
||||
*
|
||||
* @param mixed $value Current field value.
|
||||
* @param stdClass $row Complete row.
|
||||
* @param string $fieldname Name of the field to format.
|
||||
* @return string
|
||||
*/
|
||||
public function format($value, stdClass $row, string $fieldname): string {
|
||||
if ($this->get_course_field_type($fieldname) === column::TYPE_TIMESTAMP) {
|
||||
return format::userdate($value, $row);
|
||||
}
|
||||
|
||||
$options = $this->get_options_for($fieldname);
|
||||
if ($options !== null && array_key_exists($value, $options)) {
|
||||
return $options[$value];
|
||||
}
|
||||
|
||||
if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) {
|
||||
return format::boolean_as_text($value);
|
||||
}
|
||||
|
||||
if (in_array($fieldname, ['fullname', 'shortname'])) {
|
||||
if (!$row->courseid) {
|
||||
return '';
|
||||
}
|
||||
context_helper::preload_from_record($row);
|
||||
$context = context_course::instance($row->courseid);
|
||||
return format_string($value, true, ['context' => $context->id, 'escape' => false]);
|
||||
}
|
||||
|
||||
if (in_array($fieldname, ['summary'])) {
|
||||
if (!$row->courseid) {
|
||||
return '';
|
||||
}
|
||||
context_helper::preload_from_record($row);
|
||||
$context = context_course::instance($row->courseid);
|
||||
$summary = file_rewrite_pluginfile_urls($row->summary, 'pluginfile.php', $context->id, 'course', 'summary', null);
|
||||
return format_text($summary);
|
||||
}
|
||||
|
||||
return s($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
<?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\entities;
|
||||
|
||||
use context_system;
|
||||
use html_writer;
|
||||
use lang_string;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use core_user\fields;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\helpers\user_profile_fields;
|
||||
use core_reportbuilder\local\helpers\format;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* User entity class implementation.
|
||||
*
|
||||
* This entity defines all the user columns and filters to be used in any report.
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Sara Arjona <[email protected]> based on Marina Glancy code.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user extends base {
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses and their default aliases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_table_aliases(): array {
|
||||
return ['user' => 'u'];
|
||||
}
|
||||
|
||||
/**
|
||||
* The default title for this entity
|
||||
*
|
||||
* @return lang_string
|
||||
*/
|
||||
protected function get_default_entity_title(): lang_string {
|
||||
return new lang_string('entityuser', 'core_reportbuilder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the entity, add all user fields and all 'visible' user profile fields
|
||||
*
|
||||
* @return base
|
||||
*/
|
||||
public function initialise(): base {
|
||||
$userprofilefields = $this->get_user_profile_fields();
|
||||
|
||||
$columns = array_merge($this->get_all_columns(), $userprofilefields->get_columns());
|
||||
foreach ($columns as $column) {
|
||||
$this->add_column($column);
|
||||
}
|
||||
|
||||
$filters = array_merge($this->get_all_filters(), $userprofilefields->get_filters());
|
||||
foreach ($filters as $filter) {
|
||||
$this->add_filter($filter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile fields helper instance
|
||||
*
|
||||
* @return user_profile_fields
|
||||
*/
|
||||
protected function get_user_profile_fields(): user_profile_fields {
|
||||
$userprofilefields = new user_profile_fields($this->get_table_alias('user') . '.id', $this->get_entity_name());
|
||||
$userprofilefields->add_joins($this->get_joins());
|
||||
return $userprofilefields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all available columns
|
||||
*
|
||||
* These are all the columns available to use in any report that uses this entity.
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
protected function get_all_columns(): array {
|
||||
$usertablealias = $this->get_table_alias('user');
|
||||
|
||||
$fullnameselect = self::get_name_fields_select($usertablealias);
|
||||
$userpictureselect = fields::for_userpic()->get_sql($usertablealias, false, '', '', false)->selects;
|
||||
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
|
||||
|
||||
// Fullname column.
|
||||
$columns[] = (new column(
|
||||
'fullname',
|
||||
new lang_string('fullname'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_fields($fullnameselect)
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->set_is_sortable($this->is_sortable('fullname'))
|
||||
->add_callback(static function(?string $value, stdClass $row) use ($viewfullnames): string {
|
||||
if ($value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return fullname($row, $viewfullnames);
|
||||
});
|
||||
|
||||
// Formatted fullname columns (with link, picture or both).
|
||||
$fullnamefields = [
|
||||
'fullnamewithlink' => new lang_string('userfullnamewithlink', 'core_reportbuilder'),
|
||||
'fullnamewithpicture' => new lang_string('userfullnamewithpicture', 'core_reportbuilder'),
|
||||
'fullnamewithpicturelink' => new lang_string('userfullnamewithpicturelink', 'core_reportbuilder'),
|
||||
];
|
||||
foreach ($fullnamefields as $fullnamefield => $fullnamelang) {
|
||||
$column = (new column(
|
||||
$fullnamefield,
|
||||
$fullnamelang,
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_fields($fullnameselect)
|
||||
->add_field("{$usertablealias}.id")
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->set_is_sortable($this->is_sortable($fullnamefield))
|
||||
->add_callback(static function(?string $value, stdClass $row) use ($fullnamefield, $viewfullnames): string {
|
||||
global $OUTPUT;
|
||||
|
||||
if ($value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($fullnamefield === 'fullnamewithlink') {
|
||||
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
|
||||
fullname($row, $viewfullnames));
|
||||
}
|
||||
if ($fullnamefield === 'fullnamewithpicture') {
|
||||
return $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
|
||||
fullname($row, $viewfullnames);
|
||||
}
|
||||
if ($fullnamefield === 'fullnamewithpicturelink') {
|
||||
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
|
||||
$OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
|
||||
fullname($row, $viewfullnames));
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
|
||||
// Picture fields need some more data.
|
||||
if (strpos($fullnamefield, 'picture') !== false) {
|
||||
$column->add_fields($userpictureselect);
|
||||
}
|
||||
|
||||
$columns[] = $column;
|
||||
}
|
||||
|
||||
// Picture column.
|
||||
$columns[] = (new column(
|
||||
'picture',
|
||||
new lang_string('userpicture', 'core_reportbuilder'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_fields($userpictureselect)
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->set_is_sortable($this->is_sortable('picture'))
|
||||
->add_callback(static function (int $value, stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
return !empty($row->id) ? $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) : '';
|
||||
});
|
||||
|
||||
// Add all other user fields.
|
||||
$userfields = $this->get_user_fields();
|
||||
foreach ($userfields as $userfield => $userfieldlang) {
|
||||
$columntype = $this->get_user_field_type($userfield);
|
||||
|
||||
$column = (new column(
|
||||
$userfield,
|
||||
$userfieldlang,
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$usertablealias}.{$userfield}")
|
||||
->set_type($columntype)
|
||||
->set_is_sortable($this->is_sortable($userfield))
|
||||
->add_callback([$this, 'format'], $userfield);
|
||||
|
||||
// Some columns also have specific format callbacks.
|
||||
if ($userfield === 'country') {
|
||||
$column->add_callback(static function(string $country): string {
|
||||
$countries = get_string_manager()->get_list_of_countries(true);
|
||||
return $countries[$country] ?? '';
|
||||
});
|
||||
}
|
||||
|
||||
$columns[] = $column;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this field is sortable
|
||||
*
|
||||
* @param string $fieldname
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_sortable(string $fieldname): bool {
|
||||
// Some columns can't be sorted, like longtext or images.
|
||||
$nonsortable = [
|
||||
'picture',
|
||||
];
|
||||
|
||||
return !in_array($fieldname, $nonsortable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the user field for display.
|
||||
*
|
||||
* @param mixed $value Current field value.
|
||||
* @param stdClass $row Complete row.
|
||||
* @param string $fieldname Name of the field to format.
|
||||
* @return string
|
||||
*/
|
||||
public function format($value, stdClass $row, string $fieldname): string {
|
||||
if ($this->get_user_field_type($fieldname) === column::TYPE_BOOLEAN) {
|
||||
return format::boolean_as_text($value);
|
||||
}
|
||||
|
||||
if ($this->get_user_field_type($fieldname) === column::TYPE_TIMESTAMP) {
|
||||
return format::userdate($value, $row);
|
||||
}
|
||||
|
||||
return s($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL statement to select all user fields necessary for fullname() function
|
||||
*
|
||||
* @param string $usertablealias
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name_fields_select(string $usertablealias = 'u'): string {
|
||||
$userfields = array_map(static function(string $userfield) use ($usertablealias): string {
|
||||
if (!empty($usertablealias)) {
|
||||
$userfield = "{$usertablealias}.{$userfield}";
|
||||
}
|
||||
|
||||
return $userfield;
|
||||
}, fields::get_name_fields(true));
|
||||
|
||||
return implode(', ', $userfields);
|
||||
}
|
||||
|
||||
/**
|
||||
* User fields
|
||||
*
|
||||
* @return lang_string[]
|
||||
*/
|
||||
protected function get_user_fields(): array {
|
||||
return [
|
||||
'firstname' => new lang_string('firstname'),
|
||||
'lastname' => new lang_string('lastname'),
|
||||
'email' => new lang_string('email'),
|
||||
'city' => new lang_string('city'),
|
||||
'country' => new lang_string('country'),
|
||||
'firstnamephonetic' => new lang_string('firstnamephonetic'),
|
||||
'lastnamephonetic' => new lang_string('lastnamephonetic'),
|
||||
'middlename' => new lang_string('middlename'),
|
||||
'alternatename' => new lang_string('alternatename'),
|
||||
'idnumber' => new lang_string('idnumber'),
|
||||
'institution' => new lang_string('institution'),
|
||||
'department' => new lang_string('department'),
|
||||
'phone1' => new lang_string('phone1'),
|
||||
'phone2' => new lang_string('phone2'),
|
||||
'address' => new lang_string('address'),
|
||||
'lastaccess' => new lang_string('lastaccess'),
|
||||
'suspended' => new lang_string('suspended'),
|
||||
'confirmed' => new lang_string('confirmed', 'admin'),
|
||||
'username' => new lang_string('username'),
|
||||
'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return appropriate column type for given user field
|
||||
*
|
||||
* @param string $userfield
|
||||
* @return int
|
||||
*/
|
||||
protected function get_user_field_type(string $userfield): int {
|
||||
switch ($userfield) {
|
||||
case 'confirmed':
|
||||
case 'suspended':
|
||||
$fieldtype = column::TYPE_BOOLEAN;
|
||||
break;
|
||||
case 'lastaccess':
|
||||
$fieldtype = column::TYPE_TIMESTAMP;
|
||||
break;
|
||||
default:
|
||||
$fieldtype = column::TYPE_TEXT;
|
||||
break;
|
||||
}
|
||||
|
||||
return $fieldtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all available filters
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
protected function get_all_filters(): array {
|
||||
global $DB;
|
||||
|
||||
$filters = [];
|
||||
$tablealias = $this->get_table_alias('user');
|
||||
|
||||
// Fullname filter.
|
||||
$canviewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
|
||||
[$fullnamesql, $fullnameparams] = fields::get_sql_fullname($tablealias, $canviewfullnames);
|
||||
$filters[] = (new filter(
|
||||
text::class,
|
||||
'fullname',
|
||||
new lang_string('fullname'),
|
||||
$this->get_entity_name(),
|
||||
$fullnamesql,
|
||||
$fullnameparams
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// User fields filters.
|
||||
$fields = $this->get_user_fields();
|
||||
foreach ($fields as $field => $name) {
|
||||
$optionscallback = [static::class, 'get_options_for_' . $field];
|
||||
if (is_callable($optionscallback)) {
|
||||
$classname = select::class;
|
||||
} else if ($this->get_user_field_type($field) === column::TYPE_BOOLEAN) {
|
||||
$classname = boolean_select::class;
|
||||
} else if ($this->get_user_field_type($field) === column::TYPE_TIMESTAMP) {
|
||||
$classname = date::class;
|
||||
} else {
|
||||
$classname = text::class;
|
||||
}
|
||||
|
||||
$filter = (new filter(
|
||||
$classname,
|
||||
$field,
|
||||
$name,
|
||||
$this->get_entity_name(),
|
||||
$tablealias . '.' . $field
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Populate filter options by callback, if available.
|
||||
if (is_callable($optionscallback)) {
|
||||
$filter->set_options_callback($optionscallback);
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of options for the field country.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_options_for_country(): array {
|
||||
return array_map('shorten_text', get_string_manager()->get_list_of_countries());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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\filters;
|
||||
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Base class for all report filters
|
||||
*
|
||||
* Filters provide a form for collecting user input, and then return appropriate SQL fragments based on these values
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
|
||||
/** @var filter $filter */
|
||||
protected $filter;
|
||||
|
||||
/** @var string $name */
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Do not allow the constructor to be called directly or overridden
|
||||
*
|
||||
* @param filter $filter
|
||||
*/
|
||||
private function __construct(filter $filter) {
|
||||
$this->filter = $filter;
|
||||
$this->name = $filter->get_unique_identifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of a filter type, based on supplied report filter instance
|
||||
*
|
||||
* The report filter instance is used by reports/entities to define what should be filtered against, e.g. a SQL fragment
|
||||
*
|
||||
* @param filter $filter The report filter instance
|
||||
* @return static
|
||||
*/
|
||||
final public static function create(filter $filter): self {
|
||||
$filterclass = $filter->get_filter_class();
|
||||
|
||||
return new $filterclass($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter header
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function get_header(): string {
|
||||
return $this->filter->get_header();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds filter-specific form elements
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
abstract public function setup_form(MoodleQuickForm $mform): void;
|
||||
|
||||
/**
|
||||
* Returns the filter clauses to be used with SQL where
|
||||
*
|
||||
* @param array $values
|
||||
* @return array [$sql, [...$params]]
|
||||
*/
|
||||
abstract public function get_sql_filter(array $values): array;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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\filters;
|
||||
|
||||
use lang_string;
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Boolean report filter
|
||||
*
|
||||
* This filter accepts an expression that evaluates to 1 or 0, either a simple field such as "u.suspended", or a more complex
|
||||
* expression such as "CASE WHEN <EXPRESSION> THEN 1 ELSE 0 END"
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class boolean_select extends base {
|
||||
|
||||
/** @var int Any value */
|
||||
public const ANY_VALUE = 0;
|
||||
|
||||
/** @var int Checked */
|
||||
public const CHECKED = 1;
|
||||
|
||||
/** @var int Not checked */
|
||||
public const NOT_CHECKED = 2;
|
||||
|
||||
/**
|
||||
* Return an array of operators available for this filter
|
||||
*
|
||||
* @return lang_string[]
|
||||
*/
|
||||
private function get_operators(): array {
|
||||
$operators = [
|
||||
self::ANY_VALUE => new lang_string('filterisanyvalue', 'core_reportbuilder'),
|
||||
self::CHECKED => new lang_string('yes'),
|
||||
self::NOT_CHECKED => new lang_string('no'),
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup form
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
|
||||
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
|
||||
->setHiddenLabel(true);
|
||||
|
||||
$mform->setType("{$this->name}_operator", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_operator", self::ANY_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
$paramname = database::generate_param_name();
|
||||
|
||||
$operator = $values["{$this->name}_operator"] ?? self::ANY_VALUE;
|
||||
switch ($operator) {
|
||||
case self::CHECKED:
|
||||
$fieldsql .= " = :{$paramname}";
|
||||
$params[$paramname] = 1;
|
||||
break;
|
||||
case self::NOT_CHECKED:
|
||||
$fieldsql .= " = :{$paramname}";
|
||||
$params[$paramname] = 0;
|
||||
break;
|
||||
default:
|
||||
// Invalid or inactive filter.
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
return [$fieldsql, $params];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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\filters;
|
||||
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Course selector filter class implementation
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_selector extends base {
|
||||
|
||||
/**
|
||||
* Setup form
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$options = [
|
||||
'multiple' => true,
|
||||
];
|
||||
$mform->addElement('course', $this->name . '_values', get_string('selectcourses', 'core_reportbuilder'), $options)
|
||||
->setHiddenLabel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
global $DB;
|
||||
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
$courseids = $values["{$this->name}_values"] ?? [];
|
||||
if (empty($courseids)) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
$paramprefix = database::generate_param_name() . '_';
|
||||
[$courseselect, $courseparams] = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, $paramprefix);
|
||||
|
||||
return ["{$fieldsql} $courseselect", array_merge($params, $courseparams)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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\filters;
|
||||
|
||||
use lang_string;
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Date report filter
|
||||
*
|
||||
* This filter accepts a unix timestamp to perform date filtering on
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class date extends base {
|
||||
|
||||
/** @var int Any value */
|
||||
public const DATE_ANY = 0;
|
||||
|
||||
/** @var int Non-empty (positive) value */
|
||||
public const DATE_NOT_EMPTY = 1;
|
||||
|
||||
/** @var int Empty (zero) value */
|
||||
public const DATE_EMPTY = 2;
|
||||
|
||||
/** @var int Date within defined range */
|
||||
public const DATE_RANGE = 3;
|
||||
|
||||
/**
|
||||
* Return an array of operators available for this filter
|
||||
*
|
||||
* @return lang_string[]
|
||||
*/
|
||||
private function get_operators(): array {
|
||||
$operators = [
|
||||
self::DATE_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'),
|
||||
self::DATE_NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'),
|
||||
self::DATE_EMPTY => new lang_string('filterisempty', 'core_reportbuilder'),
|
||||
self::DATE_RANGE => new lang_string('filterrange', 'core_reportbuilder'),
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup form
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
|
||||
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())->setHiddenLabel(true);
|
||||
$mform->setType("{$this->name}_operator", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_operator", self::DATE_ANY);
|
||||
|
||||
$mform->addElement('date_selector', "{$this->name}_from", get_string('filterdatefrom', 'core_reportbuilder'),
|
||||
['optional' => true]);
|
||||
$mform->setType("{$this->name}_from", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_from", 0);
|
||||
$mform->hideIf("{$this->name}_from", "{$this->name}_operator", 'neq', self::DATE_RANGE);
|
||||
|
||||
$mform->addElement('date_selector', "{$this->name}_to", get_string('filterdateto', 'core_reportbuilder'),
|
||||
['optional' => true]);
|
||||
$mform->setType("{$this->name}_to", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_to", 0);
|
||||
$mform->hideIf("{$this->name}_to", "{$this->name}_operator", 'neq', self::DATE_RANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
$operator = $values["{$this->name}_operator"] ?? self::DATE_ANY;
|
||||
switch ($operator) {
|
||||
case self::DATE_NOT_EMPTY:
|
||||
$sql = "{$fieldsql} IS NOT NULL AND {$fieldsql} <> 0";
|
||||
break;
|
||||
case self::DATE_EMPTY:
|
||||
$sql = "{$fieldsql} IS NULL OR {$fieldsql} = 0";
|
||||
break;
|
||||
case self::DATE_RANGE:
|
||||
$clauses = [];
|
||||
|
||||
$datefrom = (int)($values["{$this->name}_from"] ?? 0);
|
||||
if ($datefrom > 0) {
|
||||
$paramdatefrom = database::generate_param_name();
|
||||
$clauses[] = "{$fieldsql} >= :{$paramdatefrom}";
|
||||
$params[$paramdatefrom] = $datefrom;
|
||||
}
|
||||
|
||||
$dateto = (int)($values["{$this->name}_to"] ?? 0);
|
||||
if ($dateto > 0) {
|
||||
$paramdateto = database::generate_param_name();
|
||||
$clauses[] = "{$fieldsql} < :{$paramdateto}";
|
||||
$params[$paramdateto] = $dateto;
|
||||
}
|
||||
|
||||
$sql = implode(' AND ', $clauses);
|
||||
|
||||
break;
|
||||
default:
|
||||
// Invalid or inactive filter.
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
return [$sql, $params];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?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\filters;
|
||||
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Number report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class number extends base {
|
||||
|
||||
/** @var int Any value */
|
||||
public const ANY_VALUE = 0;
|
||||
|
||||
/** @var int Is not empty */
|
||||
public const IS_NOT_EMPTY = 1;
|
||||
|
||||
/** @var int Is empty */
|
||||
public const IS_EMPTY = 2;
|
||||
|
||||
/** @var int Less than */
|
||||
public const LESS_THAN = 3;
|
||||
|
||||
/** @var int Greater than */
|
||||
public const GREATER_THAN = 4;
|
||||
|
||||
/** @var int Equal to */
|
||||
public const EQUAL_TO = 5;
|
||||
|
||||
/** @var int Equal or less than */
|
||||
public const EQUAL_OR_LESS_THAN = 6;
|
||||
|
||||
/** @var int Equal or greater than */
|
||||
public const EQUAL_OR_GREATER_THAN = 7;
|
||||
|
||||
/** @var int Range */
|
||||
public const RANGE = 8;
|
||||
|
||||
/**
|
||||
* Returns an array of comparison operators
|
||||
*
|
||||
* @return array of comparison operators
|
||||
*/
|
||||
private function get_operators(): array {
|
||||
$operators = [
|
||||
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
|
||||
self::IS_NOT_EMPTY => get_string('filterisnotempty', 'core_reportbuilder'),
|
||||
self::IS_EMPTY => get_string('filterisempty', 'core_reportbuilder'),
|
||||
self::LESS_THAN => get_string('filterlessthan', 'core_reportbuilder'),
|
||||
self::GREATER_THAN => get_string('filtergreaterthan', 'core_reportbuilder'),
|
||||
self::EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
|
||||
self::EQUAL_OR_LESS_THAN => get_string('filterequalorlessthan', 'core_reportbuilder'),
|
||||
self::EQUAL_OR_GREATER_THAN => get_string('filterequalorgreaterthan', 'core_reportbuilder'),
|
||||
self::RANGE => get_string('filterrange', 'core_reportbuilder'),
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds controls specific to this filter in the form.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(\MoodleQuickForm $mform): void {
|
||||
$objs = [];
|
||||
|
||||
$objs['select'] = $mform->createElement('select', $this->name . '_operator', null, $this->get_operators());
|
||||
$mform->setType($this->name . '_operator', PARAM_INT);
|
||||
|
||||
$objs['text'] = $mform->createElement('text', $this->name . '_value1', null, ['size' => 3]);
|
||||
$mform->setType($this->name . '_value1', PARAM_INT);
|
||||
$mform->setDefault($this->name . '_value1', 0);
|
||||
|
||||
$objs['text2'] = $mform->createElement('text', $this->name . '_value2', null, ['size' => 3]);
|
||||
$mform->setType($this->name . '_value2', PARAM_INT);
|
||||
$mform->setDefault($this->name . '_value2', 0);
|
||||
|
||||
$mform->addElement('group', $this->name . '_grp', '', $objs, '', false);
|
||||
|
||||
$mform->hideIf($this->name . '_value1', $this->name . '_operator', 'eq', self::ANY_VALUE);
|
||||
$mform->hideIf($this->name . '_value1', $this->name . '_operator', 'eq', self::IS_NOT_EMPTY);
|
||||
$mform->hideIf($this->name . '_value1', $this->name . '_operator', 'eq', self::IS_EMPTY);
|
||||
|
||||
$mform->hideIf($this->name . '_value2', $this->name . '_operator', 'noteq', self::RANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array array of two elements - SQL query and named parameters
|
||||
*/
|
||||
public function get_sql_filter(array $values) : array {
|
||||
$operator = $values["{$this->name}_operator"] ?? self::ANY_VALUE;
|
||||
$value1 = $values["{$this->name}_value1"] ?? null;
|
||||
$value2 = $values["{$this->name}_value2"] ?? null;
|
||||
|
||||
// Validate filter form values.
|
||||
if (!$this->validate_filter_values($operator, $value1, $value2)) {
|
||||
// Filter configuration is invalid. Ignore the filter.
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
$param = database::generate_param_name();
|
||||
$param2 = database::generate_param_name();
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
switch ($operator) {
|
||||
case self::ANY_VALUE:
|
||||
return ['', []];
|
||||
case self::IS_NOT_EMPTY:
|
||||
$res = "{$fieldsql} IS NOT NULL AND {$fieldsql} <> 0";
|
||||
break;
|
||||
case self::IS_EMPTY:
|
||||
$res = "{$fieldsql} IS NULL OR {$fieldsql} = 0";
|
||||
break;
|
||||
case self::LESS_THAN:
|
||||
$res = "{$fieldsql} < :{$param}";
|
||||
$params[$param] = $value1;
|
||||
break;
|
||||
case self::GREATER_THAN:
|
||||
$res = "{$fieldsql} > :{$param}";
|
||||
$params[$param] = $value1;
|
||||
break;
|
||||
case self::EQUAL_TO:
|
||||
$res = "{$fieldsql} = :{$param}";
|
||||
$params[$param] = $value1;
|
||||
break;
|
||||
case self::EQUAL_OR_LESS_THAN:
|
||||
$res = "{$fieldsql} <= :{$param}";
|
||||
$params[$param] = $value1;
|
||||
break;
|
||||
case self::EQUAL_OR_GREATER_THAN:
|
||||
$res = "{$fieldsql} >= :{$param}";
|
||||
$params[$param] = $value1;
|
||||
break;
|
||||
case self::RANGE:
|
||||
$res = "({$fieldsql} >= :{$param} AND {$fieldsql} <= :{$param2})";
|
||||
$params[$param] = $value1;
|
||||
$params[$param2] = $value2;
|
||||
break;
|
||||
default:
|
||||
// Filter configuration is invalid. Ignore the filter.
|
||||
return ['', []];
|
||||
}
|
||||
return [$res, $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate filter form values
|
||||
*
|
||||
* @param int $operator
|
||||
* @param int|null $value1
|
||||
* @param int|null $value2
|
||||
* @return bool
|
||||
*/
|
||||
private function validate_filter_values(int $operator, ?int $value1, ?int $value2): bool {
|
||||
// Check that for any of these operators value1 can not be null.
|
||||
$requirescomparisonvalue = [
|
||||
self::LESS_THAN,
|
||||
self::GREATER_THAN,
|
||||
self::EQUAL_TO,
|
||||
self::EQUAL_OR_LESS_THAN,
|
||||
self::EQUAL_OR_GREATER_THAN
|
||||
];
|
||||
if (in_array($operator, $requirescomparisonvalue) && $value1 === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When operator is between $value1 and $value2, can not be null.
|
||||
if (($operator === self::RANGE) && ($value1 === null || $value2 === null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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\filters;
|
||||
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Select report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class select extends base {
|
||||
|
||||
/** @var int Any value */
|
||||
public const ANY_VALUE = 0;
|
||||
|
||||
/** @var int Equal to */
|
||||
public const EQUAL_TO = 1;
|
||||
|
||||
/** @var int Not equal to */
|
||||
public const NOT_EQUAL_TO = 2;
|
||||
|
||||
/**
|
||||
* Returns an array of comparison operators
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_operators(): array {
|
||||
$operators = [
|
||||
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
|
||||
self::EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
|
||||
self::NOT_EQUAL_TO => get_string('filterisnotequalto', 'core_reportbuilder')
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the options for the filter as an array, to be used to populate the select input field. These options should be
|
||||
* specified when creating the filter via the {@see set_options} or {@see set_options_callback} method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_select_options(): array {
|
||||
return (array) $this->filter->get_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds controls specific to this filter in the form.
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$elements = [];
|
||||
$elements['operator'] = $mform->createElement('select', $this->name . '_operator', null, $this->get_operators());
|
||||
|
||||
// If a multi-dimensional array is passed, we need to use a different element type.
|
||||
$options = $this->get_select_options();
|
||||
$element = (count($options) == count($options, COUNT_RECURSIVE) ? 'select' : 'selectgroups');
|
||||
$elements['value'] = $mform->createElement($element, $this->name . '_value', null, $options);
|
||||
|
||||
$mform->addElement('group', $this->name . '_group', '', $elements, '', false);
|
||||
|
||||
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::ANY_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* Note that operators must be of type integer, while values can be integer or string.
|
||||
*
|
||||
* @param array $values
|
||||
* @return array array of two elements - SQL query and named parameters
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
$name = database::generate_param_name();
|
||||
|
||||
$operator = $values["{$this->name}_operator"] ?? self::ANY_VALUE;
|
||||
$value = $values["{$this->name}_value"] ?? 0;
|
||||
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
// Validate filter form values.
|
||||
if (!$this->validate_filter_values((int) $operator, $value)) {
|
||||
// Filter configuration is invalid. Ignore the filter.
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
switch ($operator) {
|
||||
case self::EQUAL_TO:
|
||||
$fieldsql .= "=:$name";
|
||||
$params[$name] = $value;
|
||||
break;
|
||||
case self::NOT_EQUAL_TO:
|
||||
$fieldsql .= "<>:$name";
|
||||
$params[$name] = $value;
|
||||
break;
|
||||
default:
|
||||
return ['', []];
|
||||
}
|
||||
return [$fieldsql, $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate filter form values
|
||||
*
|
||||
* @param int|null $operator
|
||||
* @param mixed|null $value
|
||||
* @return bool
|
||||
*/
|
||||
private function validate_filter_values(?int $operator, $value): bool {
|
||||
return !($operator === null || $value === '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?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\filters;
|
||||
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Text report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class text extends base {
|
||||
|
||||
/** @var int */
|
||||
public const ANY_VALUE = 0;
|
||||
|
||||
/** @var int */
|
||||
public const CONTAINS = 1;
|
||||
|
||||
/** @var int */
|
||||
public const DOES_NOT_CONTAIN = 2;
|
||||
|
||||
/** @var int */
|
||||
public const IS_EQUAL_TO = 3;
|
||||
|
||||
/** @var int */
|
||||
public const IS_NOT_EQUAL_TO = 4;
|
||||
|
||||
/** @var int */
|
||||
public const STARTS_WITH = 5;
|
||||
|
||||
/** @var int */
|
||||
public const ENDS_WITH = 6;
|
||||
|
||||
/** @var int */
|
||||
public const IS_EMPTY = 7;
|
||||
|
||||
/** @var int */
|
||||
public const IS_NOT_EMPTY = 8;
|
||||
|
||||
/**
|
||||
* Return an array of operators available for this filter
|
||||
*
|
||||
* @return array of comparison operators
|
||||
*/
|
||||
private function get_operators() : array {
|
||||
$operators = [
|
||||
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
|
||||
self::CONTAINS => get_string('filtercontains', 'core_reportbuilder'),
|
||||
self::DOES_NOT_CONTAIN => get_string('filterdoesnotcontain', 'core_reportbuilder'),
|
||||
self::IS_EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
|
||||
self::IS_NOT_EQUAL_TO => get_string('filterisnotequalto', 'core_reportbuilder'),
|
||||
self::STARTS_WITH => get_string('filterstartswith', 'core_reportbuilder'),
|
||||
self::ENDS_WITH => get_string('filterendswith', 'core_reportbuilder'),
|
||||
self::IS_EMPTY => get_string('filterisempty', 'core_reportbuilder'),
|
||||
self::IS_NOT_EMPTY => get_string('filterisnotempty', 'core_reportbuilder')
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds controls specific to this filter in the form.
|
||||
*
|
||||
* Operator selector use the "$this->name . '_operator'" naming convention and the fields to enter custom values should
|
||||
* use "$this->name . '_value'" or _value1/_value2/... in case there is more than one field for their naming.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(\MoodleQuickForm $mform): void {
|
||||
$elements = [];
|
||||
$elements['operator'] = $mform->createElement('select', $this->name . '_operator',
|
||||
get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()), $this->get_operators());
|
||||
$elements['value'] = $mform->createElement('text', $this->name . '_value',
|
||||
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()));
|
||||
|
||||
$mform->addElement('group', $this->name . '_group', '', $elements, '', false);
|
||||
|
||||
$mform->setType($this->name . '_value', PARAM_RAW);
|
||||
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::ANY_VALUE);
|
||||
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::IS_EMPTY);
|
||||
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::IS_NOT_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array|null $values
|
||||
* @return array array of two elements - SQL query and named parameters
|
||||
*/
|
||||
public function get_sql_filter(?array $values) : array {
|
||||
global $DB;
|
||||
$name = database::generate_param_name();
|
||||
|
||||
if (!$values) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
$operator = (int) ($values["{$this->name}_operator"] ?? self::ANY_VALUE);
|
||||
$value = $values["{$this->name}_value"] ?? '';
|
||||
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
// Validate filter form values.
|
||||
if (!$this->validate_filter_values($operator, $value)) {
|
||||
// Filter configuration is invalid. Ignore the filter.
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
switch($operator) {
|
||||
case self::CONTAINS:
|
||||
$res = $DB->sql_like($fieldsql, ":$name", false, false);
|
||||
$value = $DB->sql_like_escape($value);
|
||||
$params[$name] = "%$value%";
|
||||
break;
|
||||
case self::DOES_NOT_CONTAIN:
|
||||
$res = $DB->sql_like($fieldsql, ":$name", false, false, true);
|
||||
$value = $DB->sql_like_escape($value);
|
||||
$params[$name] = "%$value%";
|
||||
break;
|
||||
case self::IS_EQUAL_TO:
|
||||
$res = $DB->sql_equal($fieldsql, ":$name", false, false);
|
||||
$params[$name] = $value;
|
||||
break;
|
||||
case self::IS_NOT_EQUAL_TO:
|
||||
$res = $DB->sql_equal($fieldsql, ":$name", false, false, true);
|
||||
$params[$name] = $value;
|
||||
break;
|
||||
case self::STARTS_WITH:
|
||||
$res = $DB->sql_like($fieldsql, ":$name", false, false);
|
||||
$value = $DB->sql_like_escape($value);
|
||||
$params[$name] = "$value%";
|
||||
break;
|
||||
case self::ENDS_WITH:
|
||||
$res = $DB->sql_like($fieldsql, ":$name", false, false);
|
||||
$value = $DB->sql_like_escape($value);
|
||||
$params[$name] = "%$value";
|
||||
break;
|
||||
case self::IS_EMPTY: // Note we also account for field not existing here.
|
||||
$res = "COALESCE({$fieldsql}, '') = :{$name}";
|
||||
$params[$name] = '';
|
||||
break;
|
||||
case self::IS_NOT_EMPTY:
|
||||
$res = "COALESCE({$fieldsql}, '') != :{$name}";
|
||||
$params[$name] = '';
|
||||
break;
|
||||
default:
|
||||
// Filter configuration is invalid. Ignore the filter.
|
||||
return ['', []];
|
||||
}
|
||||
return array($res, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate filter form values
|
||||
*
|
||||
* @param int $operator
|
||||
* @param string|null $value
|
||||
* @return bool
|
||||
*/
|
||||
private function validate_filter_values(int $operator, ?string $value): bool {
|
||||
$operatorsthatdontrequirevalue = [
|
||||
self::ANY_VALUE,
|
||||
self::IS_EMPTY,
|
||||
self::IS_NOT_EMPTY,
|
||||
];
|
||||
|
||||
if ($value === '' && !in_array($operator, $operatorsthatdontrequirevalue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
<?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 core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\number;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use lang_string;
|
||||
use stdClass;
|
||||
use core_customfield\data;
|
||||
use core_customfield\data_controller;
|
||||
use core_customfield\field_controller;
|
||||
use core_customfield\handler;
|
||||
|
||||
/**
|
||||
* Helper class for course custom fields.
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Sara Arjona <[email protected]> based on David Matamoros <[email protected]> code.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_fields {
|
||||
|
||||
/** @var string $entityname Name of the entity */
|
||||
private $entityname;
|
||||
|
||||
/** @var handler $handler The handler for the customfields */
|
||||
private $handler;
|
||||
|
||||
/** @var int $tablefieldalias The table alias and the field name (table.field) that matches the customfield instanceid. */
|
||||
private $tablefieldalias;
|
||||
|
||||
/** @var array additional joins */
|
||||
private $joins = [];
|
||||
|
||||
/**
|
||||
* Class customfields constructor.
|
||||
*
|
||||
* @param string $tablefieldalias table alias and the field name (table.field) that matches the customfield instanceid.
|
||||
* @param string $entityname name of the entity in the report where we add custom fields.
|
||||
* @param string $component component name of full frankenstyle plugin name.
|
||||
* @param string $area name of the area (each component/plugin may define handlers for multiple areas).
|
||||
* @param int $itemid item id if the area uses them (usually not used).
|
||||
*/
|
||||
public function __construct(string $tablefieldalias, string $entityname, string $component, string $area, int $itemid = 0) {
|
||||
$this->tablefieldalias = $tablefieldalias;
|
||||
$this->entityname = $entityname;
|
||||
$this->handler = handler::get_handler($component, $area, $itemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional join that is needed.
|
||||
*
|
||||
* @param string $join
|
||||
* @return self
|
||||
*/
|
||||
public function add_join(string $join): self {
|
||||
$this->joins[trim($join)] = trim($join);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional joins that are needed.
|
||||
*
|
||||
* @param array $joins
|
||||
* @return self
|
||||
*/
|
||||
public function add_joins(array $joins): self {
|
||||
foreach ($joins as $join) {
|
||||
$this->add_join($join);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return joins
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function get_joins(): array {
|
||||
return array_values($this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom fields columns for the report.
|
||||
*
|
||||
* Column will be named as 'customfield_' + customfield shortname.
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
public function get_columns(): array {
|
||||
$columns = [];
|
||||
|
||||
$categorieswithfields = $this->handler->get_categories_with_fields();
|
||||
foreach ($categorieswithfields as $fieldcategory) {
|
||||
$categoryfields = $fieldcategory->get_fields();
|
||||
foreach ($categoryfields as $field) {
|
||||
$customdatatablealias = database::generate_alias();
|
||||
|
||||
$datacontroller = data_controller::create(0, null, $field);
|
||||
$datafield = $datacontroller->datafield();
|
||||
|
||||
$alias = 'customfield_' . $field->get('shortname');
|
||||
$selectfields = "{$customdatatablealias}.{$datafield} as {$alias}, {$customdatatablealias}.id";
|
||||
if ($datafield === 'value') {
|
||||
// We will take the format into account when displaying the individual values.
|
||||
$selectfields .= ", {$customdatatablealias}.valueformat";
|
||||
}
|
||||
|
||||
$columnname = $field->get_formatted_name();
|
||||
$columntype = $this->get_column_type($field, $datafield);
|
||||
|
||||
$newcolumn = (new column(
|
||||
'customfield_' . $field->get('shortname'),
|
||||
new lang_string('customfieldcolumn', 'core_reportbuilder', $columnname),
|
||||
$this->entityname
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
|
||||
"ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
|
||||
"AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}")
|
||||
->add_fields($selectfields)
|
||||
->set_type($columntype)
|
||||
->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
|
||||
->add_callback([$this, 'customfield_value'], $field)
|
||||
// Important. If the handler implements can_view() function, it will be called with parameter $instanceid=0.
|
||||
// This means that per-instance access validation will be ignored.
|
||||
->set_is_available($this->handler->can_view($field, 0));
|
||||
|
||||
$columns[] = $newcolumn;
|
||||
}
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column type
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @param string $datafield
|
||||
* @return int
|
||||
*/
|
||||
private function get_column_type(field_controller $field, string $datafield): int {
|
||||
if ($field->get('type') === 'checkbox') {
|
||||
return column::TYPE_BOOLEAN;
|
||||
}
|
||||
|
||||
if ($field->get('type') === 'date') {
|
||||
return column::TYPE_TIMESTAMP;
|
||||
}
|
||||
|
||||
if ($datafield === 'intvalue') {
|
||||
return column::TYPE_INTEGER;
|
||||
}
|
||||
|
||||
if ($datafield === 'decvalue') {
|
||||
return column::TYPE_FLOAT;
|
||||
}
|
||||
|
||||
if ($datafield === 'value') {
|
||||
return column::TYPE_LONGTEXT;
|
||||
}
|
||||
|
||||
return column::TYPE_TEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available filters on custom fields.
|
||||
*
|
||||
* Filter will be named as 'customfield_' + customfield shortname.
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
public function get_filters(): array {
|
||||
$filters = [];
|
||||
|
||||
$categorieswithfields = $this->handler->get_categories_with_fields();
|
||||
foreach ($categorieswithfields as $fieldcategory) {
|
||||
$categoryfields = $fieldcategory->get_fields();
|
||||
foreach ($categoryfields as $field) {
|
||||
$customdatatablealias = database::generate_alias();
|
||||
|
||||
$datacontroller = data_controller::create(0, null, $field);
|
||||
$datafield = $datacontroller->datafield();
|
||||
$typeclass = $this->get_filter_class_type($datacontroller);
|
||||
|
||||
$filter = (new filter(
|
||||
$typeclass,
|
||||
'customfield_' . $field->get('shortname'),
|
||||
new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get('name')),
|
||||
$this->entityname,
|
||||
"{$customdatatablealias}.{$datafield}"
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
|
||||
"ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
|
||||
"AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}");
|
||||
|
||||
// Options are stored inside configdata json string and we need to convert it to array.
|
||||
if ($field->get('type') === 'select') {
|
||||
$filter->set_options_callback(static function() use ($field): array {
|
||||
$options = explode("\r\n", $field->get_configdata_property('options'));
|
||||
// Method set_options starts using array at index 1. we shift one position on this array.
|
||||
// In course settings this menu has an empty option and we need to respect that.
|
||||
array_unshift($options, " ");
|
||||
unset($options[0]);
|
||||
return $options;
|
||||
});
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
}
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class for the filter element that should be used for the field
|
||||
*
|
||||
* In some situation we can assume what kind of data is stored in the customfield plugin and we can
|
||||
* display appropriate filter form element. For all others assume text filter.
|
||||
*
|
||||
* @param data_controller $datacontroller
|
||||
* @return string
|
||||
*/
|
||||
private function get_filter_class_type(data_controller $datacontroller): string {
|
||||
$type = $datacontroller->get_field()->get('type');
|
||||
|
||||
switch ($type) {
|
||||
case 'checkbox':
|
||||
$classtype = boolean_select::class;
|
||||
break;
|
||||
case 'date':
|
||||
$classtype = date::class;
|
||||
break;
|
||||
case 'select':
|
||||
$classtype = select::class;
|
||||
break;
|
||||
default:
|
||||
// To support third party field type we need to account for stored numbers.
|
||||
$datafield = $datacontroller->datafield();
|
||||
if ($datafield === 'intvalue' || $datafield === 'decvalue') {
|
||||
$classtype = number::class;
|
||||
} else {
|
||||
$classtype = text::class;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $classtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format for custom fields value. We get the correct custom field value using export_value method.
|
||||
*
|
||||
* @param mixed $value Current value.
|
||||
* @param stdClass $row Full row.
|
||||
* @param field_controller $field Field controller object.
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function customfield_value($value, stdClass $row, field_controller $field) {
|
||||
$defaults = [
|
||||
'id' => -1,
|
||||
'shortcharvalue' => $value,
|
||||
'charvalue' => $value,
|
||||
'intvalue' => (int)$value,
|
||||
'decvalue' => (float)$value,
|
||||
'value' => $value,
|
||||
'fieldid' => $field->get('id'),
|
||||
'valueformat' => FORMAT_HTML,
|
||||
];
|
||||
$row = array_intersect_key(array_merge($defaults, (array)$row), data::properties_definition());
|
||||
$data = data_controller::create(0, (object)$row, $field);
|
||||
return $data->export_value();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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 coding_exception;
|
||||
|
||||
/**
|
||||
* Helper functions for DB manipulations
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2019 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class database {
|
||||
|
||||
/** @var string Prefix for generated aliases */
|
||||
private const GENERATE_ALIAS_PREFIX = 'rbalias';
|
||||
|
||||
/** @var string Prefix for generated param names names */
|
||||
private const GENERATE_PARAM_PREFIX = 'rbparam';
|
||||
|
||||
/**
|
||||
* Generates unique table/column alias that must be used in generated SQL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_alias(): string {
|
||||
static $aliascount = 0;
|
||||
|
||||
return static::GENERATE_ALIAS_PREFIX . ($aliascount++);
|
||||
}
|
||||
/**
|
||||
* Generates unique parameter name that must be used in generated SQL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_param_name(): string {
|
||||
static $paramcount = 0;
|
||||
|
||||
return static::GENERATE_PARAM_PREFIX . ($paramcount++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that parameter names were generated using {@see generate_param_name}.
|
||||
*
|
||||
* @param array $params
|
||||
* @return bool
|
||||
* @throws coding_exception For invalid params.
|
||||
*/
|
||||
public static function validate_params(array $params): bool {
|
||||
$nonmatchingkeys = array_filter($params, static function($key): bool {
|
||||
return !preg_match('/^' . static::GENERATE_PARAM_PREFIX . '[\d]+/', $key);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
if (!empty($nonmatchingkeys)) {
|
||||
throw new coding_exception('Invalid parameter names', implode(', ', array_keys($nonmatchingkeys)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_reportbuilder\local\helpers;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class containing helper methods for format columns data as callbacks.
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Sara Arjona <[email protected]> based on Alberto Lara Hernández <[email protected]> code.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class format {
|
||||
|
||||
/**
|
||||
* Returns formatted date.
|
||||
*
|
||||
* @param int $value Unix timestamp
|
||||
* @param stdClass $row
|
||||
* @param string|null $format Format string for strftime
|
||||
* @return string
|
||||
*/
|
||||
public static function userdate(int $value, stdClass $row, ?string $format = null): string {
|
||||
return $value ? userdate($value, $format) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns yes/no string depending on the given value
|
||||
*
|
||||
* @param bool $value
|
||||
* @return string
|
||||
*/
|
||||
public static function boolean_as_text(bool $value): string {
|
||||
return $value ? get_string('yes') : get_string('no');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?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 core_text;
|
||||
|
||||
/**
|
||||
* This class handles the setting and retrieving of a users' filter values for given reports
|
||||
*
|
||||
* It is currently using the user preference API as a storage mechanism
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_filter_manager {
|
||||
|
||||
/** @var int The size of each chunk, matching the maximum length of a single user preference */
|
||||
private const PREFERENCE_CHUNK_SIZE = 1333;
|
||||
|
||||
/** @var string The prefix used to name the stored user preferences */
|
||||
private const PREFERENCE_NAME_PREFIX = 'reportbuilder-report-';
|
||||
|
||||
/**
|
||||
* Generate user preference name for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param int $index
|
||||
* @return string
|
||||
*/
|
||||
private static function user_preference_name(int $reportid, int $index): string {
|
||||
return static::PREFERENCE_NAME_PREFIX . "{$reportid}-{$index}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user filters for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param array $values
|
||||
* @param int|null $userid
|
||||
* @return bool
|
||||
*/
|
||||
public static function set(int $reportid, array $values, int $userid = null): bool {
|
||||
$jsonvalues = json_encode($values);
|
||||
|
||||
$jsonchunks = str_split($jsonvalues, static::PREFERENCE_CHUNK_SIZE);
|
||||
foreach ($jsonchunks as $index => $jsonchunk) {
|
||||
$userpreference = static::user_preference_name($reportid, $index);
|
||||
set_user_preference($userpreference, $jsonchunk, $userid);
|
||||
}
|
||||
|
||||
// Ensure any subsequent preferences are reset (to account for number of chunks decreasing).
|
||||
static::reset_all($reportid, $userid, $index + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user filters for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param int|null $userid
|
||||
* @return array
|
||||
*/
|
||||
public static function get(int $reportid, int $userid = null): array {
|
||||
$jsonvalues = '';
|
||||
$index = 0;
|
||||
|
||||
// We'll repeatedly append chunks to our JSON string, until we hit one that is below the maximum length.
|
||||
do {
|
||||
$userpreference = static::user_preference_name($reportid, $index++);
|
||||
$jsonchunk = get_user_preferences($userpreference, '', $userid);
|
||||
$jsonvalues .= $jsonchunk;
|
||||
} while (core_text::strlen($jsonchunk) === static::PREFERENCE_CHUNK_SIZE);
|
||||
|
||||
return (array) json_decode($jsonvalues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge individual user filter values for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param array $values
|
||||
* @param int|null $userid
|
||||
* @return bool
|
||||
*/
|
||||
public static function merge(int $reportid, array $values, int $userid = null): bool {
|
||||
$existing = static::get($reportid, $userid);
|
||||
|
||||
return static::set($reportid, array_merge($existing, $values), $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all user filters for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param int|null $userid
|
||||
* @param int $index If specified, then preferences will be reset starting from this index
|
||||
* @return bool
|
||||
*/
|
||||
public static function reset_all(int $reportid, int $userid = null, int $index = 0): bool {
|
||||
// We'll repeatedly retrieve and reset preferences, until we hit one that is below the maximum length.
|
||||
do {
|
||||
$userpreference = static::user_preference_name($reportid, $index++);
|
||||
$jsonchunk = get_user_preferences($userpreference, '', $userid);
|
||||
unset_user_preference($userpreference, $userid);
|
||||
} while (core_text::strlen($jsonchunk) === static::PREFERENCE_CHUNK_SIZE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset single user filter for given report
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param string $uniqueidentifier
|
||||
* @param int|null $userid
|
||||
* @return bool
|
||||
*/
|
||||
public static function reset_single(int $reportid, string $uniqueidentifier, int $userid = null): bool {
|
||||
$originalvalues = static::get($reportid, $userid);
|
||||
|
||||
// Remove any filters whose name is prefixed by given identifier.
|
||||
$values = array_filter($originalvalues, static function(string $filterkey) use ($uniqueidentifier): bool {
|
||||
return core_text::strpos($filterkey, $uniqueidentifier) !== 0;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
return static::set($reportid, $values, $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all report filters for given user
|
||||
*
|
||||
* This is primarily designed for the privacy provider, and allows us to preserve all the preference logic within this class.
|
||||
*
|
||||
* @param int $userid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_for_user(int $userid): array {
|
||||
global $DB;
|
||||
$prefs = [];
|
||||
|
||||
// We need to locate the first preference chunk of all report filters.
|
||||
$select = 'userid = :userid AND ' . $DB->sql_like('name', ':namelike');
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'namelike' => $DB->sql_like_escape(static::PREFERENCE_NAME_PREFIX) . '%-0',
|
||||
];
|
||||
$preferences = $DB->get_fieldset_select('user_preferences', 'name', $select, $params);
|
||||
|
||||
// Retrieve all found filters.
|
||||
foreach ($preferences as $preference) {
|
||||
preg_match('/^' . static::PREFERENCE_NAME_PREFIX . '(?<reportid>\d+)\-/', $preference, $matches);
|
||||
$prefs[static::PREFERENCE_NAME_PREFIX . $matches['reportid']] = static::get((int) $matches['reportid'], $userid);
|
||||
}
|
||||
|
||||
return $prefs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?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 context_system;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use lang_string;
|
||||
use profile_field_base;
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||||
|
||||
/**
|
||||
* Helper class for user profile fields.
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_profile_fields {
|
||||
|
||||
/** @var array user profile fields */
|
||||
private $userprofilefields;
|
||||
|
||||
/** @var string $entityname Name of the entity */
|
||||
private $entityname;
|
||||
|
||||
/** @var int $usertablefieldalias The user table/field alias */
|
||||
private $usertablefieldalias;
|
||||
|
||||
/** @var array additional joins */
|
||||
private $joins = [];
|
||||
|
||||
/**
|
||||
* Class userprofilefields constructor.
|
||||
*
|
||||
* @param string $usertablefieldalias The user table/field alias used when adding columns and filters.
|
||||
* @param string $entityname The entity name used when adding columns and filters.
|
||||
*/
|
||||
public function __construct(string $usertablefieldalias, string $entityname) {
|
||||
$this->usertablefieldalias = $usertablefieldalias;
|
||||
$this->entityname = $entityname;
|
||||
$this->userprofilefields = $this->get_user_profile_fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of available/visible user profile fields
|
||||
*
|
||||
* @return profile_field_base[]
|
||||
*/
|
||||
private function get_user_profile_fields(): array {
|
||||
return array_filter(profile_get_user_fields_with_data(0), static function($profilefield): bool {
|
||||
return (int)$profilefield->field->visible === (int)PROFILE_VISIBLE_ALL;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional join that is needed.
|
||||
*
|
||||
* @param string $join
|
||||
* @return self
|
||||
*/
|
||||
public function add_join(string $join): self {
|
||||
$this->joins[trim($join)] = trim($join);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional joins that are needed.
|
||||
*
|
||||
* @param array $joins
|
||||
* @return self
|
||||
*/
|
||||
public function add_joins(array $joins): self {
|
||||
foreach ($joins as $join) {
|
||||
$this->add_join($join);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return joins
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function get_joins(): array {
|
||||
return array_values($this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user profile fields visible columns.
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
public function get_columns(): array {
|
||||
$columns = [];
|
||||
|
||||
foreach ($this->userprofilefields as $profilefield) {
|
||||
$userinfotablealias = database::generate_alias();
|
||||
|
||||
$column = (new column(
|
||||
'profilefield_' . $profilefield->field->shortname,
|
||||
new lang_string('customfieldcolumn', 'core_reportbuilder',
|
||||
format_string($profilefield->field->name, true,
|
||||
['escape' => true, 'context' => context_system::instance()])),
|
||||
$this->entityname
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_join("LEFT JOIN {user_info_data} {$userinfotablealias} " .
|
||||
"ON {$userinfotablealias}.userid = {$this->usertablefieldalias} " .
|
||||
"AND {$userinfotablealias}.fieldid = {$profilefield->fieldid}")
|
||||
->add_field("{$userinfotablealias}.data", 'profilefield_' . $profilefield->field->shortname)
|
||||
->set_type($this->get_user_field_type($profilefield->field->datatype))
|
||||
->add_callback([$this, 'format_profile_field'], $profilefield);
|
||||
|
||||
$columns[] = $column;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom user profile fields filters.
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
public function get_filters(): array {
|
||||
global $DB;
|
||||
|
||||
$filters = [];
|
||||
foreach ($this->userprofilefields as $profilefield) {
|
||||
$userinfotablealias = database::generate_alias();
|
||||
$field = "{$userinfotablealias}.data";
|
||||
|
||||
switch ($profilefield->field->datatype) {
|
||||
case 'checkbox':
|
||||
$classname = boolean_select::class;
|
||||
$field = $DB->sql_cast_char2int($field);
|
||||
break;
|
||||
case 'datetime':
|
||||
$classname = date::class;
|
||||
$field = $DB->sql_cast_char2int($field);
|
||||
break;
|
||||
case 'menu':
|
||||
$classname = select::class;
|
||||
break;
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
default:
|
||||
$field = $DB->sql_compare_text($field, 255);
|
||||
$classname = text::class;
|
||||
break;
|
||||
}
|
||||
|
||||
$filter = (new filter(
|
||||
$classname,
|
||||
'profilefield_' . $profilefield->field->shortname,
|
||||
new lang_string('customfieldcolumn', 'core_reportbuilder',
|
||||
format_string($profilefield->field->name, true,
|
||||
['escape' => false, 'context' => context_system::instance()])),
|
||||
$this->entityname,
|
||||
$field
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_join("LEFT JOIN {user_info_data} {$userinfotablealias} " .
|
||||
"ON {$userinfotablealias}.userid = {$this->usertablefieldalias} " .
|
||||
"AND {$userinfotablealias}.fieldid = {$profilefield->fieldid}");
|
||||
|
||||
// If menu type then set filter options as appropriate.
|
||||
if ($profilefield->field->datatype === 'menu') {
|
||||
$filter->set_options($profilefield->options);
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile field type for report.
|
||||
*
|
||||
* @param string $userfield user field.
|
||||
* @return int the constant equivalent to this custom field type.
|
||||
*/
|
||||
protected function get_user_field_type(string $userfield): int {
|
||||
switch ($userfield) {
|
||||
case 'checkbox':
|
||||
$customfieldtype = column::TYPE_BOOLEAN;
|
||||
break;
|
||||
case 'datetime':
|
||||
$customfieldtype = column::TYPE_TIMESTAMP;
|
||||
break;
|
||||
case 'textarea':
|
||||
$customfieldtype = column::TYPE_LONGTEXT;
|
||||
break;
|
||||
case 'menu':
|
||||
case 'text':
|
||||
default:
|
||||
$customfieldtype = column::TYPE_TEXT;
|
||||
break;
|
||||
}
|
||||
return $customfieldtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatter for a profile field. It formats the field according to its type.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param stdClass $row
|
||||
* @param profile_field_base $field
|
||||
* @return string
|
||||
*/
|
||||
public static function format_profile_field($value, stdClass $row, profile_field_base $field): string {
|
||||
$field->data = $value;
|
||||
return $field->display_data();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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\models;
|
||||
|
||||
use context;
|
||||
use context_system;
|
||||
use core\persistent;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Persistent class to represent a report
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2018 Alberto Lara Hernández <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class report extends persistent {
|
||||
|
||||
/** @var string The table name. */
|
||||
public const TABLE = 'reportbuilder_report';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties(): array {
|
||||
return [
|
||||
'source' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
'type' => [
|
||||
'type' => PARAM_INT,
|
||||
'choices' => [
|
||||
base::TYPE_CUSTOM_REPORT,
|
||||
base::TYPE_SYSTEM_REPORT,
|
||||
],
|
||||
],
|
||||
'contextid' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => static function(): int {
|
||||
return context_system::instance()->id;
|
||||
}
|
||||
],
|
||||
'component' => [
|
||||
'type' => PARAM_COMPONENT,
|
||||
'default' => '',
|
||||
],
|
||||
'area' => [
|
||||
'type' => PARAM_AREA,
|
||||
'default' => '',
|
||||
],
|
||||
'itemid' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 0,
|
||||
],
|
||||
'usercreated' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => static function(): int {
|
||||
global $USER;
|
||||
|
||||
return (int) $USER->id;
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_id(): int {
|
||||
return (int) $this->raw_get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report context, used by exporters
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public function get_context(): context {
|
||||
return context::instance_by_id($this->raw_get('contextid'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?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\report;
|
||||
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
use popup_action;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class to represent a report action
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class action {
|
||||
|
||||
/** @var moodle_url $url */
|
||||
protected $url;
|
||||
|
||||
/** @var pix_icon $icon */
|
||||
protected $icon;
|
||||
|
||||
/** @var array $attributes */
|
||||
protected $attributes;
|
||||
|
||||
/** @var bool $popup */
|
||||
protected $popup;
|
||||
|
||||
/** @var callable[] $callbacks */
|
||||
protected $callbacks = [];
|
||||
|
||||
/**
|
||||
* Create an instance of an action to be added to a report. Both the parameters of the URL, and the attributes parameter
|
||||
* support placeholders which will be replaced with appropriate row values, e.g.:
|
||||
*
|
||||
* new action(new moodle_url('/', ['id' => ':id']), new pix_icon(...), ['data-id' => ':id'])
|
||||
*
|
||||
* Note that all expected placeholders should be added as base fields to the report
|
||||
*
|
||||
* @param moodle_url $url
|
||||
* @param pix_icon $icon
|
||||
* @param array $attributes
|
||||
* @param bool $popup
|
||||
*/
|
||||
public function __construct(
|
||||
moodle_url $url,
|
||||
pix_icon $icon,
|
||||
array $attributes = [],
|
||||
bool $popup = false
|
||||
) {
|
||||
$this->url = $url;
|
||||
$this->icon = $icon;
|
||||
$this->attributes = $attributes;
|
||||
$this->popup = $popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds callback to the action. Used to verify action is available to current user, or preprocess values used in placeholders
|
||||
*
|
||||
* Multiple callbacks can be added. If at least one returns false then the action will not be displayed
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return self
|
||||
*/
|
||||
public function add_callback(callable $callback): self {
|
||||
$this->callbacks[] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return renderer action icon suitable for output
|
||||
*
|
||||
* @uses core_renderer::action_icon()
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_action_link(stdClass $row): ?string {
|
||||
global $OUTPUT;
|
||||
|
||||
foreach ($this->callbacks as $callback) {
|
||||
$row = clone $row; // Clone so we don't modify the shared row inside a callback.
|
||||
if (!$callback($row)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new moodle_url instance with our filled in placeholders for this row.
|
||||
$url = new moodle_url(
|
||||
$this->url->out_omit_querystring(true),
|
||||
self::replace_placeholders($this->url->params(), $row)
|
||||
);
|
||||
|
||||
$popupaction = $this->popup ? new popup_action('click', $url) : null;
|
||||
|
||||
return $OUTPUT->action_icon($url, $this->icon, $popupaction, self::replace_placeholders($this->attributes, $row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of values, replace all placeholders with corresponding property of the given row
|
||||
*
|
||||
* @param array $values
|
||||
* @param stdClass $row
|
||||
* @return array
|
||||
*/
|
||||
private static function replace_placeholders(array $values, stdClass $row): array {
|
||||
return array_map(static function($value) use ($row) {
|
||||
return preg_replace_callback('/^:(?<property>.*)$/', static function(array $matches) use ($row): string {
|
||||
return (string) ($row->{$matches['property']} ?? '');
|
||||
}, $value);
|
||||
}, $values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<?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\report;
|
||||
|
||||
use coding_exception;
|
||||
use context;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\entities\base as entity_base;
|
||||
use core_reportbuilder\local\filters\base as filter_base;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
use core_reportbuilder\local\models\report;
|
||||
|
||||
/**
|
||||
* Base class for all reports
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
|
||||
/** @var int Custom report type value */
|
||||
public const TYPE_CUSTOM_REPORT = 0;
|
||||
|
||||
/** @var int System report type value */
|
||||
public const TYPE_SYSTEM_REPORT = 1;
|
||||
|
||||
/** @var int Default paging limit */
|
||||
public const DEFAULT_PAGESIZE = 30;
|
||||
|
||||
/** @var report $report Report persistent */
|
||||
private $report;
|
||||
|
||||
/** @var string $maintable */
|
||||
private $maintable = '';
|
||||
|
||||
/** @var string $maintablealias */
|
||||
private $maintablealias = '';
|
||||
|
||||
/** @var array $sqljoins */
|
||||
private $sqljoins = [];
|
||||
|
||||
/** @var array $sqlwheres */
|
||||
private $sqlwheres = [];
|
||||
|
||||
/** @var array $sqlparams */
|
||||
private $sqlparams = [];
|
||||
|
||||
/** @var entity_base[] $entities */
|
||||
private $entities = [];
|
||||
|
||||
/** @var lang_string[] */
|
||||
private $entitytitles = [];
|
||||
|
||||
/** @var column[] $columns */
|
||||
private $columns = [];
|
||||
|
||||
/** @var filter[] $filters */
|
||||
private $filters = [];
|
||||
|
||||
/** @var bool $downloadable Set if the report can be downloaded */
|
||||
private $downloadable = false;
|
||||
|
||||
/** @var string $downloadfilename Name of the downloaded file */
|
||||
private $downloadfilename = '';
|
||||
|
||||
/**
|
||||
* Base report constructor
|
||||
*
|
||||
* @param report $report
|
||||
*/
|
||||
public function __construct(report $report) {
|
||||
$this->report = $report;
|
||||
|
||||
// Initialise and validate the report.
|
||||
$this->initialise();
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persistent class used when initialising this report
|
||||
*
|
||||
* @return report
|
||||
*/
|
||||
final public function get_report_persistent(): report {
|
||||
return $this->report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise report. Specify which columns, filters, etc should be present
|
||||
*
|
||||
* To set the base query use:
|
||||
* - {@see set_main_table}
|
||||
* - {@see add_base_condition_simple} or {@see add_base_condition_sql}
|
||||
* - {@see add_join}
|
||||
*
|
||||
* To add content to the report use:
|
||||
* - {@see add_entity}
|
||||
* - {@see add_column}
|
||||
* - {@see add_filter}
|
||||
* - etc
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some basic validation about expected class properties
|
||||
*
|
||||
* @throws coding_exception
|
||||
*/
|
||||
protected function validate(): void {
|
||||
if (empty($this->maintable)) {
|
||||
throw new coding_exception('Report must define main table by calling $this->set_main_table()');
|
||||
}
|
||||
|
||||
if (empty($this->columns)) {
|
||||
throw new coding_exception('Report must define at least one column by calling $this->add_column()');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the main table and alias for the SQL query
|
||||
*
|
||||
* @param string $tablename
|
||||
* @param string $tablealias
|
||||
*/
|
||||
final public function set_main_table(string $tablename, string $tablealias = ''): void {
|
||||
$this->maintable = $tablename;
|
||||
$this->maintablealias = $tablealias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function get_main_table(): string {
|
||||
return $this->maintable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alias for the main table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function get_main_table_alias(): string {
|
||||
return $this->maintablealias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds report JOIN clause that is always added
|
||||
*
|
||||
* @param string $join
|
||||
* @param array $params
|
||||
* @param bool $validateparams Some queries might add non-standard params and validation could fail
|
||||
*/
|
||||
protected function add_join(string $join, array $params = [], bool $validateparams = true): void {
|
||||
if ($validateparams) {
|
||||
database::validate_params($params);
|
||||
}
|
||||
|
||||
$this->sqljoins[trim($join)] = trim($join);
|
||||
$this->sqlparams += $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report JOIN clauses
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_joins(): array {
|
||||
return array_values($this->sqljoins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define simple "field = value" clause to apply to the report query
|
||||
*
|
||||
* @param string $fieldname
|
||||
* @param mixed $fieldvalue
|
||||
*/
|
||||
final public function add_base_condition_simple(string $fieldname, $fieldvalue): void {
|
||||
if ($fieldvalue === null) {
|
||||
$this->add_base_condition_sql("{$fieldname} IS NULL");
|
||||
} else {
|
||||
$fieldvalueparam = database::generate_param_name();
|
||||
$this->add_base_condition_sql("{$fieldname} = :{$fieldvalueparam}", [
|
||||
$fieldvalueparam => $fieldvalue,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define more complex clause that will always be applied to the report query
|
||||
*
|
||||
* @param string $where
|
||||
* @param array $params Note that the param names should be generated by {@see database::generate_param_name}
|
||||
*/
|
||||
final public function add_base_condition_sql(string $where, array $params = []): void {
|
||||
database::validate_params($params);
|
||||
|
||||
$this->sqlwheres[] = trim($where);
|
||||
$this->sqlparams = $params + $this->sqlparams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return base select/params for the report query
|
||||
*
|
||||
* @return array [string $select, array $params]
|
||||
*/
|
||||
final public function get_base_condition(): array {
|
||||
return [
|
||||
implode(' AND ', $this->sqlwheres),
|
||||
$this->sqlparams,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds given entity, along with it's columns and filters, to the report
|
||||
*
|
||||
* @param entity_base $entity
|
||||
*/
|
||||
final protected function add_entity(entity_base $entity): void {
|
||||
$entityname = $entity->get_entity_name();
|
||||
$this->annotate_entity($entityname, $entity->get_entity_title());
|
||||
$this->entities[$entityname] = $entity->initialise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a new entity for the report
|
||||
*
|
||||
* @param string $name
|
||||
* @param lang_string $title
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final protected function annotate_entity(string $name, lang_string $title): void {
|
||||
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
|
||||
throw new coding_exception('Entity name must be comprised of alphanumeric character, underscore or dash');
|
||||
}
|
||||
|
||||
$this->entitytitles[$name] = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a column to the report
|
||||
*
|
||||
* @param column $column
|
||||
* @return column
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final protected function add_column(column $column): column {
|
||||
if (!array_key_exists($column->get_entity_name(), $this->entitytitles)) {
|
||||
throw new coding_exception('Invalid entity name', $column->get_entity_name());
|
||||
}
|
||||
|
||||
$name = $column->get_name();
|
||||
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
|
||||
throw new coding_exception('Column name must be comprised of alphanumeric character, underscore or dash');
|
||||
}
|
||||
|
||||
$uniqueidentifier = $column->get_unique_identifier();
|
||||
if (array_key_exists($uniqueidentifier, $this->columns)) {
|
||||
throw new coding_exception('Duplicate column identifier', $uniqueidentifier);
|
||||
}
|
||||
|
||||
$this->columns[$uniqueidentifier] = $column;
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given columns to the report from one or more entities
|
||||
*
|
||||
* Each entity must have already been added to the report before calling this method
|
||||
*
|
||||
* @param string[] $columns Unique identifier of each entity column
|
||||
* @throws coding_exception For unknown entities
|
||||
*/
|
||||
final protected function add_columns_from_entities(array $columns): void {
|
||||
foreach ($columns as $column) {
|
||||
[$entityname, $columnname] = explode(':', $column, 2);
|
||||
|
||||
if (!array_key_exists($entityname, $this->entities)) {
|
||||
throw new coding_exception('Invalid entity name', $entityname);
|
||||
}
|
||||
|
||||
$this->add_column($this->entities[$entityname]->get_column($columnname));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report column by unique identifier
|
||||
*
|
||||
* @param string $uniqueidentifier
|
||||
* @return column|null
|
||||
*/
|
||||
final public function get_column(string $uniqueidentifier): ?column {
|
||||
return $this->columns[$uniqueidentifier] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all available report columns
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
final public function get_columns(): array {
|
||||
return array_filter($this->columns, static function(column $column): bool {
|
||||
return $column->get_is_available();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter to the report
|
||||
*
|
||||
* @param filter $filter
|
||||
* @return filter
|
||||
* @throws coding_exception
|
||||
*/
|
||||
final protected function add_filter(filter $filter): filter {
|
||||
if (!array_key_exists($filter->get_entity_name(), $this->entitytitles)) {
|
||||
throw new coding_exception('Invalid entity name', $filter->get_entity_name());
|
||||
}
|
||||
|
||||
$name = $filter->get_name();
|
||||
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
|
||||
throw new coding_exception('Filter name must be comprised of alphanumeric character, underscore or dash');
|
||||
}
|
||||
|
||||
$uniqueidentifier = $filter->get_unique_identifier();
|
||||
if (array_key_exists($uniqueidentifier, $this->filters)) {
|
||||
throw new coding_exception('Duplicate filter identifier', $uniqueidentifier);
|
||||
}
|
||||
|
||||
$this->filters[$uniqueidentifier] = $filter;
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given filters to the report from one or more entities
|
||||
*
|
||||
* Each entity must have already been added to the report before calling this method
|
||||
*
|
||||
* @param string[] $filters Unique identifier of each entity filter
|
||||
* @throws coding_exception For unknown entities
|
||||
*/
|
||||
final protected function add_filters_from_entities(array $filters): void {
|
||||
foreach ($filters as $filter) {
|
||||
[$entityname, $filtername] = explode(':', $filter, 2);
|
||||
|
||||
if (!array_key_exists($entityname, $this->entities)) {
|
||||
throw new coding_exception('Invalid entity name', $entityname);
|
||||
}
|
||||
|
||||
$this->add_filter($this->entities[$entityname]->get_filter($filtername));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report filter by unique identifier
|
||||
*
|
||||
* @param string $uniqueidentifier
|
||||
* @return filter|null
|
||||
*/
|
||||
final public function get_filter(string $uniqueidentifier): ?filter {
|
||||
return $this->filters[$uniqueidentifier] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all available report filters
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
final public function get_filters(): array {
|
||||
return array_filter($this->filters, static function(filter $filter): bool {
|
||||
return $filter->get_is_available();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all report filter instances
|
||||
*
|
||||
* @return filter_base[]
|
||||
*/
|
||||
final public function get_filter_instances(): array {
|
||||
return array_map(static function(filter $filter): filter_base {
|
||||
/** @var filter_base $filterclass */
|
||||
$filterclass = $filter->get_filter_class();
|
||||
|
||||
return $filterclass::create($filter);
|
||||
}, $this->get_filters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter values of the report
|
||||
*
|
||||
* @param array $values
|
||||
* @return bool
|
||||
*/
|
||||
final public function set_filter_values(array $values): bool {
|
||||
return user_filter_manager::set($this->report->get('id'), $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter values of the report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_filter_values(): array {
|
||||
return user_filter_manager::get($this->report->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the report can be downloaded.
|
||||
*
|
||||
* @param bool $downloadable
|
||||
* @param string $downloadfilename If the report is downloadable, then a filename should be provided here
|
||||
*/
|
||||
final public function set_downloadable(bool $downloadable, string $downloadfilename = 'export'): void {
|
||||
$this->downloadable = $downloadable;
|
||||
$this->downloadfilename = $downloadfilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the report can be downloaded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function is_downloadable(): bool {
|
||||
return $this->downloadable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the downloadable report filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function get_downloadfilename(): string {
|
||||
return $this->downloadfilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report context
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public function get_context(): context {
|
||||
return $this->report->get_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
<?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\report;
|
||||
|
||||
use coding_exception;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Class to represent a report column
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class column {
|
||||
|
||||
/** @var int Column type is integer */
|
||||
public const TYPE_INTEGER = 1;
|
||||
|
||||
/** @var int Column type is text */
|
||||
public const TYPE_TEXT = 2;
|
||||
|
||||
/** @var int Column type is timestamp */
|
||||
public const TYPE_TIMESTAMP = 3;
|
||||
|
||||
/** @var int Column type is boolean */
|
||||
public const TYPE_BOOLEAN = 4;
|
||||
|
||||
/** @var int Column type is float */
|
||||
public const TYPE_FLOAT = 5;
|
||||
|
||||
/** @var int Column type is long text */
|
||||
public const TYPE_LONGTEXT = 6;
|
||||
|
||||
/** @var int $index Column index within a report */
|
||||
private $index;
|
||||
|
||||
/** @var string $columnname Internal reference to name of column */
|
||||
private $columnname;
|
||||
|
||||
/** @var lang_string $columntitle Used as a title for the column in reports */
|
||||
private $columntitle;
|
||||
|
||||
/** @var string $entityname Name of the entity this column belongs to */
|
||||
private $entityname;
|
||||
|
||||
/** @var int $type Column data type (one of the TYPE_* class constants) */
|
||||
private $type = null;
|
||||
|
||||
/** @var string[] $joins List of SQL joins for this column */
|
||||
private $joins = [];
|
||||
|
||||
/** @var array $fields */
|
||||
private $fields = [];
|
||||
|
||||
/** @var array $params */
|
||||
private $params = [];
|
||||
|
||||
/** @var array[] $callbacks Array of [callable, additionalarguments] */
|
||||
private $callbacks = [];
|
||||
|
||||
/** @var bool $issortable Used to indicate if a column is sortable */
|
||||
private $issortable = false;
|
||||
|
||||
/** @var array $attributes */
|
||||
private $attributes = [];
|
||||
|
||||
/** @var bool $available Used to know if column is available to the current user or not */
|
||||
protected $available = true;
|
||||
|
||||
/**
|
||||
* Column constructor
|
||||
*
|
||||
* For better readability use chainable methods, for example:
|
||||
*
|
||||
* $report->add_column(
|
||||
* (new column('name', new lang_string('name'), 'user'))
|
||||
* ->add_join('left join {table} t on t.id = p.tableid')
|
||||
* ->add_field('t.name')
|
||||
* ->add_callback([format::class, 'format_string']));
|
||||
*
|
||||
* @param string $name Internal name of the column
|
||||
* @param lang_string|null $title Title of the column used in reports (null for blank)
|
||||
* @param string $entityname Name of the entity this column belongs to. Typically when creating columns within entities
|
||||
* this value should be the result of calling {@see get_entity_name}, however if creating columns inside reports directly
|
||||
* it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
|
||||
*/
|
||||
public function __construct(string $name, ?lang_string $title, string $entityname) {
|
||||
$this->columnname = $name;
|
||||
$this->columntitle = $title;
|
||||
$this->entityname = $entityname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set column name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function set_name(string $name): self {
|
||||
$this->columnname = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->columnname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set column title
|
||||
*
|
||||
* @param lang_string|null $title
|
||||
* @return self
|
||||
*/
|
||||
public function set_title(?lang_string $title): self {
|
||||
$this->columntitle = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title(): string {
|
||||
return $this->columntitle ? (string) $this->columntitle : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column entity name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_entity_name(): string {
|
||||
return $this->entityname;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return unique identifier for this column
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_unique_identifier(): string {
|
||||
return $this->get_entity_name() . ':' . $this->get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column index within the current report
|
||||
*
|
||||
* @param int $index
|
||||
* @return column
|
||||
*/
|
||||
public function set_index(int $index): self {
|
||||
$this->index = $index;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column type
|
||||
*
|
||||
* @param int $type
|
||||
* @return column
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function set_type(int $type): self {
|
||||
$allowedtypes = [
|
||||
self::TYPE_INTEGER,
|
||||
self::TYPE_TEXT,
|
||||
self::TYPE_TIMESTAMP,
|
||||
self::TYPE_BOOLEAN,
|
||||
self::TYPE_FLOAT,
|
||||
self::TYPE_LONGTEXT,
|
||||
];
|
||||
if (!in_array($type, $allowedtypes)) {
|
||||
throw new coding_exception('Invalid column type', $type);
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column type
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_type(): ?int {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join clause required for this column to join to existing tables/entities
|
||||
*
|
||||
* This is necessary in the case where {@see add_field} is selecting data from a table that isn't otherwise queried
|
||||
*
|
||||
* @param string $join
|
||||
* @return self
|
||||
*/
|
||||
public function add_join(string $join): self {
|
||||
$this->joins[trim($join)] = trim($join);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple join clauses required for this column, passing each to {@see add_join}
|
||||
*
|
||||
* Typically when defining columns in entities, you should pass {@see \core_reportbuilder\local\report\base::get_joins} to
|
||||
* this method, so that all entity joins are included in the report when your column is added to it
|
||||
*
|
||||
* @param string[] $joins
|
||||
* @return self
|
||||
*/
|
||||
public function add_joins(array $joins): self {
|
||||
foreach ($joins as $join) {
|
||||
$this->add_join($join);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column joins
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_joins(): array {
|
||||
return array_values($this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to be queried from the database that is necessary for this column
|
||||
*
|
||||
* Multiple fields can be added per column, this method may be called several times. Field aliases must be unique inside
|
||||
* any given column, but there will be no conflicts if the same aliases are used in other columns in the same report
|
||||
*
|
||||
* @param string $sql SQL query, this may be a simple "tablealias.fieldname" or a complex sub-query that returns only one field
|
||||
* @param string $alias
|
||||
* @param array $params
|
||||
* @return self
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function add_field(string $sql, string $alias = '', array $params = []): self {
|
||||
database::validate_params($params);
|
||||
|
||||
// SQL ends with a space and a word - this looks like an alias was passed as part of the field.
|
||||
if (preg_match('/ \w+$/', $sql) && empty($alias)) {
|
||||
throw new coding_exception('Column alias must be passed as a separate argument', $sql);
|
||||
}
|
||||
|
||||
// If no alias was specified, auto-detect it based on common patterns ("table.column" or just "column").
|
||||
if (empty($alias) && preg_match('/^(\w+\.)?(?<fieldname>\w+)$/', $sql, $matches)) {
|
||||
$alias = $matches['fieldname'];
|
||||
}
|
||||
|
||||
if (empty($alias)) {
|
||||
throw new coding_exception('Complex columns must have an alias', $sql);
|
||||
}
|
||||
|
||||
$this->fields[$alias] = $sql;
|
||||
$this->params += $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a list of comma-separated fields
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @return self
|
||||
*/
|
||||
public function add_fields(string $sql, array $params = []): self {
|
||||
database::validate_params($params);
|
||||
|
||||
// Split SQL into separate fields (separated by comma).
|
||||
$fields = preg_split('/\s*,\s*/', $sql);
|
||||
foreach ($fields as $field) {
|
||||
// Split each field into expression, <field> <as> <alias> where "as" and "alias" are optional.
|
||||
$fieldparts = preg_split('/\s+/', $field);
|
||||
|
||||
if (count($fieldparts) == 2 || (count($fieldparts) == 3 && strtolower($fieldparts[1]) === 'as')) {
|
||||
$sql = reset($fieldparts);
|
||||
$alias = array_pop($fieldparts);
|
||||
$this->add_field($sql, $alias);
|
||||
} else {
|
||||
$this->add_field($field);
|
||||
}
|
||||
}
|
||||
|
||||
$this->params += $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a param name, add a unique prefix to ensure that the same column with params can be added multiple times to a report
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
private function unique_param_name(string $name): string {
|
||||
return "p{$this->index}_{$name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to take all fields added to the column, and return appropriate SQL and alias
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private function get_fields_sql_alias(): array {
|
||||
$fields = [];
|
||||
|
||||
foreach ($this->fields as $alias => $sql) {
|
||||
// Ensure params within SQL are prefixed with column index.
|
||||
foreach ($this->params as $name => $value) {
|
||||
$sql = preg_replace_callback('/:(?<param>' . preg_quote($name, '\b/') . ')/', function(array $matches): string {
|
||||
return ':' . $this->unique_param_name($matches['param']);
|
||||
}, $sql);
|
||||
}
|
||||
|
||||
$fields[$alias] = [
|
||||
'sql' => $sql,
|
||||
'alias' => substr("c{$this->index}_{$alias}", 0, 30),
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of SQL expressions for each field of this column
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_fields(): array {
|
||||
$fields = array_map(static function(array $field): string {
|
||||
return "{$field['sql']} AS {$field['alias']}";
|
||||
}, $this->get_fields_sql_alias());
|
||||
|
||||
return array_values($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_params(): array {
|
||||
$params = [];
|
||||
|
||||
foreach ($this->params as $name => $value) {
|
||||
$paramname = $this->unique_param_name($name);
|
||||
$params[$paramname] = $value;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an alias for this column (the generated alias of it's first field)
|
||||
*
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function get_column_alias(): string {
|
||||
if (!$fields = $this->get_fields_sql_alias()) {
|
||||
throw new coding_exception('Column ' . $this->get_unique_identifier() . ' contains no fields');
|
||||
}
|
||||
|
||||
return reset($fields)['alias'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds column callback (in the case there are multiple, they will be applied one after another)
|
||||
*
|
||||
* The callback should implement the following signature (where $value is the first column field, $row is all column
|
||||
* fields, and $additionalarguments are those passed on from this method):
|
||||
*
|
||||
* function($value, stdClass $row[, $additionalarguments]): string
|
||||
*
|
||||
* @param callable $callable function that takes arguments ($value, \stdClass $row, $additionalarguments)
|
||||
* @param mixed $additionalarguments
|
||||
* @return self
|
||||
*/
|
||||
public function add_callback(callable $callable, $additionalarguments = null): self {
|
||||
$this->callbacks[] = [$callable, $additionalarguments];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets column callback. This will overwrite any previously added callbacks {@see add_callback}
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param mixed $additionalarguments
|
||||
* @return self
|
||||
*/
|
||||
public function set_callback(callable $callable, $additionalarguments = null): self {
|
||||
$this->callbacks = [];
|
||||
return $this->add_callback($callable, $additionalarguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column as sortable
|
||||
*
|
||||
* @param bool $issortable
|
||||
* @return self
|
||||
*/
|
||||
public function set_is_sortable(bool $issortable): self {
|
||||
$this->issortable = $issortable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sortable status of column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_is_sortable(): bool {
|
||||
return $this->issortable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all values from given row for this column
|
||||
*
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
private function get_values(array $row): array {
|
||||
$values = [];
|
||||
|
||||
foreach ($this->get_fields_sql_alias() as $alias => $field) {
|
||||
$values[$alias] = $row[$field['alias']];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default column value, that being the value of it's first field
|
||||
*
|
||||
* @param array $values
|
||||
* @return mixed
|
||||
*/
|
||||
private function get_default_value(array $values) {
|
||||
$value = reset($values);
|
||||
|
||||
// Ensure default value is cast to it's strict type.
|
||||
switch ($this->get_type()) {
|
||||
case self::TYPE_INTEGER:
|
||||
case self::TYPE_TIMESTAMP:
|
||||
$value = (int) $value;
|
||||
break;
|
||||
case self::TYPE_FLOAT:
|
||||
$value = (float) $value;
|
||||
break;
|
||||
case self::TYPE_BOOLEAN:
|
||||
$value = (bool) $value;
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column value based on complete table row
|
||||
*
|
||||
* @param array $row
|
||||
* @return mixed
|
||||
*/
|
||||
public function format_value(array $row) {
|
||||
$values = $this->get_values($row);
|
||||
$value = $this->get_default_value($values);
|
||||
|
||||
// Loop through, and apply any defined callbacks.
|
||||
foreach ($this->callbacks as $callback) {
|
||||
$value = ($callback[0])($value, (object) $values, $callback[1]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column attributes (data-, class, etc.) that will be included in HTML when column is displayed
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return self
|
||||
*/
|
||||
public function add_attributes(array $attributes): self {
|
||||
$this->attributes = $attributes + $this->attributes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column HTML attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_attributes(): array {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available state of the column for the current user. For instance the column may be added to a report with the
|
||||
* expectation that only some users are able to see it
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_is_available(): bool {
|
||||
return $this->available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally set whether the column is available.
|
||||
*
|
||||
* @param bool $available
|
||||
* @return self
|
||||
*/
|
||||
public function set_is_available(bool $available): self {
|
||||
$this->available = $available;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?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\report;
|
||||
|
||||
use lang_string;
|
||||
use moodle_exception;
|
||||
use core_reportbuilder\local\filters\base;
|
||||
|
||||
/**
|
||||
* Class to represent a report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class filter {
|
||||
|
||||
/** @var string $filterclass */
|
||||
private $filterclass;
|
||||
|
||||
/** @var string $name */
|
||||
private $name;
|
||||
|
||||
/** @var lang_string $header */
|
||||
private $header;
|
||||
|
||||
/** @var string $entity */
|
||||
private $entityname;
|
||||
|
||||
/** @var string $fieldsql */
|
||||
private $fieldsql = '';
|
||||
|
||||
/** @var array $fieldparams */
|
||||
private $fieldparams = [];
|
||||
|
||||
/** @var string[] $joins */
|
||||
protected $joins = [];
|
||||
|
||||
/** @var bool $available */
|
||||
protected $available = true;
|
||||
|
||||
/** @var mixed $options */
|
||||
protected $options;
|
||||
|
||||
/** @var array $limitoperators */
|
||||
protected $limitoperators = [];
|
||||
|
||||
/**
|
||||
* Filter constructor
|
||||
*
|
||||
* @param string $filterclass Filter type class to use, must extend {@see base} filter class
|
||||
* @param string $name Internal name of the filter
|
||||
* @param lang_string $header Title of the filter used in reports
|
||||
* @param string $entityname Name of the entity this filter belongs to. Typically when creating filters within entities
|
||||
* this value should be the result of calling {@see get_entity_name}, however if creating filters inside reports directly
|
||||
* it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
|
||||
* @param string $fieldsql SQL clause to use for filtering, {@see set_field_sql}
|
||||
* @param array $fieldparams
|
||||
* @throws moodle_exception For invalid filter class
|
||||
*/
|
||||
public function __construct(
|
||||
string $filterclass,
|
||||
string $name,
|
||||
lang_string $header,
|
||||
string $entityname,
|
||||
string $fieldsql = '',
|
||||
array $fieldparams = []
|
||||
) {
|
||||
if (!class_exists($filterclass) || !is_subclass_of($filterclass, base::class)) {
|
||||
throw new moodle_exception('filterinvalid', 'reportbuilder', '', null, $filterclass);
|
||||
}
|
||||
|
||||
$this->filterclass = $filterclass;
|
||||
$this->name = $name;
|
||||
$this->header = $header;
|
||||
$this->entityname = $entityname;
|
||||
|
||||
if ($fieldsql !== '') {
|
||||
$this->set_field_sql($fieldsql, $fieldparams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter class path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filter_class(): string {
|
||||
return $this->filterclass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return header
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_header(): string {
|
||||
return $this->header->out();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header
|
||||
*
|
||||
* @param lang_string $header
|
||||
* @return self
|
||||
*/
|
||||
public function set_header(lang_string $header): self {
|
||||
$this->header = $header;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter entity name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_entity_name(): string {
|
||||
return $this->entityname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return unique identifier for this filter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_unique_identifier(): string {
|
||||
return $this->get_entity_name() . ':' . $this->get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return joins
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_joins(): array {
|
||||
return array_values($this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join clause required for this filter to join to existing tables/entities
|
||||
*
|
||||
* This is necessary in the case where {@see set_field_sql} is selecting data from a table that isn't otherwise queried
|
||||
*
|
||||
* @param string $join
|
||||
* @return self
|
||||
*/
|
||||
public function add_join(string $join): self {
|
||||
$this->joins[trim($join)] = trim($join);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple join clauses required for this filter, passing each to {@see add_join}
|
||||
*
|
||||
* Typically when defining filters in entities, you should pass {@see \core_reportbuilder\local\report\base::get_joins} to
|
||||
* this method, so that all entity joins are included in the report when your filter is used in it
|
||||
*
|
||||
* @param string[] $joins
|
||||
* @return self
|
||||
*/
|
||||
public function add_joins(array $joins): self {
|
||||
foreach ($joins as $join) {
|
||||
$this->add_join($join);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SQL expression for the field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_field_sql(): string {
|
||||
return $this->fieldsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL params for the field being filtered
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_field_params(): array {
|
||||
return $this->fieldparams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SQL expression for the field that is being filtered. It will be passed to the filter class
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @return self
|
||||
*/
|
||||
public function set_field_sql(string $sql, array $params = []): self {
|
||||
$this->fieldsql = $sql;
|
||||
$this->fieldparams = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available state of the filter for the current user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_is_available(): bool {
|
||||
return $this->available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally set whether the filter is available. For instance the filter may be added to a report with the
|
||||
* expectation that only some users are able to see it
|
||||
*
|
||||
* @param bool $available
|
||||
* @return self
|
||||
*/
|
||||
public function set_is_available(bool $available): self {
|
||||
$this->available = $available;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options for the filter in the format that the filter class expected (e.g. the "select" filter expects an array)
|
||||
*
|
||||
* This method should only be used if the options do not require any calculations/queries, in which
|
||||
* case {@see set_options_callback} should be used. For performance, {@see get_string} shouldn't be used either, use of
|
||||
* {@see lang_string} is instead encouraged
|
||||
*
|
||||
* @param mixed $options
|
||||
* @return self
|
||||
*/
|
||||
public function set_options($options): self {
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options for the filter to be returned by a callback (that recieves no arguments) in the format that the filter
|
||||
* class expects
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return self
|
||||
*/
|
||||
public function set_options_callback(callable $callback): self {
|
||||
$this->options = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for the filter, returning via the the previously set options or generated via defined options callback
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_options() {
|
||||
if (is_callable($this->options)) {
|
||||
$callable = $this->options;
|
||||
$this->options = ($callable)();
|
||||
}
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a limited subset of operators that should be used for the filter, refer to each filter class to find defined
|
||||
* operator constants
|
||||
*
|
||||
* @param array $limitoperators Simple array of operator values
|
||||
* @return self
|
||||
*/
|
||||
public function set_limited_operators(array $limitoperators): self {
|
||||
$this->limitoperators = $limitoperators;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter given operators to include only those previously defined by {@see set_limited_operators}
|
||||
*
|
||||
* @param array $operators All operators as defined by the filter class
|
||||
* @return array
|
||||
*/
|
||||
public function restrict_limited_operators(array $operators): array {
|
||||
if (empty($this->limitoperators)) {
|
||||
return $operators;
|
||||
}
|
||||
|
||||
return array_intersect_key($operators, array_flip($this->limitoperators));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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 stdClass;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Report management class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager {
|
||||
|
||||
/**
|
||||
* Return an instance of a report class from the given report persistent
|
||||
*
|
||||
* @param report $report
|
||||
* @param array $parameters
|
||||
* @return base
|
||||
* @throws source_invalid_exception
|
||||
* @throws source_unavailable_exception
|
||||
*/
|
||||
public static function get_report_from_persistent(report $report, array $parameters = []): base {
|
||||
$source = $report->get('source');
|
||||
|
||||
// Throw exception for invalid or unavailable report source.
|
||||
if (!self::report_source_exists($source)) {
|
||||
throw new source_invalid_exception($source);
|
||||
} else if (!self::report_source_available($source)) {
|
||||
throw new source_unavailable_exception($source);
|
||||
}
|
||||
|
||||
return new $source($report, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that report source exists and extends appropriate base classes
|
||||
*
|
||||
* @param string $source Full namespaced path to report definition
|
||||
* @param string $additionalbaseclass Specify addition base class that given classname should extend
|
||||
* @return bool
|
||||
*/
|
||||
public static function report_source_exists(string $source, string $additionalbaseclass = ''): bool {
|
||||
return (class_exists($source) && is_subclass_of($source, base::class) &&
|
||||
(empty($additionalbaseclass) || is_subclass_of($source, $additionalbaseclass)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify given report source is available. Note that it is assumed caller has already checked that it exists
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool
|
||||
*/
|
||||
public static function report_source_available(string $source): bool {
|
||||
return call_user_func([$source, 'is_available']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new report persistent
|
||||
*
|
||||
* @param stdClass $reportdata
|
||||
* @return report
|
||||
*/
|
||||
public static function create_report_persistent(stdClass $reportdata): report {
|
||||
return (new report(0, $reportdata))->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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 plugin_renderer_base;
|
||||
use core_reportbuilder\table\system_report_table;
|
||||
|
||||
/**
|
||||
* Report renderer class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class renderer extends plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Render a system report
|
||||
*
|
||||
* @param system_report $report
|
||||
* @return string
|
||||
*/
|
||||
protected function render_system_report(system_report $report): string {
|
||||
$context = $report->export_for_template($this);
|
||||
|
||||
return $this->render_from_template('core_reportbuilder/system_report', $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a system report table
|
||||
*
|
||||
* @param system_report_table $table
|
||||
* @return string
|
||||
*/
|
||||
protected function render_system_report_table(system_report_table $table): string {
|
||||
ob_start();
|
||||
$table->out($table->get_default_per_page(), false);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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 renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use core_reportbuilder\external\system_report_exporter;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* System report output class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report implements renderable, templatable {
|
||||
|
||||
/** @var report $report */
|
||||
protected $report;
|
||||
|
||||
/** @var base $source */
|
||||
protected $source;
|
||||
|
||||
/** @var array $parameters */
|
||||
protected $parameters;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param report $report
|
||||
* @param base $source
|
||||
* @param array $parameters
|
||||
*/
|
||||
public function __construct(report $report, base $source, array $parameters) {
|
||||
$this->report = $report;
|
||||
$this->source = $source;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export report data suitable for a template
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): stdClass {
|
||||
$exporter = new system_report_exporter($this->report, [
|
||||
'source' => $this->source,
|
||||
'parameters' => json_encode($this->parameters),
|
||||
]);
|
||||
|
||||
return $exporter->export($output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
use core_reportbuilder\local\models\report;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for core_reportbuilder
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\user_preference_provider {
|
||||
|
||||
/**
|
||||
* Returns metadata about the component
|
||||
*
|
||||
* @param collection $collection
|
||||
* @return collection
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
$collection->add_database_table(report::TABLE, [
|
||||
'name' => 'privacy:metadata:report:name',
|
||||
'usercreated' => 'privacy:metadata:report:usercreated',
|
||||
'usermodified' => 'privacy:metadata:report:usermodified',
|
||||
], 'privacy:metadata:report');
|
||||
|
||||
$collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user preferences for the component
|
||||
*
|
||||
* @param int $userid
|
||||
*/
|
||||
public static function export_user_preferences(int $userid): void {
|
||||
$preferencestring = get_string('privacy:metadata:preference:reportfilter', 'core_reportbuilder');
|
||||
|
||||
$filters = user_filter_manager::get_all_for_user($userid);
|
||||
foreach ($filters as $key => $filter) {
|
||||
writer::export_user_preference('core_reportbuilder',
|
||||
$key,
|
||||
json_encode($filter, JSON_PRETTY_PRINT),
|
||||
$preferencestring
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* User cannot access report exception
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class report_access_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct('errorreportaccess', 'reportbuilder');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* Invalid report source exception
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class source_invalid_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $source
|
||||
*/
|
||||
public function __construct(string $source) {
|
||||
parent::__construct('errorsourceinvalid', 'reportbuilder', '', null, $source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* Unavailable report source exception
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class source_unavailable_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $source
|
||||
*/
|
||||
public function __construct(string $source) {
|
||||
parent::__construct('errorsourceunavailable', 'reportbuilder', '', null, $source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?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 stdClass;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\local\report\base;
|
||||
use core_reportbuilder\local\report\column;
|
||||
|
||||
/**
|
||||
* Base class for system reports
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class system_report extends base {
|
||||
|
||||
/** @var array $parameters */
|
||||
private $parameters;
|
||||
|
||||
/** @var string[] $basefields List of base fields */
|
||||
private $basefields = [];
|
||||
|
||||
/** @var action[] $actions */
|
||||
private $actions = [];
|
||||
|
||||
/** @var column $initialsortcolumn */
|
||||
private $initialsortcolumn;
|
||||
|
||||
/** @var int $initialsortdirection */
|
||||
private $initialsortdirection;
|
||||
|
||||
/**
|
||||
* System report constructor.
|
||||
*
|
||||
* @param report $report
|
||||
* @param array $parameters
|
||||
*/
|
||||
final public function __construct(report $report, array $parameters) {
|
||||
$this->parameters = $parameters;
|
||||
|
||||
parent::__construct($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to view this report
|
||||
*
|
||||
* This is necessary to implement independently of the page that would typically embed the report because
|
||||
* subsequent pages are requested via AJAX requests, and access should be validated each time
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function can_view(): bool;
|
||||
|
||||
/**
|
||||
* Validate access to the report
|
||||
*
|
||||
* @throws report_access_exception
|
||||
*/
|
||||
final public function require_can_view(): void {
|
||||
if (!$this->can_view()) {
|
||||
throw new report_access_exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report validation
|
||||
*
|
||||
* @throws report_access_exception If user cannot access the report
|
||||
* @throws coding_exception If no default column are specified
|
||||
*/
|
||||
protected function validate(): void {
|
||||
parent::validate();
|
||||
|
||||
$this->require_can_view();
|
||||
|
||||
// Ensure the report has some default columns specified.
|
||||
if (empty($this->get_columns())) {
|
||||
throw new coding_exception('No columns added');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add list of fields that have to be always included in SQL query for actions and row classes
|
||||
*
|
||||
* Base fields are only available in system reports because they are not compatible with aggregation
|
||||
*
|
||||
* @param string $sql SQL clause for the list of fields that only uses main table or base joins
|
||||
*/
|
||||
final protected function add_base_fields(string $sql): void {
|
||||
$this->basefields[] = $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report base fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_base_fields(): array {
|
||||
return $this->basefields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an action to the report
|
||||
*
|
||||
* @param action $action
|
||||
*/
|
||||
final public function add_action(action $action): void {
|
||||
$this->actions[] = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether report has any actions
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function has_actions(): bool {
|
||||
return !empty($this->actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report actions
|
||||
*
|
||||
* @return action[]
|
||||
*/
|
||||
final public function get_actions(): array {
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all report parameters
|
||||
*
|
||||
* @param array $parameters
|
||||
*/
|
||||
final public function set_parameters(array $parameters): void {
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all report parameters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_parameters(): array {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return specific report parameter
|
||||
*
|
||||
* @param string $param
|
||||
* @param mixed $default
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
final public function get_parameter(string $param, $default, string $type) {
|
||||
if (!array_key_exists($param, $this->parameters)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return clean_param($this->parameters[$param], $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the report
|
||||
*
|
||||
* @uses \core_reportbuilder\output\renderer::render_system_report()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function output(): string {
|
||||
global $PAGE;
|
||||
|
||||
/** @var \core_reportbuilder\output\renderer $renderer */
|
||||
$renderer = $PAGE->get_renderer('core_reportbuilder');
|
||||
$report = new \core_reportbuilder\output\system_report($this->get_report_persistent(), $this, $this->parameters);
|
||||
|
||||
return $renderer->render($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS classes to add to the row. Can be overridden by system reports do define class to be added to output according to
|
||||
* content of each row
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
public function get_row_class(stdClass $row): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default 'per page' size. Can be overridden by system reports to define a different paging value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_default_per_page(): int {
|
||||
return self::DEFAULT_PAGESIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before rendering each row. Can be overridden to pre-fetch/create objects and store them in the class, which can
|
||||
* later be used in column and action callbacks
|
||||
*
|
||||
* @param stdClass $row
|
||||
*/
|
||||
public function row_callback(stdClass $row): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to download this report.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function can_be_downloaded(): bool {
|
||||
return $this->can_view() && $this->is_downloadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of column names that will be excluded when table is downloaded. Extending classes should override this method
|
||||
* as appropriate
|
||||
*
|
||||
* @return string[] Array of column unique identifiers
|
||||
*/
|
||||
public function get_exclude_columns_for_download(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set initial sort column and sort direction for the report
|
||||
*
|
||||
* @param string $uniqueidentifier
|
||||
* @param int $sortdirection One of SORT_ASC or SORT_DESC
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function set_initial_sort_column(string $uniqueidentifier, int $sortdirection): void {
|
||||
if (!$sortcolumn = $this->get_column($uniqueidentifier)) {
|
||||
throw new coding_exception('Unknown column identifier', $uniqueidentifier);
|
||||
}
|
||||
|
||||
$this->initialsortcolumn = $sortcolumn;
|
||||
$this->initialsortdirection = $sortdirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initial sort column
|
||||
*
|
||||
* @return column|null
|
||||
*/
|
||||
public function get_initial_sort_column(): ?column {
|
||||
return $this->initialsortcolumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initial sort column direction
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_initial_sort_direction(): int {
|
||||
return $this->initialsortdirection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Factory class for creating system report instances
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_factory {
|
||||
|
||||
/**
|
||||
* Create and return instance of given system report source
|
||||
*
|
||||
* @param string $source Class path of system report definition
|
||||
* @param context $context
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @param array $parameters Simple key/value pairs, accessed inside reports using $this->get_parameter()
|
||||
* @return system_report
|
||||
* @throws source_invalid_exception
|
||||
*/
|
||||
public static function create(string $source, context $context, string $component = '', string $area = '',
|
||||
int $itemid = 0, array $parameters = []): system_report {
|
||||
|
||||
// Exit early if source isn't a system report.
|
||||
if (!manager::report_source_exists($source, system_report::class)) {
|
||||
throw new source_invalid_exception($source);
|
||||
}
|
||||
|
||||
$report = static::get_report_persistent($source, $context, $component, $area, $itemid);
|
||||
|
||||
return manager::get_report_from_persistent($report, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a report source, with accompanying context information, return a persistent report instance
|
||||
*
|
||||
* @param string $source
|
||||
* @param context $context
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @return report
|
||||
*/
|
||||
private static function get_report_persistent(string $source, context $context, string $component = '', string $area = '',
|
||||
int $itemid = 0): report {
|
||||
|
||||
$reportdata = [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => $source,
|
||||
'contextid' => $context->id,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
];
|
||||
|
||||
if ($report = report::get_record($reportdata)) {
|
||||
return $report;
|
||||
}
|
||||
|
||||
return manager::create_report_persistent((object) $reportdata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<?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\table;
|
||||
|
||||
use context;
|
||||
use html_writer;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use table_sql;
|
||||
use stdClass;
|
||||
use core_table\dynamic;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\system_report;
|
||||
use core_reportbuilder\local\filters\base;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_table\local\filter\filterset;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
require_once("{$CFG->libdir}/tablelib.php");
|
||||
|
||||
/**
|
||||
* System report dynamic table class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_table extends table_sql implements dynamic, renderable {
|
||||
|
||||
/** @var string Unique ID prefix for the table */
|
||||
private const UNIQUEID_PREFIX = 'system-report-table-';
|
||||
|
||||
/** @var report $report */
|
||||
protected $report;
|
||||
|
||||
/** @var system_report $systemreport */
|
||||
protected $systemreport;
|
||||
|
||||
/**
|
||||
* Table constructor. Note that the passed unique ID value must match the pattern "system-report-table-(\d+)" so that
|
||||
* dynamic updates continue to load the same report
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param array $parameters
|
||||
* @throws moodle_exception For invalid unique ID
|
||||
*/
|
||||
public function __construct(string $uniqueid, array $parameters = []) {
|
||||
if (!preg_match('/^' . self::UNIQUEID_PREFIX . '(?<id>\d+)$/', $uniqueid, $matches)) {
|
||||
throw new moodle_exception('invalidsystemreportid', 'core_reportbuilder', '', null, $uniqueid);
|
||||
}
|
||||
|
||||
parent::__construct($uniqueid);
|
||||
|
||||
// Load the report persistent, and accompanying system report instance.
|
||||
$this->report = new report($matches['id']);
|
||||
$this->systemreport = manager::get_report_from_persistent($this->report, $parameters);
|
||||
|
||||
$fields = $this->systemreport->get_base_fields();
|
||||
$maintable = $this->systemreport->get_main_table();
|
||||
$maintablealias = $this->systemreport->get_main_table_alias();
|
||||
$joins = $this->systemreport->get_joins();
|
||||
[$where, $params] = $this->systemreport->get_base_condition();
|
||||
|
||||
$this->set_attribute('data-region', 'reportbuilder-table');
|
||||
$this->set_attribute('class', $this->attributes['class'] . ' reportbuilder-table');
|
||||
|
||||
// Download options.
|
||||
$this->showdownloadbuttonsat = [TABLE_P_BOTTOM];
|
||||
$this->is_downloading($parameters['download'] ?? null, $this->systemreport->get_downloadfilename());
|
||||
|
||||
// Retrieve all report columns. If we are downloading the report, remove as required.
|
||||
$columns = $this->systemreport->get_columns();
|
||||
if ($this->is_downloading()) {
|
||||
$columns = array_diff_key($columns,
|
||||
array_flip($this->systemreport->get_exclude_columns_for_download()));
|
||||
}
|
||||
|
||||
$columnheaders = [];
|
||||
$columnindex = 1;
|
||||
foreach ($columns as $identifier => $column) {
|
||||
$column->set_index($columnindex++);
|
||||
|
||||
$columnheaders[$column->get_column_alias()] = $column->get_title();
|
||||
|
||||
// Add each columns fields, joins and params to our report.
|
||||
$fields = array_merge($fields, $column->get_fields());
|
||||
$joins = array_merge($joins, $column->get_joins());
|
||||
$params = array_merge($params, $column->get_params());
|
||||
|
||||
// Disable sorting for some columns.
|
||||
if (!$column->get_is_sortable()) {
|
||||
$this->no_sorting($column->get_column_alias());
|
||||
}
|
||||
}
|
||||
|
||||
// If the report has any actions then append appropriate column, note that actions are excluded during download.
|
||||
if ($this->systemreport->has_actions() && !$this->is_downloading()) {
|
||||
$columnheaders['actions'] = html_writer::tag('span', get_string('actions', 'core_reportbuilder'), [
|
||||
'class' => 'sr-only',
|
||||
]);
|
||||
$this->no_sorting('actions');
|
||||
}
|
||||
|
||||
$this->define_columns(array_keys($columnheaders));
|
||||
$this->define_headers(array_values($columnheaders));
|
||||
|
||||
// Initial table sort column.
|
||||
if ($sortcolumn = $this->systemreport->get_initial_sort_column()) {
|
||||
$this->sortable(true, $sortcolumn->get_column_alias(), $this->systemreport->get_initial_sort_direction());
|
||||
}
|
||||
|
||||
// Table configuration.
|
||||
$this->initialbars(false);
|
||||
$this->collapsible(false);
|
||||
$this->pageable(true);
|
||||
$this->set_default_per_page($this->systemreport->get_default_per_page());
|
||||
|
||||
// Initialise table SQL properties.
|
||||
$fieldsql = implode(', ', $fields);
|
||||
$this->init_sql($fieldsql, "{{$maintable}} {$maintablealias}", $joins, $where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of the class for given report ID. We include report parameters here so they are present during
|
||||
* initialisation
|
||||
*
|
||||
* @param int $reportid
|
||||
* @param array $parameters
|
||||
* @return static
|
||||
*/
|
||||
public static function create(int $reportid, array $parameters): self {
|
||||
return new static(self::UNIQUEID_PREFIX . $reportid, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filterset in the table class. We set the report parameters here so that they are persisted while paging
|
||||
*
|
||||
* @param filterset $filterset
|
||||
*/
|
||||
public function set_filterset(filterset $filterset): void {
|
||||
$parameters = $filterset->get_filter('parameters')->current();
|
||||
$this->systemreport->set_parameters((array) json_decode($parameters, true));
|
||||
|
||||
parent::set_filterset($filterset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises table SQL properties
|
||||
*
|
||||
* @param string $fields
|
||||
* @param string $from
|
||||
* @param array $joins
|
||||
* @param string $where
|
||||
* @param array $params
|
||||
*/
|
||||
protected function init_sql(string $fields, string $from, array $joins, string $where, array $params): void {
|
||||
$wheres = [];
|
||||
if ($where !== '') {
|
||||
$wheres[] = $where;
|
||||
}
|
||||
|
||||
$filtervalues = $this->systemreport->get_filter_values();
|
||||
foreach ($this->systemreport->get_filters() as $filter) {
|
||||
/** @var base $filterclass */
|
||||
$filterclass = $filter->get_filter_class();
|
||||
$filterinstance = $filterclass::create($filter);
|
||||
|
||||
[$filtersql, $filterparams] = $filterinstance->get_sql_filter($filtervalues);
|
||||
if ($filtersql !== '') {
|
||||
$wheres[] = "({$filtersql})";
|
||||
$params = array_merge($params, $filterparams);
|
||||
|
||||
$joins = array_merge($joins, $filter->get_joins());
|
||||
}
|
||||
}
|
||||
|
||||
$wheresql = '1=1';
|
||||
if (!empty($wheres)) {
|
||||
$wheresql = implode(' AND ', $wheres);
|
||||
}
|
||||
|
||||
// Add unique table joins.
|
||||
$from .= ' ' . implode(' ', array_unique($joins));
|
||||
|
||||
$this->set_sql($fields, $from, $wheresql, $params);
|
||||
$this->set_count_sql("SELECT COUNT(1) FROM {$from} WHERE {$wheresql}", $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent method of the same, to make use of a recordset and avoid issues with duplicate values in the first column
|
||||
*
|
||||
* @param int $pagesize
|
||||
* @param bool $useinitialsbar
|
||||
*/
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT {$this->sql->fields} FROM {$this->sql->from} WHERE {$this->sql->where}";
|
||||
|
||||
$sort = $this->get_sql_sort();
|
||||
if ($sort) {
|
||||
$sql .= " ORDER BY {$sort}";
|
||||
}
|
||||
|
||||
if (!$this->is_downloading()) {
|
||||
$this->pagesize($pagesize, $DB->count_records_sql($this->countsql, $this->countparams));
|
||||
|
||||
$this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
|
||||
} else {
|
||||
$this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent method for retrieving row class with that defined by the system report
|
||||
*
|
||||
* @param array|stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
public function get_row_class($row) {
|
||||
return $this->systemreport->get_row_class((object) $row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format each row of returned data, executing defined callbacks for the row and each column
|
||||
*
|
||||
* @param array|stdClass $row
|
||||
* @return array
|
||||
*/
|
||||
public function format_row($row) {
|
||||
$this->systemreport->row_callback((object) $row);
|
||||
|
||||
/** @var column[] $columnsbyalias */
|
||||
$columnsbyalias = [];
|
||||
|
||||
// Create a lookup for convenience, indexed by column alias.
|
||||
$columns = $this->systemreport->get_columns();
|
||||
foreach ($columns as $column) {
|
||||
$columnsbyalias[$column->get_column_alias()] = $column;
|
||||
}
|
||||
|
||||
// Walk over the row, and for any key that matches one of our column aliases, call that columns format method.
|
||||
$row = (array) $row;
|
||||
array_walk($row, static function(&$value, $key) use ($row, $columnsbyalias): void {
|
||||
if (array_key_exists($key, $columnsbyalias)) {
|
||||
$value = $columnsbyalias[$key]->format_value($row);
|
||||
}
|
||||
});
|
||||
|
||||
if ($this->systemreport->has_actions()) {
|
||||
$row['actions'] = $this->format_row_actions((object) $row);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return formatted actions column for the row
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
private function format_row_actions(stdClass $row): string {
|
||||
$actions = array_map(static function(action $action) use ($row): string {
|
||||
return (string) $action->get_action_link($row);
|
||||
}, $this->systemreport->get_actions());
|
||||
|
||||
return implode('', $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context for the table (that of the report persistent)
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public function get_context(): context {
|
||||
return $this->report->get_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base URL of the table to the current page URL
|
||||
*/
|
||||
public function guess_base_url(): void {
|
||||
$this->baseurl = new moodle_url('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the html for the download buttons
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function download_buttons(): string {
|
||||
global $OUTPUT;
|
||||
|
||||
if ($this->systemreport->can_be_downloaded() && !$this->is_downloading()) {
|
||||
return $OUTPUT->download_dataformat_selector(
|
||||
get_string('downloadas', 'table'),
|
||||
new \moodle_url('/reportbuilder/download.php'),
|
||||
'download',
|
||||
[
|
||||
'id' => $this->report->get('id'),
|
||||
'parameters' => json_encode($this->systemreport->get_parameters()),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\table;
|
||||
|
||||
use core_table\local\filter\filterset;
|
||||
|
||||
/**
|
||||
* System report dynamic table filterset class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_table_filterset extends filterset {
|
||||
|
||||
/**
|
||||
* Get the required filters
|
||||
*
|
||||
* @return array.
|
||||
*/
|
||||
public function get_required_filters(): array {
|
||||
return [
|
||||
'parameters' => system_report_table_parameters_filter::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\table;
|
||||
|
||||
use core_table\local\filter\string_filter;
|
||||
|
||||
/**
|
||||
* Class for storing given system report parameters
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_table_parameters_filter extends string_filter {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Download a report
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use core_reportbuilder\system_report_factory;
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
require_login();
|
||||
|
||||
$reportid = required_param('id', PARAM_INT);
|
||||
$download = required_param('download', PARAM_ALPHA);
|
||||
$parameters = optional_param('parameters', null, PARAM_RAW);
|
||||
|
||||
$reportpersistent = new \core_reportbuilder\local\models\report($reportid);
|
||||
$context = $reportpersistent->get_context();
|
||||
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url(new moodle_url('/reportbuilder/download.php'));
|
||||
|
||||
$systemreport = system_report_factory::create($reportpersistent->get('source'), $context);
|
||||
if (!$systemreport->can_be_downloaded()) {
|
||||
throw new \core_reportbuilder\report_access_exception();
|
||||
}
|
||||
|
||||
// Combine original report parameters with 'download' parameter.
|
||||
$reportparameters = ['download' => $download];
|
||||
if ($parameters) {
|
||||
$reportparameters = array_merge($reportparameters, (array) json_decode($parameters));
|
||||
}
|
||||
|
||||
$outputreport = new \core_reportbuilder\output\system_report($reportpersistent, $systemreport, $reportparameters);
|
||||
echo $PAGE->get_renderer('core_reportbuilder')->render($outputreport);
|
||||
@@ -0,0 +1,53 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_reportbuilder/filters/area
|
||||
|
||||
Template for system report filters area
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": 3,
|
||||
"filtersform": "form"
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="dropdown d-flex justify-content-end mb-2">
|
||||
<!-- Filters button -->
|
||||
<button class="btn btn-sm btn-secondary" type="button" id="dropdownFiltersButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="{{#str}} filters, moodle {{/str}}">
|
||||
{{#pix}} i/filter, core {{/pix}}
|
||||
{{#str}} filters, moodle {{/str}}
|
||||
</button>
|
||||
<!-- Filters content -->
|
||||
<div class="filters-dropdown dropdown-menu dropdown-menu-right" aria-labelledby="dropdownFiltersButton">
|
||||
<div id="filters" class="reportbuilder-filters-sidebar" data-region="report-filters">
|
||||
<div class="p-3">
|
||||
{{#filtersform}}
|
||||
<div class="reportbuilder-filters-wrapper" data-region="filters-form">
|
||||
{{{filtersform}}}
|
||||
</div>
|
||||
{{/filtersform}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#js}}
|
||||
require(['core_reportbuilder/filters'], function(filters) {
|
||||
filters.init('{{id}}');
|
||||
});
|
||||
{{/js}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_reportbuilder/local/filters/footer
|
||||
|
||||
Template for the footer of a filter instance within the filter form
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_reportbuilder/local/filters/header
|
||||
|
||||
Template for the header of a filter instance within the filter form
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "Date modified"
|
||||
}
|
||||
}}
|
||||
<div class="filter pb-4 mb-3 border-bottom" data-filter-for="{{name}}">
|
||||
<div class="filter-header d-flex align-items-start justify-content-between">
|
||||
<div class="filter-name">
|
||||
{{name}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_reportbuilder/system_report
|
||||
|
||||
Template for a system report
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": 1,
|
||||
"source": "some\\class\\name",
|
||||
"parameters": [],
|
||||
"table": "table",
|
||||
"filterspresent": true,
|
||||
"filtersform": "form"
|
||||
}
|
||||
}}
|
||||
<div data-region="core_reportbuilder/system-report"
|
||||
data-reportid="{{id}}"
|
||||
data-source="{{source}}"
|
||||
data-parameter="{{parameters}}">
|
||||
<div class="reportbuilder-table-wrapper">
|
||||
{{#filterspresent}}
|
||||
{{>core_reportbuilder/local/filters/area}}
|
||||
{{/filterspresent}}
|
||||
<!-- Table -->
|
||||
{{{table}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#js}}
|
||||
require(['core_reportbuilder/report'], function(report) {
|
||||
report.init('{{id}}');
|
||||
});
|
||||
{{/js}}
|
||||
@@ -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);
|
||||
|
||||
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
|
||||
|
||||
/**
|
||||
* Behat step definitions for Reportbuilder
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_reportbuilder extends behat_base {
|
||||
|
||||
/**
|
||||
* Return the list of partial named selectors
|
||||
*
|
||||
* @return behat_component_named_selector[]
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [
|
||||
new behat_component_named_selector('Filter', [
|
||||
".//*[@data-region='filters-form']//*[@data-filter-for=%locator%]",
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Coverage information for core_reportbuilder
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
return new class extends phpunit_coverage_info {
|
||||
/** @var array The list of folders relative to the plugin root to include in coverage generation. */
|
||||
protected $includelistfolders = [
|
||||
'classes',
|
||||
];
|
||||
|
||||
/** @var array The list of files relative to the plugin root to include in coverage generation. */
|
||||
protected $includelistfiles = [];
|
||||
|
||||
/** @var array The list of folders relative to the plugin root to exclude in coverage generation. */
|
||||
protected $excludelistfolders = [];
|
||||
|
||||
/** @var array The list of files relative to the plugin root to exclude in coverage generation. */
|
||||
protected $excludelistfiles = [];
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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\external\filters;
|
||||
|
||||
use external_api;
|
||||
use externallib_advanced_testcase;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
|
||||
|
||||
/**
|
||||
* Unit tests external filters reset class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\external\filters\reset
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class reset_testcase extends externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Text execute method
|
||||
*/
|
||||
public function test_execute(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
user_filter_manager::set(5, [
|
||||
'entity:filter_name' => 'something',
|
||||
]);
|
||||
|
||||
$this->assertCount(1, user_filter_manager::get(5));
|
||||
|
||||
$result = reset::execute(5);
|
||||
$result = external_api::clean_returnvalue(reset::execute_returns(), $result);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
// We should get an empty array back.
|
||||
$this->assertEquals([], user_filter_manager::get(5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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\external;
|
||||
|
||||
use advanced_testcase;
|
||||
use context_system;
|
||||
use moodle_url;
|
||||
use core_reportbuilder\system_report_available;
|
||||
use core_reportbuilder\system_report_factory;
|
||||
|
||||
/**
|
||||
* Unit tests for system report exporter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\external\system_report_exporter
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_exporter_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Load test fixture
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_export}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function export_provider(): array {
|
||||
return [
|
||||
['With filters' => true],
|
||||
['Without filters' => false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Text execute method
|
||||
*
|
||||
* @param bool $withfilters
|
||||
*
|
||||
* @dataProvider export_provider
|
||||
*/
|
||||
public function test_export(bool $withfilters): void {
|
||||
global $PAGE;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Prevent debug warnings from flexible_table.
|
||||
$PAGE->set_url(new moodle_url('/'));
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(), '', '', 0,
|
||||
['withfilters' => $withfilters]);
|
||||
|
||||
$exporter = new system_report_exporter($systemreport->get_report_persistent(), [
|
||||
'source' => $systemreport,
|
||||
'parameters' => json_encode($systemreport->get_parameters()),
|
||||
]);
|
||||
|
||||
$data = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
|
||||
$this->assertNotEmpty($data->table);
|
||||
|
||||
if ($withfilters) {
|
||||
$this->assertEquals('{"withfilters":true}', $data->parameters);
|
||||
$this->assertTrue($data->filterspresent);
|
||||
$this->assertNotEmpty($data->filtersform);
|
||||
} else {
|
||||
$this->assertEquals('{"withfilters":false}', $data->parameters);
|
||||
$this->assertFalse($data->filterspresent);
|
||||
$this->assertEmpty($data->filtersform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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 core_reportbuilder\local\entities\course;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Testable system report fixture for testing the course entity
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_entity_report extends system_report {
|
||||
|
||||
/**
|
||||
* Initialise the report
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
$entity = new course();
|
||||
$coursetablealias = $entity->get_table_alias('course');
|
||||
$param = database::generate_param_name();
|
||||
|
||||
$this->set_main_table('course', $coursetablealias);
|
||||
$this->add_entity($entity);
|
||||
// Add a base condition to hide the site course.
|
||||
$this->add_base_condition_sql("$coursetablealias.id <> :$param", [$param => SITEID]);
|
||||
|
||||
$columns = [];
|
||||
foreach ($entity->get_columns() as $column) {
|
||||
$columns[] = $column->get_unique_identifier();
|
||||
}
|
||||
$this->add_columns_from_entities($columns);
|
||||
|
||||
$filters = [];
|
||||
foreach ($entity->get_filters() as $filter) {
|
||||
$filters[] = $filter->get_unique_identifier();
|
||||
}
|
||||
$this->add_filters_from_entities($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we can view the report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set availability of report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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 core_reportbuilder\local\report\action;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
|
||||
/**
|
||||
* Testable system report fixture
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_available extends system_report {
|
||||
|
||||
/**
|
||||
* Initialise the report
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
$this->set_main_table('user', 'u');
|
||||
$this->annotate_entity('user', new lang_string('user'));
|
||||
|
||||
$this->add_column((new column(
|
||||
'username',
|
||||
new lang_string('username'),
|
||||
'user'
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field('u.firstname')
|
||||
);
|
||||
|
||||
$withfilters = $this->get_parameter('withfilters', false, PARAM_BOOL);
|
||||
if ($withfilters) {
|
||||
$this->add_filter((new filter(
|
||||
text::class,
|
||||
'username',
|
||||
new lang_string('username'),
|
||||
'user',
|
||||
'u.username'
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
);
|
||||
}
|
||||
|
||||
$withactions = $this->get_parameter('withactions', false, PARAM_BOOL);
|
||||
if ($withactions) {
|
||||
$this->add_action(new action(
|
||||
new moodle_url('/user/profile.php', ['id' => ':id']),
|
||||
new pix_icon('e/search', get_string('view')),
|
||||
[],
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we can view the report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set availability of report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/system_report_available.php');
|
||||
|
||||
/**
|
||||
* Testable unavailable system report fixture
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_unavailable extends system_report_available {
|
||||
|
||||
/**
|
||||
* Explicitly set availability of report to false
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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 stdClass;
|
||||
use core_reportbuilder\table\system_report_table;
|
||||
|
||||
/**
|
||||
* Testable system report table for getting report data
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_system_report_table extends system_report_table {
|
||||
|
||||
/**
|
||||
* Format each row of returned data, replacing aliased column names with the original
|
||||
*
|
||||
* @param array|stdClass $row
|
||||
* @return array
|
||||
*/
|
||||
public function format_row($row): array {
|
||||
$record = parent::format_row($row);
|
||||
$result = [];
|
||||
|
||||
$columns = $this->systemreport->get_columns();
|
||||
foreach ($columns as $column) {
|
||||
$result[$column->get_name()] = $record[$column->get_column_alias()];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all table rows
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_table_rows(): array {
|
||||
global $PAGE;
|
||||
|
||||
$PAGE->set_url('/');
|
||||
|
||||
$result = [];
|
||||
|
||||
$this->guess_base_url();
|
||||
$this->setup();
|
||||
|
||||
$this->query_db(0, false);
|
||||
foreach ($this->rawdata as $record) {
|
||||
$result[] = $this->format_row($record);
|
||||
}
|
||||
|
||||
$this->close_recordset();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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 core_reportbuilder\local\entities\user;
|
||||
|
||||
/**
|
||||
* Testable system report fixture for testing the user entity
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_entity_report extends system_report {
|
||||
|
||||
/**
|
||||
* Initialise the report
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
$entity = new user();
|
||||
|
||||
$this->set_main_table('user', $entity->get_table_alias('user'));
|
||||
$this->add_entity($entity);
|
||||
|
||||
$columns = [];
|
||||
foreach ($entity->get_columns() as $column) {
|
||||
$columns[] = $column->get_unique_identifier();
|
||||
}
|
||||
$this->add_columns_from_entities($columns);
|
||||
|
||||
$filters = [];
|
||||
foreach ($entity->get_filters() as $filter) {
|
||||
$filters[] = $filter->get_unique_identifier();
|
||||
}
|
||||
$this->add_filters_from_entities($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we can view the report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set availability of report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
<?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\entities;
|
||||
|
||||
use advanced_testcase;
|
||||
use coding_exception;
|
||||
use context_system;
|
||||
use lang_string;
|
||||
use ReflectionClass;
|
||||
use core_reportbuilder\course_entity_report;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\testable_system_report_table;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
|
||||
/**
|
||||
* Unit tests for course entity
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\entities\base
|
||||
* @covers \core_reportbuilder\local\entities\course
|
||||
* @covers \core_reportbuilder\local\helpers\custom_fields
|
||||
* @covers \core_reportbuilder\local\report\base
|
||||
* @covers \core_reportbuilder\system_report
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Load required classes
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/course/lib.php");
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/testable_system_report_table.php");
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/course_entity_report.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate courses for the tests
|
||||
*/
|
||||
public function generate_courses(): array {
|
||||
$coursecategory1 = $this->getDataGenerator()->create_category();
|
||||
$course1 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => 'Course 1',
|
||||
'shortname' => 'C1',
|
||||
'idnumber' => 'IDNumber1',
|
||||
'visible' => 1,
|
||||
'startdate' => 289308600,
|
||||
'enddate' => 3445023600,
|
||||
'category' => $coursecategory1->id,
|
||||
'groupmode' => NOGROUPS,
|
||||
'enablecompletion' => 1,
|
||||
'downloadcontent' => DOWNLOAD_COURSE_CONTENT_DISABLED,
|
||||
'format' => 'topics',
|
||||
'calendartype' => 'Gregorian',
|
||||
'theme' => 'afterburner',
|
||||
'lang' => 'en',
|
||||
]);
|
||||
|
||||
$coursecategory2 = $this->getDataGenerator()->create_category();
|
||||
$course2 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => 'Course 2',
|
||||
'shortname' => 'C2',
|
||||
'idnumber' => 'IDNumber2',
|
||||
'visible' => 0,
|
||||
'startdate' => 1614726000,
|
||||
'enddate' => 1961881200,
|
||||
'category' => $coursecategory2->id,
|
||||
'groupmode' => SEPARATEGROUPS,
|
||||
'enablecompletion' => 0,
|
||||
'downloadcontent' => DOWNLOAD_COURSE_CONTENT_ENABLED,
|
||||
'format' => 'topics',
|
||||
'calendartype' => 'Gregorian',
|
||||
'theme' => 'afterburner',
|
||||
'lang' => 'es',
|
||||
]);
|
||||
|
||||
return [$coursecategory1, $course1, $coursecategory2, $course2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test callbacks are correctly applied for those columns using them
|
||||
*/
|
||||
public function test_get_columns_with_callbacks(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[$coursecategory1, $course1] = $this->generate_courses();
|
||||
$testdate = time();
|
||||
|
||||
// Add some customfields to the course.
|
||||
$cfgenerator = self::getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
$params = [
|
||||
'component' => 'core_course',
|
||||
'area' => 'course',
|
||||
'itemid' => 0,
|
||||
'contextid' => context_system::instance()->id
|
||||
];
|
||||
$category = $cfgenerator->create_category($params);
|
||||
$field1 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Customfield text 1', 'shortname' => 'cf1']);
|
||||
$cfgenerator->add_instance_data($field1, (int)$course1->id, 'Do. Or do not. There is no try');
|
||||
$field2 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Customfield text 2', 'shortname' => 'cf2']);
|
||||
$cfgenerator->add_instance_data($field2, (int)$course1->id, 'Chewie, we are home');
|
||||
$field3 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'checkbox', 'name' => 'Customfield checkbox', 'shortname' => 'cf3']);
|
||||
$cfgenerator->add_instance_data($field3, (int)$course1->id, 1);
|
||||
$field4 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'date', 'name' => 'Customfield date', 'shortname' => 'cf4']);
|
||||
$cfgenerator->add_instance_data($field4, (int)$course1->id, $testdate);
|
||||
$field5 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'select', 'name' => 'Customfield menu', 'shortname' => 'cf5',
|
||||
'configdata' => ['defaultvalue' => 'Option A', 'options' => "Option A\nOption B\nOption C"]]);
|
||||
// Select option C for course1 (options are counted starting from one).
|
||||
$cfgenerator->add_instance_data($field5, (int)$course1->id, 3);
|
||||
|
||||
$tablerows = $this->get_report_table_rows();
|
||||
$courserows = array_filter($tablerows, static function(array $row) use ($course1): bool {
|
||||
return $row['shortname'] === $course1->shortname;
|
||||
});
|
||||
$courserow = reset($courserows);
|
||||
|
||||
$this->assertEquals($coursecategory1->name, $courserow['category']);
|
||||
$this->assertEquals('Course 1', $courserow['fullname']);
|
||||
$this->assertEquals('C1', $courserow['shortname']);
|
||||
$this->assertEquals('IDNumber1', $courserow['idnumber']);
|
||||
$this->assertEquals('Yes', $courserow['visible']);
|
||||
$this->assertEquals(userdate(289308600), $courserow['startdate']);
|
||||
$this->assertEquals(userdate(3445023600), $courserow['enddate']);
|
||||
$this->assertEquals('No groups', $courserow['groupmode']);
|
||||
$this->assertEquals('Yes', $courserow['enablecompletion']);
|
||||
$this->assertEquals('No', $courserow['downloadcontent']);
|
||||
$this->assertEquals('Topics format', $courserow['format']);
|
||||
$this->assertEquals('Gregorian', $courserow['calendartype']);
|
||||
$this->assertEquals('afterburner', $courserow['theme']);
|
||||
$this->assertEquals(get_string_manager()->get_list_of_translations()['en'], $courserow['lang']);
|
||||
$expected = '<a href="https://www.example.com/moodle/course/view.php?id=' . $course1->id . '">Course 1</a>';
|
||||
$this->assertEquals($expected, $courserow['coursefullnamewithlink']);
|
||||
$expected = '<a href="https://www.example.com/moodle/course/view.php?id=' . $course1->id . '">C1</a>';
|
||||
$this->assertEquals($expected, $courserow['courseshortnamewithlink']);
|
||||
$expected = '<a href="https://www.example.com/moodle/course/view.php?id=' . $course1->id . '">IDNumber1</a>';
|
||||
$this->assertEquals($expected, $courserow['courseidnumberewithlink']);
|
||||
$this->assertEquals('Do. Or do not. There is no try', $courserow['customfield_cf1']);
|
||||
$this->assertEquals('Chewie, we are home', $courserow['customfield_cf2']);
|
||||
$this->assertEquals('Yes', $courserow['customfield_cf3']);
|
||||
$this->assertEquals(userdate($testdate), $courserow['customfield_cf4']);
|
||||
$this->assertEquals('Option C', $courserow['customfield_cf5']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filtering report by course fields
|
||||
*/
|
||||
public function test_filters(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[$coursecategory1] = $this->generate_courses();
|
||||
|
||||
// Filter by fullname field.
|
||||
$filtervalues = [
|
||||
'course:fullname_operator' => text::IS_EQUAL_TO,
|
||||
'course:fullname_value' => 'Course 1',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 1',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by shortname field.
|
||||
$filtervalues = [
|
||||
'course:shortname_operator' => text::IS_EQUAL_TO,
|
||||
'course:shortname_value' => 'C1',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 1',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by idnumber field.
|
||||
$filtervalues = [
|
||||
'course:idnumber_operator' => text::IS_EQUAL_TO,
|
||||
'course:idnumber_value' => 'IDNumber2',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 2',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by visible field.
|
||||
$filtervalues = [
|
||||
'course:visible_operator' => boolean_select::NOT_CHECKED,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 2',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by startdate field.
|
||||
$filtervalues = [
|
||||
'course:startdate_operator' => date::DATE_RANGE,
|
||||
'course:startdate_from' => 289135800,
|
||||
'course:startdate_to' => 289740600,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 1',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by category field.
|
||||
$filtervalues = [
|
||||
'course:category_operator' => select::EQUAL_TO,
|
||||
'course:category_value' => $coursecategory1->id,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 1',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by group mode field.
|
||||
$filtervalues = [
|
||||
'course:groupmode_operator' => select::EQUAL_TO,
|
||||
'course:groupmode_value' => SEPARATEGROUPS,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 2',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by enable completion field.
|
||||
$filtervalues = [
|
||||
'course:enablecompletion_operator' => boolean_select::CHECKED,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Course 1',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filtering report by course customfield
|
||||
*/
|
||||
public function test_customfield_text_filter(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => 'Philosophy and Superheroes',
|
||||
'shortname' => 'PS1',
|
||||
]);
|
||||
$course2 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => 'The game of Mathematics',
|
||||
'shortname' => 'GM1',
|
||||
]);
|
||||
|
||||
// Add some customfields to the course.
|
||||
$cfgenerator = self::getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
$params = [
|
||||
'component' => 'core_course',
|
||||
'area' => 'course',
|
||||
'itemid' => 0,
|
||||
'contextid' => context_system::instance()->id
|
||||
];
|
||||
$category = $cfgenerator->create_category($params);
|
||||
$field = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Customfield text 1', 'shortname' => 'cf']);
|
||||
$cfgenerator->add_instance_data($field, (int)$course1->id, 'Leia Organa');
|
||||
$cfgenerator->add_instance_data($field, (int)$course2->id, 'Han Solo');
|
||||
$field5 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'select', 'name' => 'Customfield menu', 'shortname' => 'cf5',
|
||||
'configdata' => ['defaultvalue' => 'Option A', 'options' => "Option A\nOption B\nOption C"]]);
|
||||
$cfgenerator->add_instance_data($field5, (int)$course1->id, 3);
|
||||
$cfgenerator->add_instance_data($field5, (int)$course2->id, 2);
|
||||
|
||||
$filtervalues = [
|
||||
'course:customfield_cf_operator' => text::IS_EQUAL_TO,
|
||||
'course:customfield_cf_value' => 'Leia Organa',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Philosophy and Superheroes',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
$filtervalues = [
|
||||
'course:customfield_cf_operator' => text::IS_EQUAL_TO,
|
||||
'course:customfield_cf_value' => 'Han Solo',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'The game of Mathematics',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by menu customfield.
|
||||
$filtervalues = [
|
||||
'course:customfield_cf5_operator' => select::EQUAL_TO,
|
||||
'course:customfield_cf5_value' => 3, // Option C.
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Philosophy and Superheroes',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by course customfield that doesn't exist.
|
||||
$filtervalues = [
|
||||
'course:customfield_cf_operator' => text::IS_EQUAL_TO,
|
||||
'course:customfield_cf_value' => 'Luke Skywalker',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEmpty($tablerows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create the report, and return it's rows
|
||||
*
|
||||
* @param array $filtervalues
|
||||
* @return array
|
||||
*/
|
||||
private function get_report_table_rows(array $filtervalues = []): array {
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => course_entity_report::TYPE_SYSTEM_REPORT,
|
||||
'source' => course_entity_report::class,
|
||||
]);
|
||||
|
||||
user_filter_manager::set($report->get('id'), $filtervalues);
|
||||
|
||||
return testable_system_report_table::create($report->get('id'), [])->get_table_rows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity table alias
|
||||
*/
|
||||
public function test_table_alias(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->assertEquals('c', $courseentity->get_table_alias('course'));
|
||||
|
||||
$courseentity->set_table_alias('course', 'newalias');
|
||||
$this->assertEquals('newalias', $courseentity->get_table_alias('course'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for invalid get table alias
|
||||
*/
|
||||
public function test_get_table_alias_invalid(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
|
||||
'Invalid table name (nonexistingalias)');
|
||||
$courseentity->get_table_alias('nonexistingalias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid entity set table alias
|
||||
*/
|
||||
public function test_set_table_alias_invalid(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: Invalid table name (nonexistent)');
|
||||
$courseentity->set_table_alias('nonexistent', 'newalias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity name
|
||||
*/
|
||||
public function test_name(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->assertEquals('course', $courseentity->get_entity_name());
|
||||
|
||||
$courseentity->set_entity_name('newentityname');
|
||||
$this->assertEquals('newentityname', $courseentity->get_entity_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid entity name
|
||||
*/
|
||||
public function test_name_invalid(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Entity name must be comprised of alphanumeric character, underscore or dash');
|
||||
$courseentity->set_entity_name('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity title
|
||||
*/
|
||||
public function test_title(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->assertEquals(new lang_string('entitycourse', 'core_reportbuilder'), $courseentity->get_entity_title());
|
||||
|
||||
$newtitle = new lang_string('fullname');
|
||||
$courseentity->set_entity_title($newtitle);
|
||||
$this->assertEquals($newtitle, $courseentity->get_entity_title());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding single join
|
||||
*/
|
||||
public function test_add_join(): void {
|
||||
$courseentity = (new course())
|
||||
->set_table_alias('course', 'c1');
|
||||
|
||||
$tablejoin = "JOIN {course} c2 ON c2.id = c1.id";
|
||||
$courseentity->add_join($tablejoin);
|
||||
|
||||
$method = (new ReflectionClass(course::class))->getMethod('get_joins');
|
||||
$method->setAccessible(true);
|
||||
$this->assertEquals([$tablejoin], $method->invoke($courseentity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding multiple join
|
||||
*/
|
||||
public function test_add_joins(): void {
|
||||
$courseentity = (new course())
|
||||
->set_table_alias('course', 'c1');
|
||||
|
||||
$tablejoins = [
|
||||
"JOIN {course} c2 ON c2.id = c1.id",
|
||||
"JOIN {course} c3 ON c3.id = c1.id",
|
||||
];
|
||||
$courseentity->add_joins($tablejoins);
|
||||
|
||||
$method = (new ReflectionClass(course::class))->getMethod('get_joins');
|
||||
$method->setAccessible(true);
|
||||
$this->assertEquals($tablejoins, $method->invoke($courseentity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for invalid get column
|
||||
*/
|
||||
public function test_get_column_invalid(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
|
||||
'Invalid column name (nonexistingcolumn)');
|
||||
$courseentity->get_column('nonexistingcolumn');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for invalid get filter
|
||||
*/
|
||||
public function test_get_filter_invalid(): void {
|
||||
$courseentity = new course();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
|
||||
'Invalid filter name (nonexistingfilter)');
|
||||
$courseentity->get_filter('nonexistingfilter');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?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\entities;
|
||||
|
||||
use advanced_testcase;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use moodle_url;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\testable_system_report_table;
|
||||
use core_reportbuilder\user_entity_report;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
|
||||
/**
|
||||
* Unit tests for user entity
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\entities\base
|
||||
* @covers \core_reportbuilder\local\entities\user
|
||||
* @covers \core_reportbuilder\local\helpers\user_profile_fields
|
||||
* @covers \core_reportbuilder\local\report\base
|
||||
* @covers \core_reportbuilder\system_report
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Load required classes
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/testable_system_report_table.php");
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/user_entity_report.php");
|
||||
require_once("{$CFG->dirroot}/user/profile/lib.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test callbacks are correctly applied for those columns using them
|
||||
*/
|
||||
public function test_columns_with_callbacks(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Add a couple of user profile fields to show on the report.
|
||||
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text',
|
||||
'shortname' => 'favcolor', 'name' => 'Favorite color']);
|
||||
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text',
|
||||
'shortname' => 'favsuperpower', 'name' => 'Favorite super power']);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user([
|
||||
'suspended' => 1,
|
||||
'confirmed' => 0,
|
||||
'country' => 'ES',
|
||||
'profile_field_favcolor' => 'Blue',
|
||||
'profile_field_favsuperpower' => 'Time travel',
|
||||
]);
|
||||
|
||||
$tablerows = $this->get_report_table_rows();
|
||||
$userrows = array_filter($tablerows, static function(array $row) use ($user): bool {
|
||||
return $row['username'] === $user->username;
|
||||
});
|
||||
$userrow = reset($userrows);
|
||||
|
||||
$this->assertEquals('Yes', $userrow['suspended']);
|
||||
$this->assertEquals('No', $userrow['confirmed']);
|
||||
$this->assertEquals('Spain', $userrow['country']);
|
||||
$this->assertEquals('Blue', $userrow['profilefield_favcolor']);
|
||||
$this->assertEquals('Time travel', $userrow['profilefield_favsuperpower']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the formatted user fullname columns
|
||||
*/
|
||||
public function test_fullname_columns(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user([]);
|
||||
|
||||
$tablerows = $this->get_report_table_rows();
|
||||
$userrows = array_filter($tablerows, static function(array $row) use ($user): bool {
|
||||
return $row['username'] === $user->username;
|
||||
});
|
||||
$userrow = reset($userrows);
|
||||
|
||||
$userfullname = fullname($user);
|
||||
$userprofile = (new moodle_url('/user/profile.php', ['id' => $user->id]))->out();
|
||||
$userpicture = $OUTPUT->user_picture($user, ['link' => false, 'alttext' => false]);
|
||||
|
||||
$this->assertEquals($userfullname, $userrow['fullname']);
|
||||
$this->assertEquals('<a href="' . $userprofile . '">' . $userfullname . '</a>', $userrow['fullnamewithlink']);
|
||||
$this->assertEquals($userpicture . $userfullname, $userrow['fullnamewithpicture']);
|
||||
$this->assertEquals('<a href="' . $userprofile . '">' . $userpicture . $userfullname . '</a>',
|
||||
$userrow['fullnamewithpicturelink']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test picture column callback
|
||||
*/
|
||||
public function test_picture_column(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user([]);
|
||||
|
||||
$tablerows = $this->get_report_table_rows();
|
||||
$userrows = array_filter($tablerows, static function(array $row) use ($user): bool {
|
||||
return $row['username'] === $user->username;
|
||||
});
|
||||
$userrow = reset($userrows);
|
||||
|
||||
$userpicture = $OUTPUT->user_picture($user, ['link' => false, 'alttext' => false]);
|
||||
$this->assertEquals($userpicture, $userrow['picture']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filtering report by user fields
|
||||
*/
|
||||
public function test_filters(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->getDataGenerator()->create_user(['firstname' => 'Daffy', 'lastname' => 'Duck', 'email' => '[email protected]',
|
||||
'city' => 'LA', 'lastaccess' => time() - YEARSECS, 'suspended' => 1]);
|
||||
$this->getDataGenerator()->create_user(['firstname' => 'Donald', 'lastname' => 'Duck', 'email' => '[email protected]',
|
||||
'city' => 'Chicago', 'lastaccess' => time(), 'suspended' => 0]);
|
||||
|
||||
// Filter by fullname field.
|
||||
$filtervalues = [
|
||||
'user:fullname_operator' => text::IS_EQUAL_TO,
|
||||
'user:fullname_value' => 'Daffy Duck',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Daffy Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by firstname field.
|
||||
$filtervalues = [
|
||||
'user:firstname_operator' => text::CONTAINS,
|
||||
'user:firstname_value' => 'Donald',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Donald Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by lastname field.
|
||||
$filtervalues = [
|
||||
'user:lastname_operator' => text::CONTAINS,
|
||||
'user:lastname_value' => 'Duck',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'Donald Duck',
|
||||
'Daffy Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by email field.
|
||||
$filtervalues = [
|
||||
'user:email_operator' => text::IS_EQUAL_TO,
|
||||
'user:email_value' => '[email protected]',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Donald Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by city field.
|
||||
$filtervalues = [
|
||||
'user:city_operator' => text::IS_EQUAL_TO,
|
||||
'user:city_value' => 'Chicago',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Donald Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by city field.
|
||||
$filtervalues = [
|
||||
'user:city_operator' => text::IS_EQUAL_TO,
|
||||
'user:city_value' => 'Chicago',
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Donald Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by lastaccess field.
|
||||
$filtervalues = [
|
||||
'user:lastaccess_operator' => date::DATE_RANGE,
|
||||
'user:lastaccess_from' => time() - YEARSECS - 100,
|
||||
'user:lastaccess_to' => time() - YEARSECS + 100,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Daffy Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
|
||||
// Filter by suspened field.
|
||||
$filtervalues = [
|
||||
'user:suspended_operator' => boolean_select::CHECKED,
|
||||
];
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Daffy Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filtering report by a user profile field
|
||||
*/
|
||||
public function test_userprofilefield_filter(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Add a user profile field to show on the report.
|
||||
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text',
|
||||
'shortname' => 'favcolor', 'name' => 'Favorite color']);
|
||||
|
||||
$this->getDataGenerator()->create_user(['firstname' => 'Daffy', 'lastname' => 'Duck', 'profile_field_favcolor' => 'Blue']);
|
||||
$this->getDataGenerator()->create_user(['firstname' => 'Donald', 'lastname' => 'Duck',
|
||||
'profile_field_favcolor' => 'Green']);
|
||||
|
||||
$filtervalues = [
|
||||
'user:profilefield_favcolor_operator' => text::IS_EQUAL_TO,
|
||||
'user:profilefield_favcolor_value' => 'Green',
|
||||
];
|
||||
|
||||
$tablerows = $this->get_report_table_rows($filtervalues);
|
||||
$this->assertEquals([
|
||||
'Donald Duck',
|
||||
], array_column($tablerows, 'fullname'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the helper method for selecting all of a users' name fields
|
||||
*/
|
||||
public function test_get_name_fields_select(): void {
|
||||
global $DB;
|
||||
|
||||
$fields = user::get_name_fields_select('u');
|
||||
$user = $DB->get_record_sql("SELECT {$fields} FROM {user} u WHERE username = :username", ['username' => 'admin']);
|
||||
|
||||
// Ensure we received back all name fields.
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'firstname',
|
||||
'lastname',
|
||||
'firstnamephonetic',
|
||||
'lastnamephonetic',
|
||||
'middlename',
|
||||
'alternatename',
|
||||
], array_keys((array) $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create the report, and return it's rows
|
||||
*
|
||||
* @param array $filtervalues
|
||||
* @return array
|
||||
*/
|
||||
private function get_report_table_rows(array $filtervalues = []): array {
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => user_entity_report::TYPE_SYSTEM_REPORT,
|
||||
'source' => user_entity_report::class,
|
||||
]);
|
||||
|
||||
user_filter_manager::set($report->get('id'), $filtervalues);
|
||||
|
||||
return testable_system_report_table::create($report->get('id'), [])->get_table_rows();
|
||||
}
|
||||
}
|
||||
@@ -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\local\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for boolean report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\boolean_select
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class boolean_select_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter_simple_provider(): array {
|
||||
return [
|
||||
[boolean_select::ANY_VALUE, true],
|
||||
[boolean_select::CHECKED, true],
|
||||
[boolean_select::NOT_CHECKED, false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*
|
||||
* @param int $operator
|
||||
* @param bool $expectuser
|
||||
*
|
||||
* @dataProvider get_sql_filter_simple_provider
|
||||
*/
|
||||
public function test_get_sql_filter_simple(int $operator, bool $expectuser): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user([
|
||||
'suspended' => 1,
|
||||
]);
|
||||
|
||||
$filter = new filter(
|
||||
boolean_select::class,
|
||||
'test',
|
||||
new lang_string('yes'),
|
||||
'testentity',
|
||||
'suspended'
|
||||
);
|
||||
|
||||
// Create instance of our filter, passing given operator.
|
||||
[$select, $params] = boolean_select::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
]);
|
||||
|
||||
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
|
||||
if ($expectuser) {
|
||||
$this->assertContains($user->username, $usernames);
|
||||
} else {
|
||||
$this->assertNotContains($user->username, $usernames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for course selector filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\course_selector
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_selector_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*/
|
||||
public function test_get_sql_filter(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "Time travel",
|
||||
]);
|
||||
|
||||
$course2 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "Quantum computing",
|
||||
]);
|
||||
|
||||
$course3 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "Space travel",
|
||||
]);
|
||||
|
||||
$filter = new filter(
|
||||
course_selector::class,
|
||||
'test',
|
||||
new \lang_string('course'),
|
||||
'testentity',
|
||||
'id'
|
||||
);
|
||||
|
||||
// Create instance of our filter, passing given courses ids.
|
||||
[$select, $params] = text::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_values' => [$course1->id, $course3->id],
|
||||
]);
|
||||
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
|
||||
$this->assertEqualsCanonicalizing(['Time travel', 'Space travel'], $fullnames);
|
||||
|
||||
// Test without passing any course id.
|
||||
[$select, $params] = text::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_values' => [],
|
||||
]);
|
||||
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
|
||||
$this->assertEqualsCanonicalizing(['Time travel', 'Quantum computing', 'Space travel', 'PHPUnit test site'], $fullnames);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?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\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for date report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\date
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class date_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter_simple_provider(): array {
|
||||
return [
|
||||
[date::DATE_ANY, true],
|
||||
[date::DATE_NOT_EMPTY, true],
|
||||
[date::DATE_EMPTY, false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*
|
||||
* @param int $operator
|
||||
* @param bool $expectuser
|
||||
*
|
||||
* @dataProvider get_sql_filter_simple_provider
|
||||
*/
|
||||
public function test_get_sql_filter_simple(int $operator, bool $expectuser): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user([
|
||||
'timecreated' => 12345,
|
||||
]);
|
||||
|
||||
$filter = new filter(
|
||||
date::class,
|
||||
'test',
|
||||
new lang_string('yes'),
|
||||
'testentity',
|
||||
'timecreated'
|
||||
);
|
||||
|
||||
// Create instance of our filter, passing given operator.
|
||||
[$select, $params] = date::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
]);
|
||||
|
||||
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
|
||||
if ($expectuser) {
|
||||
$this->assertContains($user->username, $usernames);
|
||||
} else {
|
||||
$this->assertNotContains($user->username, $usernames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL while specifying a date range
|
||||
*/
|
||||
public function test_get_sql_filter_date_range(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$userone = $this->getDataGenerator()->create_user(['timecreated' => 50]);
|
||||
$usertwo = $this->getDataGenerator()->create_user(['timecreated' => 100]);
|
||||
|
||||
$filter = new filter(
|
||||
date::class,
|
||||
'test',
|
||||
new lang_string('yes'),
|
||||
'testentity',
|
||||
'timecreated'
|
||||
);
|
||||
|
||||
// Create instance of our date range filter.
|
||||
[$select, $params] = date::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => date::DATE_RANGE,
|
||||
$filter->get_unique_identifier() . '_from' => 80,
|
||||
$filter->get_unique_identifier() . '_to' => 120,
|
||||
]);
|
||||
|
||||
// The only matching user should be our first test user.
|
||||
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
|
||||
$this->assertEquals([$usertwo->username], $usernames);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?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\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for number report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\number
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class number_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function get_sql_filter_simple_provider(): array {
|
||||
return [
|
||||
[number::ANY_VALUE, null, null, true],
|
||||
[number::IS_NOT_EMPTY, null, null, true],
|
||||
[number::IS_EMPTY, null, null, false],
|
||||
[number::LESS_THAN, 1, null, false],
|
||||
[number::LESS_THAN, 123, null, false],
|
||||
[number::LESS_THAN, 124, null, true],
|
||||
[number::GREATER_THAN, 1, null, true],
|
||||
[number::GREATER_THAN, 123, null, false],
|
||||
[number::GREATER_THAN, 124, null, false],
|
||||
[number::EQUAL_TO, 123, null, true],
|
||||
[number::EQUAL_TO, 124, null, false],
|
||||
[number::EQUAL_OR_LESS_THAN, 124, null, true],
|
||||
[number::EQUAL_OR_LESS_THAN, 123, null, true],
|
||||
[number::EQUAL_OR_LESS_THAN, 122, null, false],
|
||||
[number::EQUAL_OR_GREATER_THAN, 122, null, true],
|
||||
[number::EQUAL_OR_GREATER_THAN, 123, null, true],
|
||||
[number::EQUAL_OR_GREATER_THAN, 124, null, false],
|
||||
[number::RANGE, 122, 124, true],
|
||||
[number::RANGE, 124, 125, false],
|
||||
[number::RANGE, 122, 123, true],
|
||||
[number::RANGE, 123, 124, true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*
|
||||
* @param int $operator
|
||||
* @param int|null $value1
|
||||
* @param int|null $value2
|
||||
* @param bool $expectmatch
|
||||
*
|
||||
* @dataProvider get_sql_filter_simple_provider
|
||||
*/
|
||||
public function test_get_sql_filter_simple(int $operator, ?int $value1, ?int $value2, bool $expectmatch): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([
|
||||
'timecreated' => 123,
|
||||
]);
|
||||
|
||||
$filter = new filter(
|
||||
number::class,
|
||||
'test',
|
||||
new lang_string('course'),
|
||||
'testentity',
|
||||
'timecreated'
|
||||
);
|
||||
|
||||
// Create instance of our filter, passing given operator.
|
||||
[$select, $params] = number::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_value1' => $value1,
|
||||
$filter->get_unique_identifier() . '_value2' => $value2,
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
]);
|
||||
|
||||
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
|
||||
if ($expectmatch) {
|
||||
$this->assertContains($course->fullname, $fullnames);
|
||||
} else {
|
||||
$this->assertNotContains($course->fullname, $fullnames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_invalid}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function get_sql_filter_invalid_provider(): array {
|
||||
return [
|
||||
[number::LESS_THAN],
|
||||
[number::GREATER_THAN],
|
||||
[number::EQUAL_TO],
|
||||
[number::EQUAL_OR_LESS_THAN],
|
||||
[number::EQUAL_OR_GREATER_THAN],
|
||||
[number::RANGE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL for operators that require values
|
||||
*
|
||||
* @param int $operator
|
||||
*
|
||||
* @dataProvider get_sql_filter_invalid_provider
|
||||
*/
|
||||
public function test_get_sql_filter_invalid(int $operator): void {
|
||||
$filter = new filter(
|
||||
number::class,
|
||||
'test',
|
||||
new lang_string('course'),
|
||||
'testentity',
|
||||
'timecreated'
|
||||
);
|
||||
|
||||
[$select, $params] = number::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
]);
|
||||
|
||||
$this->assertEquals('', $select);
|
||||
$this->assertEquals([], $params);
|
||||
}
|
||||
}
|
||||
@@ -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\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for select report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\select
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class select_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter_simple_provider(): array {
|
||||
return [
|
||||
[select::ANY_VALUE, null, true],
|
||||
[select::EQUAL_TO, 'starwars', true],
|
||||
[select::EQUAL_TO, 'mandalorian', false],
|
||||
[select::NOT_EQUAL_TO, 'starwars', false],
|
||||
[select::NOT_EQUAL_TO, 'mandalorian', true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*
|
||||
* @param int $operator
|
||||
* @param string|null $value
|
||||
* @param bool $expectmatch
|
||||
*
|
||||
* @dataProvider get_sql_filter_simple_provider
|
||||
*/
|
||||
public function test_get_sql_filter_simple(int $operator, ?string $value, bool $expectmatch): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "May the course be with you",
|
||||
'shortname' => 'starwars',
|
||||
]);
|
||||
$course2 = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "This is the course",
|
||||
'shortname' => 'mandalorian',
|
||||
]);
|
||||
|
||||
$filter = (new filter(
|
||||
select::class,
|
||||
'test',
|
||||
new lang_string('course'),
|
||||
'testentity',
|
||||
'shortname'
|
||||
))->set_options([
|
||||
$course1->shortname => $course1->fullname,
|
||||
$course2->shortname => $course2->fullname,
|
||||
]);
|
||||
|
||||
// Create instance of our filter, passing given operator.
|
||||
[$select, $params] = select::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
$filter->get_unique_identifier() . '_value' => $value,
|
||||
]);
|
||||
|
||||
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
|
||||
if ($expectmatch) {
|
||||
$this->assertContains($course1->fullname, $fullnames);
|
||||
} else {
|
||||
$this->assertNotContains($course1->fullname, $fullnames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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\filters;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for text report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\filters\base
|
||||
* @covers \core_reportbuilder\local\filters\text
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class text_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter_simple_provider(): array {
|
||||
return [
|
||||
[text::ANY_VALUE, null, true],
|
||||
[text::CONTAINS, 'looking', true],
|
||||
[text::CONTAINS, 'sky', false],
|
||||
[text::DOES_NOT_CONTAIN, 'sky', true],
|
||||
[text::DOES_NOT_CONTAIN, 'looking', false],
|
||||
[text::IS_EQUAL_TO, "Hello, is it me you're looking for?", true],
|
||||
[text::IS_EQUAL_TO, 'I can see it in your eyes', false],
|
||||
[text::IS_NOT_EQUAL_TO, "Hello, is it me you're looking for?", false],
|
||||
[text::IS_NOT_EQUAL_TO, 'I can see it in your eyes', true],
|
||||
[text::STARTS_WITH, 'Hello', true],
|
||||
[text::STARTS_WITH, 'sunlight', false],
|
||||
[text::ENDS_WITH, 'looking for?', true],
|
||||
[text::ENDS_WITH, 'your heart', false],
|
||||
[text::IS_EMPTY, null, false],
|
||||
[text::IS_NOT_EMPTY, null, true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter SQL
|
||||
*
|
||||
* @param int $operator
|
||||
* @param string|null $value
|
||||
* @param bool $expectmatch
|
||||
*
|
||||
* @dataProvider get_sql_filter_simple_provider
|
||||
*/
|
||||
public function test_get_sql_filter_simple(int $operator, ?string $value, bool $expectmatch): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([
|
||||
'fullname' => "Hello, is it me you're looking for?",
|
||||
]);
|
||||
|
||||
$filter = new filter(
|
||||
text::class,
|
||||
'test',
|
||||
new lang_string('course'),
|
||||
'testentity',
|
||||
'fullname'
|
||||
);
|
||||
|
||||
// Create instance of our filter, passing given operator.
|
||||
[$select, $params] = text::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
$filter->get_unique_identifier() . '_value' => $value,
|
||||
]);
|
||||
|
||||
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
|
||||
if ($expectmatch) {
|
||||
$this->assertContains($course->fullname, $fullnames);
|
||||
} else {
|
||||
$this->assertNotContains($course->fullname, $fullnames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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 advanced_testcase;
|
||||
use core_reportbuilder\local\entities\course;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for custom fields helper
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\custom_fields
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_fields_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Generate a course with customfields
|
||||
*/
|
||||
public function generate_course_with_customfields(): custom_fields {
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Add some customfields to the course.
|
||||
$cfgenerator = self::getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
$params = [
|
||||
'component' => 'core_course',
|
||||
'area' => 'course',
|
||||
'itemid' => 0,
|
||||
'contextid' => \context_system::instance()->id
|
||||
];
|
||||
$category = $cfgenerator->create_category($params);
|
||||
$field1 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Customfield text 1', 'shortname' => 'cf1']);
|
||||
$cfgenerator->add_instance_data($field1, (int)$course->id, 'C-3PO');
|
||||
$field2 = $cfgenerator->create_field(
|
||||
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Customfield text 2', 'shortname' => 'cf2']);
|
||||
$cfgenerator->add_instance_data($field2, (int)$course->id, 'R2-D2');
|
||||
|
||||
$courseentity = new course();
|
||||
$coursealias = $courseentity->get_table_alias('course');
|
||||
|
||||
// Create an instance of the customfields helper.
|
||||
return new custom_fields($coursealias . '.id', $courseentity->get_entity_name(),
|
||||
'core_course', 'course');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_columns
|
||||
*/
|
||||
public function test_get_columns(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$customfields = $this->generate_course_with_customfields();
|
||||
$columns = $customfields->get_columns();
|
||||
$this->assertCount(2, $columns);
|
||||
[$column0, $column1] = $columns;
|
||||
$this->assertInstanceOf(column::class, $column0);
|
||||
$this->assertInstanceOf(column::class, $column1);
|
||||
$this->assertEqualsCanonicalizing(['Customfield text 1', 'Customfield text 2'],
|
||||
[$column0->get_title(), $column1->get_title()]);
|
||||
$this->assertEquals(column::TYPE_TEXT, $column0->get_type());
|
||||
$this->assertEquals('course', $column0->get_entity_name());
|
||||
$this->assertStringStartsWith('LEFT JOIN {customfield_data}', $column0->get_joins()[0]);
|
||||
// Column of type TEXT is sortable.
|
||||
$this->assertTrue($column0->get_is_sortable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_join
|
||||
*/
|
||||
public function test_add_join(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$customfields = $this->generate_course_with_customfields();
|
||||
$columns = $customfields->get_columns();
|
||||
$this->assertCount(1, ($columns[0])->get_joins());
|
||||
|
||||
$customfields->add_join('JOIN {test} t ON t.id = id');
|
||||
$columns = $customfields->get_columns();
|
||||
$this->assertCount(2, ($columns[0])->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_joins
|
||||
*/
|
||||
public function test_add_joins(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$customfields = $this->generate_course_with_customfields();
|
||||
$columns = $customfields->get_columns();
|
||||
$this->assertCount(1, ($columns[0])->get_joins());
|
||||
|
||||
$customfields->add_joins(['JOIN {test} t ON t.id = id', 'JOIN {test2} t2 ON t2.id = id']);
|
||||
$columns = $customfields->get_columns();
|
||||
$this->assertCount(3, ($columns[0])->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_filters
|
||||
*/
|
||||
public function test_get_filters(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$customfields = $this->generate_course_with_customfields();
|
||||
$filters = $customfields->get_filters();
|
||||
$this->assertCount(2, $filters);
|
||||
[$filter0, $filter1] = $filters;
|
||||
$this->assertInstanceOf(filter::class, $filter0);
|
||||
$this->assertInstanceOf(filter::class, $filter1);
|
||||
$this->assertEqualsCanonicalizing(['Customfield text 1', 'Customfield text 2'],
|
||||
[$filter0->get_header(), $filter1->get_header()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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 advanced_testcase;
|
||||
use coding_exception;
|
||||
use core_user;
|
||||
|
||||
/**
|
||||
* Unit tests for the database helper class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\database
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class database_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test generating table alias and parameter names
|
||||
*/
|
||||
public function test_generate_alias_params(): void {
|
||||
global $DB;
|
||||
|
||||
$admin = core_user::get_user_by_username('admin');
|
||||
|
||||
$usertablealias = database::generate_alias();
|
||||
$usertablealiasjoin = database::generate_alias();
|
||||
$useridalias = database::generate_alias();
|
||||
|
||||
$paramuserid = database::generate_param_name();
|
||||
$paramuserdeleted = database::generate_param_name();
|
||||
|
||||
// Ensure they are different.
|
||||
$this->assertNotEquals($usertablealias, $usertablealiasjoin);
|
||||
$this->assertNotEquals($paramuserid, $paramuserdeleted);
|
||||
|
||||
$sql = "SELECT {$usertablealias}.id AS {$useridalias}
|
||||
FROM {user} {$usertablealias}
|
||||
JOIN {user} {$usertablealiasjoin} ON {$usertablealiasjoin}.id = {$usertablealias}.id
|
||||
WHERE {$usertablealias}.id = :{$paramuserid} AND {$usertablealias}.deleted = :{$paramuserdeleted}";
|
||||
$params = [$paramuserid => $admin->id, $paramuserdeleted => 0];
|
||||
|
||||
$validated = database::validate_params($params);
|
||||
$this->assertTrue($validated);
|
||||
|
||||
$record = $DB->get_record_sql($sql, $params);
|
||||
$this->assertEquals($admin->id, $record->{$useridalias});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parameter validation
|
||||
*/
|
||||
public function test_validate_params(): void {
|
||||
$params = [
|
||||
database::generate_param_name() => 1,
|
||||
'invalidfoo' => 2,
|
||||
'invalidbar' => 4,
|
||||
];
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Invalid parameter names (invalidfoo, invalidbar)');
|
||||
database::validate_params($params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 advanced_testcase;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for the format helper
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\format
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class format_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test userdate method
|
||||
*/
|
||||
public function test_userdate(): void {
|
||||
$now = time();
|
||||
|
||||
$userdate = format::userdate($now, new stdClass());
|
||||
$this->assertEquals(userdate($now), $userdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_boolean_as_text}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function boolean_as_text_provider(): array {
|
||||
return [
|
||||
[false, get_string('no')],
|
||||
[true, get_string('yes')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test boolean as text
|
||||
*
|
||||
* @param bool $value
|
||||
* @param string $expected
|
||||
*
|
||||
* @dataProvider boolean_as_text_provider
|
||||
*/
|
||||
public function test_boolean_as_text(bool $value, string $expected): void {
|
||||
$this->assertEquals($expected, format::boolean_as_text($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?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 advanced_testcase;
|
||||
|
||||
/**
|
||||
* Unit tests for the user filter helper
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\user_filter_manager
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_filter_manager_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Helper method to return all user preferences for filters - based on the current storage backend using the same
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_filter_preferences(): array {
|
||||
return array_filter(get_user_preferences(), static function(string $key): bool {
|
||||
return strpos($key, 'reportbuilder-report-') === 0;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_provider(): array {
|
||||
return [
|
||||
'Small value' => ['foo'],
|
||||
'Large value' => [str_repeat('A', 4000)],
|
||||
'Empty value' => [''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter values
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @dataProvider get_provider
|
||||
*/
|
||||
public function test_get(string $value): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$values = [
|
||||
'entity:filter_name' => $value,
|
||||
];
|
||||
user_filter_manager::set(5, $values);
|
||||
|
||||
// Make sure we get the same value back.
|
||||
$this->assertEquals($values, user_filter_manager::get(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter values that once spanned multiple chunks
|
||||
*/
|
||||
public function test_get_large_to_small(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Set a large initial filter value.
|
||||
user_filter_manager::set(5, [
|
||||
'longvalue' => str_repeat('ABCD', 1000),
|
||||
]);
|
||||
|
||||
// Sanity check, there should be 4 (because 4000 characters plus some JSON encoding requires that many chunks).
|
||||
$preferences = $this->get_filter_preferences();
|
||||
$this->assertCount(4, $preferences);
|
||||
|
||||
$values = [
|
||||
'longvalue' => 'ABCD',
|
||||
];
|
||||
user_filter_manager::set(5, $values);
|
||||
|
||||
// Make sure we get the same value back.
|
||||
$this->assertEquals($values, user_filter_manager::get(5));
|
||||
|
||||
// Everything should now fit in a single filter preference.
|
||||
$preferences = $this->get_filter_preferences();
|
||||
$this->assertCount(1, $preferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting filter values that haven't been set
|
||||
*/
|
||||
public function test_get_empty(): void {
|
||||
$this->assertEquals([], user_filter_manager::get(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_reset_all}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function reset_all_provider(): array {
|
||||
return [
|
||||
'Small value' => ['foo'],
|
||||
'Large value' => [str_repeat('A', 4000)],
|
||||
'Empty value' => [''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test resetting all filter values
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @dataProvider reset_all_provider
|
||||
*/
|
||||
public function test_reset_all(string $value): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
user_filter_manager::set(5, [
|
||||
'entity:filter_name' => $value
|
||||
]);
|
||||
|
||||
$reset = user_filter_manager::reset_all(5);
|
||||
$this->assertTrue($reset);
|
||||
|
||||
// We should get an empty array back.
|
||||
$this->assertEquals([], user_filter_manager::get(5));
|
||||
|
||||
// All filter preferences should be removed.
|
||||
$this->assertEmpty($this->get_filter_preferences());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test resetting single filter values
|
||||
*/
|
||||
public function test_reset_single(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
user_filter_manager::set(5, [
|
||||
'entity:filter_name' => 'foo',
|
||||
'entity:filter_value' => 'bar',
|
||||
'entity:other_name' => 'baz',
|
||||
'entity:other_value' => 'bax',
|
||||
]);
|
||||
|
||||
$reset = user_filter_manager::reset_single(5, 'entity:other');
|
||||
$this->assertTrue($reset);
|
||||
|
||||
$this->assertEquals([
|
||||
'entity:filter_name' => 'foo',
|
||||
'entity:filter_value' => 'bar',
|
||||
], user_filter_manager::get(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test merging filter values
|
||||
*/
|
||||
public function test_merge(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$values = [
|
||||
'entity:filter_name' => 'foo',
|
||||
'entity:filter_value' => 'bar',
|
||||
'entity:filter2_name' => 'tree',
|
||||
'entity:filter2_value' => 'house',
|
||||
];
|
||||
|
||||
// Make sure we get the same value back.
|
||||
user_filter_manager::set(5, $values);
|
||||
$this->assertEqualsCanonicalizing($values, user_filter_manager::get(5));
|
||||
|
||||
user_filter_manager::merge(5, [
|
||||
'entity:filter_name' => 'twotimesfoo',
|
||||
'entity:filter_value' => 'twotimesbar',
|
||||
]);
|
||||
|
||||
// Make sure that both values have been changed and the other values have not been modified.
|
||||
$expected = [
|
||||
'entity:filter_name' => 'twotimesfoo',
|
||||
'entity:filter_value' => 'twotimesbar',
|
||||
'entity:filter2_name' => 'tree',
|
||||
'entity:filter2_value' => 'house',
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, user_filter_manager::get(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to get all filters from a given user
|
||||
*/
|
||||
public function test_get_all_for_user(): void {
|
||||
$this->resetAfterTest();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
$filtervalues1 = [
|
||||
'entity:filter_name' => 'foo',
|
||||
'entity:filter_value' => 'bar',
|
||||
'entity:other_name' => 'baz',
|
||||
'entity:other_value' => 'bax',
|
||||
];
|
||||
user_filter_manager::set(5, $filtervalues1);
|
||||
|
||||
$filtervalues2 = [
|
||||
'entity:filter_name' => 'blue',
|
||||
'entity:filter_value' => 'red',
|
||||
];
|
||||
user_filter_manager::set(9, $filtervalues2);
|
||||
|
||||
$this->setAdminUser();
|
||||
$values = user_filter_manager::get_all_for_user((int)$user->id);
|
||||
$this->assertEqualsCanonicalizing([$filtervalues1, $filtervalues2], [reset($values), end($values)]);
|
||||
|
||||
// Check for a user with no filters.
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$values = user_filter_manager::get_all_for_user((int)$user2->id);
|
||||
$this->assertEmpty($values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?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 advanced_testcase;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
|
||||
/**
|
||||
* Unit tests for user profile fields helper
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\user_profile_fields
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_profile_fields_test_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Generate userprofilefields
|
||||
*/
|
||||
private function generate_userprofilefields(): user_profile_fields {
|
||||
$this->getDataGenerator()->create_custom_profile_field([
|
||||
'shortname' => 'upf1', 'name' => 'User profile field text 1', 'datatype' => 'text']);
|
||||
|
||||
$this->getDataGenerator()->create_custom_profile_field([
|
||||
'shortname' => 'upf2', 'name' => 'User profile field text 2', 'datatype' => 'text']);
|
||||
|
||||
$userentity = new user();
|
||||
$useralias = $userentity->get_table_alias('user');
|
||||
|
||||
// Create an instance of the userprofilefield helper.
|
||||
return new user_profile_fields("$useralias.id", $userentity->get_entity_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_columns
|
||||
*/
|
||||
public function test_get_columns(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$userprofilefields = $this->generate_userprofilefields();
|
||||
$columns = $userprofilefields->get_columns();
|
||||
$this->assertCount(2, $columns);
|
||||
[$column0, $column1] = $columns;
|
||||
$this->assertInstanceOf(column::class, $column0);
|
||||
$this->assertInstanceOf(column::class, $column1);
|
||||
$this->assertEqualsCanonicalizing(['User profile field text 1', 'User profile field text 2'],
|
||||
[$column0->get_title(), $column1->get_title()]);
|
||||
$this->assertEquals(column::TYPE_TEXT, $column0->get_type());
|
||||
$this->assertEquals('user', $column0->get_entity_name());
|
||||
$this->assertStringStartsWith('LEFT JOIN {user_info_data}', $column0->get_joins()[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_join
|
||||
*/
|
||||
public function test_add_join(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$userprofilefields = $this->generate_userprofilefields();
|
||||
$columns = $userprofilefields->get_columns();
|
||||
$this->assertCount(1, ($columns[0])->get_joins());
|
||||
|
||||
$userprofilefields->add_join('JOIN {test} t ON t.id = id');
|
||||
$columns = $userprofilefields->get_columns();
|
||||
$this->assertCount(2, ($columns[0])->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_joins
|
||||
*/
|
||||
public function test_add_joins(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$userprofilefields = $this->generate_userprofilefields();
|
||||
$columns = $userprofilefields->get_columns();
|
||||
$this->assertCount(1, ($columns[0])->get_joins());
|
||||
|
||||
$userprofilefields->add_joins(['JOIN {test} t ON t.id = id', 'JOIN {test2} t2 ON t2.id = id']);
|
||||
$columns = $userprofilefields->get_columns();
|
||||
$this->assertCount(3, ($columns[0])->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_filters
|
||||
*/
|
||||
public function test_get_filters(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$userprofilefields = $this->generate_userprofilefields();
|
||||
$filters = $userprofilefields->get_filters();
|
||||
$this->assertCount(2, $filters);
|
||||
[$filter0, $filter1] = $filters;
|
||||
$this->assertInstanceOf(filter::class, $filter0);
|
||||
$this->assertInstanceOf(filter::class, $filter1);
|
||||
$this->assertEqualsCanonicalizing(['User profile field text 1', 'User profile field text 2'],
|
||||
[$filter0->get_header(), $filter1->get_header()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?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\report;
|
||||
|
||||
use advanced_testcase;
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for a report action
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\report\action
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class action_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test adding a callback that returns true
|
||||
*/
|
||||
public function test_add_callback_true(): void {
|
||||
$action = $this->create_action()
|
||||
->add_callback(static function(stdClass $row): bool {
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->assertNotNull($action->get_action_link(new stdClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding a callback that returns false
|
||||
*/
|
||||
public function test_add_callback_false(): void {
|
||||
$action = $this->create_action()
|
||||
->add_callback(static function(stdClass $row): bool {
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->assertNull($action->get_action_link(new stdClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that action link URL parameters have placeholders replaced
|
||||
*/
|
||||
public function test_get_action_link_url_parameters(): void {
|
||||
$action = $this->create_action(['id' => ':id', 'action' => 'edit']);
|
||||
$actionlink = $action->get_action_link((object) ['id' => 42]);
|
||||
|
||||
// This is the action URL we expect.
|
||||
$expectedactionurl = (new moodle_url('/', ['id' => 42, 'action' => 'edit']))->out(false);
|
||||
$this->assertStringContainsString("href=\"{$expectedactionurl}\"", $actionlink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that action link attributes have placeholders replaced
|
||||
*/
|
||||
public function test_get_action_link_attributes(): void {
|
||||
$action = $this->create_action([], ['data-id' => ':id', 'data-action' => 'edit']);
|
||||
$actionlink = $action->get_action_link((object) ['id' => 42]);
|
||||
|
||||
// We expect each of these attributes to exist.
|
||||
$expectedattributes = [
|
||||
'data-id' => 42,
|
||||
'data-action' => 'edit',
|
||||
];
|
||||
foreach ($expectedattributes as $key => $value) {
|
||||
$this->assertStringContainsString("{$key}=\"{$value}\"", $actionlink);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create an action instance
|
||||
*
|
||||
* @param array $urlparams
|
||||
* @param array $attributes
|
||||
* @return action
|
||||
*/
|
||||
private function create_action(array $urlparams = [], array $attributes = []): action {
|
||||
return new action(
|
||||
new moodle_url('/', $urlparams),
|
||||
new pix_icon('t/edit', get_string('edit')),
|
||||
$attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?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\report;
|
||||
|
||||
use advanced_testcase;
|
||||
use context_system;
|
||||
use core_reportbuilder\system_report_available;
|
||||
use core_reportbuilder\system_report_factory;
|
||||
|
||||
/**
|
||||
* Unit tests for report base class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\report\base
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class base_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Load required class
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_base_condition_simple
|
||||
*/
|
||||
public function test_add_base_condition_simple(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$systemreport->add_base_condition_simple('username', 'admin');
|
||||
[$where, $params] = $systemreport->get_base_condition();
|
||||
$this->assertStringMatchesFormat('username = :%a', $where);
|
||||
$this->assertEqualsCanonicalizing(['admin'], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for add_base_condition_simple null
|
||||
*/
|
||||
public function test_add_base_condition_simple_null(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$systemreport->add_base_condition_simple('username', null);
|
||||
[$where, $params] = $systemreport->get_base_condition();
|
||||
$this->assertEquals('username IS NULL', $where);
|
||||
$this->assertEmpty($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_filter_instances
|
||||
*/
|
||||
public function test_get_filter_instances(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
|
||||
'', '', 0, ['withfilters' => true]);
|
||||
$filters = $systemreport->get_filter_instances();
|
||||
$this->assertCount(1, $filters);
|
||||
$this->assertInstanceOf(\core_reportbuilder\local\filters\text::class, reset($filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for set_downloadable
|
||||
*/
|
||||
public function test_set_downloadable(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$systemreport->set_downloadable(true, 'testfilename');
|
||||
$this->assertTrue($systemreport->is_downloadable());
|
||||
$this->assertEquals('testfilename', $systemreport->get_downloadfilename());
|
||||
|
||||
$systemreport->set_downloadable(false, 'anothertestfilename');
|
||||
$this->assertFalse($systemreport->is_downloadable());
|
||||
$this->assertEquals('anothertestfilename', $systemreport->get_downloadfilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_context
|
||||
*/
|
||||
public function test_get_context(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$this->assertEquals(context_system::instance(), $systemreport->get_context());
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$contextcourse = \context_course::instance($course->id);
|
||||
$systemreport2 = system_report_factory::create(system_report_available::class, $contextcourse);
|
||||
$this->assertEquals($contextcourse, $systemreport2->get_context());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_column
|
||||
*/
|
||||
public function test_get_column(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$column = $systemreport->get_column('user:username');
|
||||
$this->assertInstanceOf(column::class, $column);
|
||||
|
||||
$column = $systemreport->get_column('user:nonexistingcolumn');
|
||||
$this->assertNull($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_filter
|
||||
*/
|
||||
public function test_get_filter(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
|
||||
'', '', 0, ['withfilters' => true]);
|
||||
$filter = $systemreport->get_filter('user:username');
|
||||
$this->assertInstanceOf(filter::class, $filter);
|
||||
|
||||
$filter = $systemreport->get_filter('user:nonexistingfilter');
|
||||
$this->assertNull($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_report_persistent
|
||||
*/
|
||||
public function test_get_report_persistent(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$persistent = $systemreport->get_report_persistent();
|
||||
$this->assertEquals(system_report_available::class, $persistent->get('source'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
<?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\report;
|
||||
|
||||
use advanced_testcase;
|
||||
use coding_exception;
|
||||
use lang_string;
|
||||
use stdClass;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Unit tests for a report column
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\report\column
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class column_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test column name getter/setter
|
||||
*/
|
||||
public function test_name(): void {
|
||||
$column = $this->create_column('test');
|
||||
$this->assertEquals('test', $column->get_name());
|
||||
|
||||
$this->assertEquals('another', $column
|
||||
->set_name('another')
|
||||
->get_name()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test column title getter/setter
|
||||
*/
|
||||
public function test_title(): void {
|
||||
$column = $this->create_column('test', new lang_string('show'));
|
||||
$this->assertEquals('Show', $column->get_title());
|
||||
|
||||
$this->assertEquals('Hide', $column
|
||||
->set_title(new lang_string('hide'))
|
||||
->get_title()
|
||||
);
|
||||
|
||||
// Column titles can also be empty.
|
||||
$this->assertEmpty($column
|
||||
->set_title(null)
|
||||
->get_title());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity name getter
|
||||
*/
|
||||
public function test_get_entity_name(): void {
|
||||
$column = $this->create_column('test', null, 'entityname');
|
||||
$this->assertEquals('entityname', $column->get_entity_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting unique identifier
|
||||
*/
|
||||
public function test_get_unique_identifier(): void {
|
||||
$column = $this->create_column('test', null, 'entityname');
|
||||
$this->assertEquals('entityname:test', $column->get_unique_identifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test column type getter/setter
|
||||
*/
|
||||
public function test_type(): void {
|
||||
$column = $this->create_column('test');
|
||||
$this->assertEquals(column::TYPE_TEXT, $column
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->get_type());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test column type with invalid value
|
||||
*/
|
||||
public function test_type_invalid(): void {
|
||||
$column = $this->create_column('test');
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Invalid column type');
|
||||
$column->set_type(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding single join
|
||||
*/
|
||||
public function test_add_join(): void {
|
||||
$column = $this->create_column('test');
|
||||
$this->assertEquals([], $column->get_joins());
|
||||
|
||||
$column->add_join('JOIN {user} u ON u.id = table.userid');
|
||||
$this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $column->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding multiple joins
|
||||
*/
|
||||
public function test_add_joins(): void {
|
||||
$tablejoins = [
|
||||
"JOIN {course} c2 ON c2.id = c1.id",
|
||||
"JOIN {course} c3 ON c3.id = c1.id",
|
||||
];
|
||||
|
||||
$column = $this->create_column('test')
|
||||
->add_joins($tablejoins);
|
||||
|
||||
$this->assertEquals($tablejoins, $column->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_add_field}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_field_provider(): array {
|
||||
return [
|
||||
['foo', '', ['foo AS c1_foo']],
|
||||
['foo', 'bar', ['foo AS c1_bar']],
|
||||
['t.foo', '', ['t.foo AS c1_foo']],
|
||||
['t.foo', 'bar', ['t.foo AS c1_bar']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding single field, and retrieving it
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $alias
|
||||
* @param array $expectedselect
|
||||
*
|
||||
* @dataProvider add_field_provider
|
||||
*/
|
||||
public function test_add_field(string $sql, string $alias, array $expectedselect): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field($sql, $alias);
|
||||
|
||||
$this->assertEquals($expectedselect, $column->get_fields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding params to field, and retrieving them
|
||||
*/
|
||||
public function test_add_field_with_params(): void {
|
||||
$param = database::generate_param_name();
|
||||
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field(":{$param}", 'foo', [$param => 'bar']);
|
||||
|
||||
// Select will look like the following: "p<index>_rbparam<counter>", where index is the column index and counter is
|
||||
// a static value of the report helper class.
|
||||
$select = $column->get_fields();
|
||||
preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_foo/', $select[0], $matches);
|
||||
|
||||
$this->assertArrayHasKey('paramname', $matches);
|
||||
$this->assertEquals([$matches['paramname'] => 'bar'], $column->get_params());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding field with alias as part of SQL throws an exception
|
||||
*/
|
||||
public function test_add_field_alias_in_sql(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1);
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Column alias must be passed as a separate argument');
|
||||
$column->add_field('foo AS bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding field with complex SQL without an alias throws an exception
|
||||
*/
|
||||
public function test_add_field_complex_without_alias(): void {
|
||||
global $DB;
|
||||
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1);
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Complex columns must have an alias');
|
||||
$column->add_field($DB->sql_concat('foo', 'bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_add_fields}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_fields_provider(): array {
|
||||
return [
|
||||
['t.foo', ['t.foo AS c1_foo']],
|
||||
['t.foo bar', ['t.foo AS c1_bar']],
|
||||
['t.foo AS bar', ['t.foo AS c1_bar']],
|
||||
['t.foo1, t.foo2 bar, t.foo3 AS baz', ['t.foo1 AS c1_foo1', 't.foo2 AS c1_bar', 't.foo3 AS c1_baz']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding fields to a column, and retrieving them
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $expectedselect
|
||||
*
|
||||
* @dataProvider add_fields_provider
|
||||
*/
|
||||
public function test_add_fields(string $sql, array $expectedselect): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_fields($sql);
|
||||
|
||||
$this->assertEquals($expectedselect, $column->get_fields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test column alias
|
||||
*/
|
||||
public function test_column_alias(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_fields('t.foo, t.bar');
|
||||
|
||||
$this->assertEquals('c1_foo', $column->get_column_alias());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test alias of column without any fields throws exception
|
||||
*/
|
||||
public function test_column_alias_no_fields(): void {
|
||||
$column = $this->create_column('test');
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$this->expectExceptionMessage('Column ' . $column->get_unique_identifier() . ' contains no fields');
|
||||
$column->add_field($column->get_column_alias());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_format_value}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function format_value_provider(): array {
|
||||
return [
|
||||
[column::TYPE_INTEGER, 42],
|
||||
[column::TYPE_TEXT, 'Hello'],
|
||||
[column::TYPE_TIMESTAMP, HOURSECS],
|
||||
[column::TYPE_BOOLEAN, 1, true],
|
||||
[column::TYPE_FLOAT, 1.23],
|
||||
[column::TYPE_LONGTEXT, 'Amigos'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that column value is returned as correctly (value plus type)
|
||||
*
|
||||
* @param int $columntype
|
||||
* @param mixed $value
|
||||
* @param mixed|null $expected Expected value, or null to indicate it should be identical to value
|
||||
*
|
||||
* @dataProvider format_value_provider
|
||||
*/
|
||||
public function test_format_value(int $columntype, $value, $expected = null): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->set_type($columntype)
|
||||
->add_field('t.foo');
|
||||
|
||||
$this->assertSame($expected ?? $value, $column->format_value([
|
||||
'c1_foo' => $value,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that column value with callback is returned
|
||||
*/
|
||||
public function test_format_value_callback(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field('t.foo')
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_callback(static function(int $value, stdClass $values) {
|
||||
return $value * 2;
|
||||
});
|
||||
|
||||
$this->assertEquals(84, $column->format_value([
|
||||
'c1_bar' => 10,
|
||||
'c1_foo' => 42,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that column value with callback (using all fields) is returned
|
||||
*/
|
||||
public function test_format_value_callback_fields(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_fields('t.foo, t.baz')
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_callback(static function(int $value, stdClass $values) {
|
||||
return $values->foo + $values->baz;
|
||||
});
|
||||
|
||||
$this->assertEquals(60, $column->format_value([
|
||||
'c1_bar' => 10,
|
||||
'c1_foo' => 42,
|
||||
'c1_baz' => 18,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that column value with callback (using arguments) is returned
|
||||
*/
|
||||
public function test_format_value_callback_arguments(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field('t.foo')
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_callback(static function(int $value, stdClass $values, int $argument) {
|
||||
return $value - $argument;
|
||||
}, 10);
|
||||
|
||||
$this->assertEquals(32, $column->format_value([
|
||||
'c1_bar' => 10,
|
||||
'c1_foo' => 42,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding multiple callbacks to a column
|
||||
*/
|
||||
public function test_add_multiple_callback(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field('t.foo')
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_callback(static function(string $value): string {
|
||||
return strrev($value);
|
||||
})
|
||||
->add_callback(static function(string $value): string {
|
||||
return strtoupper($value);
|
||||
});
|
||||
|
||||
$this->assertEquals('LIONEL', $column->format_value([
|
||||
'c1_foo' => 'lenoil',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that setting column callback overwrites previous callbacks
|
||||
*/
|
||||
public function test_set_callback(): void {
|
||||
$column = $this->create_column('test')
|
||||
->set_index(1)
|
||||
->add_field('t.foo')
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_callback(static function(string $value): string {
|
||||
return strrev($value);
|
||||
})
|
||||
->set_callback(static function(string $value): string {
|
||||
return strtoupper($value);
|
||||
});
|
||||
|
||||
$this->assertEquals('LENOIL', $column->format_value([
|
||||
'c1_foo' => 'lenoil',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is sortable
|
||||
*/
|
||||
public function test_is_sortable(): void {
|
||||
$column = $this->create_column('test');
|
||||
$this->assertFalse($column->get_is_sortable());
|
||||
|
||||
$column->set_is_sortable(true);
|
||||
$this->assertTrue($column->get_is_sortable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is available
|
||||
*/
|
||||
public function test_is_available(): void {
|
||||
$column = $this->create_column('test');
|
||||
$this->assertTrue($column->get_is_available());
|
||||
|
||||
$column->set_is_available(true);
|
||||
$this->assertTrue($column->get_is_available());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a column instance
|
||||
*
|
||||
* @param string $name
|
||||
* @param lang_string|null $title
|
||||
* @param string $entityname
|
||||
* @return column
|
||||
*/
|
||||
private function create_column(string $name, ?lang_string $title = null, string $entityname = 'column_testcase'): column {
|
||||
return new column($name, $title, $entityname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?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\report;
|
||||
|
||||
use advanced_testcase;
|
||||
use lang_string;
|
||||
use moodle_exception;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
|
||||
/**
|
||||
* Unit tests for a report filter
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\report\filter
|
||||
* @copyright 2021 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test getting filter class
|
||||
*/
|
||||
public function test_get_filter_class(): void {
|
||||
$filter = $this->create_filter('username');
|
||||
$this->assertEquals(text::class, $filter->get_filter_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test specifying invalid filter class
|
||||
*/
|
||||
public function test_invalid_filter_class(): void {
|
||||
$this->expectException(moodle_exception::class);
|
||||
$this->expectExceptionMessage('Invalid filter (sillyclass)');
|
||||
new filter('sillyclass', 'username', new lang_string('username'), 'filter_testcase');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting name
|
||||
*/
|
||||
public function test_get_name(): void {
|
||||
$filter = $this->create_filter('username');
|
||||
$this->assertEquals('username', $filter->get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting header
|
||||
*/
|
||||
public function test_get_header(): void {
|
||||
$filter = $this->create_filter('username');
|
||||
$this->assertEquals('Username', $filter->get_header());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setting header
|
||||
*/
|
||||
public function test_set_header(): void {
|
||||
$filter = $this->create_filter('username')
|
||||
->set_header(new lang_string('firstname'));
|
||||
|
||||
$this->assertEquals('First name', $filter->get_header());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting entity name
|
||||
*/
|
||||
public function test_get_entity_name(): void {
|
||||
$filter = $this->create_filter('username');
|
||||
$this->assertEquals('filter_testcase', $filter->get_entity_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting unique identifier
|
||||
*/
|
||||
public function test_get_unique_identifier(): void {
|
||||
$filter = $this->create_filter('username');
|
||||
$this->assertEquals('filter_testcase:username', $filter->get_unique_identifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting field SQL
|
||||
*/
|
||||
public function test_get_field_sql(): void {
|
||||
$filter = $this->create_filter('username', 'u.username');
|
||||
$this->assertEquals('u.username', $filter->get_field_sql());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting field params
|
||||
*/
|
||||
public function test_get_field_params(): void {
|
||||
$filter = $this->create_filter('username', 'u.username = :foo', ['foo' => 'bar']);
|
||||
$this->assertEquals(['foo' => 'bar'], $filter->get_field_params());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding single join
|
||||
*/
|
||||
public function test_add_join(): void {
|
||||
$filter = $this->create_filter('username', 'u.username');
|
||||
$this->assertEquals([], $filter->get_joins());
|
||||
|
||||
$filter->add_join('JOIN {user} u ON u.id = table.userid');
|
||||
$this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $filter->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding multiple joins
|
||||
*/
|
||||
public function test_add_joins(): void {
|
||||
$tablejoins = [
|
||||
"JOIN {course} c2 ON c2.id = c1.id",
|
||||
"JOIN {course} c3 ON c3.id = c1.id",
|
||||
];
|
||||
|
||||
$filter = $this->create_filter('username', 'u.username')
|
||||
->add_joins($tablejoins);
|
||||
|
||||
$this->assertEquals($tablejoins, $filter->get_joins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is available
|
||||
*/
|
||||
public function test_is_available(): void {
|
||||
$filter = $this->create_filter('username', 'u.username');
|
||||
$this->assertTrue($filter->get_is_available());
|
||||
|
||||
$filter->set_is_available(true);
|
||||
$this->assertTrue($filter->get_is_available());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setting filter options
|
||||
*/
|
||||
public function test_set_options(): void {
|
||||
$filter = $this->create_filter('username', 'u.username')
|
||||
->set_options([1, 2, 3]);
|
||||
|
||||
$this->assertEquals([1, 2, 3], $filter->get_options());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setting filter options via callback
|
||||
*/
|
||||
public function test_set_options_callback(): void {
|
||||
$filter = $this->create_filter('username', 'u.username')
|
||||
->set_options_callback(static function() {
|
||||
return 10 * 5;
|
||||
});
|
||||
|
||||
$this->assertEquals(50, $filter->get_options());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test restricting filter operators
|
||||
*/
|
||||
public function test_limited_operators(): void {
|
||||
$filter = $this->create_filter('username', 'u.username')
|
||||
->set_limited_operators([
|
||||
text::IS_EQUAL_TO,
|
||||
text::IS_NOT_EQUAL_TO,
|
||||
]);
|
||||
|
||||
$limitedoperators = $filter->restrict_limited_operators([
|
||||
text::CONTAINS => 'Contains',
|
||||
text::DOES_NOT_CONTAIN => 'Does not contain',
|
||||
text::IS_EQUAL_TO => 'Is equal to',
|
||||
text::IS_NOT_EQUAL_TO => 'Is not equal to',
|
||||
]);
|
||||
|
||||
$this->assertEquals([
|
||||
text::IS_EQUAL_TO => 'Is equal to',
|
||||
text::IS_NOT_EQUAL_TO => 'Is not equal to',
|
||||
], $limitedoperators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test not restricting filter operators
|
||||
*/
|
||||
public function test_unlimited_operators(): void {
|
||||
$filter = $this->create_filter('username', 'u.username');
|
||||
|
||||
$operators = [
|
||||
text::CONTAINS => 'Contains',
|
||||
text::DOES_NOT_CONTAIN => 'Does not contain',
|
||||
];
|
||||
|
||||
// If no operator limit has been set for the filter, then all available operators should be present.
|
||||
$this->assertEquals($operators, $filter->restrict_limited_operators($operators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a filter instance
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $fieldsql
|
||||
* @param array $fieldparams
|
||||
* @return filter
|
||||
*/
|
||||
private function create_filter(string $name, string $fieldsql = '', array $fieldparams = []): filter {
|
||||
return new filter(text::class, $name, new lang_string($name), 'filter_testcase', $fieldsql, $fieldparams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?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 advanced_testcase;
|
||||
use context_system;
|
||||
use stdClass;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Unit tests for the report manager class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\manager
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test creating a report instance from persistent
|
||||
*/
|
||||
public function test_get_report_from_persistent(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => system_report_available::class,
|
||||
]);
|
||||
|
||||
$systemreport = manager::get_report_from_persistent($report);
|
||||
$this->assertInstanceOf(system_report::class, $systemreport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a report instance from persistent with an invalid source
|
||||
*/
|
||||
public function test_get_report_from_persistent_invalid(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => stdClass::class,
|
||||
]);
|
||||
|
||||
$this->expectException(source_invalid_exception::class);
|
||||
manager::get_report_from_persistent($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a report instance from persistent with an unavailable source
|
||||
*/
|
||||
public function test_get_report_from_persistent_unavailable(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_unavailable.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => system_report_unavailable::class,
|
||||
]);
|
||||
|
||||
$this->expectException(source_unavailable_exception::class);
|
||||
manager::get_report_from_persistent($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test report source exists
|
||||
*/
|
||||
public function test_report_source_exists(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
$this->assertTrue(manager::report_source_exists(system_report_available::class));
|
||||
|
||||
$this->assertFalse(manager::report_source_exists(stdClass::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test report source available
|
||||
*/
|
||||
public function test_report_source_available(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
$this->assertTrue(manager::report_source_available(system_report_available::class));
|
||||
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_unavailable.php");
|
||||
$this->assertFalse(manager::report_source_available(system_report_unavailable::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a report persistent model
|
||||
*/
|
||||
public function test_create_report_persistent(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$report = manager::create_report_persistent((object) [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => \core_reportbuilder\system_report_available::class,
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(report::class, $report);
|
||||
$this->assertEquals(base::TYPE_SYSTEM_REPORT, $report->get('type'));
|
||||
$this->assertEquals(system_report_available::class, $report->get('source'));
|
||||
$this->assertInstanceOf(context_system::class, $report->get_context());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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\privacy;
|
||||
|
||||
use context_system;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\metadata\types\database_table;
|
||||
use core_privacy\local\metadata\types\user_preference;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\local\helpers\user_filter_manager;
|
||||
|
||||
/**
|
||||
* Unit tests for privacy provider
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\privacy\provider
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider_testcase extends \core_privacy\tests\provider_testcase {
|
||||
|
||||
/**
|
||||
* Test provider metadata
|
||||
*/
|
||||
public function test_get_metadata(): void {
|
||||
$collection = new collection('core_reportbuilder');
|
||||
$metadata = provider::get_metadata($collection)->get_collection();
|
||||
|
||||
$this->assertCount(2, $metadata);
|
||||
|
||||
$this->assertInstanceOf(database_table::class, $metadata[0]);
|
||||
$this->assertEquals('reportbuilder_report', $metadata[0]->get_name());
|
||||
|
||||
$this->assertInstanceOf(user_preference::class, $metadata[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to check export_user_preferences.
|
||||
*/
|
||||
public function test_export_user_preferences(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user1);
|
||||
|
||||
// Create report and set some filters for the user.
|
||||
$report1 = manager::create_report_persistent((object) [
|
||||
'type' => 1,
|
||||
'source' => 'class',
|
||||
]);
|
||||
$filtervalues1 = [
|
||||
'task_log:name_operator' => 0,
|
||||
'task_log:name_value' => 'My task logs',
|
||||
];
|
||||
user_filter_manager::set($report1->get('id'), $filtervalues1);
|
||||
|
||||
// Add a filter for user2.
|
||||
$filtervalues1user2 = [
|
||||
'task_log:name_operator' => 0,
|
||||
'task_log:name_value' => 'My task logs user2',
|
||||
];
|
||||
user_filter_manager::set($report1->get('id'), $filtervalues1user2, (int)$user2->id);
|
||||
|
||||
// Create a second report and set some filters for the user.
|
||||
$report2 = manager::create_report_persistent((object) [
|
||||
'type' => 1,
|
||||
'source' => 'class',
|
||||
]);
|
||||
$filtervalues2 = [
|
||||
'config_change:setting_operator' => 0,
|
||||
'config_change:setting_value' => str_repeat('A', 3000),
|
||||
];
|
||||
user_filter_manager::set($report2->get('id'), $filtervalues2);
|
||||
|
||||
// Switch to admin user (so we can validate preferences of our test user are still exported).
|
||||
$this->setAdminUser();
|
||||
|
||||
// Export user preferences.
|
||||
provider::export_user_preferences((int)$user1->id);
|
||||
$writer = writer::with_context(context_system::instance());
|
||||
$prefs = $writer->get_user_preferences('core_reportbuilder');
|
||||
|
||||
// Check that user preferences only contain the 2 preferences from user1.
|
||||
$this->assertCount(2, (array)$prefs);
|
||||
|
||||
// Check that exported user preferences for report1 are correct.
|
||||
$report1key = 'reportbuilder-report-' . $report1->get('id');
|
||||
$this->assertEquals(json_encode($filtervalues1, JSON_PRETTY_PRINT), $prefs->$report1key->value);
|
||||
|
||||
// Check that exported user preferences for report2 are correct.
|
||||
$report2key = 'reportbuilder-report-' . $report2->get('id');
|
||||
$this->assertEquals(json_encode($filtervalues2, JSON_PRETTY_PRINT), $prefs->$report2key->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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 advanced_testcase;
|
||||
use context_system;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for the system report factory class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\system_report_factory
|
||||
* @copyright 2020 Paul Holden <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_factory_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test creating a valid/available system report
|
||||
*/
|
||||
public function test_create(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$this->assertInstanceOf(system_report::class, $systemreport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that creating a system report returns the same persistent if it already exists
|
||||
*/
|
||||
public function test_create_previously_exists(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreportone = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$systemreporttwo = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
|
||||
// Assert we have the same persistent for each.
|
||||
$this->assertEquals($systemreportone->get_report_persistent()->get('id'),
|
||||
$systemreporttwo->get_report_persistent()->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating an system report with an invalid source
|
||||
*/
|
||||
public function test_create_invalid(): void {
|
||||
$this->expectException(source_invalid_exception::class);
|
||||
system_report_factory::create(stdClass::class, context_system::instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating an unavailable system report
|
||||
*/
|
||||
public function test_create_unavailable(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_unavailable.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->expectException(source_unavailable_exception::class);
|
||||
system_report_factory::create(system_report_unavailable::class, context_system::instance());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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 advanced_testcase;
|
||||
use context_system;
|
||||
use core_reportbuilder\local\report\action;
|
||||
|
||||
/**
|
||||
* Unit tests for the system report class
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\system_report
|
||||
* @copyright 2021 David Matamoros <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class system_report_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Test for actions
|
||||
*/
|
||||
public function test_actions(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
|
||||
'', '', 0, ['withactions' => true]);
|
||||
$actions = $systemreport->get_actions();
|
||||
$this->assertCount(1, $actions);
|
||||
$action = reset($actions);
|
||||
$this->assertInstanceOf(action::class, $action);
|
||||
|
||||
$systemreport->add_action($action);
|
||||
$actions = $systemreport->get_actions();
|
||||
$this->assertCount(2, $actions);
|
||||
$this->assertTrue($systemreport->has_actions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for parameters
|
||||
*/
|
||||
public function test_parameters(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
|
||||
'', '', 0, ['withactions' => true]);
|
||||
|
||||
$this->assertEquals(['withactions' => true], $systemreport->get_parameters());
|
||||
|
||||
$systemreport->set_parameters(['withfilters' => true, 'secondparameter' => false]);
|
||||
$this->assertEquals(['withfilters' => true, 'secondparameter' => false], $systemreport->get_parameters());
|
||||
|
||||
$this->assertFalse((bool)$systemreport->get_parameter('secondparameter', true, PARAM_BOOL));
|
||||
// Get a param that does not exist.
|
||||
$this->assertEquals('3', $systemreport->get_parameter('thirdparameter', '3', PARAM_INT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for column sorting
|
||||
*/
|
||||
public function test_initial_sort_column(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
|
||||
$systemreport->set_initial_sort_column('user:username', SORT_DESC);
|
||||
$column = $systemreport->get_column('user:username');
|
||||
|
||||
$this->assertEquals($column, $systemreport->get_initial_sort_column());
|
||||
$this->assertEquals(SORT_DESC, $systemreport->get_initial_sort_direction());
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,4 @@ $breadcrumb-divider-rtl: "◀" !default;
|
||||
@import "moodle/atto";
|
||||
@import "moodle/toasts";
|
||||
@import "moodle/navbar";
|
||||
@import "moodle/reportbuilder";
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Rportbuilder */
|
||||
.reportbuilder-table-wrapper {
|
||||
.filters-dropdown {
|
||||
width: 27rem;
|
||||
padding: 0;
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.reportbuilder-filters-wrapper {
|
||||
.mform {
|
||||
&.full-width-labels {
|
||||
.fitem.row {
|
||||
> .col-md-3,
|
||||
> .col-md-9 {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fdate_selector {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.filter {
|
||||
.filter-header {
|
||||
font-size: $h5-font-size;
|
||||
.filter-name {
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.filter-name:hover {
|
||||
white-space: normal;
|
||||
text-overflow: clip;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20017,6 +20017,38 @@ div.editor_atto_toolbar button .icon {
|
||||
#page {
|
||||
margin-top: 50px; }
|
||||
|
||||
/* Rportbuilder */
|
||||
.reportbuilder-table-wrapper .filters-dropdown {
|
||||
width: 27rem;
|
||||
padding: 0; }
|
||||
@media (max-width: 767.98px) {
|
||||
.reportbuilder-table-wrapper .filters-dropdown {
|
||||
width: 100%; } }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row > .col-md-3,
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row > .col-md-9 {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row .fdate_selector {
|
||||
flex-wrap: wrap; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform .form-group {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header {
|
||||
font-size: 1.171875rem; }
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header .filter-name {
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 1rem; }
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header .filter-name:hover {
|
||||
white-space: normal;
|
||||
text-overflow: clip;
|
||||
word-break: break-all; }
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale; }
|
||||
|
||||
@@ -20208,6 +20208,38 @@ div.editor_atto_toolbar button .icon {
|
||||
#page {
|
||||
margin-top: 50px; }
|
||||
|
||||
/* Rportbuilder */
|
||||
.reportbuilder-table-wrapper .filters-dropdown {
|
||||
width: 27rem;
|
||||
padding: 0; }
|
||||
@media (max-width: 767.98px) {
|
||||
.reportbuilder-table-wrapper .filters-dropdown {
|
||||
width: 100%; } }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row > .col-md-3,
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row > .col-md-9 {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform.full-width-labels .fitem.row .fdate_selector {
|
||||
flex-wrap: wrap; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .mform .form-group {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header {
|
||||
font-size: 1.171875rem; }
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header .filter-name {
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 1rem; }
|
||||
.reportbuilder-table-wrapper .reportbuilder-filters-wrapper .filter .filter-header .filter-name:hover {
|
||||
white-space: normal;
|
||||
text-overflow: clip;
|
||||
word-break: break-all; }
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale; }
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2021072800.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2021072800.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.0dev (Build: 20210728)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user