diff --git a/admin/settings/reportbuilder.php b/admin/settings/reportbuilder.php new file mode 100644 index 00000000000..e9ccdc70d75 --- /dev/null +++ b/admin/settings/reportbuilder.php @@ -0,0 +1,44 @@ +. + +/** + * Report builder related settings. + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +use core_admin\local\externalpage\accesscallback; +use core_reportbuilder\permission; + +defined('MOODLE_INTERNAL') || die; + +/** @var admin_root $ADMIN */ +$ADMIN->add('reports', new admin_category('reportbuilder', new lang_string('reportbuilder', 'core_reportbuilder'))); + +$ADMIN->add( + 'reportbuilder', new accesscallback( + 'customreports', + get_string('customreports', 'core_reportbuilder'), + (new moodle_url('/reportbuilder/index.php'))->out(), + static function(accesscallback $accesscallback): bool { + return permission::can_view_reports_list(); + } + ) +); diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php index 1acd8d36e07..b70818bb92a 100644 --- a/lang/en/reportbuilder.php +++ b/lang/en/reportbuilder.php @@ -23,16 +23,60 @@ */ $string['actions'] = 'Actions'; +$string['addcolumn'] = 'Add column \'{$a}\''; +$string['aggregatecolumn'] = 'Aggregate column \'{$a}\''; +$string['aggregationavg'] = 'Average'; +$string['aggregationcount'] = 'Count'; +$string['aggregationcountdistinct'] = 'Count distinct'; +$string['aggregationgroupconcat'] = 'Comma separated values'; +$string['aggregationgroupconcatdistinct'] = 'Comma separated distinct values'; +$string['aggregationmax'] = 'Maximum'; +$string['aggregationmin'] = 'Minimum'; +$string['aggregationnone'] = 'No aggregation'; +$string['aggregationpercent'] = 'Percentage'; +$string['aggregationsum'] = 'Sum'; $string['apply'] = 'Apply'; +$string['columnadded'] = 'Added column \'{$a}\''; +$string['columnaggregated'] = 'Aggregated column \'{$a}\''; +$string['columndeleted'] = 'Deleted column \'{$a}\''; +$string['columnmoved'] = 'Moved column \'{$a}\''; +$string['columnsortdirectionasc'] = 'Sort column \'{$a}\' ascending'; +$string['columnsortdirectiondesc'] = 'Sort column \'{$a}\' descending'; +$string['columnsortdisable'] = 'Disable sorting for column \'{$a}\''; +$string['columnsortenable'] = 'Enable sorting for column \'{$a}\''; +$string['columnsortupdated'] = 'Updated sorting for column \'{$a}\''; +$string['conditionadded'] = 'Added condition \'{$a}\''; +$string['conditiondeleted'] = 'Deleted condition \'{$a}\''; +$string['conditionmoved'] = 'Moved condition \'{$a}\''; +$string['conditions'] = 'Conditions'; +$string['conditions_help'] = 'Report conditions allow you to limit which data is displayed when the report is viewed. Users viewing the report cannot override these condition values.'; +$string['conditionsapplied'] = 'Conditions applied'; +$string['conditionsreset'] = 'Conditions reset'; $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['customreports'] = 'Custom reports'; +$string['deletecolumn'] = 'Delete column \'{$a}\''; +$string['deletecolumnconfirm'] = 'Are you sure you want to delete the column \'{$a}\'?'; +$string['deletecondition'] = 'Delete condition \'{$a}\''; +$string['deleteconditionconfirm'] = 'Are you sure you want to delete the condition \'{$a}\'?'; +$string['deletefilter'] = 'Delete filter \'{$a}\''; +$string['deletefilterconfirm'] = 'Are you sure you want to delete the filter \'{$a}\'?'; +$string['deletereport'] = 'Delete report'; +$string['deletereportconfirm'] = 'Are you sure you want to delete the report \'{$a}\' and all associated data?'; +$string['editdetails'] = 'Edit details'; +$string['editreportcontent'] = 'Edit report content'; +$string['editreportdetails'] = 'Edit report details'; +$string['editreportname'] = 'Edit report name'; $string['entitycourse'] = 'Course'; $string['entityuser'] = 'User'; -$string['errorreportaccess'] = 'You can not view this report'; +$string['errorreportcreate'] = 'You can not create a new report'; +$string['errorreportedit'] = 'You can not edit this report'; +$string['errorreportview'] = 'You can not view this report'; $string['errorsourceinvalid'] = 'Could not find valid report source'; $string['errorsourceunavailable'] = 'Report source is not available'; +$string['filteradded'] = 'Added filter \'{$a}\''; $string['filtercontains'] = 'Contains'; $string['filterdatecurrent'] = 'Current'; $string['filterdatedays'] = 'day(s)'; @@ -46,6 +90,7 @@ $string['filterdateseconds'] = 'second(s)'; $string['filterdateto'] = 'Date to'; $string['filterdateweeks'] = 'week(s)'; $string['filterdateyears'] = 'year(s)'; +$string['filterdeleted'] = 'Deleted filter \'{$a}\''; $string['filterdoesnotcontain'] = 'Does not contain'; $string['filterdurationunit'] = '{$a} unit'; $string['filterendswith'] = 'Ends with'; @@ -61,19 +106,61 @@ $string['filterisequalto'] = 'Is equal to'; $string['filterisnotempty'] = 'Is not empty'; $string['filterisnotequalto'] = 'Is not equal to'; $string['filterlessthan'] = 'Less than'; +$string['filtermoved'] = 'Moved filter \'{$a}\''; $string['filterrange'] = 'Range'; $string['filtersapplied'] = 'Filters applied'; $string['filtersappliedx'] = 'Filters ({$a})'; +$string['filters'] = 'Filters'; +$string['filters_help'] = 'Report filters allow users viewing the report to further limit the data being displayed, by selecting their own values for each filter. Filter values are stored on a per-user basis.'; $string['filtersreset'] = 'Filters reset'; $string['filterstartswith'] = 'Starts with'; +$string['includedefaultsetup'] = 'Include default setup'; +$string['includedefaultsetup_help'] = 'Populate report with default layout as defined by the selected source. These include pre-defined columns, filters and conditions.'; +$string['movecolumn'] = 'Move column \'{$a}\''; +$string['movecondition'] = 'Move condition \'{$a}\''; +$string['movefilter'] = 'Move filter \'{$a}\''; +$string['movesorting'] = 'Move sorting for column \'{$a}\''; +$string['newreport'] = 'New report'; +$string['noconditions'] = 'There are no conditions selected'; +$string['nofilters'] = 'There are no filters selected'; +$string['nosortablecolumns'] = 'There are no sortable columns'; +$string['privacy:metadata:column'] = 'Report column definitions'; +$string['privacy:metadata:column:uniqueidentifier'] = 'Unique identifier of the column'; +$string['privacy:metadata:column:usercreated'] = 'The ID of the user who created the column'; +$string['privacy:metadata:column:usermodified'] = 'The ID of the user who last modified the column'; +$string['privacy:metadata:filter'] = 'Report filter definitions'; +$string['privacy:metadata:filter:uniqueidentifier'] = 'Unique identifier of the filter'; +$string['privacy:metadata:filter:usercreated'] = 'The ID of the user who created the filter'; +$string['privacy:metadata:filter:usermodified'] = 'The ID of the user who last modified the filter'; $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['renamecolumn'] = 'Rename column \'{$a}\''; +$string['renamefilter'] = 'Rename filter \'{$a}\''; +$string['reportbuilder'] = 'Report builder'; +$string['reportcreated'] = 'Report created'; +$string['reportdeleted'] = 'Report deleted'; +$string['reportsettingstoggle'] = 'Show/hide settings sidebar'; +$string['reportsource'] = 'Report source'; +$string['reportsource_help'] = 'The report source defines where the data for the report will come from'; +$string['reportupdated'] = 'Report updated'; $string['resetall'] = 'Reset all'; +$string['selectacondition'] = 'Select a condition'; +$string['selectafilter'] = 'Select a filter'; +$string['selectareportsource'] = 'Select a report source'; $string['selectcourses'] = 'Select courses'; +$string['showhide'] = 'Show/hide \'{$a}\''; +$string['sorting'] = 'Sorting'; +$string['sorting_help'] = 'Define the default sort order of the columns added to the report. Users can choose their own table sorting preferences, but will fallback to the defaults provided here.'; +$string['switchedit'] = 'Switch to edit mode'; +$string['switchpreview'] = 'Switch to preview mode'; +$string['timeadded'] = 'Time added'; +$string['timecreated'] = 'Time created'; +$string['timemodified'] = 'Time modified'; $string['userfullnamewithlink'] = 'Full name with link'; $string['userfullnamewithpicture'] = 'Full name with picture'; $string['userfullnamewithpicturelink'] = 'Full name with picture and link'; +$string['usermodified'] = 'Modified by'; $string['userpicture'] = 'User picture'; diff --git a/lang/en/role.php b/lang/en/role.php index 3cd4c0b7a99..e02f2f0bed7 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -371,6 +371,9 @@ $string['rating:rate'] = 'Add ratings to items'; $string['rating:view'] = 'View the total rating you received'; $string['rating:viewany'] = 'View total ratings that anyone received'; $string['rating:viewall'] = 'View all raw ratings given by individuals'; +$string['reportbuilder:edit'] = 'Create/edit custom reports'; +$string['reportbuilder:editall'] = 'Create/edit all custom reports'; +$string['reportbuilder:view'] = 'View custom reports'; $string['resetrole'] = 'Reset'; $string['resettingrole'] = 'Resetting role \'{$a}\''; $string['restore:configure'] = 'Configure restore options'; diff --git a/lib/db/access.php b/lib/db/access.php index 4062f923901..cd6ec1d26dd 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -2627,4 +2627,31 @@ $capabilities = array( 'coursecreator' => CAP_ALLOW, ] ], + + // Allow users to view custom reports. + 'moodle/reportbuilder:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'user' => CAP_ALLOW, + ], + ], + + // Allow users to create/edit their own custom reports. + 'moodle/reportbuilder:edit' => [ + 'captype' => 'write', + 'riskbitmap' => RISK_PERSONAL, + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], + + // Allow users to create/edit all custom reports. + 'moodle/reportbuilder:editall' => [ + 'captype' => 'write', + 'riskbitmap' => RISK_PERSONAL, + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [], + ], ); diff --git a/lib/db/install.xml b/lib/db/install.xml index 51edcd67aba..67d944175f8 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -4382,8 +4382,10 @@ + + @@ -4400,5 +4402,48 @@
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
diff --git a/lib/db/services.php b/lib/db/services.php index 712c46c8c39..f69f212e64a 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2798,6 +2798,90 @@ $functions = array( 'type' => 'write', 'ajax' => true, ], + 'core_reportbuilder_reports_delete' => [ + 'classname' => 'core_reportbuilder\external\reports\delete', + 'description' => 'Delete report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_reports_get' => [ + 'classname' => 'core_reportbuilder\external\reports\get', + 'description' => 'Get custom report', + 'type' => 'read', + 'ajax' => true, + ], + 'core_reportbuilder_columns_add' => [ + 'classname' => 'core_reportbuilder\external\columns\add', + 'description' => 'Add column to report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_columns_delete' => [ + 'classname' => 'core_reportbuilder\external\columns\delete', + 'description' => 'Delete column from report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_columns_reorder' => [ + 'classname' => 'core_reportbuilder\external\columns\reorder', + 'description' => 'Re-order column within report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_columns_sort_reorder' => [ + 'classname' => 'core_reportbuilder\external\columns\sort\reorder', + 'description' => 'Re-order column sorting within report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_columns_sort_toggle' => [ + 'classname' => 'core_reportbuilder\external\columns\sort\toggle', + 'description' => 'Toggle sorting of column within report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_conditions_add' => [ + 'classname' => 'core_reportbuilder\external\conditions\add', + 'description' => 'Add condition to report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_conditions_delete' => [ + 'classname' => 'core_reportbuilder\external\conditions\delete', + 'description' => 'Delete condition from report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_conditions_reorder' => [ + 'classname' => 'core_reportbuilder\external\conditions\reorder', + 'description' => 'Re-order condition within report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_conditions_reset' => [ + 'classname' => 'core_reportbuilder\external\conditions\reset', + 'description' => 'Reset conditions for given report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_filters_add' => [ + 'classname' => 'core_reportbuilder\external\filters\add', + 'description' => 'Add filter to report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_filters_delete' => [ + 'classname' => 'core_reportbuilder\external\filters\delete', + 'description' => 'Delete filter from report', + 'type' => 'write', + 'ajax' => true, + ], + 'core_reportbuilder_filters_reorder' => [ + 'classname' => 'core_reportbuilder\external\filters\reorder', + 'description' => 'Re-order filter within report', + 'type' => 'write', + 'ajax' => true, + ], ); $services = array( diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 43cbaa688f4..9039e9d2163 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2936,5 +2936,79 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2021100600.04); } + if ($oldversion < 2021101900.01) { + $table = new xmldb_table('reportbuilder_report'); + + // Define field name to be added to reportbuilder_report. + $field = new xmldb_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'id'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field conditiondata to be added to reportbuilder_report. + $field = new xmldb_field('conditiondata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'type'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define table reportbuilder_column to be created. + $table = new xmldb_table('reportbuilder_column'); + + // Adding fields to table reportbuilder_column. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('aggregation', XMLDB_TYPE_CHAR, '32', null, null, null, null); + $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('columnorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('sortenabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('sortdirection', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null); + $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $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_column. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']); + $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']); + $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']); + + // Conditionally launch create table for reportbuilder_column. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table reportbuilder_filter to be created. + $table = new xmldb_table('reportbuilder_filter'); + + // Adding fields to table reportbuilder_filter. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('iscondition', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('filterorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $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_filter. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']); + $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']); + $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']); + + // Conditionally launch create table for reportbuilder_filter. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2021101900.01); + } + return true; } diff --git a/reportbuilder/amd/build/editor.min.js b/reportbuilder/amd/build/editor.min.js new file mode 100644 index 00000000000..3fe98cb5f1f --- /dev/null +++ b/reportbuilder/amd/build/editor.min.js @@ -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/editor",["exports","core/inplace_editable","core/notification","core/pending","core/templates","core_reportbuilder/local/selectors","core_reportbuilder/local/editor/columns","core_reportbuilder/local/editor/conditions","core_reportbuilder/local/editor/filters","core_reportbuilder/local/editor/sorting","core_reportbuilder/local/repository/reports"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;c=n(c);d=n(d);e=n(e);f=m(f);function l(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;l=function(){return a};return a}function m(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=l();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 n(a){return a&&a.__esModule?a:{default:a}}var o=!1,p=function(){var a=document.querySelector(f.regions.report);(0,g.init)(a,o);(0,h.init)(a,o);(0,i.init)(a,o);(0,j.init)(a,o);if(o){return}document.addEventListener("click",function(b){var g=b.target.closest(f.actions.toggleEditPreview);if(g){b.preventDefault();var h=new d.default("core_reportbuilder/reports:get"),i="1"!==g.dataset.editMode,j="";(0,k.getReport)(a.dataset.reportId,i).then(function(a){j=a.javascript;return e.default.render("core_reportbuilder/custom_report",a)}).then(function(b,c){return e.default.replaceNodeContents(a,b,c+j)}).then(function(){return h.resolve()}).catch(c.default.exception)}});o=!0};a.init=p}); +//# sourceMappingURL=editor.min.js.map diff --git a/reportbuilder/amd/build/editor.min.js.map b/reportbuilder/amd/build/editor.min.js.map new file mode 100644 index 00000000000..9e0fb2785fa --- /dev/null +++ b/reportbuilder/amd/build/editor.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/editor.js"],"names":["initialized","init","reportElement","document","querySelector","reportSelectors","regions","report","addEventListener","event","toggleEditViewMode","target","closest","actions","toggleEditPreview","preventDefault","pendingPromise","Pending","toggledEditMode","dataset","editMode","customjs","reportId","then","response","javascript","Templates","render","html","js","replaceNodeContents","resolve","catch","Notification","exception"],"mappings":"6rBAwBA,a,+DAGA,OACA,OACA,OACA,O,4lBAOIA,CAAAA,CAAW,G,CAKFC,CAAI,CAAG,UAAM,CACtB,GAAMC,CAAAA,CAAa,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,OAAhB,CAAwBC,MAA/C,CAAtB,CAEA,WAAkBL,CAAlB,CAAiCF,CAAjC,EACA,WAAqBE,CAArB,CAAoCF,CAApC,EACA,WAAkBE,CAAlB,CAAiCF,CAAjC,EACA,WAAkBE,CAAlB,CAAiCF,CAAjC,EAGA,GAAIA,CAAJ,CAAiB,CACb,MACH,CAGDG,QAAQ,CAACK,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAK,CAAI,CAGxC,GAAMC,CAAAA,CAAkB,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBP,CAAe,CAACQ,OAAhB,CAAwBC,iBAA7C,CAA3B,CACA,GAAIJ,CAAJ,CAAwB,CACpBD,CAAK,CAACM,cAAN,GADoB,GAGdC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAHH,CAIdC,CAAe,CAA2C,GAAxC,GAAAR,CAAkB,CAACS,OAAnB,CAA2BC,QAJ/B,CAMhBC,CAAQ,CAAG,EANK,CAQpB,gBAAUnB,CAAa,CAACiB,OAAd,CAAsBG,QAAhC,CAA0CJ,CAA1C,EACKK,IADL,CACU,SAAAC,CAAQ,CAAI,CACdH,CAAQ,CAAGG,CAAQ,CAACC,UAApB,CACA,MAAOC,WAAUC,MAAV,CAAiB,kCAAjB,CAAqDH,CAArD,CACV,CAJL,EAKKD,IALL,CAKU,SAACK,CAAD,CAAOC,CAAP,CAAc,CAChB,MAAOH,WAAUI,mBAAV,CAA8B5B,CAA9B,CAA6C0B,CAA7C,CAAmDC,CAAE,CAAGR,CAAxD,CACV,CAPL,EAQKE,IARL,CAQU,iBAAMP,CAAAA,CAAc,CAACe,OAAf,EAAN,CARV,EASKC,KATL,CASWC,UAAaC,SATxB,CAUH,CACJ,CAvBD,EAyBAlC,CAAW,GACd,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 .\n\n/**\n * Report builder editor\n *\n * @module core_reportbuilder/editor\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Templates from 'core/templates';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {init as columnsEditorInit} from 'core_reportbuilder/local/editor/columns';\nimport {init as conditionsEditorInit} from 'core_reportbuilder/local/editor/conditions';\nimport {init as filtersEditorInit} from 'core_reportbuilder/local/editor/filters';\nimport {init as sortingEditorInit} from 'core_reportbuilder/local/editor/sorting';\nimport {getReport} from 'core_reportbuilder/local/repository/reports';\n\nlet initialized = false;\n\n/**\n * Initialise editor and all it's modules\n */\nexport const init = () => {\n const reportElement = document.querySelector(reportSelectors.regions.report);\n\n columnsEditorInit(reportElement, initialized);\n conditionsEditorInit(reportElement, initialized);\n filtersEditorInit(reportElement, initialized);\n sortingEditorInit(reportElement, initialized);\n\n // Ensure we only add our listeners once (can be called multiple times by mustache template).\n if (initialized) {\n return;\n }\n\n // Add event handlers to generic report editor elements.\n document.addEventListener('click', event => {\n\n // Toggle between edit and preview mode.\n const toggleEditViewMode = event.target.closest(reportSelectors.actions.toggleEditPreview);\n if (toggleEditViewMode) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/reports:get');\n const toggledEditMode = toggleEditViewMode.dataset.editMode !== \"1\";\n\n let customjs = '';\n\n getReport(reportElement.dataset.reportId, toggledEditMode)\n .then(response => {\n customjs = response.javascript;\n return Templates.render('core_reportbuilder/custom_report', response);\n })\n .then((html, js) => {\n return Templates.replaceNodeContents(reportElement, html, js + customjs);\n })\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n\n initialized = true;\n};\n"],"file":"editor.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/filters.min.js b/reportbuilder/amd/build/filters.min.js index cb9aeb7436d..84792e00fc9 100644 --- a/reportbuilder/amd/build/filters.min.js +++ b/reportbuilder/amd/build/filters.min.js @@ -1,2 +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/fragment","core/notification","core/pending","core/str","core/templates","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,k,l){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;d=o(d);e=o(e);g=o(g);i=o(i);j=n(j);k=n(k);function m(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;m=function(){return a};return a}function n(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=m();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 o(a){return a&&a.__esModule?a:{default:a}}function p(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 q(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){p(h,d,e,f,g,"next",a)}function g(a){p(h,d,e,f,g,"throw",a)}f(void 0)})}}var r=function(){var a=q(regeneratorRuntime.mark(function a(b,c){var d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.querySelector(k.regions.filterButtonLabel);if(!(0.\n\n/**\n * Report builder filter management\n *\n * @module core_reportbuilder/filters\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {loadFragment} from 'core/fragment';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\nimport Templates from 'core/templates';\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 * Update filter button text to indicate applied filter count\n *\n * @param {Element} reportElement\n * @param {Number} filterCount\n */\nconst setFilterButtonCount = async(reportElement, filterCount) => {\n const filterButtonLabel = reportElement.querySelector(reportSelectors.regions.filterButtonLabel);\n\n if (filterCount > 0) {\n filterButtonLabel.textContent = await getString('filtersappliedx', 'core_reportbuilder', filterCount);\n } else {\n filterButtonLabel.textContent = await getString('filters', 'moodle');\n }\n};\n\n/**\n * Initialise module for given report\n *\n * @method\n * @param {Number} reportId\n * @param {Number} contextId\n */\nexport const init = (reportId, contextId) => {\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 setFilterButtonCount(reportElement, event.detail);\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(() => loadFragment('core_reportbuilder', 'filters_form', contextId, {\n reportid: reportId,\n parameters: reportElement.dataset.parameter,\n }))\n .then((html, js) => {\n Templates.replaceNodeContents(filterFormContainer, html, js);\n\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, 0);\n\n return pendingPromise.resolve();\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"} \ No newline at end of file +{"version":3,"sources":["../src/filters.js"],"names":["setFilterButtonCount","reportElement","filterCount","filterButtonLabel","querySelector","reportSelectors","regions","textContent","init","reportId","contextId","document","forReport","filterFormContainer","filtersForm","filterForm","DynamicForm","addEventListener","events","FORM_SUBMITTED","event","preventDefault","reportEvents","tableReload","detail","then","addToast","catch","Notification","exception","NOSUBMIT_BUTTON_PRESSED","pendingPromise","Pending","reportid","parameters","dataset","parameter","html","js","Templates","replaceNodeContents","resolve","style","overflowX"],"mappings":"msBAyBA,OACA,OAEA,OAEA,OACA,OACA,O,25BASMA,CAAAA,CAAoB,4CAAG,WAAMC,CAAN,CAAqBC,CAArB,yFACnBC,CADmB,CACCF,CAAa,CAACG,aAAd,CAA4BC,CAAe,CAACC,OAAhB,CAAwBH,iBAApD,CADD,MAGP,CAAd,CAAAD,CAHqB,iCAIiB,iBAAU,iBAAV,CAA6B,oBAA7B,CAAmDA,CAAnD,CAJjB,QAIrBC,CAAiB,CAACI,WAJG,8CAMiB,iBAAU,SAAV,CAAqB,QAArB,CANjB,QAMrBJ,CAAiB,CAACI,WANG,iDAAH,uD,CAiBbC,CAAI,CAAG,SAACC,CAAD,CAAWC,CAAX,CAAyB,IACnCT,CAAAA,CAAa,CAAGU,QAAQ,CAACP,aAAT,CAAuBC,CAAe,CAACO,SAAhB,CAA0BH,CAA1B,CAAvB,CADmB,CAEnCI,CAAmB,CAAGZ,CAAa,CAACG,aAAd,CAA4BC,CAAe,CAACC,OAAhB,CAAwBQ,WAApD,CAFa,CAGnCC,CAAU,CAAG,GAAIC,UAAJ,CAAgBH,CAAhB,CAAqC,oCAArC,CAHsB,CAMzCE,CAAU,CAACE,gBAAX,CAA4BF,CAAU,CAACG,MAAX,CAAkBC,cAA9C,CAA8D,SAAAC,CAAK,CAAI,CACnEA,CAAK,CAACC,cAAN,GAGA,oBAAcC,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4CtB,CAA5C,EACAD,CAAoB,CAACC,CAAD,CAAgBmB,CAAK,CAACI,MAAtB,CAApB,CAEA,iBAAU,gBAAV,CAA4B,oBAA5B,EACKC,IADL,CACUC,KADV,EAEKC,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CAVD,EAaAd,CAAU,CAACE,gBAAX,CAA4BF,CAAU,CAACG,MAAX,CAAkBY,uBAA9C,CAAuE,SAAAV,CAAK,CAAI,CAC5EA,CAAK,CAACC,cAAN,GAEA,GAAMU,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,kCAAZ,CAAvB,CAEA,mBAAavB,CAAb,EACKgB,IADL,CACU,iBAAM,iBAAU,cAAV,CAA0B,oBAA1B,CAAN,CADV,EAEKA,IAFL,CAEUC,KAFV,EAGKD,IAHL,CAGU,iBAAM,mBAAa,oBAAb,CAAmC,cAAnC,CAAmDf,CAAnD,CAA8D,CACtEuB,QAAQ,CAAExB,CAD4D,CAEtEyB,UAAU,CAAEjC,CAAa,CAACkC,OAAd,CAAsBC,SAFoC,CAA9D,CAAN,CAHV,EAOKX,IAPL,CAOU,SAACY,CAAD,CAAOC,CAAP,CAAc,CAChBC,UAAUC,mBAAV,CAA8B3B,CAA9B,CAAmDwB,CAAnD,CAAyDC,CAAzD,EAEA,oBAAchB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4CtB,CAA5C,EACAD,CAAoB,CAACC,CAAD,CAAgB,CAAhB,CAApB,CAEA,MAAO8B,CAAAA,CAAc,CAACU,OAAf,EACV,CAdL,EAeKd,KAfL,CAeWC,UAAaC,SAfxB,CAgBH,CArBD,EAwBAlB,QAAQ,CAACP,aAAT,CAAuB,cAAvB,EAAuCsC,KAAvC,CAA6CC,SAA7C,CAAyD,SAC5D,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 .\n\n/**\n * Report builder filter management\n *\n * @module core_reportbuilder/filters\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {loadFragment} from 'core/fragment';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\nimport Templates from 'core/templates';\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 {resetFilters} from 'core_reportbuilder/local/repository/filters';\n\n/**\n * Update filter button text to indicate applied filter count\n *\n * @param {Element} reportElement\n * @param {Number} filterCount\n */\nconst setFilterButtonCount = async(reportElement, filterCount) => {\n const filterButtonLabel = reportElement.querySelector(reportSelectors.regions.filterButtonLabel);\n\n if (filterCount > 0) {\n filterButtonLabel.textContent = await getString('filtersappliedx', 'core_reportbuilder', filterCount);\n } else {\n filterButtonLabel.textContent = await getString('filters', 'moodle');\n }\n};\n\n/**\n * Initialise module for given report\n *\n * @method\n * @param {Number} reportId\n * @param {Number} contextId\n */\nexport const init = (reportId, contextId) => {\n const reportElement = document.querySelector(reportSelectors.forReport(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 setFilterButtonCount(reportElement, event.detail);\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(() => loadFragment('core_reportbuilder', 'filters_form', contextId, {\n reportid: reportId,\n parameters: reportElement.dataset.parameter,\n }))\n .then((html, js) => {\n Templates.replaceNodeContents(filterFormContainer, html, js);\n\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, 0);\n\n return pendingPromise.resolve();\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"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/editor/columns.min.js b/reportbuilder/amd/build/local/editor/columns.min.js new file mode 100644 index 00000000000..c0be3fff1a6 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/columns.min.js @@ -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/local/editor/columns",["exports","jquery","core/event_dispatcher","core/inplace_editable","core/local/inplace_editable/events","core/notification","core/pending","core/pubsub","core/sortable_list","core/str","core/toast","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/columns"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=q(b);f=q(f);g=q(g);i=q(i);l=p(l);m=p(m);function o(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;o=function(){return a};return a}function p(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=o();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 q(a){return a&&a.__esModule?a:{default:a}}function r(a,b){return w(a)||v(a,b)||t(a,b)||s()}function s(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function t(a,b){if(!a)return;if("string"==typeof a)return u(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return u(a,b)}function u(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);cm){o--}(0,n.reorderColumn)(a.dataset.reportId,h,o).then(function(){return(0,j.get_string)("columnmoved","core_reportbuilder",i)}).then(k.add).then(function(){(0,c.dispatchEvent)(l.tableReload,{preservePagination:!0},a);return e.resolve()}).catch(f.default.exception)}});a.addEventListener(e.eventTypes.elementUpdated,function(b){var d=b.target.closest("[data-itemtype=\"columnaggregation\"]");if(d){var e=d.closest(m.regions.columnHeader);(0,j.get_string)("columnaggregated","core_reportbuilder",e.dataset.columnName).then(k.add).then(function(){(0,c.dispatchEvent)(l.tableReload,{},a)}).catch(f.default.exception)}})};a.init=x}); +//# sourceMappingURL=columns.min.js.map diff --git a/reportbuilder/amd/build/local/editor/columns.min.js.map b/reportbuilder/amd/build/local/editor/columns.min.js.map new file mode 100644 index 00000000000..e7f413bfafc --- /dev/null +++ b/reportbuilder/amd/build/local/editor/columns.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/editor/columns.js"],"names":["init","reportElement","initialized","addEventListener","event","reportAddColumn","target","closest","reportSelectors","actions","preventDefault","pendingPromise","Pending","dataset","reportId","uniqueIdentifier","then","data","reportEvents","publish","reportColumnsUpdated","name","addToast","tableReload","preservePagination","resolve","catch","Notification","exception","reportRemoveColumn","columnHeader","regions","columnName","key","component","param","confirmTitle","confirmText","confirmButton","confirm","columnId","columnSortableList","SortableList","reportTable","isHorizontal","getElementName","element","Promise","on","EVENTS","DRAG","info","columnPosition","targetColumnPosition","targetNextElement","find","each","cell","children","beforeCell","insertBefore","appendChild","DROP","positionChanged","siblings","length","inplaceEditableEvents","elementUpdated","columnAggregation"],"mappings":"wrBAwBA,a,+DAEA,OAIA,OACA,OAEA,OAGA,OACA,O,qjDASO,GAAMA,CAAAA,CAAI,CAAG,SAACC,CAAD,CAAgBC,CAAhB,CAAgC,CAChD,GAAIA,CAAJ,CAAiB,CACb,MACH,CAEDD,CAAa,CAACE,gBAAd,CAA+B,OAA/B,CAAwC,SAAAC,CAAK,CAAI,CAG7C,GAAMC,CAAAA,CAAe,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACC,OAAhB,CAAwBJ,eAA7C,CAAxB,CACA,GAAIA,CAAJ,CAAqB,CACjBD,CAAK,CAACM,cAAN,GAEA,GAAMC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAAvB,CAEA,gBAAUX,CAAa,CAACY,OAAd,CAAsBC,QAAhC,CAA0CT,CAAe,CAACQ,OAAhB,CAAwBE,gBAAlE,EACKC,IADL,CACU,SAAAC,CAAI,QAAI,cAAQC,CAAY,CAACC,OAAb,CAAqBC,oBAA7B,CAAmDH,CAAnD,CAAJ,CADd,EAEKD,IAFL,CAEU,iBAAM,iBAAU,aAAV,CAAyB,oBAAzB,CAA+CX,CAAe,CAACQ,OAAhB,CAAwBQ,IAAvE,CAAN,CAFV,EAGKL,IAHL,CAGUM,KAHV,EAIKN,IAJL,CAIU,UAAM,CACR,oBAAcE,CAAY,CAACK,WAA3B,CAAwC,CAACC,kBAAkB,GAAnB,CAAxC,CAAoEvB,CAApE,EACA,MAAOU,CAAAA,CAAc,CAACc,OAAf,EACV,CAPL,EAQKC,KARL,CAQWC,UAAaC,SARxB,CASH,CAGD,GAAMC,CAAAA,CAAkB,CAAGzB,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACC,OAAhB,CAAwBoB,kBAA7C,CAA3B,CACA,GAAIA,CAAJ,CAAwB,CACpBzB,CAAK,CAACM,cAAN,GADoB,GAGdoB,CAAAA,CAAY,CAAGD,CAAkB,CAACtB,OAAnB,CAA2BC,CAAe,CAACuB,OAAhB,CAAwBD,YAAnD,CAHD,CAIdE,CAAU,CAAGF,CAAY,CAACjB,OAAb,CAAqBmB,UAJpB,CAMpB,kBAAW,CACP,CAACC,GAAG,CAAE,cAAN,CAAsBC,SAAS,CAAE,oBAAjC,CAAuDC,KAAK,CAAEH,CAA9D,CADO,CAEP,CAACC,GAAG,CAAE,qBAAN,CAA6BC,SAAS,CAAE,oBAAxC,CAA8DC,KAAK,CAAEH,CAArE,CAFO,CAGP,CAACC,GAAG,CAAE,QAAN,CAAgBC,SAAS,CAAE,QAA3B,CAHO,CAAX,EAIGlB,IAJH,CAIQ,WAAgD,cAA9CoB,CAA8C,MAAhCC,CAAgC,MAAnBC,CAAmB,MACpDX,UAAaY,OAAb,CAAqBH,CAArB,CAAmCC,CAAnC,CAAgDC,CAAhD,CAA+D,IAA/D,CAAqE,UAAM,CACvE,GAAM3B,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAAvB,CAEA,mBAAaX,CAAa,CAACY,OAAd,CAAsBC,QAAnC,CAA6CgB,CAAY,CAACjB,OAAb,CAAqB2B,QAAlE,EACKxB,IADL,CACU,SAAAC,CAAI,QAAI,cAAQC,CAAY,CAACC,OAAb,CAAqBC,oBAA7B,CAAmDH,CAAnD,CAAJ,CADd,EAEKD,IAFL,CAEU,iBAAM,iBAAU,eAAV,CAA2B,oBAA3B,CAAiDgB,CAAjD,CAAN,CAFV,EAGKhB,IAHL,CAGUM,KAHV,EAIKN,IAJL,CAIU,UAAM,CACR,oBAAcE,CAAY,CAACK,WAA3B,CAAwC,CAACC,kBAAkB,GAAnB,CAAxC,CAAoEvB,CAApE,EACA,MAAOU,CAAAA,CAAc,CAACc,OAAf,EACV,CAPL,EAQKC,KARL,CAQWC,UAAaC,SARxB,CASH,CAZD,CAcH,CAnBD,EAmBGF,KAnBH,CAmBSC,UAAaC,SAnBtB,CAoBH,CACJ,CAjDD,EAoDA,GAAIa,CAAAA,CAAkB,CAAG,GAAIC,UAAJ,WAAoBlC,CAAe,CAACuB,OAAhB,CAAwBY,WAA5C,cAAoE,CAACC,YAAY,GAAb,CAApE,CAAzB,CACAH,CAAkB,CAACI,cAAnB,CAAoC,SAAAC,CAAO,QAAIC,CAAAA,OAAO,CAACtB,OAAR,CAAgBqB,CAAO,CAAC7B,IAAR,CAAa,YAAb,CAAhB,CAAJ,CAA3C,CAEA,cAAEhB,CAAF,EAAiB+C,EAAjB,CAAoBN,UAAaO,MAAb,CAAoBC,IAAxC,CAA8C,oBAA9C,CAAoE,SAAC9C,CAAD,CAAQ+C,CAAR,CAAiB,IAC3EC,CAAAA,CAAc,CAAGD,CAAI,CAACL,OAAL,CAAa7B,IAAb,CAAkB,gBAAlB,CAD0D,CAE3EoC,CAAoB,CAAGF,CAAI,CAACG,iBAAL,CAAuBrC,IAAvB,CAA4B,gBAA5B,CAFoD,CAIjF,cAAEhB,CAAF,EAAiBsD,IAAjB,CAAsB,UAAtB,EAAkCC,IAAlC,CAAuC,UAAW,CAC9C,GAAMC,CAAAA,CAAI,CAAG,cAAE,IAAF,EAAQC,QAAR,eAAwBN,CAAc,CAAG,CAAzC,GAA8C,CAA9C,CAAb,CACA,GAAIC,CAAJ,CAA0B,CACtB,GAAIM,CAAAA,CAAU,CAAG,cAAE,IAAF,EAAQD,QAAR,eAAwBL,CAAoB,CAAG,CAA/C,GAAoD,CAApD,CAAjB,CACA,KAAKO,YAAL,CAAkBH,CAAlB,CAAwBE,CAAxB,CACH,CAHD,IAGO,CACH,KAAKE,WAAL,CAAiBJ,CAAjB,CACH,CACJ,CARD,CASH,CAbD,EAeA,cAAExD,CAAF,EAAiB+C,EAAjB,CAAoBN,UAAaO,MAAb,CAAoBa,IAAxC,CAA8C,oBAA9C,CAAoE,SAAC1D,CAAD,CAAQ+C,CAAR,CAAiB,CACjF,GAAIA,CAAI,CAACY,eAAT,CAA0B,IAChBpD,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,oCAAZ,CADD,CAGhB4B,CAAQ,CAAGW,CAAI,CAACL,OAAL,CAAa7B,IAAb,CAAkB,UAAlB,CAHK,CAIhBe,CAAU,CAAGmB,CAAI,CAACL,OAAL,CAAa7B,IAAb,CAAkB,YAAlB,CAJG,CAKhBmC,CAAc,CAAGD,CAAI,CAACL,OAAL,CAAa7B,IAAb,CAAkB,gBAAlB,CALD,CAQlBoC,CAAoB,CAAGF,CAAI,CAACG,iBAAL,CAAuBrC,IAAvB,CAA4B,gBAA5B,GAAiDkC,CAAI,CAACL,OAAL,CAAakB,QAAb,GAAwBC,MAAxB,CAAiC,CARvF,CAStB,GAAIZ,CAAoB,CAAGD,CAA3B,CAA2C,CACvCC,CAAoB,EACvB,CAED,oBAAcpD,CAAa,CAACY,OAAd,CAAsBC,QAApC,CAA8C0B,CAA9C,CAAwDa,CAAxD,EACKrC,IADL,CACU,iBAAM,iBAAU,aAAV,CAAyB,oBAAzB,CAA+CgB,CAA/C,CAAN,CADV,EAEKhB,IAFL,CAEUM,KAFV,EAGKN,IAHL,CAGU,UAAM,CACR,oBAAcE,CAAY,CAACK,WAA3B,CAAwC,CAACC,kBAAkB,GAAnB,CAAxC,CAAoEvB,CAApE,EACA,MAAOU,CAAAA,CAAc,CAACc,OAAf,EACV,CANL,EAOKC,KAPL,CAOWC,UAAaC,SAPxB,CAQH,CACJ,CAvBD,EA0BA3B,CAAa,CAACE,gBAAd,CAA+B+D,aAAsBC,cAArD,CAAqE,SAAA/D,CAAK,CAAI,CAE1E,GAAMgE,CAAAA,CAAiB,CAAGhE,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqB,uCAArB,CAA1B,CACA,GAAI6D,CAAJ,CAAuB,CACnB,GAAMtC,CAAAA,CAAY,CAAGsC,CAAiB,CAAC7D,OAAlB,CAA0BC,CAAe,CAACuB,OAAhB,CAAwBD,YAAlD,CAArB,CAEA,iBAAU,kBAAV,CAA8B,oBAA9B,CAAoDA,CAAY,CAACjB,OAAb,CAAqBmB,UAAzE,EACKhB,IADL,CACUM,KADV,EAEKN,IAFL,CAEU,UAAM,CACR,oBAAcE,CAAY,CAACK,WAA3B,CAAwC,EAAxC,CAA4CtB,CAA5C,CAEH,CALL,EAMKyB,KANL,CAMWC,UAAaC,SANxB,CAOH,CACJ,CAdD,CAeH,CApHM,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 .\n\n/**\n * Report builder columns editor\n *\n * @module core_reportbuilder/local/editor/columns\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport $ from 'jquery';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport 'core/inplace_editable';\nimport {eventTypes as inplaceEditableEvents} from 'core/local/inplace_editable/events';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {publish} from 'core/pubsub';\nimport SortableList from 'core/sortable_list';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {addColumn, deleteColumn, reorderColumn} from 'core_reportbuilder/local/repository/columns';\n\n/**\n * Initialise module\n *\n * @param {Element} reportElement\n * @param {Boolean} initialized Ensure we only add our listeners once\n */\nexport const init = (reportElement, initialized) => {\n if (initialized) {\n return;\n }\n\n reportElement.addEventListener('click', event => {\n\n // Add column to report.\n const reportAddColumn = event.target.closest(reportSelectors.actions.reportAddColumn);\n if (reportAddColumn) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/columns:add');\n\n addColumn(reportElement.dataset.reportId, reportAddColumn.dataset.uniqueIdentifier)\n .then(data => publish(reportEvents.publish.reportColumnsUpdated, data))\n .then(() => getString('columnadded', 'core_reportbuilder', reportAddColumn.dataset.name))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }\n\n // Remove column from report.\n const reportRemoveColumn = event.target.closest(reportSelectors.actions.reportRemoveColumn);\n if (reportRemoveColumn) {\n event.preventDefault();\n\n const columnHeader = reportRemoveColumn.closest(reportSelectors.regions.columnHeader);\n const columnName = columnHeader.dataset.columnName;\n\n getStrings([\n {key: 'deletecolumn', component: 'core_reportbuilder', param: columnName},\n {key: 'deletecolumnconfirm', component: 'core_reportbuilder', param: columnName},\n {key: 'delete', component: 'moodle'},\n ]).then(([confirmTitle, confirmText, confirmButton]) => {\n Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {\n const pendingPromise = new Pending('core_reportbuilder/columns:remove');\n\n deleteColumn(reportElement.dataset.reportId, columnHeader.dataset.columnId)\n .then(data => publish(reportEvents.publish.reportColumnsUpdated, data))\n .then(() => getString('columndeleted', 'core_reportbuilder', columnName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n return;\n }).catch(Notification.exception);\n }\n });\n\n // Initialize sortable list to handle column moving (note JQuery dependency, see MDL-72293 for resolution).\n var columnSortableList = new SortableList(`${reportSelectors.regions.reportTable} thead tr`, {isHorizontal: true});\n columnSortableList.getElementName = element => Promise.resolve(element.data('columnName'));\n\n $(reportElement).on(SortableList.EVENTS.DRAG, 'th[data-column-id]', (event, info) => {\n const columnPosition = info.element.data('columnPosition');\n const targetColumnPosition = info.targetNextElement.data('columnPosition');\n\n $(reportElement).find('tbody tr').each(function() {\n const cell = $(this).children(`td.c${columnPosition - 1}`)[0];\n if (targetColumnPosition) {\n var beforeCell = $(this).children(`td.c${targetColumnPosition - 1}`)[0];\n this.insertBefore(cell, beforeCell);\n } else {\n this.appendChild(cell);\n }\n });\n });\n\n $(reportElement).on(SortableList.EVENTS.DROP, 'th[data-column-id]', (event, info) => {\n if (info.positionChanged) {\n const pendingPromise = new Pending('core_reportbuilder/columns:reorder');\n\n const columnId = info.element.data('columnId');\n const columnName = info.element.data('columnName');\n const columnPosition = info.element.data('columnPosition');\n\n // Select target position, if moving to the end then count number of element siblings.\n let targetColumnPosition = info.targetNextElement.data('columnPosition') || info.element.siblings().length + 2;\n if (targetColumnPosition > columnPosition) {\n targetColumnPosition--;\n }\n\n reorderColumn(reportElement.dataset.reportId, columnId, targetColumnPosition)\n .then(() => getString('columnmoved', 'core_reportbuilder', columnName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }\n });\n\n // Initialize inplace editable listeners for column aggregation.\n reportElement.addEventListener(inplaceEditableEvents.elementUpdated, event => {\n\n const columnAggregation = event.target.closest('[data-itemtype=\"columnaggregation\"]');\n if (columnAggregation) {\n const columnHeader = columnAggregation.closest(reportSelectors.regions.columnHeader);\n\n getString('columnaggregated', 'core_reportbuilder', columnHeader.dataset.columnName)\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return;\n })\n .catch(Notification.exception);\n }\n });\n};\n"],"file":"columns.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/editor/conditions.min.js b/reportbuilder/amd/build/local/editor/conditions.min.js new file mode 100644 index 00000000000..bf8403005c6 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/conditions.min.js @@ -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/local/editor/conditions",["exports","jquery","core/event_dispatcher","core/inplace_editable","core/notification","core/pending","core/sortable_list","core/str","core/templates","core/toast","core_form/dynamicform","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/conditions"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=q(b);e=q(e);f=q(f);g=q(g);i=q(i);k=q(k);l=p(l);m=p(m);function o(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;o=function(){return a};return a}function p(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=o();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 q(a){return a&&a.__esModule?a:{default:a}}function r(a,b){return w(a)||v(a,b)||t(a,b)||s()}function s(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function t(a,b){if(!a)return;if("string"==typeof a)return u(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return u(a,b)}function u(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ck){m--}(0,n.reorderCondition)(a.dataset.reportId,i,m).then(function(b){return x(a,b)}).then(function(){return(0,h.get_string)("conditionmoved","core_reportbuilder",d.element.data("conditionName"))}).then(j.add).then(function(){(0,c.dispatchEvent)(l.tableReload,{},a);return g.resolve()}).catch(e.default.exception)}})};a.init=z}); +//# sourceMappingURL=conditions.min.js.map diff --git a/reportbuilder/amd/build/local/editor/conditions.min.js.map b/reportbuilder/amd/build/local/editor/conditions.min.js.map new file mode 100644 index 00000000000..8bdd64be3e6 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/conditions.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/editor/conditions.js"],"names":["reloadSettingsConditionsRegion","reportElement","templateContext","pendingPromise","Pending","settingsConditionsRegion","querySelector","reportSelectors","regions","settingsConditions","Templates","renderForPromise","conditions","then","html","js","replaceNode","javascript","initConditionsForm","resolve","conditionFormContainer","conditionForm","DynamicForm","addEventListener","events","FORM_SUBMITTED","event","preventDefault","addToast","catch","Notification","exception","reportEvents","tableReload","NOSUBMIT_BUTTON_PRESSED","dataset","reportId","data","init","initialized","reportAddCondition","target","closest","actions","value","options","selectedIndex","text","reportRemoveCondition","conditionContainer","activeCondition","conditionName","key","component","param","confirmTitle","confirmText","confirmButton","confirm","conditionId","activeConditionsSortableList","SortableList","activeConditions","isHorizontal","getElementName","element","Promise","on","EVENTS","DROP","info","positionChanged","conditionPosition","targetConditionPosition","targetNextElement","siblings","length"],"mappings":"orBAwBA,a,+DAEA,OAGA,OACA,OACA,OAEA,OAEA,OACA,OACA,O,wjDAUMA,CAAAA,CAA8B,CAAG,SAACC,CAAD,CAAgBC,CAAhB,CAAoC,IACjEC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,sCAAZ,CADgD,CAEjEC,CAAwB,CAAGJ,CAAa,CAACK,aAAd,CAA4BC,CAAe,CAACC,OAAhB,CAAwBC,kBAApD,CAFsC,CAIvE,MAAOC,WAAUC,gBAAV,CAA2B,8CAA3B,CAA2E,CAACC,UAAU,CAAEV,CAAb,CAA3E,EACFW,IADE,CACG,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CAClBL,UAAUM,WAAV,CAAsBX,CAAtB,CAAgDS,CAAhD,CAAsDC,CAAE,CAAGb,CAAe,CAACe,UAA3E,EACAC,CAAkB,CAACjB,CAAD,CAAlB,CACA,MAAOE,CAAAA,CAAc,CAACgB,OAAf,EACV,CALE,CAMV,C,CAOKD,CAAkB,CAAG,SAAAjB,CAAa,CAAI,IAElCmB,CAAAA,CAAsB,CAAGnB,CAAa,CAACK,aAAd,CAA4BC,CAAe,CAACC,OAAhB,CAAwBC,kBAApD,CAFS,CAGlCY,CAAa,CAAG,GAAIC,UAAJ,CAAgBF,CAAhB,CAAwC,uCAAxC,CAHkB,CAMxCC,CAAa,CAACE,gBAAd,CAA+BF,CAAa,CAACG,MAAd,CAAqBC,cAApD,CAAoE,SAAAC,CAAK,CAAI,CACzEA,CAAK,CAACC,cAAN,GAEA,iBAAU,mBAAV,CAA+B,oBAA/B,EACKd,IADL,CACUe,KADV,EAEKC,KAFL,CAEWC,UAAaC,SAFxB,EAKA,oBAAcC,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChC,CAA5C,CACH,CATD,EAYAoB,CAAa,CAACE,gBAAd,CAA+BF,CAAa,CAACG,MAAd,CAAqBU,uBAApD,CAA6E,SAAAR,CAAK,CAAI,CAClFA,CAAK,CAACC,cAAN,GAEA,GAAMxB,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,qCAAZ,CAAvB,CAEA,sBAAgBH,CAAa,CAACkC,OAAd,CAAsBC,QAAtC,EACKvB,IADL,CACU,SAAAwB,CAAI,QAAIrC,CAAAA,CAA8B,CAACC,CAAD,CAAgBoC,CAAhB,CAAlC,CADd,EAEKxB,IAFL,CAEU,iBAAM,iBAAU,iBAAV,CAA6B,oBAA7B,CAAN,CAFV,EAGKA,IAHL,CAGUe,KAHV,EAIKf,IAJL,CAIU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChC,CAA5C,EACA,MAAOE,CAAAA,CAAc,CAACgB,OAAf,EACV,CAPL,EAQKU,KARL,CAQWC,UAAaC,SARxB,CASH,CAdD,CAeH,C,CAQYO,CAAI,CAAG,SAACrC,CAAD,CAAgBsC,CAAhB,CAAgC,CAChDrB,CAAkB,CAACjB,CAAD,CAAlB,CACA,GAAIsC,CAAJ,CAAiB,CACb,MACH,CAEDtC,CAAa,CAACsB,gBAAd,CAA+B,OAA/B,CAAwC,SAAAG,CAAK,CAAI,CAG7C,GAAMc,CAAAA,CAAkB,CAAGd,CAAK,CAACe,MAAN,CAAaC,OAAb,CAAqBnC,CAAe,CAACoC,OAAhB,CAAwBH,kBAA7C,CAA3B,CACA,GAAIA,CAAJ,CAAwB,CACpBd,CAAK,CAACC,cAAN,GAGA,GAAiC,GAA7B,GAAAa,CAAkB,CAACI,KAAvB,CAAsC,CAClC,MACH,CAED,GAAMzC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAAvB,CAEA,mBAAaH,CAAa,CAACkC,OAAd,CAAsBC,QAAnC,CAA6CI,CAAkB,CAACI,KAAhE,EACK/B,IADL,CACU,SAAAwB,CAAI,QAAIrC,CAAAA,CAA8B,CAACC,CAAD,CAAgBoC,CAAhB,CAAlC,CADd,EAEKxB,IAFL,CAEU,iBAAM,iBAAU,gBAAV,CAA4B,oBAA5B,CACR2B,CAAkB,CAACK,OAAnB,CAA2BL,CAAkB,CAACM,aAA9C,EAA6DC,IADrD,CAAN,CAFV,EAIKlC,IAJL,CAIUe,KAJV,EAKKf,IALL,CAKU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChC,CAA5C,EACA,MAAOE,CAAAA,CAAc,CAACgB,OAAf,EACV,CARL,EASKU,KATL,CASWC,UAAaC,SATxB,CAUH,CAGD,GAAMiB,CAAAA,CAAqB,CAAGtB,CAAK,CAACe,MAAN,CAAaC,OAAb,CAAqBnC,CAAe,CAACoC,OAAhB,CAAwBK,qBAA7C,CAA9B,CACA,GAAIA,CAAJ,CAA2B,CACvBtB,CAAK,CAACC,cAAN,GADuB,GAGjBsB,CAAAA,CAAkB,CAAGD,CAAqB,CAACN,OAAtB,CAA8BnC,CAAe,CAACC,OAAhB,CAAwB0C,eAAtD,CAHJ,CAIjBC,CAAa,CAAGF,CAAkB,CAACd,OAAnB,CAA2BgB,aAJ1B,CAMvB,kBAAW,CACP,CAACC,GAAG,CAAE,iBAAN,CAAyBC,SAAS,CAAE,oBAApC,CAA0DC,KAAK,CAAEH,CAAjE,CADO,CAEP,CAACC,GAAG,CAAE,wBAAN,CAAgCC,SAAS,CAAE,oBAA3C,CAAiEC,KAAK,CAAEH,CAAxE,CAFO,CAGP,CAACC,GAAG,CAAE,QAAN,CAAgBC,SAAS,CAAE,QAA3B,CAHO,CAAX,EAIGxC,IAJH,CAIQ,WAAgD,cAA9C0C,CAA8C,MAAhCC,CAAgC,MAAnBC,CAAmB,MACpD3B,UAAa4B,OAAb,CAAqBH,CAArB,CAAmCC,CAAnC,CAAgDC,CAAhD,CAA+D,IAA/D,CAAqE,UAAM,CACvE,GAAMtD,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,sCAAZ,CAAvB,CAEA,sBAAgBH,CAAa,CAACkC,OAAd,CAAsBC,QAAtC,CAAgDa,CAAkB,CAACd,OAAnB,CAA2BwB,WAA3E,EACK9C,IADL,CACU,SAAAwB,CAAI,QAAIrC,CAAAA,CAA8B,CAACC,CAAD,CAAgBoC,CAAhB,CAAlC,CADd,EAEKxB,IAFL,CAEU,iBAAM,iBAAU,kBAAV,CAA8B,oBAA9B,CAAoDsC,CAApD,CAAN,CAFV,EAGKtC,IAHL,CAGUe,KAHV,EAIKf,IAJL,CAIU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChC,CAA5C,EACA,MAAOE,CAAAA,CAAc,CAACgB,OAAf,EACV,CAPL,EAQKU,KARL,CAQWC,UAAaC,SARxB,CASH,CAZD,CAcH,CAnBD,EAmBGF,KAnBH,CAmBSC,UAAaC,SAnBtB,CAoBH,CACJ,CAvDD,EA0DA,GAAI6B,CAAAA,CAA4B,CAAG,GAAIC,UAAJ,WAAoBtD,CAAe,CAACC,OAAhB,CAAwBsD,gBAA5C,EAC/B,CAACC,YAAY,GAAb,CAD+B,CAAnC,CAEAH,CAA4B,CAACI,cAA7B,CAA8C,SAAAC,CAAO,QAAIC,CAAAA,OAAO,CAAC/C,OAAR,CAAgB8C,CAAO,CAAC5B,IAAR,CAAa,eAAb,CAAhB,CAAJ,CAArD,CAEA,cAAEpC,CAAF,EAAiBkE,EAAjB,CAAoBN,UAAaO,MAAb,CAAoBC,IAAxC,CAA8C9D,CAAe,CAACC,OAAhB,CAAwB0C,eAAtE,CAAuF,SAACxB,CAAD,CAAQ4C,CAAR,CAAiB,CACpG,GAAIA,CAAI,CAACC,eAAT,CAA0B,IAChBpE,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,uCAAZ,CADD,CAEhBuD,CAAW,CAAGW,CAAI,CAACL,OAAL,CAAa5B,IAAb,CAAkB,aAAlB,CAFE,CAGhBmC,CAAiB,CAAGF,CAAI,CAACL,OAAL,CAAa5B,IAAb,CAAkB,mBAAlB,CAHJ,CAMlBoC,CAAuB,CAAGH,CAAI,CAACI,iBAAL,CAAuBrC,IAAvB,CAA4B,mBAA5B,GAAoDiC,CAAI,CAACL,OAAL,CAAaU,QAAb,GAAwBC,MAAxB,CAAiC,CAN7F,CAOtB,GAAIH,CAAuB,CAAGD,CAA9B,CAAiD,CAC7CC,CAAuB,EAC1B,CAED,uBAAiBxE,CAAa,CAACkC,OAAd,CAAsBC,QAAvC,CAAiDuB,CAAjD,CAA8Dc,CAA9D,EACK5D,IADL,CACU,SAAAwB,CAAI,QAAIrC,CAAAA,CAA8B,CAACC,CAAD,CAAgBoC,CAAhB,CAAlC,CADd,EAEKxB,IAFL,CAEU,iBAAM,iBAAU,gBAAV,CAA4B,oBAA5B,CAAkDyD,CAAI,CAACL,OAAL,CAAa5B,IAAb,CAAkB,eAAlB,CAAlD,CAAN,CAFV,EAGKxB,IAHL,CAGUe,KAHV,EAIKf,IAJL,CAIU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ChC,CAA5C,EACA,MAAOE,CAAAA,CAAc,CAACgB,OAAf,EACV,CAPL,EAQKU,KARL,CAQWC,UAAaC,SARxB,CASH,CACJ,CAtBD,CAuBH,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 .\n\n/**\n * Report builder conditions editor\n *\n * @module core_reportbuilder/local/editor/conditions\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport $ from 'jquery';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport SortableList from 'core/sortable_list';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport Templates from 'core/templates';\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 {addCondition, deleteCondition, reorderCondition, resetConditions} from 'core_reportbuilder/local/repository/conditions';\n\n/**\n * Reload conditions settings region\n *\n * @param {Element} reportElement\n * @param {Object} templateContext\n * @return {Promise}\n */\nconst reloadSettingsConditionsRegion = (reportElement, templateContext) => {\n const pendingPromise = new Pending('core_reportbuilder/conditions:reload');\n const settingsConditionsRegion = reportElement.querySelector(reportSelectors.regions.settingsConditions);\n\n return Templates.renderForPromise('core_reportbuilder/local/settings/conditions', {conditions: templateContext})\n .then(({html, js}) => {\n Templates.replaceNode(settingsConditionsRegion, html, js + templateContext.javascript);\n initConditionsForm(reportElement);\n return pendingPromise.resolve();\n });\n};\n\n/**\n * Initialise conditions form, must be called on each init because the form container is re-created when switching editor modes\n *\n * @param {Element} reportElement\n */\nconst initConditionsForm = reportElement => {\n // Handle dynamic conditions form.\n const conditionFormContainer = reportElement.querySelector(reportSelectors.regions.settingsConditions);\n const conditionForm = new DynamicForm(conditionFormContainer, '\\\\core_reportbuilder\\\\form\\\\condition');\n\n // Submit report conditions.\n conditionForm.addEventListener(conditionForm.events.FORM_SUBMITTED, event => {\n event.preventDefault();\n\n getString('conditionsapplied', 'core_reportbuilder')\n .then(addToast)\n .catch(Notification.exception);\n\n // After the form has been submitted, we should trigger report table reload.\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n });\n\n // Reset report conditions.\n conditionForm.addEventListener(conditionForm.events.NOSUBMIT_BUTTON_PRESSED, event => {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/conditions:reset');\n\n resetConditions(reportElement.dataset.reportId)\n .then(data => reloadSettingsConditionsRegion(reportElement, data))\n .then(() => getString('conditionsreset', 'core_reportbuilder'))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n};\n\n/**\n * Initialise module\n *\n * @param {Element} reportElement\n * @param {Boolean} initialized Ensure we only add our listeners once\n */\nexport const init = (reportElement, initialized) => {\n initConditionsForm(reportElement);\n if (initialized) {\n return;\n }\n\n reportElement.addEventListener('click', event => {\n\n // Add condition to report.\n const reportAddCondition = event.target.closest(reportSelectors.actions.reportAddCondition);\n if (reportAddCondition) {\n event.preventDefault();\n\n // Check if dropdown is closed with no condition selected.\n if (reportAddCondition.value === '0') {\n return;\n }\n\n const pendingPromise = new Pending('core_reportbuilder/conditions:add');\n\n addCondition(reportElement.dataset.reportId, reportAddCondition.value)\n .then(data => reloadSettingsConditionsRegion(reportElement, data))\n .then(() => getString('conditionadded', 'core_reportbuilder',\n reportAddCondition.options[reportAddCondition.selectedIndex].text))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }\n\n // Remove condition from report.\n const reportRemoveCondition = event.target.closest(reportSelectors.actions.reportRemoveCondition);\n if (reportRemoveCondition) {\n event.preventDefault();\n\n const conditionContainer = reportRemoveCondition.closest(reportSelectors.regions.activeCondition);\n const conditionName = conditionContainer.dataset.conditionName;\n\n getStrings([\n {key: 'deletecondition', component: 'core_reportbuilder', param: conditionName},\n {key: 'deleteconditionconfirm', component: 'core_reportbuilder', param: conditionName},\n {key: 'delete', component: 'moodle'},\n ]).then(([confirmTitle, confirmText, confirmButton]) => {\n Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {\n const pendingPromise = new Pending('core_reportbuilder/conditions:remove');\n\n deleteCondition(reportElement.dataset.reportId, conditionContainer.dataset.conditionId)\n .then(data => reloadSettingsConditionsRegion(reportElement, data))\n .then(() => getString('conditiondeleted', 'core_reportbuilder', conditionName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n return;\n }).catch(Notification.exception);\n }\n });\n\n // Initialize sortable list to handle active conditions moving (note JQuery dependency, see MDL-72293 for resolution).\n var activeConditionsSortableList = new SortableList(`${reportSelectors.regions.activeConditions}`,\n {isHorizontal: false});\n activeConditionsSortableList.getElementName = element => Promise.resolve(element.data('conditionName'));\n\n $(reportElement).on(SortableList.EVENTS.DROP, reportSelectors.regions.activeCondition, (event, info) => {\n if (info.positionChanged) {\n const pendingPromise = new Pending('core_reportbuilder/conditions:reorder');\n const conditionId = info.element.data('conditionId');\n const conditionPosition = info.element.data('conditionPosition');\n\n // Select target position, if moving to the end then count number of element siblings.\n let targetConditionPosition = info.targetNextElement.data('conditionPosition') || info.element.siblings().length + 2;\n if (targetConditionPosition > conditionPosition) {\n targetConditionPosition--;\n }\n\n reorderCondition(reportElement.dataset.reportId, conditionId, targetConditionPosition)\n .then(data => reloadSettingsConditionsRegion(reportElement, data))\n .then(() => getString('conditionmoved', 'core_reportbuilder', info.element.data('conditionName')))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }\n });\n};\n"],"file":"conditions.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/editor/filters.min.js b/reportbuilder/amd/build/local/editor/filters.min.js new file mode 100644 index 00000000000..94073c6165b --- /dev/null +++ b/reportbuilder/amd/build/local/editor/filters.min.js @@ -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/local/editor/filters",["exports","jquery","core/event_dispatcher","core/inplace_editable","core/notification","core/pending","core/sortable_list","core/str","core/templates","core/toast","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/filters"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=p(b);e=p(e);f=p(f);g=p(g);i=p(i);k=o(k);l=o(l);function n(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;n=function(){return a};return a}function o(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=n();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 p(a){return a&&a.__esModule?a:{default:a}}function q(a,b){return v(a)||u(a,b)||s(a,b)||r()}function r(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function s(a,b){if(!a)return;if("string"==typeof a)return t(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return t(a,b)}function t(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ci){k--}(0,m.reorderFilter)(a.dataset.reportId,g,k).then(function(b){return w(a,b)}).then(function(){return(0,h.get_string)("filtermoved","core_reportbuilder",c.element.data("filterName"))}).then(j.add).then(function(){return d.resolve()}).catch(e.default.exception)}})};a.init=x}); +//# sourceMappingURL=filters.min.js.map diff --git a/reportbuilder/amd/build/local/editor/filters.min.js.map b/reportbuilder/amd/build/local/editor/filters.min.js.map new file mode 100644 index 00000000000..b1c860b2061 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/filters.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/editor/filters.js"],"names":["reloadSettingsFiltersRegion","reportElement","templateContext","pendingPromise","Pending","settingsFiltersRegion","querySelector","reportSelectors","regions","settingsFilters","Templates","renderForPromise","filters","then","html","js","replaceNode","resolve","init","initialized","addEventListener","event","reportAddFilter","target","closest","actions","preventDefault","value","dataset","reportId","data","options","selectedIndex","text","addToast","catch","Notification","exception","reportRemoveFilter","filterContainer","activeFilter","filterName","key","component","param","confirmTitle","confirmText","confirmButton","confirm","filterId","reportEvents","tableReload","activeFiltersSortableList","SortableList","activeFilters","isHorizontal","getElementName","element","Promise","on","EVENTS","DROP","info","positionChanged","filterPosition","targetFilterPosition","targetNextElement","siblings","length"],"mappings":"opBAwBA,a,+DAEA,OAGA,OACA,OACA,OAEA,OAEA,OACA,O,wjDAUMA,CAAAA,CAA2B,CAAG,SAACC,CAAD,CAAgBC,CAAhB,CAAoC,IAC9DC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAD6C,CAE9DC,CAAqB,CAAGJ,CAAa,CAACK,aAAd,CAA4BC,CAAe,CAACC,OAAhB,CAAwBC,eAApD,CAFsC,CAIpE,MAAOC,WAAUC,gBAAV,CAA2B,2CAA3B,CAAwE,CAACC,OAAO,CAAEV,CAAV,CAAxE,EACFW,IADE,CACG,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CAClBL,UAAUM,WAAV,CAAsBX,CAAtB,CAA6CS,CAA7C,CAAmDC,CAAnD,EACA,MAAOZ,CAAAA,CAAc,CAACc,OAAf,EACV,CAJE,CAKV,C,CAQYC,CAAI,CAAG,SAACjB,CAAD,CAAgBkB,CAAhB,CAAgC,CAChD,GAAIA,CAAJ,CAAiB,CACb,MACH,CAEDlB,CAAa,CAACmB,gBAAd,CAA+B,OAA/B,CAAwC,SAAAC,CAAK,CAAI,CAG7C,GAAMC,CAAAA,CAAe,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBjB,CAAe,CAACkB,OAAhB,CAAwBH,eAA7C,CAAxB,CACA,GAAIA,CAAJ,CAAqB,CACjBD,CAAK,CAACK,cAAN,GAGA,GAA8B,GAA1B,GAAAJ,CAAe,CAACK,KAApB,CAAmC,CAC/B,MACH,CAED,GAAMxB,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAAvB,CAEA,gBAAUH,CAAa,CAAC2B,OAAd,CAAsBC,QAAhC,CAA0CP,CAAe,CAACK,KAA1D,EACKd,IADL,CACU,SAAAiB,CAAI,QAAI9B,CAAAA,CAA2B,CAACC,CAAD,CAAgB6B,CAAhB,CAA/B,CADd,EAEKjB,IAFL,CAEU,iBAAM,iBAAU,aAAV,CAAyB,oBAAzB,CACRS,CAAe,CAACS,OAAhB,CAAwBT,CAAe,CAACU,aAAxC,EAAuDC,IAD/C,CAAN,CAFV,EAIKpB,IAJL,CAIUqB,KAJV,EAKKrB,IALL,CAKU,iBAAMV,CAAAA,CAAc,CAACc,OAAf,EAAN,CALV,EAMKkB,KANL,CAMWC,UAAaC,SANxB,CAOH,CAGD,GAAMC,CAAAA,CAAkB,CAAGjB,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBjB,CAAe,CAACkB,OAAhB,CAAwBa,kBAA7C,CAA3B,CACA,GAAIA,CAAJ,CAAwB,CACpBjB,CAAK,CAACK,cAAN,GADoB,GAGda,CAAAA,CAAe,CAAGD,CAAkB,CAACd,OAAnB,CAA2BjB,CAAe,CAACC,OAAhB,CAAwBgC,YAAnD,CAHJ,CAIdC,CAAU,CAAGF,CAAe,CAACX,OAAhB,CAAwBa,UAJvB,CAMpB,kBAAW,CACP,CAACC,GAAG,CAAE,cAAN,CAAsBC,SAAS,CAAE,oBAAjC,CAAuDC,KAAK,CAAEH,CAA9D,CADO,CAEP,CAACC,GAAG,CAAE,qBAAN,CAA6BC,SAAS,CAAE,oBAAxC,CAA8DC,KAAK,CAAEH,CAArE,CAFO,CAGP,CAACC,GAAG,CAAE,QAAN,CAAgBC,SAAS,CAAE,QAA3B,CAHO,CAAX,EAIG9B,IAJH,CAIQ,WAAgD,cAA9CgC,CAA8C,MAAhCC,CAAgC,MAAnBC,CAAmB,MACpDX,UAAaY,OAAb,CAAqBH,CAArB,CAAmCC,CAAnC,CAAgDC,CAAhD,CAA+D,IAA/D,CAAqE,UAAM,CACvE,GAAM5C,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAAvB,CAEA,mBAAaH,CAAa,CAAC2B,OAAd,CAAsBC,QAAnC,CAA6CU,CAAe,CAACX,OAAhB,CAAwBqB,QAArE,EACKpC,IADL,CACU,SAAAiB,CAAI,QAAI9B,CAAAA,CAA2B,CAACC,CAAD,CAAgB6B,CAAhB,CAA/B,CADd,EAEKjB,IAFL,CAEU,iBAAM,iBAAU,eAAV,CAA2B,oBAA3B,CAAiD4B,CAAjD,CAAN,CAFV,EAGK5B,IAHL,CAGUqB,KAHV,EAIKrB,IAJL,CAIU,UAAM,CACR,oBAAcqC,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4ClD,CAA5C,EACA,MAAOE,CAAAA,CAAc,CAACc,OAAf,EACV,CAPL,EAQKkB,KARL,CAQWC,UAAaC,SARxB,CASH,CAZD,CAcH,CAnBD,EAmBGF,KAnBH,CAmBSC,UAAaC,SAnBtB,CAoBH,CACJ,CApDD,EAuDA,GAAIe,CAAAA,CAAyB,CAAG,GAAIC,UAAJ,WAAoB9C,CAAe,CAACC,OAAhB,CAAwB8C,aAA5C,QAAgE,CAACC,YAAY,GAAb,CAAhE,CAAhC,CACAH,CAAyB,CAACI,cAA1B,CAA2C,SAAAC,CAAO,QAAIC,CAAAA,OAAO,CAACzC,OAAR,CAAgBwC,CAAO,CAAC3B,IAAR,CAAa,YAAb,CAAhB,CAAJ,CAAlD,CAEA,cAAE7B,CAAF,EAAiB0D,EAAjB,CAAoBN,UAAaO,MAAb,CAAoBC,IAAxC,CAA8C,oBAA9C,CAAoE,SAACxC,CAAD,CAAQyC,CAAR,CAAiB,CACjF,GAAIA,CAAI,CAACC,eAAT,CAA0B,IAChB5D,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,oCAAZ,CADD,CAEhB6C,CAAQ,CAAGa,CAAI,CAACL,OAAL,CAAa3B,IAAb,CAAkB,UAAlB,CAFK,CAGhBkC,CAAc,CAAGF,CAAI,CAACL,OAAL,CAAa3B,IAAb,CAAkB,gBAAlB,CAHD,CAMlBmC,CAAoB,CAAGH,CAAI,CAACI,iBAAL,CAAuBpC,IAAvB,CAA4B,gBAA5B,GAAiDgC,CAAI,CAACL,OAAL,CAAaU,QAAb,GAAwBC,MAAxB,CAAiC,CANvF,CAOtB,GAAIH,CAAoB,CAAGD,CAA3B,CAA2C,CACvCC,CAAoB,EACvB,CAED,oBAAchE,CAAa,CAAC2B,OAAd,CAAsBC,QAApC,CAA8CoB,CAA9C,CAAwDgB,CAAxD,EACKpD,IADL,CACU,SAAAiB,CAAI,QAAI9B,CAAAA,CAA2B,CAACC,CAAD,CAAgB6B,CAAhB,CAA/B,CADd,EAEKjB,IAFL,CAEU,iBAAM,iBAAU,aAAV,CAAyB,oBAAzB,CAA+CiD,CAAI,CAACL,OAAL,CAAa3B,IAAb,CAAkB,YAAlB,CAA/C,CAAN,CAFV,EAGKjB,IAHL,CAGUqB,KAHV,EAIKrB,IAJL,CAIU,iBAAMV,CAAAA,CAAc,CAACc,OAAf,EAAN,CAJV,EAKKkB,KALL,CAKWC,UAAaC,SALxB,CAMH,CACJ,CAnBD,CAoBH,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 .\n\n/**\n * Report builder filters editor\n *\n * @module core_reportbuilder/local/editor/filters\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport $ from 'jquery';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport SortableList from 'core/sortable_list';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport Templates from 'core/templates';\nimport {add as addToast} from 'core/toast';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {addFilter, deleteFilter, reorderFilter} from 'core_reportbuilder/local/repository/filters';\n\n/**\n * Reload filters settings region\n *\n * @param {Element} reportElement\n * @param {Object} templateContext\n * @return {Promise}\n */\nconst reloadSettingsFiltersRegion = (reportElement, templateContext) => {\n const pendingPromise = new Pending('core_reportbuilder/filters:reload');\n const settingsFiltersRegion = reportElement.querySelector(reportSelectors.regions.settingsFilters);\n\n return Templates.renderForPromise('core_reportbuilder/local/settings/filters', {filters: templateContext})\n .then(({html, js}) => {\n Templates.replaceNode(settingsFiltersRegion, html, js);\n return pendingPromise.resolve();\n });\n};\n\n/**\n * Initialise module\n *\n * @param {Element} reportElement\n * @param {Boolean} initialized Ensure we only add our listeners once\n */\nexport const init = (reportElement, initialized) => {\n if (initialized) {\n return;\n }\n\n reportElement.addEventListener('click', event => {\n\n // Add filter to report.\n const reportAddFilter = event.target.closest(reportSelectors.actions.reportAddFilter);\n if (reportAddFilter) {\n event.preventDefault();\n\n // Check if dropdown is closed with no filter selected.\n if (reportAddFilter.value === '0') {\n return;\n }\n\n const pendingPromise = new Pending('core_reportbuilder/filters:add');\n\n addFilter(reportElement.dataset.reportId, reportAddFilter.value)\n .then(data => reloadSettingsFiltersRegion(reportElement, data))\n .then(() => getString('filteradded', 'core_reportbuilder',\n reportAddFilter.options[reportAddFilter.selectedIndex].text))\n .then(addToast)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n\n // Remove filter from report.\n const reportRemoveFilter = event.target.closest(reportSelectors.actions.reportRemoveFilter);\n if (reportRemoveFilter) {\n event.preventDefault();\n\n const filterContainer = reportRemoveFilter.closest(reportSelectors.regions.activeFilter);\n const filterName = filterContainer.dataset.filterName;\n\n getStrings([\n {key: 'deletefilter', component: 'core_reportbuilder', param: filterName},\n {key: 'deletefilterconfirm', component: 'core_reportbuilder', param: filterName},\n {key: 'delete', component: 'moodle'},\n ]).then(([confirmTitle, confirmText, confirmButton]) => {\n Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {\n const pendingPromise = new Pending('core_reportbuilder/filters:remove');\n\n deleteFilter(reportElement.dataset.reportId, filterContainer.dataset.filterId)\n .then(data => reloadSettingsFiltersRegion(reportElement, data))\n .then(() => getString('filterdeleted', 'core_reportbuilder', filterName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n return;\n }).catch(Notification.exception);\n }\n });\n\n // Initialize sortable list to handle active filters moving (note JQuery dependency, see MDL-72293 for resolution).\n var activeFiltersSortableList = new SortableList(`${reportSelectors.regions.activeFilters} ul`, {isHorizontal: false});\n activeFiltersSortableList.getElementName = element => Promise.resolve(element.data('filterName'));\n\n $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-filter-id]', (event, info) => {\n if (info.positionChanged) {\n const pendingPromise = new Pending('core_reportbuilder/filters:reorder');\n const filterId = info.element.data('filterId');\n const filterPosition = info.element.data('filterPosition');\n\n // Select target position, if moving to the end then count number of element siblings.\n let targetFilterPosition = info.targetNextElement.data('filterPosition') || info.element.siblings().length + 2;\n if (targetFilterPosition > filterPosition) {\n targetFilterPosition--;\n }\n\n reorderFilter(reportElement.dataset.reportId, filterId, targetFilterPosition)\n .then(data => reloadSettingsFiltersRegion(reportElement, data))\n .then(() => getString('filtermoved', 'core_reportbuilder', info.element.data('filterName')))\n .then(addToast)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n};\n"],"file":"filters.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/editor/sorting.min.js b/reportbuilder/amd/build/local/editor/sorting.min.js new file mode 100644 index 00000000000..3e7465508e0 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/sorting.min.js @@ -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/local/editor/sorting",["exports","jquery","core/inplace_editable","core/notification","core/pending","core/pubsub","core/sortable_list","core/str","core/toast","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/sorting","core/templates","core/event_dispatcher","core_reportbuilder/local/events"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=q(b);d=q(d);e=q(e);g=q(g);j=p(j);l=q(l);n=p(n);function o(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;o=function(){return a};return a}function p(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=o();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 q(a){return a&&a.__esModule?a:{default:a}}var r={ASCENDING:4,DESCENDING:3},s=function(a){var b=new e.default("core_reportbuilder/sorting:reload"),c=document.querySelector(j.regions.settingsSorting);return l.default.renderForPromise("core_reportbuilder/local/settings/sorting",{sorting:a}).then(function(a){var d=a.html,e=a.js;l.default.replaceNode(c,d,e);return b.resolve()})},t=function(a,b,c,d){var e=a.dataset.reportId,f=b.closest("li"),g=f.dataset.columnSortId,j=f.dataset.columnSortName;return(0,k.toggleColumnSorting)(e,g,c,d).then(s).then(function(){return(0,h.get_string)("columnsortupdated","core_reportbuilder",j)}).then(i.add).then(function(){(0,m.dispatchEvent)(n.tableReload,{},a);return null})},u=function(a,c){if(c){return}(0,f.subscribe)(n.publish.reportColumnsUpdated,function(a){return s(a).catch(d.default.exception)});a.addEventListener("click",function(b){var c=b.target.closest(j.actions.reportToggleColumnSort);if(c){b.preventDefault();var f=new e.default("core_reportbuilder/sorting:toggle"),g=parseInt(c.closest("li").dataset.columnSortDirection);t(a,c,c.checked,g).then(function(){return f.resolve()}).catch(d.default.exception)}var h=b.target.closest(j.actions.reportToggleColumnSortDirection);if(h){b.preventDefault();var i=new e.default("core_reportbuilder/sorting:direction"),k=h.closest("li"),l=k.dataset.columnSortEnabled,m=parseInt(k.dataset.columnSortDirection);if(m===r.ASCENDING){m=r.DESCENDING}else if(m===r.DESCENDING){m=r.ASCENDING}t(a,h,l,m).then(function(){return i.resolve()}).catch(d.default.exception)}});var l=new g.default("".concat(j.regions.settingsSorting," ul"),{isHorizontal:!1});l.getElementName=function(a){return Promise.resolve(a.data("columnSortName"))};(0,b.default)(a).on(g.default.EVENTS.DROP,"li[data-column-sort-id]",function(b,c){if(c.positionChanged){var f=new e.default("core_reportbuilder/sorting:reorder"),g=c.element.data("columnSortId"),j=c.element.data("columnSortPosition"),l=c.targetNextElement.data("columnSortPosition")||c.element.siblings().length+2;if(l>j){l--}(0,k.reorderColumnSorting)(a.dataset.reportId,g,l).then(s).then(function(){return(0,h.get_string)("columnsortupdated","core_reportbuilder",c.element.data("columnSortName"))}).then(i.add).then(function(){(0,m.dispatchEvent)(n.tableReload,{},a);return null}).then(function(){return f.resolve()}).catch(d.default.exception)}})};a.init=u}); +//# sourceMappingURL=sorting.min.js.map diff --git a/reportbuilder/amd/build/local/editor/sorting.min.js.map b/reportbuilder/amd/build/local/editor/sorting.min.js.map new file mode 100644 index 00000000000..9b528132be9 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/sorting.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/editor/sorting.js"],"names":["SORTORDER","ASCENDING","DESCENDING","reloadSettingsSortingRegion","context","pendingPromise","Pending","settingsSortingRegion","document","querySelector","reportSelectors","regions","settingsSorting","Templates","renderForPromise","sorting","then","html","js","replaceNode","resolve","updateSorting","reportElement","element","sortenabled","sortdirection","reportId","dataset","listElement","closest","columnId","columnSortId","columnName","columnSortName","addToast","reportEvents","tableReload","init","initialized","publish","reportColumnsUpdated","data","catch","Notification","exception","addEventListener","event","toggleSorting","target","actions","reportToggleColumnSort","preventDefault","parseInt","columnSortDirection","checked","toggleSortDirection","reportToggleColumnSortDirection","columnSortEnabled","columnsSortingSortableList","SortableList","isHorizontal","getElementName","Promise","on","EVENTS","DROP","info","positionChanged","columnPosition","targetColumnSortPosition","targetNextElement","siblings","length"],"mappings":"oqBAwBA,a,+DAEA,OAEA,OACA,OAEA,OAGA,OAEA,OAEA,O,4lBAGMA,CAAAA,CAAS,CAAG,CACdC,SAAS,CAAE,CADG,CAEdC,UAAU,CAAE,CAFE,C,CAWZC,CAA2B,CAAG,SAAAC,CAAO,CAAI,IACrCC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CADoB,CAErCC,CAAqB,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,OAAhB,CAAwBC,eAA/C,CAFa,CAI3C,MAAOC,WAAUC,gBAAV,CAA2B,2CAA3B,CAAwE,CAACC,OAAO,CAAEX,CAAV,CAAxE,EACFY,IADE,CACG,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CAClBL,UAAUM,WAAV,CAAsBZ,CAAtB,CAA6CU,CAA7C,CAAmDC,CAAnD,EACA,MAAOb,CAAAA,CAAc,CAACe,OAAf,EACV,CAJE,CAKV,C,CAWKC,CAAa,CAAG,SAACC,CAAD,CAAgBC,CAAhB,CAAyBC,CAAzB,CAAsCC,CAAtC,CAAwD,IACpEC,CAAAA,CAAQ,CAAGJ,CAAa,CAACK,OAAd,CAAsBD,QADmC,CAEpEE,CAAW,CAAGL,CAAO,CAACM,OAAR,CAAgB,IAAhB,CAFsD,CAGpEC,CAAQ,CAAGF,CAAW,CAACD,OAAZ,CAAoBI,YAHqC,CAIpEC,CAAU,CAAGJ,CAAW,CAACD,OAAZ,CAAoBM,cAJmC,CAM1E,MAAO,0BAAoBP,CAApB,CAA8BI,CAA9B,CAAwCN,CAAxC,CAAqDC,CAArD,EACFT,IADE,CACGb,CADH,EAEFa,IAFE,CAEG,iBAAM,iBAAU,mBAAV,CAA+B,oBAA/B,CAAqDgB,CAArD,CAAN,CAFH,EAGFhB,IAHE,CAGGkB,KAHH,EAIFlB,IAJE,CAIG,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4Cd,CAA5C,EACA,MAAO,KACV,CAPE,CAQV,C,CAQYe,CAAI,CAAG,SAACf,CAAD,CAAgBgB,CAAhB,CAAgC,CAChD,GAAIA,CAAJ,CAAiB,CACb,MACH,CAGD,gBAAUH,CAAY,CAACI,OAAb,CAAqBC,oBAA/B,CAAqD,SAAAC,CAAI,QAAItC,CAAAA,CAA2B,CAACsC,CAAD,CAA3B,CACxDC,KADwD,CAClDC,UAAaC,SADqC,CAAJ,CAAzD,EAIAtB,CAAa,CAACuB,gBAAd,CAA+B,OAA/B,CAAwC,SAAAC,CAAK,CAAI,CAG7C,GAAMC,CAAAA,CAAa,CAAGD,CAAK,CAACE,MAAN,CAAanB,OAAb,CAAqBnB,CAAe,CAACuC,OAAhB,CAAwBC,sBAA7C,CAAtB,CACA,GAAIH,CAAJ,CAAmB,CACfD,CAAK,CAACK,cAAN,GADe,GAGT9C,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAHR,CAITmB,CAAa,CAAG2B,QAAQ,CAACL,CAAa,CAAClB,OAAd,CAAsB,IAAtB,EAA4BF,OAA5B,CAAoC0B,mBAArC,CAJf,CAMfhC,CAAa,CAACC,CAAD,CAAgByB,CAAhB,CAA+BA,CAAa,CAACO,OAA7C,CAAsD7B,CAAtD,CAAb,CACKT,IADL,CACU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CADV,EAEKsB,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CAGD,GAAMW,CAAAA,CAAmB,CAAGT,CAAK,CAACE,MAAN,CAAanB,OAAb,CAAqBnB,CAAe,CAACuC,OAAhB,CAAwBO,+BAA7C,CAA5B,CACA,GAAID,CAAJ,CAAyB,CACrBT,CAAK,CAACK,cAAN,GADqB,GAGf9C,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,sCAAZ,CAHF,CAIfsB,CAAW,CAAG2B,CAAmB,CAAC1B,OAApB,CAA4B,IAA5B,CAJC,CAKfL,CAAW,CAAGI,CAAW,CAACD,OAAZ,CAAoB8B,iBALnB,CAOjBhC,CAAa,CAAG2B,QAAQ,CAACxB,CAAW,CAACD,OAAZ,CAAoB0B,mBAArB,CAPP,CAQrB,GAAI5B,CAAa,GAAKzB,CAAS,CAACC,SAAhC,CAA2C,CACvCwB,CAAa,CAAGzB,CAAS,CAACE,UAC7B,CAFD,IAEO,IAAIuB,CAAa,GAAKzB,CAAS,CAACE,UAAhC,CAA4C,CAC/CuB,CAAa,CAAGzB,CAAS,CAACC,SAC7B,CAEDoB,CAAa,CAACC,CAAD,CAAgBiC,CAAhB,CAAqC/B,CAArC,CAAkDC,CAAlD,CAAb,CACKT,IADL,CACU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CADV,EAEKsB,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CACJ,CAnCD,EAsCA,GAAIc,CAAAA,CAA0B,CAAG,GAAIC,UAAJ,WAAoBjD,CAAe,CAACC,OAAhB,CAAwBC,eAA5C,QAAkE,CAACgD,YAAY,GAAb,CAAlE,CAAjC,CACAF,CAA0B,CAACG,cAA3B,CAA4C,SAAAtC,CAAO,QAAIuC,CAAAA,OAAO,CAAC1C,OAAR,CAAgBG,CAAO,CAACkB,IAAR,CAAa,gBAAb,CAAhB,CAAJ,CAAnD,CAEA,cAAEnB,CAAF,EAAiByC,EAAjB,CAAoBJ,UAAaK,MAAb,CAAoBC,IAAxC,CAA8C,yBAA9C,CAAyE,SAACnB,CAAD,CAAQoB,CAAR,CAAiB,CACtF,GAAIA,CAAI,CAACC,eAAT,CAA0B,IAChB9D,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,oCAAZ,CADD,CAEhBwB,CAAQ,CAAGoC,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,cAAlB,CAFK,CAGhB2B,CAAc,CAAGF,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,oBAAlB,CAHD,CAMlB4B,CAAwB,CAAGH,CAAI,CAACI,iBAAL,CAAuB7B,IAAvB,CAA4B,oBAA5B,GAAqDyB,CAAI,CAAC3C,OAAL,CAAagD,QAAb,GAAwBC,MAAxB,CAAiC,CAN/F,CAOtB,GAAIH,CAAwB,CAAGD,CAA/B,CAA+C,CAC3CC,CAAwB,EAC3B,CAED,2BAAqB/C,CAAa,CAACK,OAAd,CAAsBD,QAA3C,CAAqDI,CAArD,CAA+DuC,CAA/D,EACKrD,IADL,CACUb,CADV,EAEKa,IAFL,CAEU,iBAAM,iBAAU,mBAAV,CAA+B,oBAA/B,CAAqDkD,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,gBAAlB,CAArD,CAAN,CAFV,EAGKzB,IAHL,CAGUkB,KAHV,EAIKlB,IAJL,CAIU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4Cd,CAA5C,EACA,MAAO,KACV,CAPL,EAQKN,IARL,CAQU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CARV,EASKsB,KATL,CASWC,UAAaC,SATxB,CAUH,CACJ,CAvBD,CAwBH,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 .\n\n/**\n * Report builder columns sorting editor\n *\n * @module core_reportbuilder/local/editor/sorting\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport $ from 'jquery';\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {subscribe} from 'core/pubsub';\nimport SortableList from 'core/sortable_list';\nimport {get_string as getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {reorderColumnSorting, toggleColumnSorting} from 'core_reportbuilder/local/repository/sorting';\nimport Templates from 'core/templates';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport * as reportEvents from 'core_reportbuilder/local/events';\n\n// These constants match PHP consts SORT_ASC, SORT_DESC.\nconst SORTORDER = {\n ASCENDING: 4,\n DESCENDING: 3,\n};\n\n/**\n * Reload sorting settings region\n *\n * @param {Object} context\n * @return {Promise}\n */\nconst reloadSettingsSortingRegion = context => {\n const pendingPromise = new Pending('core_reportbuilder/sorting:reload');\n const settingsSortingRegion = document.querySelector(reportSelectors.regions.settingsSorting);\n\n return Templates.renderForPromise('core_reportbuilder/local/settings/sorting', {sorting: context})\n .then(({html, js}) => {\n Templates.replaceNode(settingsSortingRegion, html, js);\n return pendingPromise.resolve();\n });\n};\n\n/**\n * Updates column sorting\n *\n * @param {Element} reportElement\n * @param {Element} element\n * @param {Number} sortenabled\n * @param {Number} sortdirection\n * @return {Promise}\n */\nconst updateSorting = (reportElement, element, sortenabled, sortdirection) => {\n const reportId = reportElement.dataset.reportId;\n const listElement = element.closest('li');\n const columnId = listElement.dataset.columnSortId;\n const columnName = listElement.dataset.columnSortName;\n\n return toggleColumnSorting(reportId, columnId, sortenabled, sortdirection)\n .then(reloadSettingsSortingRegion)\n .then(() => getString('columnsortupdated', 'core_reportbuilder', columnName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return null;\n });\n};\n\n/**\n * Initialise module\n *\n * @param {Element} reportElement\n * @param {Boolean} initialized Ensure we only add our listeners once\n */\nexport const init = (reportElement, initialized) => {\n if (initialized) {\n return;\n }\n\n // Update sorting region each time report columns are updated (added or removed).\n subscribe(reportEvents.publish.reportColumnsUpdated, data => reloadSettingsSortingRegion(data)\n .catch(Notification.exception)\n );\n\n reportElement.addEventListener('click', event => {\n\n // Enable/disable sorting on columns.\n const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort);\n if (toggleSorting) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/sorting:toggle');\n const sortdirection = parseInt(toggleSorting.closest('li').dataset.columnSortDirection);\n\n updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n\n // Change column sort direction.\n const toggleSortDirection = event.target.closest(reportSelectors.actions.reportToggleColumnSortDirection);\n if (toggleSortDirection) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/sorting:direction');\n const listElement = toggleSortDirection.closest('li');\n const sortenabled = listElement.dataset.columnSortEnabled;\n\n let sortdirection = parseInt(listElement.dataset.columnSortDirection);\n if (sortdirection === SORTORDER.ASCENDING) {\n sortdirection = SORTORDER.DESCENDING;\n } else if (sortdirection === SORTORDER.DESCENDING) {\n sortdirection = SORTORDER.ASCENDING;\n }\n\n updateSorting(reportElement, toggleSortDirection, sortenabled, sortdirection)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n\n // Initialize sortable list to handle column sorting moving (note JQuery dependency, see MDL-72293 for resolution).\n var columnsSortingSortableList = new SortableList(`${reportSelectors.regions.settingsSorting} ul`, {isHorizontal: false});\n columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName'));\n\n $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-column-sort-id]', (event, info) => {\n if (info.positionChanged) {\n const pendingPromise = new Pending('core_reportbuilder/sorting:reorder');\n const columnId = info.element.data('columnSortId');\n const columnPosition = info.element.data('columnSortPosition');\n\n // Select target position, if moving to the end then count number of element siblings.\n let targetColumnSortPosition = info.targetNextElement.data('columnSortPosition') || info.element.siblings().length + 2;\n if (targetColumnSortPosition > columnPosition) {\n targetColumnSortPosition--;\n }\n\n reorderColumnSorting(reportElement.dataset.reportId, columnId, targetColumnSortPosition)\n .then(reloadSettingsSortingRegion)\n .then(() => getString('columnsortupdated', 'core_reportbuilder', info.element.data('columnSortName')))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return null;\n })\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n};\n"],"file":"sorting.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/events.min.js b/reportbuilder/amd/build/local/events.min.js index 6a67e2031ef..12bfe48d2b9 100644 --- a/reportbuilder/amd/build/local/events.min.js +++ b/reportbuilder/amd/build/local/events.min.js @@ -1,2 +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}); +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",publish:{reportColumnsUpdated:"core_reportbuilder_report_columns_updated"}};return a.default}); //# sourceMappingURL=events.min.js.map diff --git a/reportbuilder/amd/build/local/events.min.js.map b/reportbuilder/amd/build/local/events.min.js.map index b3e28edea3a..15a70ccab98 100644 --- a/reportbuilder/amd/build/local/events.min.js.map +++ b/reportbuilder/amd/build/local/events.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/events.js"],"names":["tableReload"],"mappings":"2JA6Be,CAeXA,WAAW,CAAE,iCAfF,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 .\n\n/**\n * Report builder events\n *\n * @module core_reportbuilder/local/events\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Events for the Report builder subsystem\n *\n * @constant\n * @property {String} tableReload See {@link event:tableReload}\n */\nexport default {\n /**\n * Trigger table reloading\n *\n * @event tableReload\n * @type {CustomEvent}\n * @property {object} detail\n * @property {Boolean} detail.preservePagination Whether current pagination should be preserved (default false)\n *\n * @example Triggering table reload\n * import {dispatchEvent} from 'core/event_dispatcher';\n * import * as reportEvents from 'core_reportbuilder/local/events';\n *\n * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...));\n */\n tableReload: 'core_reportbuilder_table_reload',\n};\n"],"file":"events.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/events.js"],"names":["tableReload","publish","reportColumnsUpdated"],"mappings":"2JA6Be,CAeXA,WAAW,CAAE,iCAfF,CAgBXC,OAAO,CAAE,CACLC,oBAAoB,CAAE,2CADjB,CAhBE,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 .\n\n/**\n * Report builder events\n *\n * @module core_reportbuilder/local/events\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Events for the Report builder subsystem\n *\n * @constant\n * @property {String} tableReload See {@link event:tableReload}\n */\nexport default {\n /**\n * Trigger table reloading\n *\n * @event tableReload\n * @type {CustomEvent}\n * @property {object} detail\n * @property {Boolean} detail.preservePagination Whether current pagination should be preserved (default false)\n *\n * @example Triggering table reload\n * import {dispatchEvent} from 'core/event_dispatcher';\n * import * as reportEvents from 'core_reportbuilder/local/events';\n *\n * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...));\n */\n tableReload: 'core_reportbuilder_table_reload',\n publish: {\n reportColumnsUpdated: 'core_reportbuilder_report_columns_updated',\n },\n};\n"],"file":"events.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/columns.min.js b/reportbuilder/amd/build/local/repository/columns.min.js new file mode 100644 index 00000000000..f660ccb3236 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/columns.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/columns",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.reorderColumn=a.deleteColumn=a.addColumn=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c){return b.default.call([{methodname:"core_reportbuilder_columns_add",args:{reportid:a,uniqueidentifier:c}}])[0]};a.addColumn=c;var d=function(a,c){return b.default.call([{methodname:"core_reportbuilder_columns_delete",args:{reportid:a,columnid:c}}])[0]};a.deleteColumn=d;var e=function(a,c,d){return b.default.call([{methodname:"core_reportbuilder_columns_reorder",args:{reportid:a,columnid:c,position:d}}])[0]};a.reorderColumn=e}); +//# sourceMappingURL=columns.min.js.map diff --git a/reportbuilder/amd/build/local/repository/columns.min.js.map b/reportbuilder/amd/build/local/repository/columns.min.js.map new file mode 100644 index 00000000000..c0628a133bd --- /dev/null +++ b/reportbuilder/amd/build/local/repository/columns.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/repository/columns.js"],"names":["addColumn","reportId","uniqueIdentifier","Ajax","call","methodname","args","reportid","uniqueidentifier","deleteColumn","columnId","columnid","reorderColumn","position"],"mappings":"4MAwBA,uDASO,GAAMA,CAAAA,CAAS,CAAG,SAACC,CAAD,CAAWC,CAAX,CAAgC,CAMrD,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEN,CAAX,CAAqBO,gBAAgB,CAAEN,CAAvC,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,cAgBA,GAAMO,CAAAA,CAAY,CAAG,SAACR,CAAD,CAAWS,CAAX,CAAwB,CAMhD,MAAOP,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEN,CAAX,CAAqBU,QAAQ,CAAED,CAA/B,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,iBAiBA,GAAME,CAAAA,CAAa,CAAG,SAACX,CAAD,CAAWS,CAAX,CAAqBG,CAArB,CAAkC,CAM3D,MAAOV,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,oCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEN,CAAX,CAAqBU,QAAQ,CAAED,CAA/B,CAAyCG,QAAQ,CAAEA,CAAnD,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 .\n\n/**\n * Module to handle column AJAX requests\n *\n * @module core_reportbuilder/local/repository/columns\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Add column to given report\n *\n * @param {Number} reportId\n * @param {String} uniqueIdentifier\n * @return {Promise}\n */\nexport const addColumn = (reportId, uniqueIdentifier) => {\n const request = {\n methodname: 'core_reportbuilder_columns_add',\n args: {reportid: reportId, uniqueidentifier: uniqueIdentifier}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Remove column from given report\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @return {Promise}\n */\nexport const deleteColumn = (reportId, columnId) => {\n const request = {\n methodname: 'core_reportbuilder_columns_delete',\n args: {reportid: reportId, columnid: columnId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Re-order column within a report\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @param {Number} position\n * @return {Promise}\n */\nexport const reorderColumn = (reportId, columnId, position) => {\n const request = {\n methodname: 'core_reportbuilder_columns_reorder',\n args: {reportid: reportId, columnid: columnId, position: position}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"columns.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/conditions.min.js b/reportbuilder/amd/build/local/repository/conditions.min.js new file mode 100644 index 00000000000..b25f22e8666 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/conditions.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/conditions",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.reorderCondition=a.deleteCondition=a.addCondition=a.resetConditions=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_reportbuilder_conditions_reset",args:{reportid:a}}])[0]};a.resetConditions=c;var d=function(a,c){return b.default.call([{methodname:"core_reportbuilder_conditions_add",args:{reportid:a,uniqueidentifier:c}}])[0]};a.addCondition=d;var e=function(a,c){return b.default.call([{methodname:"core_reportbuilder_conditions_delete",args:{reportid:a,conditionid:c}}])[0]};a.deleteCondition=e;var f=function(a,c,d){return b.default.call([{methodname:"core_reportbuilder_conditions_reorder",args:{reportid:a,conditionid:c,position:d}}])[0]};a.reorderCondition=f}); +//# sourceMappingURL=conditions.min.js.map diff --git a/reportbuilder/amd/build/local/repository/conditions.min.js.map b/reportbuilder/amd/build/local/repository/conditions.min.js.map new file mode 100644 index 00000000000..693f1fc627d --- /dev/null +++ b/reportbuilder/amd/build/local/repository/conditions.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/repository/conditions.js"],"names":["resetConditions","reportId","Ajax","call","methodname","args","reportid","addCondition","uniqueIdentifier","uniqueidentifier","deleteCondition","conditionId","conditionid","reorderCondition","position"],"mappings":"0OAwBA,uDAQO,GAAMA,CAAAA,CAAe,CAAG,SAAAC,CAAQ,CAAI,CAMvC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,qCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,oBAgBA,GAAMM,CAAAA,CAAY,CAAG,SAACN,CAAD,CAAWO,CAAX,CAAgC,CAMxD,MAAON,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBQ,gBAAgB,CAAED,CAAvC,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,iBAgBA,GAAME,CAAAA,CAAe,CAAG,SAACT,CAAD,CAAWU,CAAX,CAA2B,CAMtD,MAAOT,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBW,WAAW,CAAED,CAAlC,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,oBAiBA,GAAME,CAAAA,CAAgB,CAAG,SAACZ,CAAD,CAAWU,CAAX,CAAwBG,CAAxB,CAAqC,CAMjE,MAAOZ,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,uCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBW,WAAW,CAAED,CAAlC,CAA+CG,QAAQ,CAAEA,CAAzD,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 .\n\n/**\n * Module to handle condition AJAX requests\n *\n * @module core_reportbuilder/local/repository/conditions\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \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 conditions for given report\n *\n * @param {Number} reportId\n * @return {Promise}\n */\nexport const resetConditions = reportId => {\n const request = {\n methodname: 'core_reportbuilder_conditions_reset',\n args: {reportid: reportId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Add condition to given report\n *\n * @param {Number} reportId\n * @param {String} uniqueIdentifier\n * @return {Promise}\n */\nexport const addCondition = (reportId, uniqueIdentifier) => {\n const request = {\n methodname: 'core_reportbuilder_conditions_add',\n args: {reportid: reportId, uniqueidentifier: uniqueIdentifier}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Remove condition from given report\n *\n * @param {Number} reportId\n * @param {Number} conditionId\n * @return {Promise}\n */\nexport const deleteCondition = (reportId, conditionId) => {\n const request = {\n methodname: 'core_reportbuilder_conditions_delete',\n args: {reportid: reportId, conditionid: conditionId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Reorder a condition in a given report\n *\n * @param {Number} reportId\n * @param {Number} conditionId\n * @param {Number} position\n * @return {Promise}\n */\nexport const reorderCondition = (reportId, conditionId, position) => {\n const request = {\n methodname: 'core_reportbuilder_conditions_reorder',\n args: {reportid: reportId, conditionid: conditionId, position: position}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"conditions.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/filters.min.js b/reportbuilder/amd/build/local/repository/filters.min.js index c68a07eddac..765fa025c0f 100644 --- a/reportbuilder/amd/build/local/repository/filters.min.js +++ b/reportbuilder/amd/build/local/repository/filters.min.js @@ -1,2 +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}); +define ("core_reportbuilder/local/repository/filters",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.reorderFilter=a.deleteFilter=a.addFilter=a.resetFilters=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.resetFilters=c;var d=function(a,c){return b.default.call([{methodname:"core_reportbuilder_filters_add",args:{reportid:a,uniqueidentifier:c}}])[0]};a.addFilter=d;var e=function(a,c){return b.default.call([{methodname:"core_reportbuilder_filters_delete",args:{reportid:a,filterid:c}}])[0]};a.deleteFilter=e;var f=function(a,c,d){return b.default.call([{methodname:"core_reportbuilder_filters_reorder",args:{reportid:a,filterid:c,position:d}}])[0]};a.reorderFilter=f}); //# sourceMappingURL=filters.min.js.map diff --git a/reportbuilder/amd/build/local/repository/filters.min.js.map b/reportbuilder/amd/build/local/repository/filters.min.js.map index 9dea17e3682..5527b51ec96 100644 --- a/reportbuilder/amd/build/local/repository/filters.min.js.map +++ b/reportbuilder/amd/build/local/repository/filters.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/repository/filters.js"],"names":["reset","reportId","Ajax","call","methodname","args","reportid"],"mappings":"yKAuBA,uDASO,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 .\n\n/**\n * Module to handle filter AJAX requests\n *\n * @module core_reportbuilder/local/repository/filters\n * @copyright 2021 Paul Holden \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 * @method\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"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/repository/filters.js"],"names":["resetFilters","reportId","Ajax","call","methodname","args","reportid","addFilter","uniqueIdentifier","uniqueidentifier","deleteFilter","filterId","filterid","reorderFilter","position"],"mappings":"2NAuBA,uDASO,GAAMA,CAAAA,CAAY,CAAG,SAAAC,CAAQ,CAAI,CAMpC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,kCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,iBAgBA,GAAMM,CAAAA,CAAS,CAAG,SAACN,CAAD,CAAWO,CAAX,CAAgC,CAMrD,MAAON,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBQ,gBAAgB,CAAED,CAAvC,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,cAgBA,GAAME,CAAAA,CAAY,CAAG,SAACT,CAAD,CAAWU,CAAX,CAAwB,CAMhD,MAAOT,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBW,QAAQ,CAAED,CAA/B,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,iBAiBA,GAAME,CAAAA,CAAa,CAAG,SAACZ,CAAD,CAAWU,CAAX,CAAqBG,CAArB,CAAkC,CAM3D,MAAOZ,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,oCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBW,QAAQ,CAAED,CAA/B,CAAyCG,QAAQ,CAAEA,CAAnD,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 .\n\n/**\n * Module to handle filter AJAX requests\n *\n * @module core_reportbuilder/local/repository/filters\n * @copyright 2021 Paul Holden \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 * @method\n * @param {Number} reportId\n * @return {Promise}\n */\nexport const resetFilters = reportId => {\n const request = {\n methodname: 'core_reportbuilder_filters_reset',\n args: {reportid: reportId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Add a filter to the given report\n *\n * @param {Number} reportId\n * @param {String} uniqueIdentifier\n * @return {Promise}\n */\nexport const addFilter = (reportId, uniqueIdentifier) => {\n const request = {\n methodname: 'core_reportbuilder_filters_add',\n args: {reportid: reportId, uniqueidentifier: uniqueIdentifier}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Remove filter from given report\n *\n * @param {Number} reportId\n * @param {Number} filterId\n * @return {Promise}\n */\nexport const deleteFilter = (reportId, filterId) => {\n const request = {\n methodname: 'core_reportbuilder_filters_delete',\n args: {reportid: reportId, filterid: filterId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Reorder a filter in a given report\n *\n * @param {Number} reportId\n * @param {Number} filterId\n * @param {Number} position\n * @return {Promise}\n */\nexport const reorderFilter = (reportId, filterId, position) => {\n const request = {\n methodname: 'core_reportbuilder_filters_reorder',\n args: {reportid: reportId, filterid: filterId, position: position}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"filters.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/modals.min.js b/reportbuilder/amd/build/local/repository/modals.min.js new file mode 100644 index 00000000000..846e20380f9 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/modals.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/modals",["exports","core_form/modalform","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.createReportModal=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d=function(a,d){var e=2.\n\n/**\n * Module to handle modal form requests\n *\n * @module core_reportbuilder/local/repository/modals\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Open the New Report modal form\n *\n * @param {EventTarget} triggerElement\n * @param {Promise} modalTitle\n * @param {Number} reportId\n * @return {ModalForm}\n */\nexport const createReportModal = (triggerElement, modalTitle, reportId = 0) => {\n return new ModalForm({\n modalConfig: {\n title: modalTitle,\n },\n formClass: 'core_reportbuilder\\\\form\\\\report',\n args: {\n id: reportId,\n },\n saveButtonText: getString('save', 'moodle'),\n returnFocus: triggerElement,\n });\n};\n"],"file":"modals.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/reports.min.js b/reportbuilder/amd/build/local/repository/reports.min.js new file mode 100644 index 00000000000..8c7c05de4f7 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/reports.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/reports",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getReport=a.deleteReport=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_reportbuilder_reports_delete",args:{reportid:a}}])[0]};a.deleteReport=c;var d=function(a,c){return b.default.call([{methodname:"core_reportbuilder_reports_get",args:{reportid:a,editmode:c}}])[0]};a.getReport=d}); +//# sourceMappingURL=reports.min.js.map diff --git a/reportbuilder/amd/build/local/repository/reports.min.js.map b/reportbuilder/amd/build/local/repository/reports.min.js.map new file mode 100644 index 00000000000..da83a55bf5e --- /dev/null +++ b/reportbuilder/amd/build/local/repository/reports.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/repository/reports.js"],"names":["deleteReport","reportId","Ajax","call","methodname","args","reportid","getReport","editMode","editmode"],"mappings":"4LAwBA,uDAQO,GAAMA,CAAAA,CAAY,CAAG,SAAAC,CAAQ,CAAI,CAMpC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,iBAgBA,GAAMM,CAAAA,CAAS,CAAG,SAACN,CAAD,CAAWO,CAAX,CAAwB,CAM7C,MAAON,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEL,CAAX,CAAqBQ,QAAQ,CAAED,CAA/B,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 .\n\n/**\n * Module to handle report AJAX requests\n *\n * @module core_reportbuilder/local/repository/reports\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Delete given report\n *\n * @param {Number} reportId\n * @return {Promise}\n */\nexport const deleteReport = reportId => {\n const request = {\n methodname: 'core_reportbuilder_reports_delete',\n args: {reportid: reportId}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get report content\n *\n * @param {Number} reportId\n * @param {Boolean} editMode\n * @return {Promise}\n */\nexport const getReport = (reportId, editMode) => {\n const request = {\n methodname: 'core_reportbuilder_reports_get',\n args: {reportid: reportId, editmode: editMode}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"reports.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/sorting.min.js b/reportbuilder/amd/build/local/repository/sorting.min.js new file mode 100644 index 00000000000..5943332fefe --- /dev/null +++ b/reportbuilder/amd/build/local/repository/sorting.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/sorting",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.toggleColumnSorting=a.reorderColumnSorting=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"core_reportbuilder_columns_sort_reorder",args:{reportid:a,columnid:c,position:d}}])[0]};a.reorderColumnSorting=c;var d=function(a,c,d,e){return b.default.call([{methodname:"core_reportbuilder_columns_sort_toggle",args:{reportid:a,columnid:c,enabled:d,direction:e}}])[0]};a.toggleColumnSorting=d}); +//# sourceMappingURL=sorting.min.js.map diff --git a/reportbuilder/amd/build/local/repository/sorting.min.js.map b/reportbuilder/amd/build/local/repository/sorting.min.js.map new file mode 100644 index 00000000000..418716b9c69 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/sorting.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/repository/sorting.js"],"names":["reorderColumnSorting","reportId","columnId","position","Ajax","call","methodname","args","reportid","columnid","toggleColumnSorting","enabled","direction"],"mappings":"8MAwBA,uDAUO,GAAMA,CAAAA,CAAoB,CAAG,SAACC,CAAD,CAAWC,CAAX,CAAqBC,CAArB,CAAkC,CAMlE,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,yCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEP,CAAX,CAAqBQ,QAAQ,CAAEP,CAA/B,CAAyCC,QAAQ,CAAEA,CAAnD,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,yBAkBA,GAAMO,CAAAA,CAAmB,CAAG,SAACT,CAAD,CAAWC,CAAX,CAAqBS,CAArB,CAA8BC,CAA9B,CAA4C,CAM3E,MAAOR,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEP,CAAX,CAAqBQ,QAAQ,CAAEP,CAA/B,CAAyCS,OAAO,CAAEA,CAAlD,CAA2DC,SAAS,CAAEA,CAAtE,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 .\n\n/**\n * Module to handle column AJAX requests\n *\n * @module core_reportbuilder/local/repository/sorting\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Re-order sort column position\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @param {Number} position\n * @return {Promise}\n */\nexport const reorderColumnSorting = (reportId, columnId, position) => {\n const request = {\n methodname: 'core_reportbuilder_columns_sort_reorder',\n args: {reportid: reportId, columnid: columnId, position: position}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Enables/disabled sorting on column\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @param {Boolean} enabled\n * @param {Number} direction\n * @return {Promise}\n */\nexport const toggleColumnSorting = (reportId, columnId, enabled, direction) => {\n const request = {\n methodname: 'core_reportbuilder_columns_sort_toggle',\n args: {reportid: reportId, columnid: columnId, enabled: enabled, direction: direction}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"sorting.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/selectors.min.js b/reportbuilder/amd/build/local/selectors.min.js index 3e84ec64249..20bfe1e626b 100644 --- a/reportbuilder/amd/build/local/selectors.min.js +++ b/reportbuilder/amd/build/local/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{systemReport:"[data-region=\"core_reportbuilder/system-report\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]"}};b.forSystemReport=function(a){return"".concat(b.regions.systemReport,"[data-reportid=\"").concat(a,"\"]")};a.default=b;return a.default}); +define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{report:"[data-region=\"core_reportbuilder/report\"]",reportTable:"[data-region=\"reportbuilder-table\"]",columnHeader:"[data-region=\"column-header\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]",sidebarMenu:"[data-region=\"sidebar-menu\"]",sidebarCard:"[data-region=\"sidebar-card\"]",settingsConditions:"[data-region=\"settings-conditions\"]",activeConditions:"[data-region=\"active-conditions\"]",activeCondition:"[data-region=\"active-condition\"]",settingsFilters:"[data-region=\"settings-filters\"]",activeFilters:"[data-region=\"active-filters\"]",activeFilter:"[data-region=\"active-filter\"]",settingsSorting:"[data-region=\"settings-sorting\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]",reportCreate:"[data-action=\"report-create\"]",reportEdit:"[data-action=\"report-edit\"]",reportDelete:"[data-action=\"report-delete\"]",reportAddColumn:"[data-action=\"report-add-column\"]",reportRemoveColumn:"[data-action=\"report-remove-column\"]",reportAddCondition:"[data-action=\"report-add-condition\"]",reportRemoveCondition:"[data-action=\"report-remove-condition\"]",reportAddFilter:"[data-action=\"report-add-filter\"]",reportRemoveFilter:"[data-action=\"report-remove-filter\"]",reportToggleColumnSort:"[data-action=\"report-toggle-column-sorting\"]",reportToggleColumnSortDirection:"[data-action=\"report-toggle-sort-direction\"]",sidebarSearch:"[data-action=\"sidebar-search\"]",toggleEditPreview:"[data-action=\"toggle-edit-preview\"]"}};b.forReport=function(a){return"".concat(b.regions.report,"[data-report-id=\"").concat(a,"\"]")};a.default=b;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/reportbuilder/amd/build/local/selectors.min.js.map b/reportbuilder/amd/build/local/selectors.min.js.map index 908c8bab73f..e3491b01a44 100644 --- a/reportbuilder/amd/build/local/selectors.min.js.map +++ b/reportbuilder/amd/build/local/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","systemReport","filterButtonLabel","filtersForm","actions","reportActionPopup","forSystemReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,oDADT,CAELC,iBAAiB,CAAE,uCAFd,CAGLC,WAAW,CAAE,gCAHR,CADK,CAMdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CANK,CAAlB,CAkBAN,CAAS,CAACO,eAAV,CAA4B,SAAAC,CAAQ,kBAAOR,CAAS,CAACC,OAAV,CAAkBC,YAAzB,6BAAwDM,CAAxD,QAApC,C,UAEeR,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n systemReport: '[data-region=\"core_reportbuilder/system-report\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n }\n};\n\n/**\n * Selector for given report\n *\n * @method forSystemReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","report","reportTable","columnHeader","filterButtonLabel","filtersForm","sidebarMenu","sidebarCard","settingsConditions","activeConditions","activeCondition","settingsFilters","activeFilters","activeFilter","settingsSorting","actions","reportActionPopup","reportCreate","reportEdit","reportDelete","reportAddColumn","reportRemoveColumn","reportAddCondition","reportRemoveCondition","reportAddFilter","reportRemoveFilter","reportToggleColumnSort","reportToggleColumnSortDirection","sidebarSearch","toggleEditPreview","forReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,MAAM,CAAE,6CADH,CAELC,WAAW,CAAE,uCAFR,CAGLC,YAAY,CAAE,iCAHT,CAILC,iBAAiB,CAAE,uCAJd,CAKLC,WAAW,CAAE,gCALR,CAMLC,WAAW,CAAE,gCANR,CAOLC,WAAW,CAAE,gCAPR,CAQLC,kBAAkB,CAAE,uCARf,CASLC,gBAAgB,CAAE,qCATb,CAULC,eAAe,CAAE,oCAVZ,CAWLC,eAAe,CAAE,oCAXZ,CAYLC,aAAa,CAAE,kCAZV,CAaLC,YAAY,CAAE,iCAbT,CAcLC,eAAe,CAAE,oCAdZ,CADK,CAiBdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CAELC,YAAY,CAAE,iCAFT,CAGLC,UAAU,CAAE,+BAHP,CAILC,YAAY,CAAE,iCAJT,CAKLC,eAAe,CAAE,qCALZ,CAMLC,kBAAkB,CAAE,wCANf,CAOLC,kBAAkB,CAAE,wCAPf,CAQLC,qBAAqB,CAAE,2CARlB,CASLC,eAAe,CAAE,qCATZ,CAULC,kBAAkB,CAAE,wCAVf,CAWLC,sBAAsB,CAAE,gDAXnB,CAYLC,+BAA+B,CAAE,gDAZ5B,CAaLC,aAAa,CAAE,kCAbV,CAcLC,iBAAiB,CAAE,uCAdd,CAjBK,CAAlB,CA0CA9B,CAAS,CAAC+B,SAAV,CAAsB,SAAAC,CAAQ,kBAAOhC,CAAS,CAACC,OAAV,CAAkBC,MAAzB,8BAAmD8B,CAAnD,QAA9B,C,UAEehC,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 .\n\n/**\n * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n report: '[data-region=\"core_reportbuilder/report\"]',\n reportTable: '[data-region=\"reportbuilder-table\"]',\n columnHeader: '[data-region=\"column-header\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n sidebarMenu: '[data-region=\"sidebar-menu\"]',\n sidebarCard: '[data-region=\"sidebar-card\"]',\n settingsConditions: '[data-region=\"settings-conditions\"]',\n activeConditions: '[data-region=\"active-conditions\"]',\n activeCondition: '[data-region=\"active-condition\"]',\n settingsFilters: '[data-region=\"settings-filters\"]',\n activeFilters: '[data-region=\"active-filters\"]',\n activeFilter: '[data-region=\"active-filter\"]',\n settingsSorting: '[data-region=\"settings-sorting\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n reportCreate: '[data-action=\"report-create\"]',\n reportEdit: '[data-action=\"report-edit\"]',\n reportDelete: '[data-action=\"report-delete\"]',\n reportAddColumn: '[data-action=\"report-add-column\"]',\n reportRemoveColumn: '[data-action=\"report-remove-column\"]',\n reportAddCondition: '[data-action=\"report-add-condition\"]',\n reportRemoveCondition: '[data-action=\"report-remove-condition\"]',\n reportAddFilter: '[data-action=\"report-add-filter\"]',\n reportRemoveFilter: '[data-action=\"report-remove-filter\"]',\n reportToggleColumnSort: '[data-action=\"report-toggle-column-sorting\"]',\n reportToggleColumnSortDirection: '[data-action=\"report-toggle-sort-direction\"]',\n sidebarSearch: '[data-action=\"sidebar-search\"]',\n toggleEditPreview: '[data-action=\"toggle-edit-preview\"]',\n },\n};\n\n/**\n * Selector for given report\n *\n * @method forReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forReport = reportId => `${SELECTORS.regions.report}[data-report-id=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/report.min.js b/reportbuilder/amd/build/report.min.js index 54a9849e106..212c8f03c3e 100644 --- a/reportbuilder/amd/build/report.min.js +++ b/reportbuilder/amd/build/report.min.js @@ -1,2 +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)}}());document.addEventListener("click",function(a){var b=a.target.closest(c.actions.reportActionPopup);if(null===b){return}a.preventDefault();var d=JSON.parse(b.dataset.popupAction);window.openpopup(a,d.jsfunctionargs)})};a.init=j}); +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=!1,k=function(){if(j){return}document.addEventListener(b.tableReload,function(){var a=i(regeneratorRuntime.mark(function a(b){var f,g,h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:g=b.target.closest(c.regions.report);if(!(null===g)){a.next=3;break}return a.abrupt("return");case 3:h=g.querySelector(e.main.region);i=(null===(f=b.detail)||void 0===f?void 0:f.preservePagination)?null:1;a.next=7;return(0,d.setPageNumber)(h,i,!1).then(d.refreshTableContent);case 7:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}());document.addEventListener("click",function(a){var b=a.target.closest(c.actions.reportActionPopup);if(null===b){return}a.preventDefault();var d=JSON.parse(b.dataset.popupAction);window.openpopup(a,d.jsfunctionargs)});j=!0};a.init=k}); //# sourceMappingURL=report.min.js.map diff --git a/reportbuilder/amd/build/report.min.js.map b/reportbuilder/amd/build/report.min.js.map index a9de2c83148..a59df83b8e2 100644 --- a/reportbuilder/amd/build/report.min.js.map +++ b/reportbuilder/amd/build/report.min.js.map @@ -1 +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","reportActionPopup","actions","preventDefault","popupAction","JSON","parse","dataset","window","openpopup","jsfunctionargs"],"mappings":"ojBAuBA,OACA,OAEA,O,q2BAQO,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,yDAcAnB,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAG,CAAK,CAAI,CACxC,GAAMgB,CAAAA,CAAiB,CAAGhB,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACa,OAAhB,CAAwBD,iBAA7C,CAA1B,CACA,GAA0B,IAAtB,GAAAA,CAAJ,CAAgC,CAC5B,MACH,CACDhB,CAAK,CAACkB,cAAN,GACA,GAAMC,CAAAA,CAAW,CAAGC,IAAI,CAACC,KAAL,CAAWL,CAAiB,CAACM,OAAlB,CAA0BH,WAArC,CAApB,CACAI,MAAM,CAACC,SAAP,CAAiBxB,CAAjB,CAAwBmB,CAAW,CAACM,cAApC,CACH,CARD,CASH,CAzBM,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 .\n\n/**\n * Report builder report management\n *\n * @module core_reportbuilder/report\n * @copyright 2021 Paul Holden \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 for given report\n *\n * @method\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 // Listen for trigger popup events.\n document.addEventListener('click', event => {\n const reportActionPopup = event.target.closest(reportSelectors.actions.reportActionPopup);\n if (reportActionPopup === null) {\n return;\n }\n event.preventDefault();\n const popupAction = JSON.parse(reportActionPopup.dataset.popupAction);\n window.openpopup(event, popupAction.jsfunctionargs);\n });\n};\n"],"file":"report.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/report.js"],"names":["initialized","init","document","addEventListener","reportEvents","tableReload","event","reportElement","target","closest","reportSelectors","regions","report","tableRoot","querySelector","tableSelectors","main","region","pageNumber","detail","preservePagination","then","refreshTableContent","reportActionPopup","actions","preventDefault","popupAction","JSON","parse","dataset","window","openpopup","jsfunctionargs"],"mappings":"ojBAuBA,OACA,OAEA,O,w2BAEIA,CAAAA,CAAW,G,CAOFC,CAAI,CAAG,UAAM,CAEtB,GAAID,CAAJ,CAAiB,CAEb,MACH,CAGDE,QAAQ,CAACC,gBAAT,CAA0BC,CAAY,CAACC,WAAvC,4CAAoD,WAAMC,CAAN,+FAC1CC,CAD0C,CAC1BD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACC,OAAhB,CAAwBC,MAA7C,CAD0B,MAE1B,IAAlB,GAAAL,CAF4C,mDAM1CM,CAN0C,CAM9BN,CAAa,CAACO,aAAd,CAA4BC,CAAc,CAACC,IAAf,CAAoBC,MAAhD,CAN8B,CAO1CC,CAP0C,CAO7B,WAAAZ,CAAK,CAACa,MAAN,uBAAcC,kBAAd,EAAmC,IAAnC,CAA0C,CAPb,gBAS1C,oBAAcP,CAAd,CAAyBK,CAAzB,KACDG,IADC,CACIC,qBADJ,CAT0C,yCAApD,yDAcApB,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAG,CAAK,CAAI,CACxC,GAAMiB,CAAAA,CAAiB,CAAGjB,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBC,CAAe,CAACc,OAAhB,CAAwBD,iBAA7C,CAA1B,CACA,GAA0B,IAAtB,GAAAA,CAAJ,CAAgC,CAC5B,MACH,CACDjB,CAAK,CAACmB,cAAN,GACA,GAAMC,CAAAA,CAAW,CAAGC,IAAI,CAACC,KAAL,CAAWL,CAAiB,CAACM,OAAlB,CAA0BH,WAArC,CAApB,CACAI,MAAM,CAACC,SAAP,CAAiBzB,CAAjB,CAAwBoB,CAAW,CAACM,cAApC,CACH,CARD,EAUAhC,CAAW,GACd,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 .\n\n/**\n * Report builder report management\n *\n * @module core_reportbuilder/report\n * @copyright 2021 Paul Holden \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\nlet initialized = false;\n\n/**\n * Initialise module for given report\n *\n * @method\n */\nexport const init = () => {\n\n if (initialized) {\n // We already added the event listeners (can be called multiple times by mustache template).\n return;\n }\n\n // Listen for the table reload event.\n document.addEventListener(reportEvents.tableReload, async(event) => {\n const reportElement = event.target.closest(reportSelectors.regions.report);\n if (reportElement === null) {\n return;\n }\n\n const tableRoot = reportElement.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 // Listen for trigger popup events.\n document.addEventListener('click', event => {\n const reportActionPopup = event.target.closest(reportSelectors.actions.reportActionPopup);\n if (reportActionPopup === null) {\n return;\n }\n event.preventDefault();\n const popupAction = JSON.parse(reportActionPopup.dataset.popupAction);\n window.openpopup(event, popupAction.jsfunctionargs);\n });\n\n initialized = true;\n};\n"],"file":"report.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/reports_list.min.js b/reportbuilder/amd/build/reports_list.min.js new file mode 100644 index 00000000000..af03106cf2a --- /dev/null +++ b/reportbuilder/amd/build/reports_list.min.js @@ -0,0 +1,2 @@ +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_reportbuilder/reports_list",["exports","core/event_dispatcher","core/notification","core/pending","core/str","core/toast","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/reports","core_reportbuilder/local/repository/modals"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;c=m(c);d=m(d);g=l(g);h=l(h);function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a){return a&&a.__esModule?a:{default:a}}function n(a,b){return s(a)||r(a,b)||p(a,b)||o()}function o(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function p(a,b){if(!a)return;if("string"==typeof a)return q(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return q(a,b)}function q(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Report builder reports list management\n *\n * @module core_reportbuilder/reports_list\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {deleteReport} from 'core_reportbuilder/local/repository/reports';\nimport {createReportModal} from 'core_reportbuilder/local/repository/modals';\n\n/**\n * Initialise module\n */\nexport const init = () => {\n document.addEventListener('click', event => {\n const reportCreate = event.target.closest(reportSelectors.actions.reportCreate);\n if (reportCreate) {\n event.preventDefault();\n\n // Redirect user to editing interface for the report after submission.\n const reportModal = createReportModal(event.target, getString('newreport', 'core_reportbuilder'));\n reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, event => {\n window.location.href = event.detail;\n });\n\n reportModal.show();\n }\n\n const reportEdit = event.target.closest(reportSelectors.actions.reportEdit);\n if (reportEdit) {\n event.preventDefault();\n\n // Reload current report page after submission.\n const reportModal = createReportModal(event.target, getString('editreportdetails', 'core_reportbuilder'),\n reportEdit.dataset.reportId);\n reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, () => {\n const reportElement = event.target.closest(reportSelectors.regions.report);\n\n getString('reportupdated', 'core_reportbuilder')\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return;\n })\n .catch(Notification.exception);\n });\n\n reportModal.show();\n }\n\n const reportDelete = event.target.closest(reportSelectors.actions.reportDelete);\n if (reportDelete) {\n event.preventDefault();\n\n getStrings([\n {key: 'deletereport', component: 'core_reportbuilder'},\n {key: 'deletereportconfirm', component: 'core_reportbuilder', param: reportDelete.dataset.reportName},\n {key: 'delete', component: 'moodle'},\n ]).then(([confirmTitle, confirmText, confirmButton]) => {\n Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {\n const pendingPromise = new Pending('core_reportbuilder/reports:delete');\n const reportElement = event.target.closest(reportSelectors.regions.report);\n\n deleteReport(reportDelete.dataset.reportId)\n .then(() => getString('reportdeleted', 'core_reportbuilder'))\n .then(addToast)\n .then(() => {\n pendingPromise.resolve();\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return;\n })\n .catch(Notification.exception);\n });\n return;\n }).catch(Notification.exception);\n }\n });\n};\n"],"file":"reports_list.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/sidebar.min.js b/reportbuilder/amd/build/sidebar.min.js new file mode 100644 index 00000000000..de16bc9401d --- /dev/null +++ b/reportbuilder/amd/build/sidebar.min.js @@ -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/sidebar",["exports","core/pending","core/utils","core_reportbuilder/local/selectors"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);d=f(d);function e(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;e=function(){return a};return a}function f(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=e();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var f in a){if(Object.prototype.hasOwnProperty.call(a,f)){var g=d?Object.getOwnPropertyDescriptor(a,f):null;if(g&&(g.get||g.set)){Object.defineProperty(c,f,g)}else{c[f]=a[f]}}}c.default=a;if(b){b.set(a,c)}return c}var g={EXPANDED:"show",COLLAPSED:"collapsed",SHOW:"d-flex",HIDE:"d-none"},h=function(a,c){var e=new b.default("core_reportbuilder/sidebar:cardFilter"),f=c.querySelectorAll(d.regions.sidebarCard),h=c.querySelectorAll(".list-group-item"),j=a.target.value.toLowerCase();h.forEach(function(a){var b=a.dataset.name.toLowerCase();if(b.includes(j)){a.classList.replace(g.HIDE,g.SHOW)}else{a.classList.replace(g.SHOW,g.HIDE)}});f.forEach(function(a){if(0.\n\n/**\n * Report builder sidebar component\n *\n * @module core_reportbuilder/sidebar\n * @package core_reportbuilder\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport {debounce} from 'core/utils';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\n\nconst DEBOUNCE_TIMER = 250;\n\nconst CLASSES = {\n EXPANDED: 'show',\n COLLAPSED: 'collapsed',\n SHOW: 'd-flex',\n HIDE: 'd-none',\n};\n\n/**\n * Initialise module\n *\n * @param {Event} event\n * @param {Element} sidebarMenu\n */\nconst sidebarCardFilter = (event, sidebarMenu) => {\n const pendingPromise = new Pending('core_reportbuilder/sidebar:cardFilter');\n\n const sidebarCards = sidebarMenu.querySelectorAll(reportSelectors.regions.sidebarCard);\n const sidebarItems = sidebarMenu.querySelectorAll('.list-group-item');\n const searchTerm = event.target.value.toLowerCase();\n\n // Toggle items according to match against search term.\n sidebarItems.forEach(item => {\n let itemName = item.dataset.name.toLowerCase();\n if (itemName.includes(searchTerm)) {\n item.classList.replace(CLASSES.HIDE, CLASSES.SHOW);\n } else {\n item.classList.replace(CLASSES.SHOW, CLASSES.HIDE);\n }\n });\n\n // Toggle cards according to whether they have any visible items.\n sidebarCards.forEach(card => {\n if (card.querySelectorAll(`.${CLASSES.SHOW}`).length > 0) {\n card.classList.remove(CLASSES.HIDE);\n expandCard(card);\n } else {\n card.classList.add(CLASSES.HIDE);\n }\n });\n\n pendingPromise.resolve();\n};\n\n/**\n * Show a collapsed card.\n * This function simulates the behaviour of JQuery show method on a collapsible element.\n *\n * @param {Element} card\n */\nconst expandCard = (card) => {\n let cardButton = card.querySelector('[data-toggle=\"collapse\"]');\n if (cardButton.classList.contains(CLASSES.COLLAPSED)) {\n cardButton.classList.remove(CLASSES.COLLAPSED);\n cardButton.setAttribute('aria-expanded', \"true\");\n let cardContent = card.querySelector(cardButton.dataset.target);\n cardContent.classList.add(CLASSES.EXPANDED);\n }\n};\n\n/**\n * Initialise module\n */\nexport const init = () => {\n const sidebarMenu = document.querySelector(reportSelectors.regions.sidebarMenu);\n const sidebarSearch = sidebarMenu.querySelector(reportSelectors.actions.sidebarSearch);\n\n // Debounce the event listener to allow the user to finish typing.\n sidebarSearch.addEventListener('keyup', event => {\n const pendingPromise = new Pending('core_reportbuilder/sidebar:keyup');\n\n debounce(sidebarCardFilter, DEBOUNCE_TIMER)(event, sidebarMenu);\n setTimeout(() => {\n pendingPromise.resolve();\n }, DEBOUNCE_TIMER);\n });\n};\n"],"file":"sidebar.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/src/editor.js b/reportbuilder/amd/src/editor.js new file mode 100644 index 00000000000..9da84f0e6cf --- /dev/null +++ b/reportbuilder/amd/src/editor.js @@ -0,0 +1,83 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder editor + * + * @module core_reportbuilder/editor + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import 'core/inplace_editable'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import Templates from 'core/templates'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {init as columnsEditorInit} from 'core_reportbuilder/local/editor/columns'; +import {init as conditionsEditorInit} from 'core_reportbuilder/local/editor/conditions'; +import {init as filtersEditorInit} from 'core_reportbuilder/local/editor/filters'; +import {init as sortingEditorInit} from 'core_reportbuilder/local/editor/sorting'; +import {getReport} from 'core_reportbuilder/local/repository/reports'; + +let initialized = false; + +/** + * Initialise editor and all it's modules + */ +export const init = () => { + const reportElement = document.querySelector(reportSelectors.regions.report); + + columnsEditorInit(reportElement, initialized); + conditionsEditorInit(reportElement, initialized); + filtersEditorInit(reportElement, initialized); + sortingEditorInit(reportElement, initialized); + + // Ensure we only add our listeners once (can be called multiple times by mustache template). + if (initialized) { + return; + } + + // Add event handlers to generic report editor elements. + document.addEventListener('click', event => { + + // Toggle between edit and preview mode. + const toggleEditViewMode = event.target.closest(reportSelectors.actions.toggleEditPreview); + if (toggleEditViewMode) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/reports:get'); + const toggledEditMode = toggleEditViewMode.dataset.editMode !== "1"; + + let customjs = ''; + + getReport(reportElement.dataset.reportId, toggledEditMode) + .then(response => { + customjs = response.javascript; + return Templates.render('core_reportbuilder/custom_report', response); + }) + .then((html, js) => { + return Templates.replaceNodeContents(reportElement, html, js + customjs); + }) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); + + initialized = true; +}; diff --git a/reportbuilder/amd/src/filters.js b/reportbuilder/amd/src/filters.js index dffb4467ea1..25c4c54af47 100644 --- a/reportbuilder/amd/src/filters.js +++ b/reportbuilder/amd/src/filters.js @@ -31,7 +31,7 @@ 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'; +import {resetFilters} from 'core_reportbuilder/local/repository/filters'; /** * Update filter button text to indicate applied filter count @@ -57,7 +57,7 @@ const setFilterButtonCount = async(reportElement, filterCount) => { * @param {Number} contextId */ export const init = (reportId, contextId) => { - const reportElement = document.querySelector(reportSelectors.forSystemReport(reportId)); + const reportElement = document.querySelector(reportSelectors.forReport(reportId)); const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm); const filterForm = new DynamicForm(filterFormContainer, '\\core_reportbuilder\\form\\filter'); diff --git a/reportbuilder/amd/src/local/editor/columns.js b/reportbuilder/amd/src/local/editor/columns.js new file mode 100644 index 00000000000..ff6f50c8b6c --- /dev/null +++ b/reportbuilder/amd/src/local/editor/columns.js @@ -0,0 +1,163 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder columns editor + * + * @module core_reportbuilder/local/editor/columns + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import $ from 'jquery'; +import {dispatchEvent} from 'core/event_dispatcher'; +import 'core/inplace_editable'; +import {eventTypes as inplaceEditableEvents} from 'core/local/inplace_editable/events'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {publish} from 'core/pubsub'; +import SortableList from 'core/sortable_list'; +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import {add as addToast} from 'core/toast'; +import * as reportEvents from 'core_reportbuilder/local/events'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {addColumn, deleteColumn, reorderColumn} from 'core_reportbuilder/local/repository/columns'; + +/** + * Initialise module + * + * @param {Element} reportElement + * @param {Boolean} initialized Ensure we only add our listeners once + */ +export const init = (reportElement, initialized) => { + if (initialized) { + return; + } + + reportElement.addEventListener('click', event => { + + // Add column to report. + const reportAddColumn = event.target.closest(reportSelectors.actions.reportAddColumn); + if (reportAddColumn) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/columns:add'); + + addColumn(reportElement.dataset.reportId, reportAddColumn.dataset.uniqueIdentifier) + .then(data => publish(reportEvents.publish.reportColumnsUpdated, data)) + .then(() => getString('columnadded', 'core_reportbuilder', reportAddColumn.dataset.name)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + } + + // Remove column from report. + const reportRemoveColumn = event.target.closest(reportSelectors.actions.reportRemoveColumn); + if (reportRemoveColumn) { + event.preventDefault(); + + const columnHeader = reportRemoveColumn.closest(reportSelectors.regions.columnHeader); + const columnName = columnHeader.dataset.columnName; + + getStrings([ + {key: 'deletecolumn', component: 'core_reportbuilder', param: columnName}, + {key: 'deletecolumnconfirm', component: 'core_reportbuilder', param: columnName}, + {key: 'delete', component: 'moodle'}, + ]).then(([confirmTitle, confirmText, confirmButton]) => { + Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => { + const pendingPromise = new Pending('core_reportbuilder/columns:remove'); + + deleteColumn(reportElement.dataset.reportId, columnHeader.dataset.columnId) + .then(data => publish(reportEvents.publish.reportColumnsUpdated, data)) + .then(() => getString('columndeleted', 'core_reportbuilder', columnName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + }); + return; + }).catch(Notification.exception); + } + }); + + // Initialize sortable list to handle column moving (note JQuery dependency, see MDL-72293 for resolution). + var columnSortableList = new SortableList(`${reportSelectors.regions.reportTable} thead tr`, {isHorizontal: true}); + columnSortableList.getElementName = element => Promise.resolve(element.data('columnName')); + + $(reportElement).on(SortableList.EVENTS.DRAG, 'th[data-column-id]', (event, info) => { + const columnPosition = info.element.data('columnPosition'); + const targetColumnPosition = info.targetNextElement.data('columnPosition'); + + $(reportElement).find('tbody tr').each(function() { + const cell = $(this).children(`td.c${columnPosition - 1}`)[0]; + if (targetColumnPosition) { + var beforeCell = $(this).children(`td.c${targetColumnPosition - 1}`)[0]; + this.insertBefore(cell, beforeCell); + } else { + this.appendChild(cell); + } + }); + }); + + $(reportElement).on(SortableList.EVENTS.DROP, 'th[data-column-id]', (event, info) => { + if (info.positionChanged) { + const pendingPromise = new Pending('core_reportbuilder/columns:reorder'); + + const columnId = info.element.data('columnId'); + const columnName = info.element.data('columnName'); + const columnPosition = info.element.data('columnPosition'); + + // Select target position, if moving to the end then count number of element siblings. + let targetColumnPosition = info.targetNextElement.data('columnPosition') || info.element.siblings().length + 2; + if (targetColumnPosition > columnPosition) { + targetColumnPosition--; + } + + reorderColumn(reportElement.dataset.reportId, columnId, targetColumnPosition) + .then(() => getString('columnmoved', 'core_reportbuilder', columnName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + } + }); + + // Initialize inplace editable listeners for column aggregation. + reportElement.addEventListener(inplaceEditableEvents.elementUpdated, event => { + + const columnAggregation = event.target.closest('[data-itemtype="columnaggregation"]'); + if (columnAggregation) { + const columnHeader = columnAggregation.closest(reportSelectors.regions.columnHeader); + + getString('columnaggregated', 'core_reportbuilder', columnHeader.dataset.columnName) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return; + }) + .catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/local/editor/conditions.js b/reportbuilder/amd/src/local/editor/conditions.js new file mode 100644 index 00000000000..d15bbaa6c4c --- /dev/null +++ b/reportbuilder/amd/src/local/editor/conditions.js @@ -0,0 +1,197 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder conditions editor + * + * @module core_reportbuilder/local/editor/conditions + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import $ from 'jquery'; +import {dispatchEvent} from 'core/event_dispatcher'; +import 'core/inplace_editable'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import SortableList from 'core/sortable_list'; +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import Templates from 'core/templates'; +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 {addCondition, deleteCondition, reorderCondition, resetConditions} from 'core_reportbuilder/local/repository/conditions'; + +/** + * Reload conditions settings region + * + * @param {Element} reportElement + * @param {Object} templateContext + * @return {Promise} + */ +const reloadSettingsConditionsRegion = (reportElement, templateContext) => { + const pendingPromise = new Pending('core_reportbuilder/conditions:reload'); + const settingsConditionsRegion = reportElement.querySelector(reportSelectors.regions.settingsConditions); + + return Templates.renderForPromise('core_reportbuilder/local/settings/conditions', {conditions: templateContext}) + .then(({html, js}) => { + Templates.replaceNode(settingsConditionsRegion, html, js + templateContext.javascript); + initConditionsForm(reportElement); + return pendingPromise.resolve(); + }); +}; + +/** + * Initialise conditions form, must be called on each init because the form container is re-created when switching editor modes + * + * @param {Element} reportElement + */ +const initConditionsForm = reportElement => { + // Handle dynamic conditions form. + const conditionFormContainer = reportElement.querySelector(reportSelectors.regions.settingsConditions); + const conditionForm = new DynamicForm(conditionFormContainer, '\\core_reportbuilder\\form\\condition'); + + // Submit report conditions. + conditionForm.addEventListener(conditionForm.events.FORM_SUBMITTED, event => { + event.preventDefault(); + + getString('conditionsapplied', 'core_reportbuilder') + .then(addToast) + .catch(Notification.exception); + + // After the form has been submitted, we should trigger report table reload. + dispatchEvent(reportEvents.tableReload, {}, reportElement); + }); + + // Reset report conditions. + conditionForm.addEventListener(conditionForm.events.NOSUBMIT_BUTTON_PRESSED, event => { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/conditions:reset'); + + resetConditions(reportElement.dataset.reportId) + .then(data => reloadSettingsConditionsRegion(reportElement, data)) + .then(() => getString('conditionsreset', 'core_reportbuilder')) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + }); +}; + +/** + * Initialise module + * + * @param {Element} reportElement + * @param {Boolean} initialized Ensure we only add our listeners once + */ +export const init = (reportElement, initialized) => { + initConditionsForm(reportElement); + if (initialized) { + return; + } + + reportElement.addEventListener('click', event => { + + // Add condition to report. + const reportAddCondition = event.target.closest(reportSelectors.actions.reportAddCondition); + if (reportAddCondition) { + event.preventDefault(); + + // Check if dropdown is closed with no condition selected. + if (reportAddCondition.value === '0') { + return; + } + + const pendingPromise = new Pending('core_reportbuilder/conditions:add'); + + addCondition(reportElement.dataset.reportId, reportAddCondition.value) + .then(data => reloadSettingsConditionsRegion(reportElement, data)) + .then(() => getString('conditionadded', 'core_reportbuilder', + reportAddCondition.options[reportAddCondition.selectedIndex].text)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + } + + // Remove condition from report. + const reportRemoveCondition = event.target.closest(reportSelectors.actions.reportRemoveCondition); + if (reportRemoveCondition) { + event.preventDefault(); + + const conditionContainer = reportRemoveCondition.closest(reportSelectors.regions.activeCondition); + const conditionName = conditionContainer.dataset.conditionName; + + getStrings([ + {key: 'deletecondition', component: 'core_reportbuilder', param: conditionName}, + {key: 'deleteconditionconfirm', component: 'core_reportbuilder', param: conditionName}, + {key: 'delete', component: 'moodle'}, + ]).then(([confirmTitle, confirmText, confirmButton]) => { + Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => { + const pendingPromise = new Pending('core_reportbuilder/conditions:remove'); + + deleteCondition(reportElement.dataset.reportId, conditionContainer.dataset.conditionId) + .then(data => reloadSettingsConditionsRegion(reportElement, data)) + .then(() => getString('conditiondeleted', 'core_reportbuilder', conditionName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + }); + return; + }).catch(Notification.exception); + } + }); + + // Initialize sortable list to handle active conditions moving (note JQuery dependency, see MDL-72293 for resolution). + var activeConditionsSortableList = new SortableList(`${reportSelectors.regions.activeConditions}`, + {isHorizontal: false}); + activeConditionsSortableList.getElementName = element => Promise.resolve(element.data('conditionName')); + + $(reportElement).on(SortableList.EVENTS.DROP, reportSelectors.regions.activeCondition, (event, info) => { + if (info.positionChanged) { + const pendingPromise = new Pending('core_reportbuilder/conditions:reorder'); + const conditionId = info.element.data('conditionId'); + const conditionPosition = info.element.data('conditionPosition'); + + // Select target position, if moving to the end then count number of element siblings. + let targetConditionPosition = info.targetNextElement.data('conditionPosition') || info.element.siblings().length + 2; + if (targetConditionPosition > conditionPosition) { + targetConditionPosition--; + } + + reorderCondition(reportElement.dataset.reportId, conditionId, targetConditionPosition) + .then(data => reloadSettingsConditionsRegion(reportElement, data)) + .then(() => getString('conditionmoved', 'core_reportbuilder', info.element.data('conditionName'))) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/local/editor/filters.js b/reportbuilder/amd/src/local/editor/filters.js new file mode 100644 index 00000000000..eb84b81c009 --- /dev/null +++ b/reportbuilder/amd/src/local/editor/filters.js @@ -0,0 +1,147 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder filters editor + * + * @module core_reportbuilder/local/editor/filters + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import $ from 'jquery'; +import {dispatchEvent} from 'core/event_dispatcher'; +import 'core/inplace_editable'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import SortableList from 'core/sortable_list'; +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import Templates from 'core/templates'; +import {add as addToast} from 'core/toast'; +import * as reportEvents from 'core_reportbuilder/local/events'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {addFilter, deleteFilter, reorderFilter} from 'core_reportbuilder/local/repository/filters'; + +/** + * Reload filters settings region + * + * @param {Element} reportElement + * @param {Object} templateContext + * @return {Promise} + */ +const reloadSettingsFiltersRegion = (reportElement, templateContext) => { + const pendingPromise = new Pending('core_reportbuilder/filters:reload'); + const settingsFiltersRegion = reportElement.querySelector(reportSelectors.regions.settingsFilters); + + return Templates.renderForPromise('core_reportbuilder/local/settings/filters', {filters: templateContext}) + .then(({html, js}) => { + Templates.replaceNode(settingsFiltersRegion, html, js); + return pendingPromise.resolve(); + }); +}; + +/** + * Initialise module + * + * @param {Element} reportElement + * @param {Boolean} initialized Ensure we only add our listeners once + */ +export const init = (reportElement, initialized) => { + if (initialized) { + return; + } + + reportElement.addEventListener('click', event => { + + // Add filter to report. + const reportAddFilter = event.target.closest(reportSelectors.actions.reportAddFilter); + if (reportAddFilter) { + event.preventDefault(); + + // Check if dropdown is closed with no filter selected. + if (reportAddFilter.value === '0') { + return; + } + + const pendingPromise = new Pending('core_reportbuilder/filters:add'); + + addFilter(reportElement.dataset.reportId, reportAddFilter.value) + .then(data => reloadSettingsFiltersRegion(reportElement, data)) + .then(() => getString('filteradded', 'core_reportbuilder', + reportAddFilter.options[reportAddFilter.selectedIndex].text)) + .then(addToast) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + + // Remove filter from report. + const reportRemoveFilter = event.target.closest(reportSelectors.actions.reportRemoveFilter); + if (reportRemoveFilter) { + event.preventDefault(); + + const filterContainer = reportRemoveFilter.closest(reportSelectors.regions.activeFilter); + const filterName = filterContainer.dataset.filterName; + + getStrings([ + {key: 'deletefilter', component: 'core_reportbuilder', param: filterName}, + {key: 'deletefilterconfirm', component: 'core_reportbuilder', param: filterName}, + {key: 'delete', component: 'moodle'}, + ]).then(([confirmTitle, confirmText, confirmButton]) => { + Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => { + const pendingPromise = new Pending('core_reportbuilder/filters:remove'); + + deleteFilter(reportElement.dataset.reportId, filterContainer.dataset.filterId) + .then(data => reloadSettingsFiltersRegion(reportElement, data)) + .then(() => getString('filterdeleted', 'core_reportbuilder', filterName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + }); + return; + }).catch(Notification.exception); + } + }); + + // Initialize sortable list to handle active filters moving (note JQuery dependency, see MDL-72293 for resolution). + var activeFiltersSortableList = new SortableList(`${reportSelectors.regions.activeFilters} ul`, {isHorizontal: false}); + activeFiltersSortableList.getElementName = element => Promise.resolve(element.data('filterName')); + + $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-filter-id]', (event, info) => { + if (info.positionChanged) { + const pendingPromise = new Pending('core_reportbuilder/filters:reorder'); + const filterId = info.element.data('filterId'); + const filterPosition = info.element.data('filterPosition'); + + // Select target position, if moving to the end then count number of element siblings. + let targetFilterPosition = info.targetNextElement.data('filterPosition') || info.element.siblings().length + 2; + if (targetFilterPosition > filterPosition) { + targetFilterPosition--; + } + + reorderFilter(reportElement.dataset.reportId, filterId, targetFilterPosition) + .then(data => reloadSettingsFiltersRegion(reportElement, data)) + .then(() => getString('filtermoved', 'core_reportbuilder', info.element.data('filterName'))) + .then(addToast) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/local/editor/sorting.js b/reportbuilder/amd/src/local/editor/sorting.js new file mode 100644 index 00000000000..5b6325b7192 --- /dev/null +++ b/reportbuilder/amd/src/local/editor/sorting.js @@ -0,0 +1,170 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder columns sorting editor + * + * @module core_reportbuilder/local/editor/sorting + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import $ from 'jquery'; +import 'core/inplace_editable'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {subscribe} from 'core/pubsub'; +import SortableList from 'core/sortable_list'; +import {get_string as getString} from 'core/str'; +import {add as addToast} from 'core/toast'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {reorderColumnSorting, toggleColumnSorting} from 'core_reportbuilder/local/repository/sorting'; +import Templates from 'core/templates'; +import {dispatchEvent} from 'core/event_dispatcher'; +import * as reportEvents from 'core_reportbuilder/local/events'; + +// These constants match PHP consts SORT_ASC, SORT_DESC. +const SORTORDER = { + ASCENDING: 4, + DESCENDING: 3, +}; + +/** + * Reload sorting settings region + * + * @param {Object} context + * @return {Promise} + */ +const reloadSettingsSortingRegion = context => { + const pendingPromise = new Pending('core_reportbuilder/sorting:reload'); + const settingsSortingRegion = document.querySelector(reportSelectors.regions.settingsSorting); + + return Templates.renderForPromise('core_reportbuilder/local/settings/sorting', {sorting: context}) + .then(({html, js}) => { + Templates.replaceNode(settingsSortingRegion, html, js); + return pendingPromise.resolve(); + }); +}; + +/** + * Updates column sorting + * + * @param {Element} reportElement + * @param {Element} element + * @param {Number} sortenabled + * @param {Number} sortdirection + * @return {Promise} + */ +const updateSorting = (reportElement, element, sortenabled, sortdirection) => { + const reportId = reportElement.dataset.reportId; + const listElement = element.closest('li'); + const columnId = listElement.dataset.columnSortId; + const columnName = listElement.dataset.columnSortName; + + return toggleColumnSorting(reportId, columnId, sortenabled, sortdirection) + .then(reloadSettingsSortingRegion) + .then(() => getString('columnsortupdated', 'core_reportbuilder', columnName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return null; + }); +}; + +/** + * Initialise module + * + * @param {Element} reportElement + * @param {Boolean} initialized Ensure we only add our listeners once + */ +export const init = (reportElement, initialized) => { + if (initialized) { + return; + } + + // Update sorting region each time report columns are updated (added or removed). + subscribe(reportEvents.publish.reportColumnsUpdated, data => reloadSettingsSortingRegion(data) + .catch(Notification.exception) + ); + + reportElement.addEventListener('click', event => { + + // Enable/disable sorting on columns. + const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort); + if (toggleSorting) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/sorting:toggle'); + const sortdirection = parseInt(toggleSorting.closest('li').dataset.columnSortDirection); + + updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + + // Change column sort direction. + const toggleSortDirection = event.target.closest(reportSelectors.actions.reportToggleColumnSortDirection); + if (toggleSortDirection) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/sorting:direction'); + const listElement = toggleSortDirection.closest('li'); + const sortenabled = listElement.dataset.columnSortEnabled; + + let sortdirection = parseInt(listElement.dataset.columnSortDirection); + if (sortdirection === SORTORDER.ASCENDING) { + sortdirection = SORTORDER.DESCENDING; + } else if (sortdirection === SORTORDER.DESCENDING) { + sortdirection = SORTORDER.ASCENDING; + } + + updateSorting(reportElement, toggleSortDirection, sortenabled, sortdirection) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); + + // Initialize sortable list to handle column sorting moving (note JQuery dependency, see MDL-72293 for resolution). + var columnsSortingSortableList = new SortableList(`${reportSelectors.regions.settingsSorting} ul`, {isHorizontal: false}); + columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName')); + + $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-column-sort-id]', (event, info) => { + if (info.positionChanged) { + const pendingPromise = new Pending('core_reportbuilder/sorting:reorder'); + const columnId = info.element.data('columnSortId'); + const columnPosition = info.element.data('columnSortPosition'); + + // Select target position, if moving to the end then count number of element siblings. + let targetColumnSortPosition = info.targetNextElement.data('columnSortPosition') || info.element.siblings().length + 2; + if (targetColumnSortPosition > columnPosition) { + targetColumnSortPosition--; + } + + reorderColumnSorting(reportElement.dataset.reportId, columnId, targetColumnSortPosition) + .then(reloadSettingsSortingRegion) + .then(() => getString('columnsortupdated', 'core_reportbuilder', info.element.data('columnSortName'))) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return null; + }) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/local/events.js b/reportbuilder/amd/src/local/events.js index 28b67c59aa2..0e791813014 100644 --- a/reportbuilder/amd/src/local/events.js +++ b/reportbuilder/amd/src/local/events.js @@ -43,4 +43,7 @@ export default { * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...)); */ tableReload: 'core_reportbuilder_table_reload', + publish: { + reportColumnsUpdated: 'core_reportbuilder_report_columns_updated', + }, }; diff --git a/reportbuilder/amd/src/local/repository/columns.js b/reportbuilder/amd/src/local/repository/columns.js new file mode 100644 index 00000000000..0804e25b251 --- /dev/null +++ b/reportbuilder/amd/src/local/repository/columns.js @@ -0,0 +1,74 @@ +// 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 . + +/** + * Module to handle column AJAX requests + * + * @module core_reportbuilder/local/repository/columns + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Add column to given report + * + * @param {Number} reportId + * @param {String} uniqueIdentifier + * @return {Promise} + */ +export const addColumn = (reportId, uniqueIdentifier) => { + const request = { + methodname: 'core_reportbuilder_columns_add', + args: {reportid: reportId, uniqueidentifier: uniqueIdentifier} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Remove column from given report + * + * @param {Number} reportId + * @param {Number} columnId + * @return {Promise} + */ +export const deleteColumn = (reportId, columnId) => { + const request = { + methodname: 'core_reportbuilder_columns_delete', + args: {reportid: reportId, columnid: columnId} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Re-order column within a report + * + * @param {Number} reportId + * @param {Number} columnId + * @param {Number} position + * @return {Promise} + */ +export const reorderColumn = (reportId, columnId, position) => { + const request = { + methodname: 'core_reportbuilder_columns_reorder', + args: {reportid: reportId, columnid: columnId, position: position} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/repository/conditions.js b/reportbuilder/amd/src/local/repository/conditions.js new file mode 100644 index 00000000000..ad0216617ac --- /dev/null +++ b/reportbuilder/amd/src/local/repository/conditions.js @@ -0,0 +1,89 @@ +// 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 . + +/** + * Module to handle condition AJAX requests + * + * @module core_reportbuilder/local/repository/conditions + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Reset all conditions for given report + * + * @param {Number} reportId + * @return {Promise} + */ +export const resetConditions = reportId => { + const request = { + methodname: 'core_reportbuilder_conditions_reset', + args: {reportid: reportId} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Add condition to given report + * + * @param {Number} reportId + * @param {String} uniqueIdentifier + * @return {Promise} + */ +export const addCondition = (reportId, uniqueIdentifier) => { + const request = { + methodname: 'core_reportbuilder_conditions_add', + args: {reportid: reportId, uniqueidentifier: uniqueIdentifier} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Remove condition from given report + * + * @param {Number} reportId + * @param {Number} conditionId + * @return {Promise} + */ +export const deleteCondition = (reportId, conditionId) => { + const request = { + methodname: 'core_reportbuilder_conditions_delete', + args: {reportid: reportId, conditionid: conditionId} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Reorder a condition in a given report + * + * @param {Number} reportId + * @param {Number} conditionId + * @param {Number} position + * @return {Promise} + */ +export const reorderCondition = (reportId, conditionId, position) => { + const request = { + methodname: 'core_reportbuilder_conditions_reorder', + args: {reportid: reportId, conditionid: conditionId, position: position} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/repository/filters.js b/reportbuilder/amd/src/local/repository/filters.js index 3a82d9d6b7b..4af4d12b840 100644 --- a/reportbuilder/amd/src/local/repository/filters.js +++ b/reportbuilder/amd/src/local/repository/filters.js @@ -30,7 +30,7 @@ import Ajax from 'core/ajax'; * @param {Number} reportId * @return {Promise} */ -export const reset = reportId => { +export const resetFilters = reportId => { const request = { methodname: 'core_reportbuilder_filters_reset', args: {reportid: reportId} @@ -38,3 +38,52 @@ export const reset = reportId => { return Ajax.call([request])[0]; }; + +/** + * Add a filter to the given report + * + * @param {Number} reportId + * @param {String} uniqueIdentifier + * @return {Promise} + */ +export const addFilter = (reportId, uniqueIdentifier) => { + const request = { + methodname: 'core_reportbuilder_filters_add', + args: {reportid: reportId, uniqueidentifier: uniqueIdentifier} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Remove filter from given report + * + * @param {Number} reportId + * @param {Number} filterId + * @return {Promise} + */ +export const deleteFilter = (reportId, filterId) => { + const request = { + methodname: 'core_reportbuilder_filters_delete', + args: {reportid: reportId, filterid: filterId} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Reorder a filter in a given report + * + * @param {Number} reportId + * @param {Number} filterId + * @param {Number} position + * @return {Promise} + */ +export const reorderFilter = (reportId, filterId, position) => { + const request = { + methodname: 'core_reportbuilder_filters_reorder', + args: {reportid: reportId, filterid: filterId, position: position} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/repository/modals.js b/reportbuilder/amd/src/local/repository/modals.js new file mode 100644 index 00000000000..59144de52f2 --- /dev/null +++ b/reportbuilder/amd/src/local/repository/modals.js @@ -0,0 +1,48 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Module to handle modal form requests + * + * @module core_reportbuilder/local/repository/modals + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import ModalForm from 'core_form/modalform'; +import {get_string as getString} from 'core/str'; + +/** + * Open the New Report modal form + * + * @param {EventTarget} triggerElement + * @param {Promise} modalTitle + * @param {Number} reportId + * @return {ModalForm} + */ +export const createReportModal = (triggerElement, modalTitle, reportId = 0) => { + return new ModalForm({ + modalConfig: { + title: modalTitle, + }, + formClass: 'core_reportbuilder\\form\\report', + args: { + id: reportId, + }, + saveButtonText: getString('save', 'moodle'), + returnFocus: triggerElement, + }); +}; diff --git a/reportbuilder/amd/src/local/repository/reports.js b/reportbuilder/amd/src/local/repository/reports.js new file mode 100644 index 00000000000..161ea3bf200 --- /dev/null +++ b/reportbuilder/amd/src/local/repository/reports.js @@ -0,0 +1,56 @@ +// 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 . + +/** + * Module to handle report AJAX requests + * + * @module core_reportbuilder/local/repository/reports + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Delete given report + * + * @param {Number} reportId + * @return {Promise} + */ +export const deleteReport = reportId => { + const request = { + methodname: 'core_reportbuilder_reports_delete', + args: {reportid: reportId} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Get report content + * + * @param {Number} reportId + * @param {Boolean} editMode + * @return {Promise} + */ +export const getReport = (reportId, editMode) => { + const request = { + methodname: 'core_reportbuilder_reports_get', + args: {reportid: reportId, editmode: editMode} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/repository/sorting.js b/reportbuilder/amd/src/local/repository/sorting.js new file mode 100644 index 00000000000..bcd5a03b04c --- /dev/null +++ b/reportbuilder/amd/src/local/repository/sorting.js @@ -0,0 +1,60 @@ +// 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 . + +/** + * Module to handle column AJAX requests + * + * @module core_reportbuilder/local/repository/sorting + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Re-order sort column position + * + * @param {Number} reportId + * @param {Number} columnId + * @param {Number} position + * @return {Promise} + */ +export const reorderColumnSorting = (reportId, columnId, position) => { + const request = { + methodname: 'core_reportbuilder_columns_sort_reorder', + args: {reportid: reportId, columnid: columnId, position: position} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Enables/disabled sorting on column + * + * @param {Number} reportId + * @param {Number} columnId + * @param {Boolean} enabled + * @param {Number} direction + * @return {Promise} + */ +export const toggleColumnSorting = (reportId, columnId, enabled, direction) => { + const request = { + methodname: 'core_reportbuilder_columns_sort_toggle', + args: {reportid: reportId, columnid: columnId, enabled: enabled, direction: direction} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/selectors.js b/reportbuilder/amd/src/local/selectors.js index 1c0972b3061..839c229a378 100644 --- a/reportbuilder/amd/src/local/selectors.js +++ b/reportbuilder/amd/src/local/selectors.js @@ -31,22 +31,46 @@ */ const SELECTORS = { regions: { - systemReport: '[data-region="core_reportbuilder/system-report"]', + report: '[data-region="core_reportbuilder/report"]', + reportTable: '[data-region="reportbuilder-table"]', + columnHeader: '[data-region="column-header"]', filterButtonLabel: '[data-region="filter-button-label"]', filtersForm: '[data-region="filters-form"]', + sidebarMenu: '[data-region="sidebar-menu"]', + sidebarCard: '[data-region="sidebar-card"]', + settingsConditions: '[data-region="settings-conditions"]', + activeConditions: '[data-region="active-conditions"]', + activeCondition: '[data-region="active-condition"]', + settingsFilters: '[data-region="settings-filters"]', + activeFilters: '[data-region="active-filters"]', + activeFilter: '[data-region="active-filter"]', + settingsSorting: '[data-region="settings-sorting"]', }, actions: { reportActionPopup: '[data-action="report-action-popup"]', - } + reportCreate: '[data-action="report-create"]', + reportEdit: '[data-action="report-edit"]', + reportDelete: '[data-action="report-delete"]', + reportAddColumn: '[data-action="report-add-column"]', + reportRemoveColumn: '[data-action="report-remove-column"]', + reportAddCondition: '[data-action="report-add-condition"]', + reportRemoveCondition: '[data-action="report-remove-condition"]', + reportAddFilter: '[data-action="report-add-filter"]', + reportRemoveFilter: '[data-action="report-remove-filter"]', + reportToggleColumnSort: '[data-action="report-toggle-column-sorting"]', + reportToggleColumnSortDirection: '[data-action="report-toggle-sort-direction"]', + sidebarSearch: '[data-action="sidebar-search"]', + toggleEditPreview: '[data-action="toggle-edit-preview"]', + }, }; /** * Selector for given report * - * @method forSystemReport + * @method forReport * @param {Number} reportId * @return {String} */ -SELECTORS.forSystemReport = reportId => `${SELECTORS.regions.systemReport}[data-reportid="${reportId}"]`; +SELECTORS.forReport = reportId => `${SELECTORS.regions.report}[data-report-id="${reportId}"]`; export default SELECTORS; diff --git a/reportbuilder/amd/src/report.js b/reportbuilder/amd/src/report.js index 12822235785..98d24878310 100644 --- a/reportbuilder/amd/src/report.js +++ b/reportbuilder/amd/src/report.js @@ -26,21 +26,28 @@ import * as reportSelectors from 'core_reportbuilder/local/selectors'; import {setPageNumber, refreshTableContent} from 'core_table/dynamic'; import * as tableSelectors from 'core_table/local/dynamic/selectors'; +let initialized = false; + /** * Initialise module for given report * * @method - * @param {Number} reportId */ -export const init = reportId => { +export const init = () => { + + if (initialized) { + // We already added the event listeners (can be called multiple times by mustache template). + return; + } + // Listen for the table reload event. document.addEventListener(reportEvents.tableReload, async(event) => { - const triggerElement = event.target.closest(reportSelectors.forSystemReport(reportId)); - if (triggerElement === null) { + const reportElement = event.target.closest(reportSelectors.regions.report); + if (reportElement === null) { return; } - const tableRoot = triggerElement.querySelector(tableSelectors.main.region); + const tableRoot = reportElement.querySelector(tableSelectors.main.region); const pageNumber = event.detail?.preservePagination ? null : 1; await setPageNumber(tableRoot, pageNumber, false) @@ -57,4 +64,6 @@ export const init = reportId => { const popupAction = JSON.parse(reportActionPopup.dataset.popupAction); window.openpopup(event, popupAction.jsfunctionargs); }); + + initialized = true; }; diff --git a/reportbuilder/amd/src/reports_list.js b/reportbuilder/amd/src/reports_list.js new file mode 100644 index 00000000000..236c15957d1 --- /dev/null +++ b/reportbuilder/amd/src/reports_list.js @@ -0,0 +1,104 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder reports list management + * + * @module core_reportbuilder/reports_list + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import {dispatchEvent} from 'core/event_dispatcher'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import {add as addToast} from 'core/toast'; +import * as reportEvents from 'core_reportbuilder/local/events'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {deleteReport} from 'core_reportbuilder/local/repository/reports'; +import {createReportModal} from 'core_reportbuilder/local/repository/modals'; + +/** + * Initialise module + */ +export const init = () => { + document.addEventListener('click', event => { + const reportCreate = event.target.closest(reportSelectors.actions.reportCreate); + if (reportCreate) { + event.preventDefault(); + + // Redirect user to editing interface for the report after submission. + const reportModal = createReportModal(event.target, getString('newreport', 'core_reportbuilder')); + reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, event => { + window.location.href = event.detail; + }); + + reportModal.show(); + } + + const reportEdit = event.target.closest(reportSelectors.actions.reportEdit); + if (reportEdit) { + event.preventDefault(); + + // Reload current report page after submission. + const reportModal = createReportModal(event.target, getString('editreportdetails', 'core_reportbuilder'), + reportEdit.dataset.reportId); + reportModal.addEventListener(reportModal.events.FORM_SUBMITTED, () => { + const reportElement = event.target.closest(reportSelectors.regions.report); + + getString('reportupdated', 'core_reportbuilder') + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return; + }) + .catch(Notification.exception); + }); + + reportModal.show(); + } + + const reportDelete = event.target.closest(reportSelectors.actions.reportDelete); + if (reportDelete) { + event.preventDefault(); + + getStrings([ + {key: 'deletereport', component: 'core_reportbuilder'}, + {key: 'deletereportconfirm', component: 'core_reportbuilder', param: reportDelete.dataset.reportName}, + {key: 'delete', component: 'moodle'}, + ]).then(([confirmTitle, confirmText, confirmButton]) => { + Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => { + const pendingPromise = new Pending('core_reportbuilder/reports:delete'); + const reportElement = event.target.closest(reportSelectors.regions.report); + + deleteReport(reportDelete.dataset.reportId) + .then(() => getString('reportdeleted', 'core_reportbuilder')) + .then(addToast) + .then(() => { + pendingPromise.resolve(); + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return; + }) + .catch(Notification.exception); + }); + return; + }).catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/sidebar.js b/reportbuilder/amd/src/sidebar.js new file mode 100644 index 00000000000..9324b5c04c0 --- /dev/null +++ b/reportbuilder/amd/src/sidebar.js @@ -0,0 +1,106 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder sidebar component + * + * @module core_reportbuilder/sidebar + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Pending from 'core/pending'; +import {debounce} from 'core/utils'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; + +const DEBOUNCE_TIMER = 250; + +const CLASSES = { + EXPANDED: 'show', + COLLAPSED: 'collapsed', + SHOW: 'd-flex', + HIDE: 'd-none', +}; + +/** + * Initialise module + * + * @param {Event} event + * @param {Element} sidebarMenu + */ +const sidebarCardFilter = (event, sidebarMenu) => { + const pendingPromise = new Pending('core_reportbuilder/sidebar:cardFilter'); + + const sidebarCards = sidebarMenu.querySelectorAll(reportSelectors.regions.sidebarCard); + const sidebarItems = sidebarMenu.querySelectorAll('.list-group-item'); + const searchTerm = event.target.value.toLowerCase(); + + // Toggle items according to match against search term. + sidebarItems.forEach(item => { + let itemName = item.dataset.name.toLowerCase(); + if (itemName.includes(searchTerm)) { + item.classList.replace(CLASSES.HIDE, CLASSES.SHOW); + } else { + item.classList.replace(CLASSES.SHOW, CLASSES.HIDE); + } + }); + + // Toggle cards according to whether they have any visible items. + sidebarCards.forEach(card => { + if (card.querySelectorAll(`.${CLASSES.SHOW}`).length > 0) { + card.classList.remove(CLASSES.HIDE); + expandCard(card); + } else { + card.classList.add(CLASSES.HIDE); + } + }); + + pendingPromise.resolve(); +}; + +/** + * Show a collapsed card. + * This function simulates the behaviour of JQuery show method on a collapsible element. + * + * @param {Element} card + */ +const expandCard = (card) => { + let cardButton = card.querySelector('[data-toggle="collapse"]'); + if (cardButton.classList.contains(CLASSES.COLLAPSED)) { + cardButton.classList.remove(CLASSES.COLLAPSED); + cardButton.setAttribute('aria-expanded', "true"); + let cardContent = card.querySelector(cardButton.dataset.target); + cardContent.classList.add(CLASSES.EXPANDED); + } +}; + +/** + * Initialise module + */ +export const init = () => { + const sidebarMenu = document.querySelector(reportSelectors.regions.sidebarMenu); + const sidebarSearch = sidebarMenu.querySelector(reportSelectors.actions.sidebarSearch); + + // Debounce the event listener to allow the user to finish typing. + sidebarSearch.addEventListener('keyup', event => { + const pendingPromise = new Pending('core_reportbuilder/sidebar:keyup'); + + debounce(sidebarCardFilter, DEBOUNCE_TIMER)(event, sidebarMenu); + setTimeout(() => { + pendingPromise.resolve(); + }, DEBOUNCE_TIMER); + }); +}; diff --git a/reportbuilder/classes/datasource.php b/reportbuilder/classes/datasource.php new file mode 100644 index 00000000000..9a2f575a17e --- /dev/null +++ b/reportbuilder/classes/datasource.php @@ -0,0 +1,244 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder; + +use coding_exception; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\local\models\filter as filter_model; +use core_reportbuilder\local\report\base; +use core_reportbuilder\local\report\column; +use core_reportbuilder\local\report\filter; + +/** + * Class datasource + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class datasource extends base { + + /** + * Return user friendly name of the datasource + * + * @return string + */ + abstract public static function get_name(): string; + + /** + * Add columns from the given entity name to be available to use in a custom report + * + * @param string $entityname + * @param array $include Include only these columns, if omitted then include all + * @param array $exclude Exclude these columns, if omitted then exclude none + * @throws coding_exception If both $include and $exclude are non-empty + */ + final protected function add_columns_from_entity(string $entityname, array $include = [], array $exclude = []): void { + if (!empty($include) && !empty($exclude)) { + throw new coding_exception('Cannot specify columns to include and exclude simultaneously'); + } + + $entity = $this->get_entity($entityname); + + // Retrieve filtered columns from entity, respecting given $include/$exclude parameters. + $columns = array_filter($entity->get_columns(), static function(column $column) use ($include, $exclude): bool { + if (!empty($include)) { + return in_array($column->get_name(), $include); + } + + if (!empty($exclude)) { + return !in_array($column->get_name(), $exclude); + } + + return true; + }); + + foreach ($columns as $column) { + $this->add_column($column); + } + } + + /** + * Add default datasource columns to the report + * + * This method is optional and can be called when the report is created to add the default columns defined in the + * selected datasource. + */ + public function add_default_columns(): void { + $reportid = $this->get_report_persistent()->get('id'); + $columnidentifiers = $this->get_default_columns(); + foreach ($columnidentifiers as $uniqueidentifier) { + report::add_report_column($reportid, $uniqueidentifier); + } + } + + /** + * Return the columns that will be added to the report once is created + * + * @return string[] + */ + abstract public function get_default_columns(): array; + + /** + * Add filters from the given entity name to be available to use in a custom report + * + * @param string $entityname + * @param array $include Include only these filters, if omitted then include all + * @param array $exclude Exclude these filters, if omitted then exclude none + * @throws coding_exception If both $include and $exclude are non-empty + */ + final protected function add_filters_from_entity(string $entityname, array $include = [], array $exclude = []): void { + if (!empty($include) && !empty($exclude)) { + throw new coding_exception('Cannot specify filters to include and exclude simultaneously'); + } + + $entity = $this->get_entity($entityname); + + // Retrieve filtered filters from entity, respecting given $include/$exclude parameters. + $filters = array_filter($entity->get_filters(), static function(filter $filter) use ($include, $exclude): bool { + if (!empty($include)) { + return in_array($filter->get_name(), $include); + } + + if (!empty($exclude)) { + return !in_array($filter->get_name(), $exclude); + } + + return true; + }); + + foreach ($filters as $filter) { + $this->add_filter($filter); + } + } + + /** + * Add default datasource filters to the report + * + * This method is optional and can be called when the report is created to add the default filters defined in the + * selected datasource. + */ + public function add_default_filters(): void { + $reportid = $this->get_report_persistent()->get('id'); + $filteridentifiers = $this->get_default_filters(); + foreach ($filteridentifiers as $uniqueidentifier) { + report::add_report_filter($reportid, $uniqueidentifier); + } + } + + /** + * Return the filters that will be added to the report once is created + * + * @return string[] + */ + abstract public function get_default_filters(): array; + + /** + * Return all configured report filters + * + * @return filter[] + */ + public function get_active_filters(): array { + $filters = []; + + $activefilters = filter_model::get_filter_records($this->get_report_persistent()->get('id'), 'filterorder'); + foreach ($activefilters as $filter) { + $instance = $this->get_filter($filter->get('uniqueidentifier')); + if ($instance !== null && $instance->get_is_available()) { + $filters[$instance->get_unique_identifier()] = $instance + ->set_persistent($filter); + } + } + + return $filters; + } + + /** + * Add conditions from the given entity name to be available to use in a custom report + * + * @param string $entityname + * @param array $include Include only these conditions, if omitted then include all + * @param array $exclude Exclude these conditions, if omitted then exclude none + * @throws coding_exception If both $include and $exclude are non-empty + */ + final protected function add_conditions_from_entity(string $entityname, array $include = [], array $exclude = []): void { + if (!empty($include) && !empty($exclude)) { + throw new coding_exception('Cannot specify conditions to include and exclude simultaneously'); + } + + $entity = $this->get_entity($entityname); + + // Retrieve filtered conditions from entity, respecting given $include/$exclude parameters. + $conditions = array_filter($entity->get_conditions(), static function(filter $condition) use ($include, $exclude): bool { + if (!empty($include)) { + return in_array($condition->get_name(), $include); + } + + if (!empty($exclude)) { + return !in_array($condition->get_name(), $exclude); + } + + return true; + }); + + foreach ($conditions as $condition) { + $this->add_condition($condition); + } + } + + /** + * Add default datasource conditions to the report + * + * This method is optional and can be called when the report is created to add the default conditions defined in the + * selected datasource. + */ + public function add_default_conditions(): void { + $reportid = $this->get_report_persistent()->get('id'); + $conditionidentifiers = $this->get_default_conditions(); + foreach ($conditionidentifiers as $uniqueidentifier) { + report::add_report_condition($reportid, $uniqueidentifier); + } + } + + /** + * Return the conditions that will be added to the report once is created + * + * @return string[] + */ + abstract public function get_default_conditions(): array; + + /** + * Return all configured report conditions + * + * @return filter[] + */ + public function get_active_conditions(): array { + $conditions = []; + + $activeconditions = filter_model::get_condition_records($this->get_report_persistent()->get('id'), 'filterorder'); + foreach ($activeconditions as $condition) { + $instance = $this->get_condition($condition->get('uniqueidentifier')); + if ($instance !== null && $instance->get_is_available()) { + $conditions[$instance->get_unique_identifier()] = $instance; + } + } + + return $conditions; + } +} diff --git a/reportbuilder/classes/event/report_created.php b/reportbuilder/classes/event/report_created.php new file mode 100644 index 00000000000..56920ac58bb --- /dev/null +++ b/reportbuilder/classes/event/report_created.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\event; + +use core\event\base; +use core_reportbuilder\local\models\report; +use moodle_url; + +/** + * Report builder custom report created event class. + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @property-read array $other { + * Extra information about the event. + * + * - string name: The name of the report + * - string source: The report source class + * } + */ +class report_created extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = report::TABLE; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an instance from a report object + * + * @param report $report + * @return self + */ + public static function create_from_object(report $report): self { + $eventparams = [ + 'context' => $report->get_context(), + 'objectid' => $report->get('id'), + 'other' => [ + 'name' => $report->get('name'), + 'source' => $report->get('source'), + ] + ]; + $event = self::create($eventparams); + $event->add_record_snapshot($event->objecttable, $report->to_record()); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('reportcreated', 'core_reportbuilder'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' created the custom report with id '$this->objectid'."; + } + + /** + * Returns relevant URL. + * + * @return moodle_url + */ + public function get_url(): moodle_url { + return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]); + } +} diff --git a/reportbuilder/classes/event/report_deleted.php b/reportbuilder/classes/event/report_deleted.php new file mode 100644 index 00000000000..785defcab79 --- /dev/null +++ b/reportbuilder/classes/event/report_deleted.php @@ -0,0 +1,86 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\event; + +use core\event\base; +use core_reportbuilder\local\models\report; + +/** + * Report builder custom report deleted event class. + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @property-read array $other { + * Extra information about the event. + * + * - string name: The name of the report + * - string source: The report source class + * } + */ +class report_deleted extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = report::TABLE; + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an instance from a report object + * + * @param report $report + * @return self + */ + public static function create_from_object(report $report): self { + $eventparams = [ + 'context' => $report->get_context(), + 'objectid' => $report->get('id'), + 'other' => [ + 'name' => $report->get('name'), + 'source' => $report->get('source'), + ] + ]; + $event = self::create($eventparams); + $event->add_record_snapshot($event->objecttable, $report->to_record()); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('reportdeleted', 'core_reportbuilder'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' deleted the custom report with id '$this->objectid'."; + } +} diff --git a/reportbuilder/classes/event/report_updated.php b/reportbuilder/classes/event/report_updated.php new file mode 100644 index 00000000000..d99b3b8d9d3 --- /dev/null +++ b/reportbuilder/classes/event/report_updated.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\event; + +use core\event\base; +use core_reportbuilder\local\models\report; +use moodle_url; + +/** + * Report builder custom report updated event class. + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @property-read array $other { + * Extra information about the event. + * + * - string name: The name of the report + * - string source: The report source class + * } + */ +class report_updated extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = report::TABLE; + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an instance from a report object + * + * @param report $report + * @return self + */ + public static function create_from_object(report $report): self { + $eventparams = [ + 'context' => $report->get_context(), + 'objectid' => $report->get('id'), + 'other' => [ + 'name' => $report->get('name'), + 'source' => $report->get('source'), + ] + ]; + $event = self::create($eventparams); + $event->add_record_snapshot($event->objecttable, $report->to_record()); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('reportupdated', 'core_reportbuilder'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' updated the custom report with id '$this->objectid'."; + } + + /** + * Returns relevant URL. + * + * @return moodle_url + */ + public function get_url(): moodle_url { + return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]); + } +} diff --git a/reportbuilder/classes/external/columns/add.php b/reportbuilder/classes/external/columns/add.php new file mode 100644 index 00000000000..f79b163c24c --- /dev/null +++ b/reportbuilder/classes/external/columns/add.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns; + +use core_reportbuilder\external\custom_report_columns_sorting_exporter; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for adding report columns + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class add 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'), + 'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the column'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param string $uniqueidentifier + * @return array + */ + public static function execute(int $reportid, string $uniqueidentifier): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::add_report_column($reportid, $uniqueidentifier); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/columns/delete.php b/reportbuilder/classes/external/columns/delete.php new file mode 100644 index 00000000000..270f177866b --- /dev/null +++ b/reportbuilder/classes/external/columns/delete.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns; + +use core_reportbuilder\external\custom_report_columns_sorting_exporter; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for deleting report columns + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete 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'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @return array + */ + public static function execute(int $reportid, int $columnid): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::delete_report_column($reportid, $columnid); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/columns/reorder.php b/reportbuilder/classes/external/columns/reorder.php new file mode 100644 index 00000000000..d2624fc2744 --- /dev/null +++ b/reportbuilder/classes/external/columns/reorder.php @@ -0,0 +1,90 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns; + +use external_api; +use external_function_parameters; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for re-ordering report columns + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder 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'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + 'position' => new external_value(PARAM_INT, 'New column position') + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return bool + */ + public static function execute(int $reportid, int $columnid, int $position): bool { + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + return report::reorder_report_column($reportid, $columnid, $position); + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_value { + return new external_value(PARAM_BOOL, 'Success'); + } +} diff --git a/reportbuilder/classes/external/columns/sort/reorder.php b/reportbuilder/classes/external/columns/sort/reorder.php new file mode 100644 index 00000000000..72482cb3a64 --- /dev/null +++ b/reportbuilder/classes/external/columns/sort/reorder.php @@ -0,0 +1,100 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\external\custom_report_columns_sorting_exporter; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for re-ordering report column sorting + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder 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'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + 'position' => new external_value(PARAM_INT, 'New column sort position'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return array + */ + public static function execute(int $reportid, int $columnid, int $position): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::reorder_report_column_sorting($reportid, $columnid, $position); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/columns/sort/toggle.php b/reportbuilder/classes/external/columns/sort/toggle.php new file mode 100644 index 00000000000..640d6f37663 --- /dev/null +++ b/reportbuilder/classes/external/columns/sort/toggle.php @@ -0,0 +1,104 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\external\custom_report_columns_sorting_exporter; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for toggling report column sorting + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class toggle 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'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + 'enabled' => new external_value(PARAM_BOOL, 'Sort enabled'), + 'direction' => new external_value(PARAM_INT, 'Sort direction', VALUE_DEFAULT, SORT_ASC), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @param bool $enabled + * @param int $direction + * @return array + */ + public static function execute(int $reportid, int $columnid, bool $enabled, int $direction = SORT_ASC): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'enabled' => $enabled, + 'direction' => $direction, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'enabled' => $enabled, + 'direction' => $direction, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::toggle_report_column_sorting($reportid, $columnid, $enabled, $direction); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/conditions/add.php b/reportbuilder/classes/external/conditions/add.php new file mode 100644 index 00000000000..1210363b309 --- /dev/null +++ b/reportbuilder/classes/external/conditions/add.php @@ -0,0 +1,102 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\conditions; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_conditions_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for adding report conditions + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class add 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'), + 'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the condition'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param string $uniqueidentifier + * @return array + */ + public static function execute(int $reportid, string $uniqueidentifier): array { + global $PAGE, $OUTPUT; + + [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::add_report_condition($reportid, $uniqueidentifier); + + // Set current URL and force bootstrap_renderer to initiate moodle page. + $PAGE->set_url('/'); + $OUTPUT->header(); + $PAGE->start_collecting_javascript_requirements(); + + $exporter = new custom_report_conditions_exporter(null, ['report' => $report]); + + $export = $exporter->export($PAGE->get_renderer('core')); + $export->javascript = $PAGE->requires->get_end_code(); + + return (array) $export; + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_single_structure { + return custom_report_conditions_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/conditions/delete.php b/reportbuilder/classes/external/conditions/delete.php new file mode 100644 index 00000000000..48a57cdf11c --- /dev/null +++ b/reportbuilder/classes/external/conditions/delete.php @@ -0,0 +1,102 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\conditions; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_conditions_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for deleting report conditions + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete 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'), + 'conditionid' => new external_value(PARAM_INT, 'Condition ID'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $conditionid + * @return array + */ + public static function execute(int $reportid, int $conditionid): array { + global $PAGE, $OUTPUT; + + [ + 'reportid' => $reportid, + 'conditionid' => $conditionid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'conditionid' => $conditionid, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::delete_report_condition($reportid, $conditionid); + + // Set current URL and force bootstrap_renderer to initiate moodle page. + $PAGE->set_url('/'); + $OUTPUT->header(); + $PAGE->start_collecting_javascript_requirements(); + + $exporter = new custom_report_conditions_exporter(null, ['report' => $report]); + + $export = $exporter->export($PAGE->get_renderer('core')); + $export->javascript = $PAGE->requires->get_end_code(); + + return (array) $export; + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_single_structure { + return custom_report_conditions_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/conditions/reorder.php b/reportbuilder/classes/external/conditions/reorder.php new file mode 100644 index 00000000000..fe97c327acf --- /dev/null +++ b/reportbuilder/classes/external/conditions/reorder.php @@ -0,0 +1,106 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\conditions; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_conditions_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for re-ordering report conditions + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder 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'), + 'conditionid' => new external_value(PARAM_INT, 'Condition ID'), + 'position' => new external_value(PARAM_INT, 'New condition position') + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $conditionid + * @param int $position + * @return array + */ + public static function execute(int $reportid, int $conditionid, int $position): array { + global $PAGE, $OUTPUT; + + [ + 'reportid' => $reportid, + 'conditionid' => $conditionid, + 'position' => $position, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'conditionid' => $conditionid, + 'position' => $position, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::reorder_report_condition($reportid, $conditionid, $position); + + // Set current URL and force bootstrap_renderer to initiate moodle page. + $PAGE->set_url('/'); + $OUTPUT->header(); + $PAGE->start_collecting_javascript_requirements(); + + $exporter = new custom_report_conditions_exporter(null, ['report' => $report]); + + $export = $exporter->export($PAGE->get_renderer('core')); + $export->javascript = $PAGE->requires->get_end_code(); + + return (array) $export; + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_single_structure { + return custom_report_conditions_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/conditions/reset.php b/reportbuilder/classes/external/conditions/reset.php new file mode 100644 index 00000000000..6bea18b05ea --- /dev/null +++ b/reportbuilder/classes/external/conditions/reset.php @@ -0,0 +1,97 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\conditions; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_conditions_exporter; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for resetting report conditions + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @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 array + */ + public static function execute(int $reportid): array { + global $PAGE, $OUTPUT; + + [ + 'reportid' => $reportid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + $report->set_condition_values([]); + + // Set current URL and force bootstrap_renderer to initiate moodle page. + $PAGE->set_url('/'); + $OUTPUT->header(); + $PAGE->start_collecting_javascript_requirements(); + + $exporter = new custom_report_conditions_exporter(null, ['report' => $report]); + + $export = $exporter->export($PAGE->get_renderer('core')); + $export->javascript = $PAGE->requires->get_end_code(); + + return (array) $export; + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_single_structure { + return custom_report_conditions_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/custom_report_columns_sorting_exporter.php b/reportbuilder/classes/external/custom_report_columns_sorting_exporter.php new file mode 100644 index 00000000000..e2b8b96f753 --- /dev/null +++ b/reportbuilder/classes/external/custom_report_columns_sorting_exporter.php @@ -0,0 +1,131 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use pix_icon; +use renderer_base; +use core\external\exporter; +use core_reportbuilder\local\models\column; +use core_reportbuilder\local\report\base; + +/** + * Custom report columns sorting exporter class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_columns_sorting_exporter extends exporter { + + /** + * Return a list of objects that are related to the exporter + * + * @return array + */ + protected static function define_related(): array { + return [ + 'report' => base::class, + ]; + } + + /** + * Return the list of additional properties for read structure and export + * + * @return array[] + */ + protected static function define_other_properties(): array { + return [ + 'hassortablecolumns' => [ + 'type' => PARAM_BOOL, + ], + 'sortablecolumns' => [ + 'type' => [ + 'id' => ['type' => PARAM_INT], + 'title' => ['type' => PARAM_TEXT], + 'heading' => ['type' => PARAM_TEXT], + 'sortdirection' => ['type' => PARAM_INT], + 'sortenabled' => ['type' => PARAM_INT], + 'sortorder' => ['type' => PARAM_INT], + 'sorticon' => [ + 'type' => [ + 'key' => ['type' => PARAM_RAW], + 'component' => ['type' => PARAM_COMPONENT], + 'title' => ['type' => PARAM_NOTAGS], + ], + ], + 'movetitle' => ['type' => PARAM_TEXT], + 'sortenabledtitle' => ['type' => PARAM_TEXT], + ], + 'optional' => true, + 'multiple' => true, + ], + 'helpicon' => [ + 'type' => PARAM_RAW, + ], + ]; + } + + /** + * Get the additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + /** @var base $report */ + $report = $this->related['report']; + + $reportid = $report->get_report_persistent()->get('id'); + $activecolumns = column::get_records(['reportid' => $reportid], 'sortorder'); + $sortablecolumns = array_filter($activecolumns, function(column $persistent) use($report) { + $column = $report->get_column($persistent->get('uniqueidentifier')); + return $column && $column->get_is_sortable(); + }); + + $sortablecolumns = array_map(function(column $persistent) use ($report) { + $columntitle = $report->get_column($persistent->get('uniqueidentifier'))->get_title(); + $columnheading = $persistent->get('heading') ?: $columntitle; + + $columnsortascending = ($persistent->get('sortdirection') == SORT_ASC); + $sortenabledtitle = $persistent->get('sortenabled') ? 'columnsortdisable' : 'columnsortenable'; + $sortdirectiontitle = $columnsortascending ? 'columnsortdirectiondesc' : 'columnsortdirectionasc'; + + $icon = $columnsortascending ? 't/uplong' : 't/downlong'; + $sorticon = new pix_icon($icon, get_string($sortdirectiontitle, 'core_reportbuilder', $columntitle)); + + return [ + 'id' => $persistent->get('id'), + 'title' => $columntitle, + 'heading' => $columnheading, + 'sortdirection' => $persistent->get('sortdirection'), + 'sortenabled' => (int)$persistent->get('sortenabled'), + 'sortorder' => $persistent->get('sortorder'), + 'sorticon' => $sorticon->export_for_pix(), + 'movetitle' => get_string('movesorting', 'core_reportbuilder', $columntitle), + 'sortenabledtitle' => get_string($sortenabledtitle, 'core_reportbuilder', $columntitle), + ]; + }, $sortablecolumns); + + return [ + 'hassortablecolumns' => !empty($sortablecolumns), + 'sortablecolumns' => array_values($sortablecolumns), + 'helpicon' => $output->help_icon('sorting', 'core_reportbuilder'), + ]; + } +} diff --git a/reportbuilder/classes/external/custom_report_conditions_exporter.php b/reportbuilder/classes/external/custom_report_conditions_exporter.php new file mode 100644 index 00000000000..3a19532814f --- /dev/null +++ b/reportbuilder/classes/external/custom_report_conditions_exporter.php @@ -0,0 +1,151 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use renderer_base; +use core\external\exporter; +use core_reportbuilder\form\condition; +use core_reportbuilder\local\report\base; +use core_reportbuilder\local\models\filter; + +/** + * Custom report conditions exporter class + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_conditions_exporter extends exporter { + + /** + * Return a list of objects that are related to the exporter + * + * @return array + */ + protected static function define_related(): array { + return [ + 'report' => base::class, + ]; + } + + /** + * Return the list of additional properties for read structure and export + * + * @return array[] + */ + protected static function define_other_properties(): array { + return [ + 'hasavailableconditions' => [ + 'type' => PARAM_BOOL, + ], + 'availableconditions' => [ + 'type' => [ + 'optiongroup' => [ + 'type' => [ + 'text' => ['type' => PARAM_TEXT], + 'values' => [ + 'type' => [ + 'value' => ['type' => PARAM_TEXT], + 'visiblename' => ['type' => PARAM_TEXT], + ], + 'multiple' => true, + ], + ], + ], + ], + 'multiple' => true, + 'optional' => true + ], + 'hasactiveconditions' => [ + 'type' => PARAM_BOOL, + ], + 'activeconditionsform' => [ + 'type' => PARAM_RAW, + 'optional' => true, + ], + 'helpicon' => [ + 'type' => PARAM_RAW, + 'optional' => true, + ], + 'javascript' => [ + 'type' => PARAM_RAW, + 'optional' => true, + ], + ]; + } + + /** + * Get the additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + /** @var base $report */ + $report = $this->related['report']; + $reportid = $report->get_report_persistent()->get('id'); + + // Current condition instances contained in the report. + $conditioninstances = filter::get_condition_records($reportid, 'filterorder'); + $conditionidentifiers = array_map(static function(filter $condition): string { + return $condition->get('uniqueidentifier'); + }, $conditioninstances); + + $availableconditions = []; + + // Populate available conditions. + foreach ($report->get_conditions() as $condition) { + if (in_array($condition->get_unique_identifier(), $conditionidentifiers)) { + continue; + } + + $entityname = $condition->get_entity_name(); + if (!array_key_exists($entityname, $availableconditions)) { + $availableconditions[$entityname] = [ + 'optiongroup' => [ + 'text' => $report->get_entity_title($entityname)->out(), + 'values' => [], + ], + ]; + } + + $availableconditions[$entityname]['optiongroup']['values'][] = [ + 'value' => $condition->get_unique_identifier(), + 'visiblename' => $condition->get_header(), + ]; + } + + // Generate filters form if report contains any filters. + $conditionspresent = !empty($conditioninstances); + if ($conditionspresent) { + $conditionsform = new condition(null, null, 'post', '', [], true, [ + 'reportid' => $reportid, + ]); + $conditionsform->set_data_for_dynamic_submission(); + } + + return [ + 'hasavailableconditions' => !empty($availableconditions), + 'availableconditions' => array_values($availableconditions), + 'hasactiveconditions' => $conditionspresent, + 'activeconditionsform' => $conditionspresent ? $conditionsform->render() : '', + 'helpicon' => $output->help_icon('conditions', 'core_reportbuilder'), + ]; + } +} diff --git a/reportbuilder/classes/external/custom_report_exporter.php b/reportbuilder/classes/external/custom_report_exporter.php new file mode 100644 index 00000000000..2f446b84ea5 --- /dev/null +++ b/reportbuilder/classes/external/custom_report_exporter.php @@ -0,0 +1,181 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use renderer_base; +use core\persistent; +use core\external\persistent_exporter; +use core_reportbuilder\manager; +use core_reportbuilder\datasource; +use core_reportbuilder\form\filter as form_filter; +use core_reportbuilder\local\models\report; +use core_reportbuilder\table\custom_report_table; +use core_reportbuilder\table\custom_report_table_filterset; +use core_reportbuilder\table\custom_report_table_view; +use core_reportbuilder\table\custom_report_table_view_filterset; +use core_reportbuilder\local\helpers\report as report_helper; + +/** + * Custom report exporter class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_exporter extends persistent_exporter { + + /** @var report The persistent object we will export. */ + protected $persistent = null; + + /** @var bool */ + protected $editmode; + + /** @var bool $showeditbutton When showing the report on view.php the Edit button has to be hidden */ + protected $showeditbutton; + + /** @var string */ + protected $download; + + /** + * report_exporter constructor. + * + * @param persistent $persistent + * @param array $related + * @param bool $editmode + * @param bool $showeditbutton + * @param string $download + */ + public function __construct(persistent $persistent, array $related = [], bool $editmode = true, + bool $showeditbutton = true, string $download = '') { + + parent::__construct($persistent, $related); + $this->editmode = $editmode; + $this->showeditbutton = $showeditbutton; + $this->download = $download; + } + /** + * 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 [ + ]; + } + + /** + * Return a list of additional properties used only for display + * + * @return array + */ + protected static function define_other_properties(): array { + return [ + 'table' => ['type' => PARAM_RAW], + 'sidebarmenucards' => ['type' => custom_report_menu_cards_exporter::read_properties_definition()], + 'conditions' => ['type' => custom_report_conditions_exporter::read_properties_definition()], + 'filters' => ['type' => custom_report_filters_exporter::read_properties_definition()], + 'sorting' => ['type' => custom_report_columns_sorting_exporter::read_properties_definition()], + 'filtersapplied' => ['type' => PARAM_INT], + 'filtersform' => [ + 'type' => PARAM_RAW, + 'optional' => true, + ], + 'editmode' => ['type' => PARAM_INT], + 'showeditbutton' => ['type' => PARAM_BOOL], + 'javascript' => ['type' => PARAM_RAW], + ]; + } + + /** + * Get additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + $filtersform = ''; + $menucards = []; + + if ($this->editmode) { + $table = custom_report_table::create($this->persistent->get('id')); + $table->set_filterset(new custom_report_table_filterset()); + } else { + $table = custom_report_table_view::create($this->persistent->get('id'), $this->download); + $table->set_filterset(new custom_report_table_view_filterset()); + + // Generate filters form if report contains any filters. + $source = $this->persistent->get('source'); + /** @var datasource $datasource */ + $datasource = new $source($this->persistent); + + if (!empty($datasource->get_active_filters())) { + $filtersform = $this->generate_filters_form()->render(); + } + } + + $report = manager::get_report_from_persistent($this->persistent); + + $conditionsexporter = new custom_report_conditions_exporter(null, ['report' => $report]); + $filtersexporter = new custom_report_filters_exporter(null, ['report' => $report]); + $sortingexporter = new custom_report_columns_sorting_exporter(null, ['report' => $report]); + if ($this->editmode) { + $menucardexporter = new custom_report_menu_cards_exporter(null, [ + 'menucards' => report_helper::get_available_columns($report->get_report_persistent()) + ]); + $menucards = (array) $menucardexporter->export($output); + } + + return [ + 'table' => $output->render($table), + 'sidebarmenucards' => $menucards, + 'conditions' => (array) $conditionsexporter->export($output), + 'filters' => (array) $filtersexporter->export($output), + 'sorting' => (array) $sortingexporter->export($output), + 'filtersapplied' => $report->get_applied_filter_count(), + 'filtersform' => $filtersform, + 'editmode' => (int)$this->editmode, + 'showeditbutton' => $this->showeditbutton, + 'javascript' => '', + ]; + } + + /** + * Generate filters form for the report + * + * @return form_filter + */ + private function generate_filters_form(): form_filter { + $filtersform = new form_filter(null, null, 'post', '', [], true, [ + 'reportid' => $this->persistent->get('id'), + 'parameters' => json_encode([]), + ]); + $filtersform->set_data_for_dynamic_submission(); + + return $filtersform; + } +} diff --git a/reportbuilder/classes/external/custom_report_filters_exporter.php b/reportbuilder/classes/external/custom_report_filters_exporter.php new file mode 100644 index 00000000000..f05219cb960 --- /dev/null +++ b/reportbuilder/classes/external/custom_report_filters_exporter.php @@ -0,0 +1,162 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use renderer_base; +use core\external\exporter; +use core_reportbuilder\local\report\base; +use core_reportbuilder\local\models\filter; +use core_reportbuilder\output\filter_heading_editable; + +/** + * Custom report filters exporter class + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_filters_exporter extends exporter { + + /** + * Return a list of objects that are related to the exporter + * + * @return array + */ + protected static function define_related(): array { + return [ + 'report' => base::class, + ]; + } + + /** + * Return the list of additional properties for read structure and export + * + * @return array[] + */ + protected static function define_other_properties(): array { + return [ + 'hasavailablefilters' => [ + 'type' => PARAM_BOOL, + ], + 'availablefilters' => [ + 'type' => [ + 'optiongroup' => [ + 'type' => [ + 'text' => ['type' => PARAM_TEXT], + 'values' => [ + 'type' => [ + 'value' => ['type' => PARAM_TEXT], + 'visiblename' => ['type' => PARAM_TEXT], + ], + 'multiple' => true, + ], + ], + ], + ], + 'multiple' => true, + 'optional' => true + ], + 'hasactivefilters' => [ + 'type' => PARAM_BOOL, + ], + 'activefilters' => [ + 'type' => [ + 'id' => ['type' => PARAM_INT], + 'heading' => ['type' => PARAM_TEXT], + 'headingeditable' => ['type' => PARAM_RAW], + 'sortorder' => ['type' => PARAM_INT], + 'movetitle' => ['type' => PARAM_TEXT], + 'entityname' => ['type' => PARAM_TEXT], + ], + 'multiple' => true, + 'optional' => true + ], + 'helpicon' => [ + 'type' => PARAM_RAW, + ], + ]; + } + + /** + * Get the additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + /** @var base $report */ + $report = $this->related['report']; + + // Current filter instances contained in the report. + $filterinstances = filter::get_filter_records($report->get_report_persistent()->get('id'), 'filterorder'); + $filteridentifiers = array_map(static function(filter $filter): string { + return $filter->get('uniqueidentifier'); + }, $filterinstances); + + $availablefilters = $activefilters = []; + + // Populate available filters. + foreach ($report->get_filters() as $filter) { + if (in_array($filter->get_unique_identifier(), $filteridentifiers)) { + continue; + } + + $entityname = $filter->get_entity_name(); + if (!array_key_exists($entityname, $availablefilters)) { + $availablefilters[$entityname] = [ + 'optiongroup' => [ + 'text' => $report->get_entity_title($entityname)->out(), + 'values' => [], + ], + ]; + } + + $availablefilters[$entityname]['optiongroup']['values'][] = [ + 'value' => $filter->get_unique_identifier(), + 'visiblename' => $filter->get_header(), + ]; + } + + // Populate active filters. + foreach ($filterinstances as $filter) { + $editable = new filter_heading_editable($filter->get('id')); + + $filterinstance = $report->get_filter($filter->get('uniqueidentifier')); + $entityname = $filterinstance->get_entity_name(); + $displayvalue = $filterinstance->get_header(); + + $activefilters[] = [ + 'id' => $filter->get('id'), + 'entityname' => $report->get_entity_title($entityname)->out(), + 'heading' => $displayvalue, + 'headingeditable' => $editable->render($output), + 'sortorder' => $filter->get('filterorder'), + 'movetitle' => get_string('movefilter', 'core_reportbuilder', $displayvalue), + ]; + } + + return [ + 'hasavailablefilters' => !empty($availablefilters), + 'availablefilters' => array_values($availablefilters), + 'hasactivefilters' => !empty($activefilters), + 'activefilters' => $activefilters, + 'helpicon' => $output->help_icon('filters', 'core_reportbuilder'), + ]; + } +} diff --git a/reportbuilder/classes/external/custom_report_menu_cards_exporter.php b/reportbuilder/classes/external/custom_report_menu_cards_exporter.php new file mode 100644 index 00000000000..d46b1e5890a --- /dev/null +++ b/reportbuilder/classes/external/custom_report_menu_cards_exporter.php @@ -0,0 +1,97 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use renderer_base; +use core\external\exporter; + +/** + * Custom report menu cards exporter class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_menu_cards_exporter extends exporter { + + /** + * Return a list of objects that are related to the exporter + * + * @return array + */ + protected static function define_related(): array { + return [ + 'menucards' => 'array[]', + ]; + } + + /** + * Return the list of additional properties for read structure and export + * + * @return array[] + */ + protected static function define_other_properties(): array { + return [ + 'menucards' => [ + 'type' => [ + 'name' => [ + 'type' => PARAM_TEXT, + 'optional' => true, + ], + 'key' => [ + 'type' => PARAM_TEXT, + 'optional' => true, + ], + 'items' => [ + 'type' => [ + 'name' => [ + 'type' => PARAM_TEXT, + ], + 'identifier' => [ + 'type' => PARAM_TEXT, + ], + 'title' => [ + 'type' => PARAM_TEXT, + ], + 'action' => [ + 'type' => PARAM_TEXT, + ], + ], + 'optional' => true, + 'multiple' => true, + ], + ], + 'optional' => true, + 'multiple' => true, + ], + ]; + } + + /** + * Get the additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + return [ + 'menucards' => $this->related['menucards'] + ]; + } +} diff --git a/reportbuilder/classes/external/filters/add.php b/reportbuilder/classes/external/filters/add.php new file mode 100644 index 00000000000..2977ec93d65 --- /dev/null +++ b/reportbuilder/classes/external/filters/add.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\filters; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_filters_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for adding report filters + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class add 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'), + 'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the filter'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param string $uniqueidentifier + * @return array + */ + public static function execute(int $reportid, string $uniqueidentifier): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::add_report_filter($reportid, $uniqueidentifier); + + $exporter = new custom_report_filters_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_filters_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/filters/delete.php b/reportbuilder/classes/external/filters/delete.php new file mode 100644 index 00000000000..ee57495c0a4 --- /dev/null +++ b/reportbuilder/classes/external/filters/delete.php @@ -0,0 +1,96 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\filters; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_filters_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for deleting report filters + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete 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'), + 'filterid' => new external_value(PARAM_INT, 'Filter ID'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $filterid + * @return array + */ + public static function execute(int $reportid, int $filterid): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'filterid' => $filterid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'filterid' => $filterid, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::delete_report_filter($reportid, $filterid); + + $exporter = new custom_report_filters_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_filters_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/filters/reorder.php b/reportbuilder/classes/external/filters/reorder.php new file mode 100644 index 00000000000..55cee5e5817 --- /dev/null +++ b/reportbuilder/classes/external/filters/reorder.php @@ -0,0 +1,100 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\filters; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_filters_exporter; +use core_reportbuilder\local\helpers\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for re-ordering report filters + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder 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'), + 'filterid' => new external_value(PARAM_INT, 'Filter ID'), + 'position' => new external_value(PARAM_INT, 'New filter position') + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $filterid + * @param int $position + * @return array + */ + public static function execute(int $reportid, int $filterid, int $position): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'filterid' => $filterid, + 'position' => $position, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'filterid' => $filterid, + 'position' => $position, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::reorder_report_filter($reportid, $filterid, $position); + + $exporter = new custom_report_filters_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_filters_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/reports/delete.php b/reportbuilder/classes/external/reports/delete.php new file mode 100644 index 00000000000..c00e06dc2a0 --- /dev/null +++ b/reportbuilder/classes/external/reports/delete.php @@ -0,0 +1,84 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\reports; + +use external_api; +use external_function_parameters; +use external_value; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\local\models\report as report_model; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for deleting reports + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete 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, + ]); + + // Load the report model for deletion. Note we don't use the manager class because it validates the report source, + // and we want user to be able to delete report, even if it's no longer associated with a valid source. + $report = new report_model($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report); + + return report::delete_report($reportid); + } + + /** + * External method return value + * + * @return external_value + */ + public static function execute_returns(): external_value { + return new external_value(PARAM_BOOL, 'Success'); + } +} diff --git a/reportbuilder/classes/external/reports/get.php b/reportbuilder/classes/external/reports/get.php new file mode 100644 index 00000000000..d788247579d --- /dev/null +++ b/reportbuilder/classes/external/reports/get.php @@ -0,0 +1,104 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\reports; + +use external_api; +use external_value; +use external_single_structure; +use external_function_parameters; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\output\custom_report; +use core_reportbuilder\external\custom_report_exporter; +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for getting a custom report + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get 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'), + 'editmode' => new external_value(PARAM_BOOL, 'Whether editing mode is enabled', VALUE_DEFAULT, 0), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param bool $editmode + * @return array + */ + public static function execute(int $reportid, bool $editmode): array { + global $PAGE, $OUTPUT; + + [ + 'reportid' => $reportid, + 'editmode' => $editmode, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'editmode' => $editmode, + ]); + + $report = manager::get_report_from_id($reportid); + self::validate_context($report->get_context()); + + if ($editmode) { + permission::require_can_edit_report($report->get_report_persistent()); + } else { + permission::require_can_view_report($report->get_report_persistent()); + } + + // Set current URL and force bootstrap_renderer to initiate moodle page. + $PAGE->set_url(new moodle_url('/')); + $OUTPUT->header(); + $PAGE->start_collecting_javascript_requirements(); + + $renderer = $PAGE->get_renderer('core_reportbuilder'); + $context = (new custom_report($report->get_report_persistent(), $editmode))->export_for_template($renderer); + $context->javascript = $PAGE->requires->get_end_code(); + + return (array)$context; + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/form/condition.php b/reportbuilder/classes/form/condition.php new file mode 100644 index 00000000000..e51907719f7 --- /dev/null +++ b/reportbuilder/classes/form/condition.php @@ -0,0 +1,159 @@ +. + +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\permission; +use core_reportbuilder\local\report\base; +use core_reportbuilder\local\models\filter; +use core_reportbuilder\local\models\report; + +/** + * Dynamic condition form + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class condition extends dynamic_form { + + /** + * Return instance of the report using the condition form + * + * @return base + */ + private function get_report(): base { + $report = new report($this->optional_param('reportid', 0, PARAM_INT)); + $parameters = (array) json_decode($this->optional_param('parameters', '', PARAM_RAW)); + + return manager::get_report_from_persistent($report, $parameters); + } + + /** + * 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_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 { + permission::require_can_edit_report($this->get_report()->get_report_persistent()); + } + + /** + * 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_report()->set_condition_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_report()->get_condition_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('/'); + } + + /** + * Condition form definition + */ + 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); + + // Wrap the form elements inside an outer container, as drag/drop requires draggable elements to be immediate + // descendants of said container. Note this is identified by it's data-region property in the editor module. + $mform->addElement('html', '
'); + + // Allow each condition instance to add itself to this form, wrapping each inside custom header/footer template. + $conditioninstances = $this->get_report()->get_condition_instances(); + + $conditions = filter::get_condition_records($this->get_report()->get_report_persistent()->get('id'), 'filterorder'); + foreach ($conditions as $condition) { + $conditioninstance = $conditioninstances[$condition->get('uniqueidentifier')]; + + $entityname = $conditioninstance->get_entity_name(); + $displayvalue = $conditioninstance->get_header(); + + $mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/conditions/header', [ + 'id' => $condition->get('id'), + 'entityname' => $this->get_report()->get_entity_title($entityname), + 'heading' => $displayvalue, + 'sortorder' => $condition->get('filterorder'), + 'movetitle' => get_string('movecondition', 'core_reportbuilder', $displayvalue), + ])); + + $conditioninstance->setup_form($mform); + $mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/conditions/footer', [])); + } + $mform->addElement('html', '
'); + $this->set_display_vertical(); + + // We'll add a second submit button to the form that will be used to reset current report conditions. + $mform->registerNoSubmitButton('resetconditions'); + + $buttons = []; + $buttons[] = $mform->createElement('submit', 'submitbutton', get_string('apply', 'core_reportbuilder')); + $buttons[] = $mform->createElement('submit', 'resetconditions', get_string('resetall', 'core_reportbuilder'), + null, null, ['customclassoverride' => 'btn-link']); + + $mform->addGroup($buttons, 'buttonar', '', [' '], false); + $mform->closeHeaderBefore('buttonar'); + + $mform->disable_form_change_checker(); + } +} diff --git a/reportbuilder/classes/form/filter.php b/reportbuilder/classes/form/filter.php index 11cfb466c17..29d2ce2afac 100644 --- a/reportbuilder/classes/form/filter.php +++ b/reportbuilder/classes/form/filter.php @@ -19,11 +19,13 @@ declare(strict_types=1); namespace core_reportbuilder\form; use context; +use core_reportbuilder\local\report\base; +use core_reportbuilder\permission; use moodle_url; use core_form\dynamic_form; use core_reportbuilder\manager; -use core_reportbuilder\system_report; use core_reportbuilder\local\models\report; +use core_reportbuilder\local\models\filter as filter_model; /** * Dynamic filter form @@ -37,16 +39,13 @@ class filter extends dynamic_form { /** * Return instance of the system report using the filter form * - * @return system_report + * @return base */ - private function get_system_report(): system_report { - $report = new report($this->optional_param('reportid', 0, PARAM_INT)); + private function get_report(): base { + $reportpersistent = 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 manager::get_report_from_persistent($reportpersistent, $parameters); } /** @@ -55,7 +54,7 @@ class filter extends dynamic_form { * @return context */ protected function get_context_for_dynamic_submission(): context { - return ($this->get_system_report())->get_context(); + return ($this->get_report())->get_context(); } /** @@ -64,7 +63,12 @@ class filter extends dynamic_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(); + $reportpersistent = $this->get_report()->get_report_persistent(); + if ($reportpersistent->get('type') === base::TYPE_CUSTOM_REPORT) { + permission::require_can_view_report($reportpersistent); + } else { + $this->get_report()->require_can_view(); + } } /** @@ -77,9 +81,9 @@ class filter extends dynamic_form { // Remove some unneeded fields, apply filters. unset($values->reportid, $values->parameters); - $this->get_system_report()->set_filter_values((array) $values); + $this->get_report()->set_filter_values((array) $values); - return $this->get_system_report()->get_applied_filter_count(); + return $this->get_report()->get_applied_filter_count(); } /** @@ -91,7 +95,7 @@ class filter extends dynamic_form { 'parameters' => $this->optional_param('parameters', 0, PARAM_RAW), ]; - $this->set_data(array_merge($defaults, $this->get_system_report()->get_filter_values())); + $this->set_data(array_merge($defaults, $this->get_report()->get_filter_values())); } /** @@ -118,9 +122,17 @@ class filter extends dynamic_form { $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) { + $filterinstances = $this->get_report()->get_filter_instances(); + foreach ($filterinstances as $filterinstance) { + // Check if filter has a custom header set. + if ($filterinstance->get_filter_persistent() && !empty($filterinstance->get_filter_persistent()->get('heading'))) { + $header = $filterinstance->get_filter_persistent()->get('heading'); + } else { + $header = $filterinstance->get_header(); + } + $mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/filters/header', [ - 'name' => $filterinstance->get_header(), + 'name' => $header, ])); $filterinstance->setup_form($mform); diff --git a/reportbuilder/classes/form/report.php b/reportbuilder/classes/form/report.php new file mode 100644 index 00000000000..303b84a222a --- /dev/null +++ b/reportbuilder/classes/form/report.php @@ -0,0 +1,150 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\form; + +use context; +use context_system; +use core_reportbuilder\permission; +use moodle_url; +use core_form\dynamic_form; +use core_reportbuilder\datasource; +use core_reportbuilder\manager; +use core_reportbuilder\local\helpers\report as reporthelper; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("$CFG->libdir/formslib.php"); + +/** + * Report details form + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report extends dynamic_form { + + /** + * Return instance of the custom report we are editing, or null when creating a new report + * + * @return datasource|null + */ + protected function get_custom_report(): ?datasource { + if ($reportid = $this->optional_param('id', 0, PARAM_INT)) { + /** @var datasource $customreport */ + $customreport = manager::get_report_from_id($reportid); + + return $customreport; + } + + return null; + } + + /** + * Return the context for the form, it should be that of the custom report itself, or system when creating a new report + * + * @return context + */ + public function get_context_for_dynamic_submission(): context { + if ($report = $this->get_custom_report()) { + return $report->get_context(); + } else { + return context_system::instance(); + } + } + + /** + * Ensure current user is able to use this form + * + * A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't + */ + protected function check_access_for_dynamic_submission(): void { + $report = $this->get_custom_report(); + + if ($report) { + permission::require_can_edit_report($report->get_report_persistent()); + } else { + permission::require_can_create_report(); + } + } + + /** + * Form definition + */ + public function definition() { + $mform = $this->_form; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('text', 'name', get_string('name')); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', null, 'required', null, 'client'); + $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255); + + // Allow user to select report source if creating a new report. + if (!$this->get_custom_report()) { + $default = ['' => ['' => get_string('selectareportsource', 'core_reportbuilder')]]; + $mform->addElement('selectgroups', 'source', get_string('reportsource', 'core_reportbuilder'), + array_merge($default, manager::get_report_datasources())); + $mform->addRule('source', null, 'required', null, 'client'); + $mform->addHelpButton('source', 'reportsource', 'core_reportbuilder'); + + $mform->addElement('advcheckbox', 'includedefaultsetup', get_string('includedefaultsetup', 'core_reportbuilder')); + $mform->setDefault('includedefaultsetup', 1); + $mform->addHelpButton('includedefaultsetup', 'includedefaultsetup', 'core_reportbuilder'); + } + } + + /** + * Process the form submission + * + * @return string The URL to advance to upon completion + */ + public function process_dynamic_submission() { + $data = $this->get_data(); + + if ($data->id) { + $reportpersistent = reporthelper::update_report($data); + } else { + $reportpersistent = reporthelper::create_report($data, (bool)$data->includedefaultsetup); + } + + return (new moodle_url('/reportbuilder/edit.php', ['id' => $reportpersistent->get('id')]))->out(false); + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + if ($report = $this->get_custom_report()) { + $this->set_data($report->get_report_persistent()->to_record()); + } + } + + /** + * URL of the page using this form + * + * @return moodle_url + */ + public function get_page_url_for_dynamic_submission(): moodle_url { + return new moodle_url('/reportbuilder/index.php'); + } +} diff --git a/reportbuilder/classes/local/aggregation/avg.php b/reportbuilder/classes/local/aggregation/avg.php new file mode 100644 index 00000000000..09e79b9ee25 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/avg.php @@ -0,0 +1,77 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column average aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class avg extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationavg', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all numeric columns + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return in_array($columntype, [ + column::TYPE_INTEGER, + column::TYPE_FLOAT, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + return "AVG(1.0 * {$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return sprintf('%.1f', (float) reset($values)); + } +} diff --git a/reportbuilder/classes/local/aggregation/base.php b/reportbuilder/classes/local/aggregation/base.php new file mode 100644 index 00000000000..828514c6b3d --- /dev/null +++ b/reportbuilder/classes/local/aggregation/base.php @@ -0,0 +1,77 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Base class for column aggregation types + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class base { + + /** + * Return the class name of the aggregation type + * + * @return string + */ + final public static function get_class_name(): string { + $namespacedclass = explode('\\', get_called_class()); + + return end($namespacedclass); + } + + /** + * Return the display name of the aggregation + * + * @return lang_string + */ + abstract public static function get_name(): lang_string; + + /** + * Whether the aggregation is compatible with the given column type + * + * @param int $columntype The type as defined by the {@see column::set_type} method + * @return bool + */ + abstract public static function compatible(int $columntype): bool; + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + abstract public static function get_field_sql(string $field, int $columntype): string; + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + abstract public static function format_value($value, array $values, array $callbacks); +} diff --git a/reportbuilder/classes/local/aggregation/count.php b/reportbuilder/classes/local/aggregation/count.php new file mode 100644 index 00000000000..e3bb2108088 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/count.php @@ -0,0 +1,80 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column count aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class count extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationcount', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all column types + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return true; + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + global $DB; + + if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { + $field = $DB->sql_compare_text($field, 255); + } + + return "COUNT({$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return (int) reset($values); + } +} diff --git a/reportbuilder/classes/local/aggregation/countdistinct.php b/reportbuilder/classes/local/aggregation/countdistinct.php new file mode 100644 index 00000000000..7d936e0ebc8 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/countdistinct.php @@ -0,0 +1,80 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column count distinct aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class countdistinct extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationcountdistinct', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all column types + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return true; + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + global $DB; + + if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { + $field = $DB->sql_compare_text($field, 255); + } + + return "COUNT(DISTINCT {$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return (int) reset($values); + } +} diff --git a/reportbuilder/classes/local/aggregation/groupconcat.php b/reportbuilder/classes/local/aggregation/groupconcat.php new file mode 100644 index 00000000000..4367aa1abad --- /dev/null +++ b/reportbuilder/classes/local/aggregation/groupconcat.php @@ -0,0 +1,78 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column group concatenation aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class groupconcat extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationgroupconcat', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all non-timestamp columns + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return !in_array($columntype, [ + column::TYPE_TIMESTAMP, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + global $DB; + + return $DB->sql_group_concat($field); + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return $value; + } +} diff --git a/reportbuilder/classes/local/aggregation/groupconcatdistinct.php b/reportbuilder/classes/local/aggregation/groupconcatdistinct.php new file mode 100644 index 00000000000..831a9175bf1 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/groupconcatdistinct.php @@ -0,0 +1,90 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column group concatenation distinct aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class groupconcatdistinct extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationgroupconcatdistinct', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all non-timestamp columns in MySQL and Postgres only + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + global $DB; + + $dbsupportedtype = in_array($DB->get_dbfamily(), [ + 'mysql', + 'postgres', + ]); + + return $dbsupportedtype && !in_array($columntype, [ + column::TYPE_TIMESTAMP, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + global $DB; + + // DB limitations mean we only support MySQL and Postgres, and each handle it differently. + if ($DB->get_dbfamily() === 'postgres') { + return "STRING_AGG(DISTINCT CAST({$field} AS VARCHAR), ', ')"; + } else { + return $DB->sql_group_concat("DISTINCT {$field}"); + } + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return $value; + } +} diff --git a/reportbuilder/classes/local/aggregation/max.php b/reportbuilder/classes/local/aggregation/max.php new file mode 100644 index 00000000000..c801b9101e8 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/max.php @@ -0,0 +1,83 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column max aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class max extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationmax', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all numeric/date/boolean types + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return in_array($columntype, [ + column::TYPE_INTEGER, + column::TYPE_FLOAT, + column::TYPE_TIMESTAMP, + column::TYPE_BOOLEAN, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + return "MAX({$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + foreach ($callbacks as $callback) { + $value = ($callback[0])($value, (object) $values, $callback[1]); + } + + return $value; + } +} diff --git a/reportbuilder/classes/local/aggregation/min.php b/reportbuilder/classes/local/aggregation/min.php new file mode 100644 index 00000000000..efde0020068 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/min.php @@ -0,0 +1,83 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column min aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class min extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationmin', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all numeric/date/boolean types + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return in_array($columntype, [ + column::TYPE_INTEGER, + column::TYPE_FLOAT, + column::TYPE_TIMESTAMP, + column::TYPE_BOOLEAN, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + return "MIN({$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + foreach ($callbacks as $callback) { + $value = ($callback[0])($value, (object) $values, $callback[1]); + } + + return $value; + } +} diff --git a/reportbuilder/classes/local/aggregation/percent.php b/reportbuilder/classes/local/aggregation/percent.php new file mode 100644 index 00000000000..62fae8afa59 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/percent.php @@ -0,0 +1,77 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\helpers\format; +use core_reportbuilder\local\report\column; + +/** + * Column percent aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class percent extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationpercent', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all boolean columns + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return in_array($columntype, [ + column::TYPE_BOOLEAN, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + return "AVG(1.0 * {$field}) * 100.0"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return format::percent((float) reset($values)); + } +} diff --git a/reportbuilder/classes/local/aggregation/sum.php b/reportbuilder/classes/local/aggregation/sum.php new file mode 100644 index 00000000000..119fb52f508 --- /dev/null +++ b/reportbuilder/classes/local/aggregation/sum.php @@ -0,0 +1,78 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\aggregation; + +use lang_string; +use core_reportbuilder\local\report\column; + +/** + * Column sum aggregation type + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class sum extends base { + + /** + * Return aggregation name + * + * @return lang_string + */ + public static function get_name(): lang_string { + return new lang_string('aggregationsum', 'core_reportbuilder'); + } + + /** + * This aggregation can be performed on all numeric and boolean columns + * + * @param int $columntype + * @return bool + */ + public static function compatible(int $columntype): bool { + return in_array($columntype, [ + column::TYPE_INTEGER, + column::TYPE_FLOAT, + column::TYPE_BOOLEAN, + ]); + } + + /** + * Return the aggregated field SQL + * + * @param string $field + * @param int $columntype + * @return string + */ + public static function get_field_sql(string $field, int $columntype): string { + return "SUM({$field})"; + } + + /** + * Return formatted value for column when applying aggregation + * + * @param mixed $value + * @param array $values + * @param array $callbacks + * @return mixed + */ + public static function format_value($value, array $values, array $callbacks) { + return (int) reset($values); + } +} diff --git a/reportbuilder/classes/local/entities/base.php b/reportbuilder/classes/local/entities/base.php index a91ed7b2a96..e371b2d889a 100644 --- a/reportbuilder/classes/local/entities/base.php +++ b/reportbuilder/classes/local/entities/base.php @@ -50,6 +50,9 @@ abstract class base { /** @var filter[] $filters List of filters for the entity */ private $filters = []; + /** @var filter[] $conditions List of conditions for the entity */ + private $conditions = []; + /** * Database tables that this entity uses and their default aliases * @@ -240,7 +243,7 @@ abstract class base { * Add a filter to the entity * * @param filter $filter - * @return $this + * @return self */ final protected function add_filter(filter $filter): self { $this->filters[$filter->get_name()] = $filter; @@ -270,4 +273,39 @@ abstract class base { return $this->filters[$name]; } + + /** + * Add a condition to the entity + * + * @param filter $condition + * @return $this + */ + final protected function add_condition(filter $condition): self { + $this->conditions[$condition->get_name()] = $condition; + return $this; + } + + /** + * Returns entity conditions + * + * @return filter[] + */ + final public function get_conditions(): array { + return $this->conditions; + } + + /** + * Returns an entity condition + * + * @param string $name + * @return filter + * @throws coding_exception For invalid condition name + */ + final public function get_condition(string $name): filter { + if (!array_key_exists($name, $this->conditions)) { + throw new coding_exception('Invalid condition name', $name); + } + + return $this->conditions[$name]; + } } diff --git a/reportbuilder/classes/local/entities/user.php b/reportbuilder/classes/local/entities/user.php index 89c5b704864..39c3b6dd224 100644 --- a/reportbuilder/classes/local/entities/user.php +++ b/reportbuilder/classes/local/entities/user.php @@ -80,6 +80,11 @@ class user extends base { $this->add_filter($filter); } + $conditions = array_merge($this->get_all_filters(), $userprofilefields->get_filters()); + foreach ($conditions as $condition) { + $this->add_condition($condition); + } + return $this; } @@ -333,8 +338,6 @@ class user extends base { * @return filter[] */ protected function get_all_filters(): array { - global $DB; - $filters = []; $tablealias = $this->get_table_alias('user'); diff --git a/reportbuilder/classes/local/filters/base.php b/reportbuilder/classes/local/filters/base.php index b58df250f9c..9e94c06b0d9 100644 --- a/reportbuilder/classes/local/filters/base.php +++ b/reportbuilder/classes/local/filters/base.php @@ -20,6 +20,7 @@ namespace core_reportbuilder\local\filters; use MoodleQuickForm; use core_reportbuilder\local\report\filter; +use core_reportbuilder\local\models\filter as filter_model; /** * Base class for all report filters @@ -71,6 +72,26 @@ abstract class base { return $this->filter->get_header(); } + /** + * Returns the filter's entity name + * + * @return string + */ + final public function get_entity_name(): string { + return $this->filter->get_entity_name(); + } + + /** + * Returns the filter persistent + * + * Note that filters for system reports don't store a persistent and will return null. + * + * @return filter_model|null + */ + final public function get_filter_persistent(): ?filter_model { + return $this->filter->get_persistent(); + } + /** * Adds filter-specific form elements * diff --git a/reportbuilder/classes/local/helpers/aggregation.php b/reportbuilder/classes/local/helpers/aggregation.php new file mode 100644 index 00000000000..8eaa9878440 --- /dev/null +++ b/reportbuilder/classes/local/helpers/aggregation.php @@ -0,0 +1,80 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\helpers; + +use core_collator; +use core_component; +use core_reportbuilder\local\aggregation\base; + +/** + * Helper class for column aggregation related methods + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class aggregation { + + /** + * Helper method to convert aggregation class name into fully qualified namespaced class + * + * @param string $aggregation + * @return string + */ + public static function get_full_classpath(string $aggregation): string { + return "\\core_reportbuilder\\local\\aggregation\\{$aggregation}"; + } + + /** + * Validate whether given class is a valid aggregation type + * + * @param string $aggregationclass Fully qualified namespaced class, see {@see get_full_classpath} for converting value + * stored in column persistent to full path + * @return bool + */ + public static function valid(string $aggregationclass): bool { + return class_exists($aggregationclass) && is_subclass_of($aggregationclass, base::class); + } + + /** + * Get available aggregation types for given column type + * + * @param int $columntype + * @param array $exclude List of types to exclude, if omitted then include all available types + * @return string[] Aggregation types indexed by [shortname => name] + */ + public static function get_column_aggregations(int $columntype, array $exclude = []): array { + $types = []; + + $classes = core_component::get_component_classes_in_namespace('core_reportbuilder', 'local\\aggregation'); + foreach ($classes as $class => $path) { + /** @var base $aggregationclass */ + $aggregationclass = $class; + if (static::valid($aggregationclass) && $aggregationclass::compatible($columntype) && + !in_array($aggregationclass::get_class_name(), $exclude)) { + + $types[$aggregationclass::get_class_name()] = (string) $aggregationclass::get_name(); + } + } + + core_collator::asort($types, core_collator::SORT_STRING); + + return $types; + } +} diff --git a/reportbuilder/classes/local/helpers/format.php b/reportbuilder/classes/local/helpers/format.php index 660a3f1554c..8eae420a6b8 100644 --- a/reportbuilder/classes/local/helpers/format.php +++ b/reportbuilder/classes/local/helpers/format.php @@ -50,4 +50,14 @@ class format { public static function boolean_as_text(bool $value): string { return $value ? get_string('yes') : get_string('no'); } + + /** + * Returns float value as a percentage + * + * @param float $value + * @return string + */ + public static function percent(float $value): string { + return sprintf('%.1f', $value) . '%'; + } } diff --git a/reportbuilder/classes/local/helpers/report.php b/reportbuilder/classes/local/helpers/report.php new file mode 100644 index 00000000000..ac2e5fc8b1d --- /dev/null +++ b/reportbuilder/classes/local/helpers/report.php @@ -0,0 +1,446 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\helpers; + +use stdClass; +use invalid_parameter_exception; +use core\persistent; +use core_reportbuilder\datasource; +use core_reportbuilder\manager; +use core_reportbuilder\local\models\column; +use core_reportbuilder\local\models\filter; +use core_reportbuilder\local\models\report as report_model; + +/** + * Helper class for manipulating custom reports and their elements (columns, filters, conditions, etc) + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report { + + /** + * Create custom report + * + * @param stdClass $data + * @param bool $default If $default is set to true it will populate report with default layout as defined by the selected + * source. These include pre-defined columns, filters and conditions. + * @return report_model + */ + public static function create_report(stdClass $data, bool $default = true): report_model { + // TODO move this properties_definition validation into the persistents, or resolve MDL-71086. + $data = (object) array_merge(array_intersect_key((array) $data, report_model::properties_definition()), [ + 'type' => datasource::TYPE_CUSTOM_REPORT, + ]); + + $reportpersistent = manager::create_report_persistent($data); + + // Add datasource default columns, filters and conditions to the report. + if ($default) { + $source = $reportpersistent->get('source'); + /** @var datasource $datasource */ + $datasource = new $source($reportpersistent, []); + $datasource->add_default_columns(); + $datasource->add_default_filters(); + $datasource->add_default_conditions(); + } + + return $reportpersistent; + } + + /** + * Update custom report + * + * @param stdClass $data + * @return report_model + */ + public static function update_report(stdClass $data): report_model { + $report = report_model::get_record(['id' => $data->id, 'type' => datasource::TYPE_CUSTOM_REPORT]); + if ($report === false) { + throw new invalid_parameter_exception('Invalid report'); + } + + $report->set('name', $data->name) + ->update(); + + return $report; + } + + /** + * Delete custom report + * + * @param int $reportid + * @return bool + * @throws invalid_parameter_exception + */ + public static function delete_report(int $reportid): bool { + $report = report_model::get_record(['id' => $reportid, 'type' => datasource::TYPE_CUSTOM_REPORT]); + if ($report === false) { + throw new invalid_parameter_exception('Invalid report'); + } + + return $report->delete(); + } + + /** + * Add given column to report + * + * @param int $reportid + * @param string $uniqueidentifier + * @return column + * @throws invalid_parameter_exception + */ + public static function add_report_column(int $reportid, string $uniqueidentifier): column { + $report = manager::get_report_from_id($reportid); + + if (!array_key_exists($uniqueidentifier, $report->get_columns())) { + throw new invalid_parameter_exception('Invalid column'); + } + + $column = new column(0, (object) [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + 'columnorder' => column::get_max_columnorder($reportid, 'columnorder') + 1, + 'sortorder' => column::get_max_columnorder($reportid, 'sortorder') + 1, + ]); + + return $column->create(); + } + + /** + * Delete given column from report + * + * @param int $reportid + * @param int $columnid + * @return bool + * @throws invalid_parameter_exception + */ + public static function delete_report_column(int $reportid, int $columnid): bool { + global $DB; + + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + // After deletion, re-index remaining report columns. + if ($result = $column->delete()) { + $sqlupdateorder = ' + UPDATE {' . column::TABLE . '} + SET columnorder = columnorder - 1 + WHERE reportid = :reportid + AND columnorder > :columnorder'; + + $DB->execute($sqlupdateorder, ['reportid' => $reportid, 'columnorder' => $column->get('columnorder')]); + } + + return $result; + } + + /** + * Re-order given column within a report + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return bool + * @throws invalid_parameter_exception + */ + public static function reorder_report_column(int $reportid, int $columnid, int $position): bool { + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + // Get the rest of the report columns, excluding the one we are moving. + $columns = column::get_records_select('reportid = :reportid AND id <> :id', [ + 'reportid' => $reportid, + 'id' => $columnid, + ], 'columnorder'); + + return static::reorder_persistents_by_field($column, $columns, $position, 'columnorder'); + } + + /** + * Re-order given column sorting within a report + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return bool + * @throws invalid_parameter_exception + */ + public static function reorder_report_column_sorting(int $reportid, int $columnid, int $position): bool { + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + // Get the rest of the report columns, excluding the one we are moving. + $columns = column::get_records_select('reportid = :reportid AND id <> :id', [ + 'reportid' => $reportid, + 'id' => $columnid, + ], 'sortorder'); + + return static::reorder_persistents_by_field($column, $columns, $position, 'sortorder'); + } + + /** + * Toggle sorting options for given column within a report + * + * @param int $reportid + * @param int $columnid + * @param bool $enabled + * @param int $direction + * @return bool + * @throws invalid_parameter_exception + */ + public static function toggle_report_column_sorting(int $reportid, int $columnid, bool $enabled, + int $direction = SORT_ASC): bool { + + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + return $column + ->set('sortenabled', $enabled) + ->set('sortdirection', $direction) + ->update(); + } + + /** + * Add given condition to report + * + * @param int $reportid + * @param string $uniqueidentifier + * @return filter + * @throws invalid_parameter_exception + */ + public static function add_report_condition(int $reportid, string $uniqueidentifier): filter { + $report = manager::get_report_from_id($reportid); + + if (!array_key_exists($uniqueidentifier, $report->get_conditions())) { + throw new invalid_parameter_exception('Invalid condition'); + } + + $condition = new filter(0, (object) [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + 'iscondition' => true, + 'filterorder' => filter::get_max_filterorder($reportid, true) + 1, + ]); + + return $condition->create(); + } + + /** + * Delete given condition from report + * + * @param int $reportid + * @param int $conditionid + * @return bool + * @throws invalid_parameter_exception + */ + public static function delete_report_condition(int $reportid, int $conditionid): bool { + global $DB; + + $condition = filter::get_condition_record($reportid, $conditionid); + if ($condition === false) { + throw new invalid_parameter_exception('Invalid condition'); + } + + // After deletion, re-index remaining report conditions. + if ($result = $condition->delete()) { + $sqlupdateorder = ' + UPDATE {' . filter::TABLE . '} + SET filterorder = filterorder - 1 + WHERE reportid = :reportid + AND filterorder > :filterorder + AND iscondition = 1'; + + $DB->execute($sqlupdateorder, ['reportid' => $reportid, 'filterorder' => $condition->get('filterorder')]); + } + + return $result; + } + + /** + * Re-order given condition within a report + * + * @param int $reportid + * @param int $conditionid + * @param int $position + * @return bool + * @throws invalid_parameter_exception + */ + public static function reorder_report_condition(int $reportid, int $conditionid, int $position): bool { + $condition = filter::get_condition_record($reportid, $conditionid); + if ($condition === false) { + throw new invalid_parameter_exception('Invalid condition'); + } + + // Get the rest of the report conditions, excluding the one we are moving. + $conditions = filter::get_records_select('reportid = :reportid AND iscondition = 1 AND id <> :id', [ + 'reportid' => $reportid, + 'id' => $conditionid, + ], 'filterorder'); + + return static::reorder_persistents_by_field($condition, $conditions, $position, 'filterorder'); + } + + /** + * Add given filter to report + * + * @param int $reportid + * @param string $uniqueidentifier + * @return filter + * @throws invalid_parameter_exception + */ + public static function add_report_filter(int $reportid, string $uniqueidentifier): filter { + $report = manager::get_report_from_id($reportid); + + $reportfilters = $report->get_filters(); + if (!array_key_exists($uniqueidentifier, $reportfilters)) { + throw new invalid_parameter_exception('Invalid filter'); + } + + $filter = new filter(0, (object) [ + 'reportid' => $reportid, + 'uniqueidentifier' => $uniqueidentifier, + 'filterorder' => filter::get_max_filterorder($reportid) + 1, + ]); + + return $filter->create(); + } + + /** + * Delete given filter from report + * + * @param int $reportid + * @param int $filterid + * @return bool + * @throws invalid_parameter_exception + */ + public static function delete_report_filter(int $reportid, int $filterid): bool { + global $DB; + + $filter = filter::get_filter_record($reportid, $filterid); + if ($filter === false) { + throw new invalid_parameter_exception('Invalid filter'); + } + + // After deletion, re-index remaining report filters. + if ($result = $filter->delete()) { + $sqlupdateorder = ' + UPDATE {' . filter::TABLE . '} + SET filterorder = filterorder - 1 + WHERE reportid = :reportid + AND filterorder > :filterorder + AND iscondition = 0'; + + $DB->execute($sqlupdateorder, ['reportid' => $reportid, 'filterorder' => $filter->get('filterorder')]); + } + + return $result; + } + + /** + * Re-order given filter within a report + * + * @param int $reportid + * @param int $filterid + * @param int $position + * @return bool + * @throws invalid_parameter_exception + */ + public static function reorder_report_filter(int $reportid, int $filterid, int $position): bool { + $filter = filter::get_filter_record($reportid, $filterid); + if ($filter === false) { + throw new invalid_parameter_exception('Invalid filter'); + } + + // Get the rest of the report filters, excluding the one we are moving. + $filters = filter::get_records_select('reportid = :reportid AND iscondition = 0 AND id <> :id', [ + 'reportid' => $reportid, + 'id' => $filterid, + ], 'filterorder'); + + return static::reorder_persistents_by_field($filter, $filters, $position, 'filterorder'); + } + + /** + * Get available columns for a given report + * + * @param report_model $persistent + * @return array + */ + public static function get_available_columns(report_model $persistent) : array { + $available = []; + + $report = manager::get_report_from_persistent($persistent); + + // Get current report columns. + foreach ($report->get_columns() as $column) { + $entityname = $column->get_entity_name(); + $entitytitle = $column->get_title(); + if (!array_key_exists($entityname, $available)) { + $available[$entityname] = [ + 'name' => (string) $report->get_entity_title($entityname), + 'key' => $entityname, + 'items' => [], + ]; + } + + $available[$entityname]['items'][] = [ + 'name' => $entitytitle, + 'identifier' => $column->get_unique_identifier(), + 'title' => get_string('addcolumn', 'core_reportbuilder', $entitytitle), + 'action' => 'report-add-column' + ]; + } + + return array_values($available); + } + + /** + * Helper method for re-ordering given persistents (columns, filters, etc) + * + * @param persistent $persistent The persistent we are moving + * @param persistent[] $persistents The rest of the persistents + * @param int $position + * @param string $field The field we need to update + * @return bool + */ + private static function reorder_persistents_by_field(persistent $persistent, array $persistents, int $position, + string $field): bool { + + // Splice into new position. + array_splice($persistents, $position - 1, 0, [$persistent]); + + $fieldorder = 1; + foreach ($persistents as $persistent) { + $persistent->set($field, $fieldorder++) + ->update(); + } + + return true; + } +} diff --git a/reportbuilder/classes/local/models/column.php b/reportbuilder/classes/local/models/column.php new file mode 100644 index 00000000000..a4a4bf29825 --- /dev/null +++ b/reportbuilder/classes/local/models/column.php @@ -0,0 +1,122 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\models; + +use lang_string; +use core\persistent; + +/** + * Persistent class to represent a report column + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class column extends persistent { + + /** @var string The table name. */ + public const TABLE = 'reportbuilder_column'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties(): array { + return [ + 'reportid' => [ + 'type' => PARAM_INT, + ], + 'uniqueidentifier' => [ + 'type' => PARAM_RAW, + ], + 'aggregation' => [ + 'type' => PARAM_ALPHANUMEXT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'heading' => [ + 'type' => PARAM_TEXT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'columnorder' => [ + 'type' => PARAM_INT, + ], + 'sortenabled' => [ + 'type' => PARAM_BOOL, + 'default' => false, + ], + 'sortdirection' => [ + 'type' => PARAM_INT, + 'choices' => [SORT_ASC, SORT_DESC], + 'default' => SORT_ASC, + ], + 'sortorder' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'usercreated' => [ + 'type' => PARAM_INT, + 'default' => static function(): int { + global $USER; + + return (int) $USER->id; + }, + ], + ]; + } + + /** + * Validate reportid property + * + * @param int $reportid + * @return bool|lang_string + */ + protected function validate_reportid(int $reportid) { + if (!report::record_exists($reportid)) { + return new lang_string('invaliddata', 'error'); + } + + return true; + } + + /** + * Return the report this column belongs to + * + * @return report + */ + public function get_report(): report { + return new report($this->get('reportid')); + } + + /** + * Helper method to return the current maximum column order value for a report + * + * @param int $reportid + * @param string $columnname + * @return int + */ + public static function get_max_columnorder(int $reportid, string $columnname): int { + global $DB; + + return (int) $DB->get_field(static::TABLE, "MAX({$columnname})", ['reportid' => $reportid], MUST_EXIST); + } +} diff --git a/reportbuilder/classes/local/models/filter.php b/reportbuilder/classes/local/models/filter.php new file mode 100644 index 00000000000..82d47230239 --- /dev/null +++ b/reportbuilder/classes/local/models/filter.php @@ -0,0 +1,155 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\models; + +use lang_string; +use core\persistent; + +/** + * Persistent class to represent a report filter/condition + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class filter extends persistent { + + /** @var string The table name. */ + public const TABLE = 'reportbuilder_filter'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties(): array { + return [ + 'reportid' => [ + 'type' => PARAM_INT, + ], + 'uniqueidentifier' => [ + 'type' => PARAM_RAW, + ], + 'heading' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_ALLOWED, + 'default' => null, + ], + 'iscondition' => [ + 'type' => PARAM_BOOL, + 'default' => false, + ], + 'filterorder' => [ + 'type' => PARAM_INT, + ], + 'usercreated' => [ + 'type' => PARAM_INT, + 'default' => static function(): int { + global $USER; + + return (int) $USER->id; + }, + ], + ]; + } + + /** + * Validate reportid property + * + * @param int $reportid + * @return bool|lang_string + */ + protected function validate_reportid(int $reportid) { + if (!report::record_exists($reportid)) { + return new lang_string('invaliddata', 'error'); + } + + return true; + } + + /** + * Return the report this filter belongs to + * + * @return report + */ + public function get_report(): report { + return new report($this->get('reportid')); + } + + /** + * Return filter record + * + * @param int $reportid + * @param int $filterid + * @return false|static + */ + public static function get_filter_record(int $reportid, int $filterid) { + return self::get_record(['id' => $filterid, 'reportid' => $reportid, 'iscondition' => 0]); + } + + /** + * Return filter records for report + * + * @param int $reportid + * @param string $sort + * @param string $order + * @return static[] + */ + public static function get_filter_records(int $reportid, string $sort = '', string $order = 'ASC'): array { + return self::get_records(['reportid' => $reportid, 'iscondition' => 0], $sort, $order); + } + + /** + * Return condition record + * + * @param int $reportid + * @param int $conditionid + * @return false|static + */ + public static function get_condition_record(int $reportid, int $conditionid) { + return self::get_record(['id' => $conditionid, 'reportid' => $reportid, 'iscondition' => 1]); + } + + /** + * Return condition records for report + * + * @param int $reportid + * @param string $sort + * @param string $order + * @return static[] + */ + public static function get_condition_records(int $reportid, string $sort = '', string $order = 'ASC'): array { + return self::get_records(['reportid' => $reportid, 'iscondition' => 1], $sort, $order); + } + + /** + * Helper method to return the current maximum filter order value for a report + * + * @param int $reportid + * @param bool $iscondition + * @return int + */ + public static function get_max_filterorder(int $reportid, bool $iscondition = false): int { + global $DB; + + $params = ['reportid' => $reportid, 'iscondition' => (int) $iscondition]; + + return (int) $DB->get_field(static::TABLE, "MAX(filterorder)", $params, MUST_EXIST); + } +} diff --git a/reportbuilder/classes/local/models/report.php b/reportbuilder/classes/local/models/report.php index c0c03d31d06..1cc209ffb45 100644 --- a/reportbuilder/classes/local/models/report.php +++ b/reportbuilder/classes/local/models/report.php @@ -21,6 +21,9 @@ namespace core_reportbuilder\local\models; use context; use context_system; use core\persistent; +use core_reportbuilder\event\report_created; +use core_reportbuilder\event\report_deleted; +use core_reportbuilder\event\report_updated; use core_reportbuilder\local\report\base; /** @@ -42,6 +45,11 @@ class report extends persistent { */ protected static function define_properties(): array { return [ + 'name' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_ALLOWED, + 'default' => null, + ], 'source' => [ 'type' => PARAM_RAW, ], @@ -52,6 +60,11 @@ class report extends persistent { base::TYPE_SYSTEM_REPORT, ], ], + 'conditiondata' => [ + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + 'default' => null, + ], 'contextid' => [ 'type' => PARAM_INT, 'default' => static function(): int { @@ -81,6 +94,56 @@ class report extends persistent { ]; } + /** + * Trigger report created event when persistent is created + */ + protected function after_create(): void { + if ($this->get('type') === base::TYPE_CUSTOM_REPORT) { + report_created::create_from_object($this)->trigger(); + } + } + + /** + * Cascade report deletion, first deleting any linked persistents + */ + protected function before_delete(): void { + $reportparams = ['reportid' => $this->get('id')]; + + // Columns. + foreach (column::get_records($reportparams) as $column) { + $column->delete(); + } + + // Filters. + foreach (filter::get_records($reportparams) as $filter) { + $filter->delete(); + } + } + + /** + * Throw report deleted event when persistent is deleted + * + * @param bool $result + */ + protected function after_delete($result): void { + if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) { + return; + } + report_deleted::create_from_object($this)->trigger(); + } + + /** + * Throw report updated event when persistent is updated + * + * @param bool $result + */ + protected function after_update($result): void { + if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) { + return; + } + report_updated::create_from_object($this)->trigger(); + } + /** * Return report context, used by exporters * @@ -89,4 +152,13 @@ class report extends persistent { public function get_context(): context { return context::instance_by_id($this->raw_get('contextid')); } + + /** + * Return formatted report name + * + * @return string + */ + public function get_formatted_name(): string { + return format_string($this->raw_get('name'), true, ['context' => $this->get_context(), 'escape' => true]); + } } diff --git a/reportbuilder/classes/local/report/base.php b/reportbuilder/classes/local/report/base.php index 4ab8a5b3982..1bc150219c1 100644 --- a/reportbuilder/classes/local/report/base.php +++ b/reportbuilder/classes/local/report/base.php @@ -72,6 +72,9 @@ abstract class base { /** @var column[] $columns */ private $columns = []; + /** @var filter[] $conditions */ + private $conditions = []; + /** @var filter[] $filters */ private $filters = []; @@ -119,13 +122,6 @@ abstract class base { */ abstract protected function initialise(): void; - /** - * Output the report - * - * @return string - */ - abstract public function output(): string; - /** * Get the report availability. Sub-classes should override this method to declare themselves unavailable, for example if * they require classes that aren't present due to missing plugin @@ -258,6 +254,21 @@ abstract class base { $this->entities[$entityname] = $entity->initialise(); } + /** + * Returns the entity added to the report from the given entity name + * + * @param string $name + * @return entity_base + * @throws coding_exception + */ + final protected function get_entity(string $name): entity_base { + if (!array_key_exists($name, $this->entities)) { + throw new coding_exception('Invalid entity name', $name); + } + + return $this->entities[$name]; + } + /** * Define a new entity for the report * @@ -273,6 +284,21 @@ abstract class base { $this->entitytitles[$name] = $title; } + /** + * Returns title of given report entity + * + * @param string $name + * @return lang_string + * @throws coding_exception + */ + final public function get_entity_title(string $name): lang_string { + if (!array_key_exists($name, $this->entitytitles)) { + throw new coding_exception('Invalid entity name', $name); + } + + return $this->entitytitles[$name]; + } + /** * Adds a column to the report * @@ -300,23 +326,30 @@ abstract class base { return $column; } + /** + * Add given column to the report from an entity + * + * The entity must have already been added to the report before calling this method + * + * @param string $uniqueidentifier + * @return column + */ + final protected function add_column_from_entity(string $uniqueidentifier): column { + [$entityname, $columnname] = explode(':', $uniqueidentifier, 2); + + return $this->add_column($this->get_entity($entityname)->get_column($columnname)); + } + /** * Add given columns to the report from one or more entities * * 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)); + $this->add_column_from_entity($column); } } @@ -341,6 +374,128 @@ abstract class base { }); } + /** + * Adds a condition to the report + * + * @param filter $condition + * @return filter + * @throws coding_exception + */ + final protected function add_condition(filter $condition): filter { + if (!array_key_exists($condition->get_entity_name(), $this->entitytitles)) { + throw new coding_exception('Invalid entity name', $condition->get_entity_name()); + } + + $name = $condition->get_name(); + if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) { + throw new coding_exception('Condition name must be comprised of alphanumeric character, underscore or dash'); + } + + $uniqueidentifier = $condition->get_unique_identifier(); + if (array_key_exists($uniqueidentifier, $this->conditions)) { + throw new coding_exception('Duplicate condition identifier', $uniqueidentifier); + } + + $this->conditions[$uniqueidentifier] = $condition; + + return $condition; + } + + /** + * Add given condition to the report from an entity + * + * The entity must have already been added to the report before calling this method + * + * @param string $uniqueidentifier + * @return filter + */ + final protected function add_condition_from_entity(string $uniqueidentifier): filter { + [$entityname, $conditionname] = explode(':', $uniqueidentifier, 2); + + return $this->add_condition($this->get_entity($entityname)->get_condition($conditionname)); + } + + /** + * Add given conditions to the report from one or more entities + * + * Each entity must have already been added to the report before calling this method + * + * @param string[] $conditions Unique identifier of each entity condition + */ + final protected function add_conditions_from_entities(array $conditions): void { + foreach ($conditions as $condition) { + $this->add_condition_from_entity($condition); + } + } + + /** + * Return report condition by unique identifier + * + * @param string $uniqueidentifier + * @return filter|null + */ + final public function get_condition(string $uniqueidentifier): ?filter { + return $this->conditions[$uniqueidentifier] ?? null; + } + + /** + * Return all available report conditions + * + * @return filter[] + */ + final public function get_conditions(): array { + return array_filter($this->conditions, static function(filter $condition): bool { + return $condition->get_is_available(); + }); + } + + /** + * Return all active report conditions (by default, all available conditions) + * + * @return filter[] + */ + public function get_active_conditions(): array { + return $this->get_conditions(); + } + + /** + * Return all active report condition instances + * + * @return filter_base[] + */ + final public function get_condition_instances(): array { + return array_map(static function(filter $condition): filter_base { + /** @var filter_base $conditionclass */ + $conditionclass = $condition->get_filter_class(); + + return $conditionclass::create($condition); + }, $this->get_active_conditions()); + } + + /** + * Set the condition values of the report + * + * @param array $values + * @return bool + */ + final public function set_condition_values(array $values): bool { + $this->report->set('conditiondata', json_encode($values)) + ->save(); + + return true; + } + + /** + * Get the condition values of the report + * + * @return array + */ + final public function get_condition_values(): array { + $conditions = (string) $this->report->get('conditiondata'); + + return (array) json_decode($conditions); + } + /** * Adds a filter to the report * @@ -368,23 +523,30 @@ abstract class base { return $filter; } + /** + * Add given filter to the report from an entity + * + * The entity must have already been added to the report before calling this method + * + * @param string $uniqueidentifier + * @return filter + */ + final protected function add_filter_from_entity(string $uniqueidentifier): filter { + [$entityname, $filtername] = explode(':', $uniqueidentifier, 2); + + return $this->add_filter($this->get_entity($entityname)->get_filter($filtername)); + } + /** * 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)); + $this->add_filter_from_entity($filter); } } @@ -410,7 +572,16 @@ abstract class base { } /** - * Return all report filter instances + * Return all active report filters (by default, all available filters) + * + * @return filter[] + */ + public function get_active_filters(): array { + return $this->get_filters(); + } + + /** + * Return all active report filter instances * * @return filter_base[] */ @@ -420,7 +591,7 @@ abstract class base { $filterclass = $filter->get_filter_class(); return $filterclass::create($filter); - }, $this->get_filters()); + }, $this->get_active_filters()); } /** diff --git a/reportbuilder/classes/local/report/column.php b/reportbuilder/classes/local/report/column.php index af1d5fb477e..0a43f2a308f 100644 --- a/reportbuilder/classes/local/report/column.php +++ b/reportbuilder/classes/local/report/column.php @@ -20,7 +20,10 @@ namespace core_reportbuilder\local\report; use coding_exception; use lang_string; +use core_reportbuilder\local\helpers\aggregation; use core_reportbuilder\local\helpers\database; +use core_reportbuilder\local\aggregation\base; +use core_reportbuilder\local\models\column as column_model; /** * Class to represent a report column @@ -62,7 +65,7 @@ final class column { private $entityname; /** @var int $type Column data type (one of the TYPE_* class constants) */ - private $type = null; + private $type = self::TYPE_TEXT; /** @var string[] $joins List of SQL joins for this column */ private $joins = []; @@ -76,6 +79,9 @@ final class column { /** @var array[] $callbacks Array of [callable, additionalarguments] */ private $callbacks = []; + /** @var base|null $aggregation Aggregation type to apply to column */ + private $aggregation = null; + /** @var bool $issortable Used to indicate if a column is sortable */ private $issortable = false; @@ -85,6 +91,9 @@ final class column { /** @var bool $available Used to know if column is available to the current user or not */ protected $available = true; + /** @var column_model $persistent */ + protected $persistent; + /** * Column constructor * @@ -171,7 +180,7 @@ final class column { * Set the column index within the current report * * @param int $index - * @return column + * @return self */ public function set_index(int $index): self { $this->index = $index; @@ -179,10 +188,13 @@ final class column { } /** - * Set the column type + * Set the column type, if not called then the type will be assumed to be {@see TYPE_TEXT} + * + * The type of a column is used to cast the first column field passed to any callbacks {@see add_callback} as well as the + * aggregation options available for the column * * @param int $type - * @return column + * @return self * @throws coding_exception */ public function set_type(int $type): self { @@ -203,11 +215,11 @@ final class column { } /** - * Return column type + * Return column type, that being one of the TYPE_* class constants * - * @return int|null + * @return int */ - public function get_type(): ?int { + public function get_type(): int { return $this->type; } @@ -355,13 +367,38 @@ final class 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()); + $fieldsalias = $this->get_fields_sql_alias(); + + // We aggregate the first field only. + if (!empty($this->aggregation)) { + $field = reset($fieldsalias); + $fields = [$this->aggregation::get_field_sql($field['sql'], $this->get_type()) . " AS {$field['alias']}"]; + } else { + $fields = array_map(static function(array $field): string { + return "{$field['sql']} AS {$field['alias']}"; + }, $fieldsalias); + } return array_values($fields); } + /** + * Return suitable SQL fragment for grouping by the column fields (during aggregation) + * + * @return array + */ + public function get_groupby_sql(): array { + global $DB; + + $fieldsalias = $this->get_fields_sql_alias(); + + // Note that we can reference field aliases in GROUP BY only in MySQL/Postgres. + $usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']); + $columnname = $usealias ? 'alias' : 'sql'; + + return array_column($fieldsalias, $columnname); + } + /** * Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report * @@ -398,6 +435,8 @@ final class column { * 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): * + * The type of the $value parameter passed to the callback is determined by calling {@see set_type} + * * function($value, stdClass $row[, $additionalarguments]): string * * @param callable $callable function that takes arguments ($value, \stdClass $row, $additionalarguments) @@ -421,6 +460,34 @@ final class column { return $this->add_callback($callable, $additionalarguments); } + /** + * Set column aggregation type + * + * @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc + * @return self + * @throws coding_exception For invalid aggregation type, or one that is incompatible with column type + */ + public function set_aggregation(?string $aggregation): self { + if (!empty($aggregation)) { + $aggregation = aggregation::get_full_classpath($aggregation); + if (!aggregation::valid($aggregation) || !$aggregation::compatible($this->get_type())) { + throw new coding_exception('Invalid column aggregation', $aggregation); + } + } + + $this->aggregation = $aggregation; + return $this; + } + + /** + * Get column aggregation type + * + * @return base|null + */ + public function get_aggregation(): ?string { + return $this->aggregation; + } + /** * Sets the column as sortable * @@ -493,9 +560,13 @@ final class column { $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]); + // If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks. + if (!empty($this->aggregation)) { + $value = $this->aggregation::format_value($value, $values, $this->callbacks); + } else { + foreach ($this->callbacks as $callback) { + $value = ($callback[0])($value, (object) $values, $callback[1]); + } } return $value; @@ -541,4 +612,24 @@ final class column { $this->available = $available; return $this; } + + /** + * Set column persistent + * + * @param column_model $persistent + * @return self + */ + public function set_persistent(column_model $persistent): self { + $this->persistent = $persistent; + return $this; + } + + /** + * Return column persistent + * + * @return mixed + */ + public function get_persistent(): column_model { + return $this->persistent; + } } diff --git a/reportbuilder/classes/local/report/filter.php b/reportbuilder/classes/local/report/filter.php index aa7757b96af..b6d884354b3 100644 --- a/reportbuilder/classes/local/report/filter.php +++ b/reportbuilder/classes/local/report/filter.php @@ -21,6 +21,7 @@ namespace core_reportbuilder\local\report; use lang_string; use moodle_exception; use core_reportbuilder\local\filters\base; +use core_reportbuilder\local\models\filter as filter_model; /** * Class to represent a report filter @@ -61,6 +62,9 @@ final class filter { /** @var array $limitoperators */ protected $limitoperators = []; + /** @var filter_model $persistent */ + protected $persistent; + /** * Filter constructor * @@ -307,4 +311,24 @@ final class filter { return array_intersect_key($operators, array_flip($this->limitoperators)); } + + /** + * Set filter persistent + * + * @param filter_model $persistent + * @return self + */ + public function set_persistent(filter_model $persistent): self { + $this->persistent = $persistent; + return $this; + } + + /** + * Return filter persistent + * + * @return filter_model|null + */ + public function get_persistent(): ?filter_model { + return $this->persistent ?? null; + } } diff --git a/reportbuilder/classes/local/systemreports/reports_list.php b/reportbuilder/classes/local/systemreports/reports_list.php new file mode 100644 index 00000000000..998421e4caa --- /dev/null +++ b/reportbuilder/classes/local/systemreports/reports_list.php @@ -0,0 +1,300 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\systemreports; + +use html_writer; +use lang_string; +use moodle_url; +use pix_icon; +use stdClass; +use core_reportbuilder\datasource; +use core_reportbuilder\manager; +use core_reportbuilder\system_report; +use core_reportbuilder\local\entities\user; +use core_reportbuilder\local\filters\date; +use core_reportbuilder\local\filters\text; +use core_reportbuilder\local\filters\select; +use core_reportbuilder\local\helpers\format; +use core_reportbuilder\local\report\action; +use core_reportbuilder\local\report\column; +use core_reportbuilder\local\report\filter; +use core_reportbuilder\output\report_name_editable; +use core_reportbuilder\local\models\report; +use core_reportbuilder\permission; + +/** + * Reports list + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reports_list extends system_report { + + /** + * The name of our internal report entity + * + * @return string + */ + private function get_report_entity_name(): string { + return 'report'; + } + + /** + * Initialise the report + */ + protected function initialise(): void { + $this->set_main_table('reportbuilder_report', 'rb'); + + $this->add_base_condition_simple('rb.type', self::TYPE_CUSTOM_REPORT); + $this->add_base_fields('rb.id, rb.name, rb.source, rb.type, rb.usercreated'); // Necessary for actions/row class. + + // Join user entity for "User modified" column. + $entityuser = new user(); + $entityuseralias = $entityuser->get_table_alias('user'); + + $this->add_entity($entityuser + ->add_join("JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = rb.usermodified") + ); + + // Define our internal entity for report elements. + $this->annotate_entity($this->get_report_entity_name(), + new lang_string('customreports', 'core_reportbuilder')); + + $this->add_columns(); + $this->add_filters(); + $this->add_actions(); + + $this->set_downloadable(false); + } + + /** + * Ensure we can view the report + * + * @return bool + */ + protected function can_view(): bool { + return permission::can_view_reports_list(); + } + + /** + * Dim the table row for invalid datasource + * + * @param stdClass $row + * @return string + */ + public function get_row_class(stdClass $row): string { + return $this->report_source_valid($row->source) ? '' : 'dimmed_text'; + } + + /** + * Add columns to report + */ + protected function add_columns(): void { + $tablealias = $this->get_main_table_alias(); + + // Report name column. + $this->add_column((new column( + 'name', + new lang_string('name'), + $this->get_report_entity_name() + )) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.name, {$tablealias}.id") + ->set_is_sortable(true) + ->add_callback(function(string $value, stdClass $row) { + global $PAGE; + + $reportid = (int) $row->id; + + $editable = new report_name_editable($reportid); + return $editable->render($PAGE->get_renderer('core')); + }) + ); + + // Report source column. + $this->add_column((new column( + 'source', + new lang_string('reportsource', 'core_reportbuilder'), + $this->get_report_entity_name() + )) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.source") + ->set_is_sortable(true) + ->add_callback(function(string $value, stdClass $row) { + if (!$this->report_source_valid($value)) { + // Add danger badge if report source is not valid (either it's missing, or has errors). + return html_writer::span(get_string('errorsourceinvalid', 'core_reportbuilder'), 'badge badge-danger'); + } + + return call_user_func([$value, 'get_name']); + }) + ); + + // Time created column. + $this->add_column((new column( + 'timecreated', + new lang_string('timecreated', 'core_reportbuilder'), + $this->get_report_entity_name() + )) + ->set_type(column::TYPE_TIMESTAMP) + ->add_fields("{$tablealias}.timecreated") + ->set_is_sortable(true) + ->add_callback([format::class, 'userdate']) + ); + + // Time modified column. + $this->add_column((new column( + 'timemodified', + new lang_string('timemodified', 'core_reportbuilder'), + $this->get_report_entity_name() + )) + ->set_type(column::TYPE_TIMESTAMP) + ->add_fields("{$tablealias}.timemodified") + ->set_is_sortable(true) + ->add_callback([format::class, 'userdate']) + ); + + // The user who modified the report. + $this->add_column_from_entity('user:fullname'); + + // Initial sorting. + $this->set_initial_sort_column('report:timecreated', SORT_DESC); + } + + /** + * Add filters to report + */ + protected function add_filters(): void { + $tablealias = $this->get_main_table_alias(); + + // Name filter. + $this->add_filter((new filter( + text::class, + 'name', + new lang_string('name'), + $this->get_report_entity_name(), + "{$tablealias}.name" + ))); + + // Source filter. + $this->add_filter((new filter( + select::class, + 'source', + new lang_string('reportsource', 'core_reportbuilder'), + $this->get_report_entity_name(), + "{$tablealias}.source" + )) + ->set_options_callback(static function(): array { + return manager::get_report_datasources(); + }) + ); + + // Time created filter. + $this->add_filter((new filter( + date::class, + 'timecreated', + new lang_string('timecreated', 'core_reportbuilder'), + $this->get_report_entity_name(), + "{$tablealias}.timecreated" + )) + ->set_limited_operators([ + date::DATE_ANY, + date::DATE_RANGE, + ]) + ); + } + + /** + * Add actions to report + */ + protected function add_actions(): void { + // Edit content action. + $this->add_action((new action( + new moodle_url('/reportbuilder/edit.php', ['id' => ':id']), + new pix_icon('t/right', get_string('editreportcontent', 'core_reportbuilder')) + )) + ->add_callback(function(stdClass $row): bool { + return $this->report_source_valid($row->source) && permission::can_edit_report($this->get_report_from_row($row)); + }) + ); + + // Edit details action. + $this->add_action((new action( + new moodle_url('#'), + new pix_icon('t/edit', get_string('editreportdetails', 'core_reportbuilder')), + ['data-action' => 'report-edit', 'data-report-id' => ':id'] + )) + ->add_callback(function(stdClass $row): bool { + return $this->report_source_valid($row->source) && permission::can_edit_report($this->get_report_from_row($row)); + }) + ); + + // Preview action. + $this->add_action((new action( + new moodle_url('/reportbuilder/view.php', ['id' => ':id']), + new pix_icon('i/search', get_string('view', 'moodle')), + [] + )) + ->add_callback(function(stdClass $row): bool { + // We check this only to give the action to editors, because normal users can just click on the report name. + return $this->report_source_valid($row->source) && permission::can_edit_report($this->get_report_from_row($row)); + }) + ); + + // Delete action. + $this->add_action((new action( + new moodle_url('#'), + new pix_icon('t/delete', get_string('deletereport', 'core_reportbuilder')), + ['data-action' => 'report-delete', 'data-report-id' => ':id', 'data-report-name' => ':name'] + )) + ->add_callback(function(stdClass $row): bool { + // We don't check whether report is valid to ensure editor can always delete them. + return permission::can_edit_report($this->get_report_from_row($row)); + }) + ); + } + + /** + * Helper to determine whether given report source is valid (it both exists, and is available) + * + * @param string $source + * @return bool + */ + private function report_source_valid(string $source): bool { + return manager::report_source_exists($source, datasource::class) && manager::report_source_available($source); + } + + /** + * Helper to return the report persistent from the row object. + * + * Note that this persistent, for performance reasons, is not complete and only contains id/type/usercreated fields, which + * are needed for the permission methods. + * + * @param stdClass $row + * @return report + */ + private function get_report_from_row(stdClass $row): report { + return new report(0, (object)[ + 'id' => $row->id, + 'type' => $row->type, + 'usercreated' => $row->usercreated, + ]); + } +} diff --git a/reportbuilder/classes/manager.php b/reportbuilder/classes/manager.php index 550e7f3153a..015cc3f948f 100644 --- a/reportbuilder/classes/manager.php +++ b/reportbuilder/classes/manager.php @@ -18,6 +18,8 @@ declare(strict_types=1); namespace core_reportbuilder; +use core_component; +use core_plugin_manager; use stdClass; use core_reportbuilder\local\models\report; use core_reportbuilder\local\report\base; @@ -53,6 +55,19 @@ class manager { return new $source($report, $parameters); } + /** + * Return an instance of a report class from the given report ID + * + * @param int $reportid + * @param array $parameters + * @return base + */ + public static function get_report_from_id(int $reportid, array $parameters = []): base { + $report = new report($reportid); + + return self::get_report_from_persistent($report, $parameters); + } + /** * Verify that report source exists and extends appropriate base classes * @@ -84,4 +99,30 @@ class manager { public static function create_report_persistent(stdClass $reportdata): report { return (new report(0, $reportdata))->create(); } + + /** + * Return an array of all valid report sources across the site + * + * @return array[][] Indexed by [component => [class => name]] + */ + public static function get_report_datasources(): array { + $sources = array(); + + $datasources = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\datasource'); + foreach ($datasources as $class => $path) { + if (self::report_source_exists($class, datasource::class) && self::report_source_available($class)) { + [$component] = explode('\\', $class); + + if ($plugininfo = core_plugin_manager::instance()->get_plugin_info($component)) { + $componentname = $plugininfo->displayname; + } else { + $componentname = get_string('site'); + } + + $sources[$componentname][$class] = call_user_func([$class, 'get_name']); + } + } + + return $sources; + } } diff --git a/reportbuilder/classes/output/column_aggregation_editable.php b/reportbuilder/classes/output/column_aggregation_editable.php new file mode 100644 index 00000000000..c2bf76eb99d --- /dev/null +++ b/reportbuilder/classes/output/column_aggregation_editable.php @@ -0,0 +1,85 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\output; + +use core_external; +use core\output\inplace_editable; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\aggregation; +use core_reportbuilder\local\models\column; + +/** + * Column aggregation editable component + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class column_aggregation_editable extends inplace_editable { + + /** + * Class constructor + * + * @param int $columnid + * @param column|null $column + */ + public function __construct(int $columnid, ?column $column = null) { + if ($column === null) { + $column = new column($columnid); + } + + $report = $column->get_report(); + $editable = permission::can_edit_report($report); + + $columninstance = manager::get_report_from_persistent($report) + ->get_column($column->get('uniqueidentifier')); + + $currentvalue = (string) $column->get('aggregation'); + + parent::__construct('core_reportbuilder', 'columnaggregation', $column->get('id'), $editable, null, $currentvalue, + get_string('aggregatecolumn', 'core_reportbuilder', $columninstance->get_title())); + + $options = aggregation::get_column_aggregations($columninstance->get_type()); + $this->set_type_select(['' => get_string('aggregationnone', 'core_reportbuilder')] + $options); + } + + /** + * Update column persistent and return self, called from inplace_editable callback + * + * @param int $columnid + * @param string $value + * @return self + */ + public static function update(int $columnid, string $value): self { + $column = new column($columnid); + + $report = $column->get_report(); + + core_external::validate_context($report->get_context()); + permission::require_can_edit_report($report); + + $value = clean_param($value, PARAM_TEXT); + $column + ->set('aggregation', $value) + ->update(); + + return new self(0, $column); + } +} diff --git a/reportbuilder/classes/output/column_heading_editable.php b/reportbuilder/classes/output/column_heading_editable.php new file mode 100644 index 00000000000..1b6a6cf97a5 --- /dev/null +++ b/reportbuilder/classes/output/column_heading_editable.php @@ -0,0 +1,81 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\output; + +use core_external; +use core\output\inplace_editable; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\models\column; + +/** + * Column heading editable component + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class column_heading_editable extends inplace_editable { + + /** + * Class constructor + * + * @param int $columnid + * @param column|null $column + */ + public function __construct(int $columnid, ?column $column = null) { + if ($column === null) { + $column = new column($columnid); + } + + $report = $column->get_report(); + $editable = permission::can_edit_report($report); + + $columninstance = manager::get_report_from_persistent($report) + ->get_column($column->get('uniqueidentifier')); + + $displayvalue = $column->get('heading') ?: $columninstance->get_title(); + + parent::__construct('core_reportbuilder', 'columnheading', $column->get('id'), $editable, $displayvalue, $displayvalue, + get_string('renamecolumn', 'core_reportbuilder', $columninstance->get_title())); + } + + /** + * Update column persistent and return self, called from inplace_editable callback + * + * @param int $columnid + * @param string $value + * @return self + */ + public static function update(int $columnid, string $value): self { + $column = new column($columnid); + + $report = $column->get_report(); + + core_external::validate_context($report->get_context()); + permission::require_can_edit_report($report); + + $value = clean_param($value, PARAM_TEXT); + $column + ->set('heading', $value) + ->update(); + + return new self(0, $column); + } +} diff --git a/reportbuilder/classes/output/custom_report.php b/reportbuilder/classes/output/custom_report.php new file mode 100644 index 00000000000..3c3a25ebac1 --- /dev/null +++ b/reportbuilder/classes/output/custom_report.php @@ -0,0 +1,77 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\output; + +use core_reportbuilder\external\custom_report_exporter; +use core_reportbuilder\local\models\report; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * Custom report output class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report implements renderable, templatable { + + /** @var report $reportpersistent */ + protected $persistent; + + /** @var bool $editmode */ + protected $editmode; + + /** @var bool $showeditbutton */ + protected $showeditbutton; + + /** @var string $download */ + protected $download; + + /** + * Class constructor + * + * @param report $reportpersistent + * @param bool $editmode + * @param bool $showeditbutton + * @param string $download + */ + public function __construct(report $reportpersistent, bool $editmode = true, bool $showeditbutton = true, + string $download = '') { + + $this->persistent = $reportpersistent; + $this->editmode = $editmode; + $this->showeditbutton = $showeditbutton; + $this->download = $download; + } + + /** + * Export report data suitable for a template + * + * @param renderer_base $output + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + $exporter = new custom_report_exporter($this->persistent, [], $this->editmode, $this->showeditbutton, $this->download); + + return $exporter->export($output); + } +} diff --git a/reportbuilder/classes/output/filter_heading_editable.php b/reportbuilder/classes/output/filter_heading_editable.php new file mode 100644 index 00000000000..2353a43ddfc --- /dev/null +++ b/reportbuilder/classes/output/filter_heading_editable.php @@ -0,0 +1,81 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\output; + +use core_external; +use core_reportbuilder\local\models\filter; +use core\output\inplace_editable; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\models\report; + +/** + * Filter heading editable component + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class filter_heading_editable extends inplace_editable { + + /** + * Class constructor + * + * @param int $filterid + * @param filter|null $filter + */ + public function __construct(int $filterid, ?filter $filter = null) { + if ($filter === null) { + $filter = new filter($filterid); + } + + $report = $filter->get_report(); + $editable = permission::can_edit_report($report); + + $filterinstance = manager::get_report_from_persistent($report) + ->get_filter($filter->get('uniqueidentifier')); + + $displayvalue = $filter->get('heading') ?: $filterinstance->get_header(); + + parent::__construct('core_reportbuilder', 'filterheading', $filter->get('id'), $editable, $displayvalue, + $displayvalue, get_string('renamefilter', 'core_reportbuilder', $filterinstance->get_header())); + } + + /** + * Update filter persistent and return self, called from inplace_editable callback + * + * @param int $filterid + * @param string $value + * @return self + */ + public static function update(int $filterid, string $value): self { + $filter = new filter($filterid); + + $report = $filter->get_report(); + core_external::validate_context($report->get_context()); + permission::require_can_edit_report($report); + + $value = clean_param($value, PARAM_TEXT); + $filter + ->set('heading', $value) + ->update(); + + return new self(0, $filter); + } +} diff --git a/reportbuilder/classes/output/renderer.php b/reportbuilder/classes/output/renderer.php index b9cdb45873f..bf954921e0f 100644 --- a/reportbuilder/classes/output/renderer.php +++ b/reportbuilder/classes/output/renderer.php @@ -18,7 +18,10 @@ declare(strict_types=1); namespace core_reportbuilder\output; +use html_writer; use plugin_renderer_base; +use core_reportbuilder\table\custom_report_table; +use core_reportbuilder\table\custom_report_table_view; use core_reportbuilder\table\system_report_table; /** @@ -56,4 +59,58 @@ class renderer extends plugin_renderer_base { return $output; } + + /** + * Render a custom report + * + * @param custom_report $report + * @return string + */ + protected function render_custom_report(custom_report $report): string { + $context = $report->export_for_template($this); + + return $this->render_from_template('core_reportbuilder/custom_report', $context); + } + + /** + * Render a custom report table + * + * @param custom_report_table $table + * @return string + */ + protected function render_custom_report_table(custom_report_table $table): string { + ob_start(); + $table->out($table->get_default_per_page(), false); + $output = ob_get_contents(); + ob_end_clean(); + + return $output; + } + + /** + * Render a custom report table (view only mode) + * + * @param custom_report_table_view $table + * @return string + */ + protected function render_custom_report_table_view(custom_report_table_view $table): string { + ob_start(); + $table->out($table->get_default_per_page(), false); + $output = ob_get_contents(); + ob_end_clean(); + + return $output; + } + + /** + * Renders the New report button + * + * @return string + */ + public function render_new_report_button(): string { + return html_writer::tag('button', get_string('newreport', 'core_reportbuilder'), [ + 'class' => 'btn btn-primary my-auto', + 'data-action' => 'report-create', + ]); + } } diff --git a/reportbuilder/classes/output/report_name_editable.php b/reportbuilder/classes/output/report_name_editable.php new file mode 100644 index 00000000000..93f6889c839 --- /dev/null +++ b/reportbuilder/classes/output/report_name_editable.php @@ -0,0 +1,82 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\output; + +use core_external; +use html_writer; +use moodle_url; +use core\output\inplace_editable; +use core_reportbuilder\permission; +use core_reportbuilder\local\models\report; + +/** + * Report name editable component + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report_name_editable extends inplace_editable { + + /** + * Class constructor + * + * @param int $reportid + * @param report|null $report + */ + public function __construct(int $reportid, ?report $report = null) { + if ($report === null) { + $report = new report($reportid); + } + + $editable = permission::can_edit_report($report); + + $url = $editable + ? new moodle_url('/reportbuilder/edit.php', ['id' => $report->get('id')]) + : new moodle_url('/reportbuilder/view.php', ['id' => $report->get('id')]); + + $displayvalue = html_writer::link($url, $report->get_formatted_name()); + + parent::__construct('core_reportbuilder', 'reportname', $report->get('id'), $editable, $displayvalue, $report->get('name'), + get_string('editreportname', 'core_reportbuilder')); + } + + /** + * Update report persistent and return self, called from inplace_editable callback + * + * @param int $reportid + * @param string $value + * @return self + */ + public static function update(int $reportid, string $value): self { + $report = new report($reportid); + + core_external::validate_context($report->get_context()); + permission::require_can_edit_report($report); + + $value = clean_param($value, PARAM_TEXT); + if ($value !== '') { + $report + ->set('name', $value) + ->update(); + } + + return new self(0, $report); + } +} diff --git a/reportbuilder/classes/permission.php b/reportbuilder/classes/permission.php new file mode 100644 index 00000000000..ed733d7eeac --- /dev/null +++ b/reportbuilder/classes/permission.php @@ -0,0 +1,143 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder; + +use context_system; +use core_reportbuilder\local\models\report; +use core_reportbuilder\local\report\base; + +/** + * Report permission class + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class permission { + + /** + * Require given user can view reports list + * + * @param int|null $userid User ID to check, or the current user if omitted + * @throws report_access_exception + */ + public static function require_can_view_reports_list(?int $userid = null): void { + if (!static::can_view_reports_list($userid)) { + throw new report_access_exception(); + } + } + + /** + * Whether given user can view reports list + * + * @param int|null $userid User ID to check, or the current user if omitted + * @return bool + */ + public static function can_view_reports_list(?int $userid = null): bool { + return has_capability('moodle/reportbuilder:view', context_system::instance(), $userid); + } + + /** + * Require given user can view report + * + * @param report $report + * @param int|null $userid User ID to check, or the current user if omitted + * @throws report_access_exception + */ + public static function require_can_view_report(report $report, ?int $userid = null): void { + if (!static::can_view_report($report, $userid)) { + throw new report_access_exception('errorreportview'); + } + } + + /** + * Whether given user can view report + * + * @param report $report + * @param int|null $userid User ID to check, or the current user if omitted + * @return bool + */ + public static function can_view_report(report $report, ?int $userid = null): bool { + if (!static::can_view_reports_list()) { + return false; + } + + return true; // TODO: Audience. + } + + /** + * Require given user can edit report + * + * @param report $report + * @param int|null $userid User ID to check, or the current user if omitted + * @return void + * @throws report_access_exception + */ + public static function require_can_edit_report(report $report, ?int $userid = null): void { + if (!static::can_edit_report($report, $userid)) { + throw new report_access_exception('errorreportedit'); + } + } + + /** + * Whether given user can edit report + * + * @param report $report + * @param int|null $userid User ID to check, or the current user if omitted + * @return bool + */ + public static function can_edit_report(report $report, ?int $userid = null): bool { + global $USER; + + // We can only edit custom reports. + if ($report->get('type') !== base::TYPE_CUSTOM_REPORT) { + return false; + } + + $userid = $userid ?: (int) $USER->id; + if ($report->get('usercreated') === $userid) { + return has_capability('moodle/reportbuilder:edit', context_system::instance(), $userid); + } else { + return has_capability('moodle/reportbuilder:editall', context_system::instance(), $userid); + } + } + + /** + * Whether given user can create a new report + * + * @param int|null $userid User ID to check, or the current user if omitted + * @return bool + */ + public static function can_create_report(?int $userid = null): bool { + $capabilities = ['moodle/reportbuilder:edit', 'moodle/reportbuilder:editall']; + return has_any_capability($capabilities, context_system::instance(), $userid); + } + + /** + * Require given user can create a new report + * + * @param int|null $userid User ID to check, or the current user if omitted + * @throws report_access_exception + */ + public static function require_can_create_report(?int $userid = null): void { + if (!static::can_create_report($userid)) { + throw new report_access_exception('errorreportcreate'); + } + } +} diff --git a/reportbuilder/classes/privacy/provider.php b/reportbuilder/classes/privacy/provider.php index a260154e716..2e49bcd0c23 100644 --- a/reportbuilder/classes/privacy/provider.php +++ b/reportbuilder/classes/privacy/provider.php @@ -21,7 +21,9 @@ 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\column; use core_reportbuilder\local\models\report; +use core_reportbuilder\local\models\filter; /** * Privacy Subsystem for core_reportbuilder @@ -47,6 +49,18 @@ class provider implements 'usermodified' => 'privacy:metadata:report:usermodified', ], 'privacy:metadata:report'); + $collection->add_database_table(column::TABLE, [ + 'uniqueidentifier' => 'privacy:metadata:column:uniqueidentifier', + 'usercreated' => 'privacy:metadata:column:usercreated', + 'usermodified' => 'privacy:metadata:column:usermodified', + ], 'privacy:metadata:column'); + + $collection->add_database_table(filter::TABLE, [ + 'uniqueidentifier' => 'privacy:metadata:filter:uniqueidentifier', + 'usercreated' => 'privacy:metadata:filter:usercreated', + 'usermodified' => 'privacy:metadata:filter:usermodified', + ], 'privacy:metadata:filter'); + $collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter'); return $collection; diff --git a/reportbuilder/classes/report_access_exception.php b/reportbuilder/classes/report_access_exception.php index 3783747553f..80e5c0ef88f 100644 --- a/reportbuilder/classes/report_access_exception.php +++ b/reportbuilder/classes/report_access_exception.php @@ -31,8 +31,10 @@ class report_access_exception extends moodle_exception { /** * Constructor + * + * @param string $errorcode */ - public function __construct() { - parent::__construct('errorreportaccess', 'reportbuilder'); + public function __construct(string $errorcode = 'errorreportview') { + parent::__construct($errorcode, 'reportbuilder'); } } diff --git a/reportbuilder/classes/table/base_report_table.php b/reportbuilder/classes/table/base_report_table.php new file mode 100644 index 00000000000..f055c83bb95 --- /dev/null +++ b/reportbuilder/classes/table/base_report_table.php @@ -0,0 +1,221 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\table; + +use context; +use moodle_url; +use renderable; +use table_sql; +use html_writer; +use core_table\dynamic; +use core_reportbuilder\local\helpers\database; +use core_reportbuilder\local\filters\base; +use core_reportbuilder\local\models\report; +use core_reportbuilder\local\report\base as base_report; +use core_reportbuilder\local\report\filter; +use core\output\notification; + +defined('MOODLE_INTERNAL') || die; + +require_once("{$CFG->libdir}/tablelib.php"); + +/** + * Base report dynamic table class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class base_report_table extends table_sql implements dynamic, renderable { + + /** @var report $persistent */ + protected $persistent; + + /** @var base_report $report */ + protected $report; + + /** @var string $groupbysql */ + protected $groupbysql = ''; + + /** @var bool $applyfilters */ + protected $applyfilters = true; + + /** + * Initialises table SQL properties + * + * @param string $fields + * @param string $from + * @param array $joins + * @param string $where + * @param array $params + * @param array $groupby + */ + protected function init_sql(string $fields, string $from, array $joins, string $where, array $params, + array $groupby = []): void { + + $wheres = []; + if ($where !== '') { + $wheres[] = $where; + } + + // For each condition, we need to ensure their values are always accounted for in the report. + $conditionvalues = $this->report->get_condition_values(); + foreach ($this->report->get_active_conditions() as $condition) { + [$conditionsql, $conditionparams] = $this->get_filter_sql($condition, $conditionvalues); + if ($conditionsql !== '') { + $joins = array_merge($joins, $condition->get_joins()); + $wheres[] = "({$conditionsql})"; + $params = array_merge($params, $conditionparams); + } + } + + // For each filter, we also need to apply their values (will differ according to user viewing the report). + if ($this->applyfilters) { + $filtervalues = $this->report->get_filter_values(); + foreach ($this->report->get_active_filters() as $filter) { + [$filtersql, $filterparams] = $this->get_filter_sql($filter, $filtervalues); + if ($filtersql !== '') { + $joins = array_merge($joins, $filter->get_joins()); + $wheres[] = "({$filtersql})"; + $params = array_merge($params, $filterparams); + } + } + } + + // Join all the filters into a SQL WHERE clause, falling back to all records. + if (!empty($wheres)) { + $wheresql = implode(' AND ', $wheres); + } else { + $wheresql = '1=1'; + } + + if (!empty($groupby)) { + $this->groupbysql = 'GROUP BY ' . implode(', ', $groupby); + } + + // Add unique table joins. + $from .= ' ' . implode(' ', array_unique($joins)); + + $this->set_sql($fields, $from, $wheresql, $params); + + $counttablealias = database::generate_alias(); + $this->set_count_sql(" + SELECT COUNT(1) + FROM (SELECT {$fields} + FROM {$from} + WHERE {$wheresql} + {$this->groupbysql} + ) {$counttablealias}", $params); + } + + /** + * Whether active filters should be applied to the report, defaults to true except in the case where we are editing a report + * and we do not want filters to be applied to it + * + * @param bool $applyfilters + */ + public function set_filters_applied(bool $applyfilters): void { + $this->applyfilters = $applyfilters; + } + + /** + * Return SQL fragments from given filter instance suitable for inclusion in table SQL + * + * @param filter $filter + * @param array $filtervalues + * @return array [$sql, $params] + */ + private function get_filter_sql(filter $filter, array $filtervalues): array { + /** @var base $filterclass */ + $filterclass = $filter->get_filter_class(); + + return $filterclass::create($filter)->get_sql_filter($filtervalues); + } + + /** + * 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} {$this->groupbysql}"; + + $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); + } + } + + /** + * Get the context for the table (that of the report persistent) + * + * @return context + */ + public function get_context(): context { + return $this->persistent->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('/'); + } + + /** + * Override print_nothing_to_display to modity the output styles. + */ + public function print_nothing_to_display() { + global $OUTPUT; + + echo $this->get_dynamic_table_html_start(); + echo $this->render_reset_button(); + + echo $OUTPUT->render(new notification(get_string('nothingtodisplay'), notification::NOTIFY_INFO, false)); + + echo $this->get_dynamic_table_html_end(); + } + + /** + * Override start of HTML to remove top pagination. + */ + public function start_html() { + // Render the dynamic table header. + echo $this->get_dynamic_table_html_start(); + + // Render button to allow user to reset table preferences. + echo $this->render_reset_button(); + + $this->wrap_html_start(); + + echo html_writer::start_tag('div', ['class' => 'no-overflow']); + echo html_writer::start_tag('table', $this->attributes); + } +} diff --git a/reportbuilder/classes/table/custom_report_table.php b/reportbuilder/classes/table/custom_report_table.php new file mode 100644 index 00000000000..909d7d163f5 --- /dev/null +++ b/reportbuilder/classes/table/custom_report_table.php @@ -0,0 +1,280 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\table; + +use core\output\notification; +use html_writer; +use moodle_exception; +use moodle_url; +use stdClass; +use core_reportbuilder\manager; +use core_reportbuilder\local\models\column as column_model; +use core_reportbuilder\local\models\report; +use core_reportbuilder\local\report\column; +use core_reportbuilder\output\column_aggregation_editable; +use core_reportbuilder\output\column_heading_editable; + +/** + * Custom report dynamic table class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_table extends base_report_table { + + /** @var string Unique ID prefix for the table */ + private const UNIQUEID_PREFIX = 'custom-report-table-'; + + /** @var bool Whether filters should be applied in report (we don't want them when editing) */ + protected const REPORT_APPLY_FILTERS = false; + + /** + * Table constructor. Note that the passed unique ID value must match the pattern "custom-report-table-(\d+)" so that + * dynamic updates continue to load the same report + * + * @param string $uniqueid + * @param string $download + * @throws moodle_exception For invalid unique ID + */ + public function __construct(string $uniqueid, string $download = '') { + if (!preg_match('/^' . self::UNIQUEID_PREFIX . '(?\d+)$/', $uniqueid, $matches)) { + throw new moodle_exception('invalidcustomreportid', 'core_reportbuilder', '', null, $uniqueid); + } + + parent::__construct($uniqueid); + + $this->define_baseurl(new moodle_url('/reportbuilder/edit.php', ['id' => $matches['id']])); + + // Load the report persistent, and accompanying system report instance. + $this->persistent = new report($matches['id']); + $this->report = manager::get_report_from_persistent($this->persistent); + + $fields = $groupby = []; + $maintable = $this->report->get_main_table(); + $maintablealias = $this->report->get_main_table_alias(); + $joins = $this->report->get_joins(); + [$where, $params] = $this->report->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($download ?? null, $this->persistent->get_formatted_name()); + + // Retrieve all report columns, exit early if there are none. + $columns = $this->get_active_columns(); + if (empty($columns)) { + $this->init_sql('*', "{{$maintable}} {$maintablealias}", [], '1=0', []); + return; + } + + // If we are aggregating any columns, we should group by the remaining ones. + $aggregatedcolumns = array_filter($columns, static function(column $column): bool { + return !empty($column->get_aggregation()); + }); + $hasaggregatedcolumns = !empty($aggregatedcolumns); + + $columnheaders = []; + foreach ($columns as $column) { + $columnheaders[$column->get_column_alias()] = $column->get_persistent()->get('heading') ?: $column->get_title(); + + $columnaggregation = $column->get_aggregation(); + if ($hasaggregatedcolumns && empty($columnaggregation)) { + $groupby = array_merge($groupby, $column->get_groupby_sql()); + } + + // 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()); + } + } + + $this->define_columns(array_keys($columnheaders)); + $this->define_headers(array_values($columnheaders)); + + // Table configuration. + $this->initialbars(false); + $this->collapsible(false); + $this->pageable(true); + + // Initialise table SQL properties. + $this->set_filters_applied(static::REPORT_APPLY_FILTERS); + + $fieldsql = implode(', ', $fields); + $this->init_sql($fieldsql, "{{$maintable}} {$maintablealias}", $joins, $where, $params, $groupby); + } + + /** + * Return a new instance of the class for given report ID + * + * @param int $reportid + * @param string $download + * @return static + */ + public static function create(int $reportid, string $download = ''): self { + return new static(self::UNIQUEID_PREFIX . $reportid, $download); + } + + /** + * Get user preferred sort columns, overriding those of parent. If user has no preferences then use report defaults + * + * @return array + */ + public function get_sort_columns() { + $sortcolumns = parent::get_sort_columns(); + if (empty($sortcolumns)) { + $columns = $this->get_active_columns(); + + $instances = column_model::get_records([ + 'reportid' => $this->report->get_report_persistent()->get('id'), + 'sortenabled' => 1, + ], 'sortorder'); + + foreach ($instances as $instance) { + $column = $columns[$instance->get('id')] ?? null; + if ($column !== null && $column->get_is_available()) { + $sortcolumns[$column->get_column_alias()] = $instance->get('sortdirection'); + } + } + } + + return $sortcolumns; + } + + /** + * 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) { + $columns = $this->get_active_columns(); + + $formattedrow = []; + foreach ($columns as $column) { + $formattedrow[$column->get_column_alias()] = $column->format_value((array) $row); + } + + return $formattedrow; + } + + /** + * Download is disabled when editing the report + * + * @return string + */ + public function download_buttons(): string { + return ''; + } + + /** + * Get the columns of the custom report, returned instances being valid and available for the user + * + * @return column[] Indexed by column ID + */ + private function get_active_columns(): array { + $columns = []; + + $instances = column_model::get_records(['reportid' => $this->report->get_report_persistent()->get('id')], 'columnorder'); + foreach ($instances as $index => $instance) { + $column = $this->report->get_column($instance->get('uniqueidentifier')); + if ($column !== null && $column->get_is_available()) { + $column->set_persistent($instance); + // We should clone the report column to ensure if it's added twice to a report, each operates independently. + $columns[$instance->get('id')] = clone $column + ->set_index($index) + ->set_aggregation($instance->get('aggregation')); + } + } + + return $columns; + } + + /** + * Override parent method for printing headers so we can render our custom controls in each cell + */ + public function print_headers() { + global $OUTPUT, $PAGE; + + $columns = $this->get_active_columns(); + if (empty($columns)) { + return; + } + + $columns = array_values($columns); + $renderer = $PAGE->get_renderer('core'); + + echo html_writer::start_tag('thead'); + echo html_writer::start_tag('tr'); + + foreach ($this->headers as $index => $title) { + $column = $columns[$index]; + + $headingeditable = new column_heading_editable(0, $column->get_persistent()); + $aggregationeditable = new column_aggregation_editable(0, $column->get_persistent()); + + // Render table header cell, with all editing controls. + $headercell = $OUTPUT->render_from_template('core_reportbuilder/table_header_cell', [ + 'entityname' => $this->report->get_entity_title($column->get_entity_name()), + 'name' => $column->get_title(), + 'headingeditable' => $headingeditable->render($renderer), + 'aggregationeditable' => $aggregationeditable->render($renderer), + 'movetitle' => get_string('movecolumn', 'core_reportbuilder', $column->get_title()), + ]); + + echo html_writer::tag('th', $headercell, [ + 'class' => 'border-right border-left', + 'scope' => 'col', + 'data-region' => 'column-header', + 'data-column-id' => $column->get_persistent()->get('id'), + 'data-column-name' => $column->get_title(), + 'data-column-position' => $index + 1, + ]); + } + + echo html_writer::end_tag('tr'); + echo html_writer::end_tag('thead'); + } + + /** + * Override print_nothing_to_display to ensure that column headers are always added. + */ + public function print_nothing_to_display() { + global $OUTPUT; + + $this->start_html(); + $this->print_headers(); + echo html_writer::end_tag('table'); + echo html_writer::end_tag('div'); + $this->wrap_html_finish(); + + $notification = (new notification(get_string('nothingtodisplay'), notification::NOTIFY_INFO, false)) + ->set_extra_classes(['mt-3']); + echo $OUTPUT->render($notification); + + echo $this->get_dynamic_table_html_end(); + } +} diff --git a/reportbuilder/classes/table/custom_report_table_filterset.php b/reportbuilder/classes/table/custom_report_table_filterset.php new file mode 100644 index 00000000000..5c7ad871def --- /dev/null +++ b/reportbuilder/classes/table/custom_report_table_filterset.php @@ -0,0 +1,40 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\table; + +use core_table\local\filter\filterset; + +/** + * Custom report dynamic table filterset class + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_table_filterset extends filterset { + + /** + * Get the required filters + * + * @return array. + */ + public function get_required_filters(): array { + return []; + } +} diff --git a/reportbuilder/classes/table/custom_report_table_view.php b/reportbuilder/classes/table/custom_report_table_view.php new file mode 100644 index 00000000000..e16fd8e6201 --- /dev/null +++ b/reportbuilder/classes/table/custom_report_table_view.php @@ -0,0 +1,61 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\table; + +use moodle_url; + +/** + * Custom report view dynamic table class + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_table_view extends custom_report_table { + + /** @var bool We do want to apply filters when viewing or previewing report */ + protected const REPORT_APPLY_FILTERS = true; + + /** + * Override printed headers, to use those of grandparent class + */ + public function print_headers() { + base_report_table::print_headers(); + } + + /** + * Get the html for the download buttons + * + * @return string + */ + public function download_buttons(): string { + global $OUTPUT; + + if (!$this->is_downloading()) { + return $OUTPUT->download_dataformat_selector( + get_string('downloadas', 'table'), + new moodle_url('/reportbuilder/download.php'), + 'download', + ['id' => $this->persistent->get('id')] + ); + } + + return ''; + } +} diff --git a/reportbuilder/classes/table/custom_report_table_view_filterset.php b/reportbuilder/classes/table/custom_report_table_view_filterset.php new file mode 100644 index 00000000000..45216863f84 --- /dev/null +++ b/reportbuilder/classes/table/custom_report_table_view_filterset.php @@ -0,0 +1,29 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\table; + +/** + * Custom report dynamic table filterset class + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_table_view_filterset extends custom_report_table_filterset { +} diff --git a/reportbuilder/classes/table/system_report_table.php b/reportbuilder/classes/table/system_report_table.php index f4471bf3425..28d09abc6b9 100644 --- a/reportbuilder/classes/table/system_report_table.php +++ b/reportbuilder/classes/table/system_report_table.php @@ -18,26 +18,17 @@ declare(strict_types=1); namespace core_reportbuilder\table; -use context; +use core_table\local\filter\filterset; 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 * @@ -45,17 +36,11 @@ require_once("{$CFG->libdir}/tablelib.php"); * @copyright 2020 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class system_report_table extends table_sql implements dynamic, renderable { +class system_report_table extends base_report_table { /** @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 @@ -72,27 +57,27 @@ class system_report_table extends table_sql implements dynamic, renderable { 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); + $this->persistent = new report($matches['id']); + $this->report = manager::get_report_from_persistent($this->persistent, $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(); + $fields = $this->report->get_base_fields(); + $maintable = $this->report->get_main_table(); + $maintablealias = $this->report->get_main_table_alias(); + $joins = $this->report->get_joins(); + [$where, $params] = $this->report->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()); + $this->is_downloading($parameters['download'] ?? null, $this->report->get_downloadfilename()); // Retrieve all report columns. If we are downloading the report, remove as required. - $columns = $this->systemreport->get_columns(); + $columns = $this->report->get_columns(); if ($this->is_downloading()) { $columns = array_diff_key($columns, - array_flip($this->systemreport->get_exclude_columns_for_download())); + array_flip($this->report->get_exclude_columns_for_download())); } $columnheaders = []; @@ -119,7 +104,7 @@ class system_report_table extends table_sql implements dynamic, renderable { } // 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()) { + if ($this->report->has_actions() && !$this->is_downloading()) { $columnheaders['actions'] = html_writer::tag('span', get_string('actions', 'core_reportbuilder'), [ 'class' => 'sr-only', ]); @@ -130,15 +115,15 @@ class system_report_table extends table_sql implements dynamic, renderable { $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()); + if ($sortcolumn = $this->report->get_initial_sort_column()) { + $this->sortable(true, $sortcolumn->get_column_alias(), $this->report->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()); + $this->set_default_per_page($this->report->get_default_per_page()); // Initialise table SQL properties. $fieldsql = implode(', ', $fields); @@ -164,78 +149,11 @@ class system_report_table extends table_sql implements dynamic, renderable { */ public function set_filterset(filterset $filterset): void { $parameters = $filterset->get_filter('parameters')->current(); - $this->systemreport->set_parameters((array) json_decode($parameters, true)); + $this->report->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 * @@ -243,7 +161,7 @@ class system_report_table extends table_sql implements dynamic, renderable { * @return string */ public function get_row_class($row) { - return $this->systemreport->get_row_class((object) $row); + return $this->report->get_row_class((object) $row); } /** @@ -253,13 +171,13 @@ class system_report_table extends table_sql implements dynamic, renderable { * @return array */ public function format_row($row) { - $this->systemreport->row_callback((object) $row); + $this->report->row_callback((object) $row); /** @var column[] $columnsbyalias */ $columnsbyalias = []; // Create a lookup for convenience, indexed by column alias. - $columns = $this->systemreport->get_columns(); + $columns = $this->report->get_columns(); foreach ($columns as $column) { $columnsbyalias[$column->get_column_alias()] = $column; } @@ -272,7 +190,7 @@ class system_report_table extends table_sql implements dynamic, renderable { } }); - if ($this->systemreport->has_actions()) { + if ($this->report->has_actions()) { $row['actions'] = $this->format_row_actions((object) $row); } @@ -288,27 +206,11 @@ class system_report_table extends table_sql implements dynamic, renderable { 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()); + }, $this->report->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 * @@ -317,14 +219,14 @@ class system_report_table extends table_sql implements dynamic, renderable { public function download_buttons(): string { global $OUTPUT; - if ($this->systemreport->can_be_downloaded() && !$this->is_downloading()) { + if ($this->report->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()), + 'id' => $this->persistent->get('id'), + 'parameters' => json_encode($this->report->get_parameters()), ] ); } diff --git a/reportbuilder/download.php b/reportbuilder/download.php index 503960de3dc..2a646cf7ee9 100644 --- a/reportbuilder/download.php +++ b/reportbuilder/download.php @@ -24,6 +24,7 @@ declare(strict_types=1); +use core_reportbuilder\permission; use core_reportbuilder\system_report_factory; require_once(__DIR__ . '/../config.php'); @@ -40,16 +41,23 @@ $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(); -} +if ($reportpersistent->get('type') === \core_reportbuilder\local\report\base::TYPE_SYSTEM_REPORT) { + $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)); -} + // 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); + $outputreport = new \core_reportbuilder\output\system_report($reportpersistent, $systemreport, $reportparameters); + echo $PAGE->get_renderer('core_reportbuilder')->render($outputreport); +} else { + permission::require_can_view_report($reportpersistent); + + $customreport = new \core_reportbuilder\output\custom_report($reportpersistent, false, false, $download); + echo $PAGE->get_renderer('core_reportbuilder')->render($customreport); +} diff --git a/reportbuilder/edit.php b/reportbuilder/edit.php new file mode 100644 index 00000000000..f3727c73dd7 --- /dev/null +++ b/reportbuilder/edit.php @@ -0,0 +1,56 @@ +. + +/** + * Edit a custom report + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +use core_reportbuilder\manager; +use core_reportbuilder\output\custom_report; +use core_reportbuilder\permission; + +require_once(__DIR__ . '/../config.php'); +require_once("{$CFG->libdir}/adminlib.php"); + +$reportid = required_param('id', PARAM_INT); + +admin_externalpage_setup('customreports', null, ['id' => $reportid], new moodle_url('/reportbuilder/edit.php')); + +$report = manager::get_report_from_id($reportid); +permission::require_can_edit_report($report->get_report_persistent()); + +$PAGE->set_context($report->get_context()); + +/** @var \core_reportbuilder\output\renderer $renderer */ +$renderer = $PAGE->get_renderer('core_reportbuilder'); + +$reportname = $report->get_report_persistent()->get_formatted_name(); +$PAGE->navbar->add($reportname); +$PAGE->set_title($reportname); +$PAGE->set_heading($reportname); + +echo $OUTPUT->header(); + +$customreport = (new custom_report($report->get_report_persistent())); +echo $renderer->render($customreport); + +echo $OUTPUT->footer(); diff --git a/reportbuilder/index.php b/reportbuilder/index.php new file mode 100644 index 00000000000..25e18aec0df --- /dev/null +++ b/reportbuilder/index.php @@ -0,0 +1,53 @@ +. + +/** + * List of custom reports + * + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +use core_reportbuilder\permission; +use core_reportbuilder\system_report_factory; +use core_reportbuilder\local\systemreports\reports_list; + +require_once(__DIR__ . '/../config.php'); +require_once("{$CFG->libdir}/adminlib.php"); + +admin_externalpage_setup('customreports'); + +$PAGE->requires->js_call_amd('core_reportbuilder/reports_list', 'init'); + +echo $OUTPUT->header(); +echo html_writer::start_div('d-flex justify-content-between mb-2'); +echo $OUTPUT->heading(get_string('customreports', 'core_reportbuilder')); + +if (permission::can_create_report()) { + /** @var \core_reportbuilder\output\renderer $renderer */ + $renderer = $PAGE->get_renderer('core_reportbuilder'); + echo $renderer->render_new_report_button(); +} + +echo html_writer::end_div(); + +$report = system_report_factory::create(reports_list::class, context_system::instance()); +echo $report->output(); + +echo $OUTPUT->footer(); diff --git a/reportbuilder/lib.php b/reportbuilder/lib.php index 628e1bf302f..e303e9d09e8 100644 --- a/reportbuilder/lib.php +++ b/reportbuilder/lib.php @@ -42,3 +42,32 @@ function core_reportbuilder_output_fragment_filters_form(array $params): string return $filtersform->render(); } + +/** + * Plugin inplace editable implementation + * + * @param string $itemtype + * @param int $itemid + * @param string $newvalue + * @return \core\output\inplace_editable|bool + */ +function core_reportbuilder_inplace_editable($itemtype, $itemid, $newvalue) { + $itemid = (int) $itemid; + + switch ($itemtype) { + case 'reportname': + return \core_reportbuilder\output\report_name_editable::update($itemid, $newvalue); + + case 'columnheading': + return \core_reportbuilder\output\column_heading_editable::update($itemid, $newvalue); + + case 'columnaggregation': + return \core_reportbuilder\output\column_aggregation_editable::update($itemid, $newvalue); + + case 'filterheading': + return \core_reportbuilder\output\filter_heading_editable::update($itemid, $newvalue); + + default: + return false; + } +} diff --git a/reportbuilder/templates/custom_report.mustache b/reportbuilder/templates/custom_report.mustache new file mode 100644 index 00000000000..a5a013aff6d --- /dev/null +++ b/reportbuilder/templates/custom_report.mustache @@ -0,0 +1,147 @@ +{{! + 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 . +}} +{{! + @template core_reportbuilder/custom_report + + Template for a custom report + + Example context (json): + { + "id": 1, + "source": "some\\class\\name", + "table": "table", + "editmode": true, + "showeditbutton": true, + "sidebarmenucards": [{ + "menucards": [{ + "name": "General", + "items": [{ + "name": "Manually added users", + "identifier": "core_reportbuilder:users" + }] + }] + }], + "conditions": [{ + "hasavailableconditions": true, + "availableconditions": [{ + "optiongroup": [{ + "text": "User", + "values": [{ + "value": 1, + "visiblename": "User name" + }] + }] + }], + "hasactiveconditions": true, + "activeconditionsform": "form" + }], + "filters": [{ + "hasavailablefilters": true, + "availablefilters": [{ + "optiongroup": [{ + "text": "User", + "values": [{ + "value": 1, + "visiblename": "User name" + }] + }] + }], + "hasactivefilters": true, + "activefilters": [{ + "heading": "Email address", + "entityname": "user", + "headingeditable": "Email address" + }] + }], + "sorting": [{ + "hassortablecolumns": true, + "sortablecolumns": [{ + "id": 1, + "title": "Email address", + "sortdirection": "4", + "sortenabled": true, + "sortorder": 1, + "sorticon": [{ + "key": "t/uplong", + "component": "core", + "title": "Sort column 'Email address' ascending" + }], + "heading": "Email address" + }] + }] + } +}} +
+
+ {{#editmode}} + + {{> core_reportbuilder/local/sidebar-menu/area}} + {{/editmode}} + +
+
+ +
+ + {{{table}}} +
+
+
+ + {{> core_reportbuilder/local/settings/area}} +
+
+ +{{#js}} + require(['core_reportbuilder/editor', 'core_reportbuilder/report'], + function(editor, report) { + editor.init(); + report.init(); + }); +{{/js}} diff --git a/reportbuilder/templates/local/conditions/footer.mustache b/reportbuilder/templates/local/conditions/footer.mustache new file mode 100644 index 00000000000..91b02a2261a --- /dev/null +++ b/reportbuilder/templates/local/conditions/footer.mustache @@ -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 . +}} +{{! + @template core_reportbuilder/local/conditions/footer + + Template for the footer of a filter instance within the filter form + + Example context (json): + { + } +}} + \ No newline at end of file diff --git a/reportbuilder/templates/local/conditions/header.mustache b/reportbuilder/templates/local/conditions/header.mustache new file mode 100644 index 00000000000..07b1aaa2146 --- /dev/null +++ b/reportbuilder/templates/local/conditions/header.mustache @@ -0,0 +1,41 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_reportbuilder/local/conditions/header + + Template for the header of a condition instance within the condition form + + Example context (json): + { + "id": 1, + "heading": "Date modified", + "entityname": "Course" + } +}} +
+
+ {{>core/drag_handle}} +
+
+ {{entityname}} • {{heading}} +
+
+ + {{#pix}}e/cancel, core, {{#str}}deletecondition, core_reportbuilder, {{{heading}}}{{/str}}{{/pix}} + +
\ No newline at end of file diff --git a/reportbuilder/templates/local/filters/area.mustache b/reportbuilder/templates/local/filters/area.mustache index 7e09cafe3a8..15bda276340 100644 --- a/reportbuilder/templates/local/filters/area.mustache +++ b/reportbuilder/templates/local/filters/area.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template core_reportbuilder/filters/area + @template core_reportbuilder/local/filters/area Template for system report filters area @@ -24,29 +24,28 @@ "id": 3, "contextid": 1, "filtersapplied": 3, + "showeditbutton": false, "filtersform": "form" } }} -