Merge branch 'MDL-67914-master' of git://github.com/lameze/moodle

This commit is contained in:
Eloy Lafuente (stronk7)
2020-04-01 23:41:38 +02:00
18 changed files with 837 additions and 154 deletions
+8
View File
@@ -2744,6 +2744,14 @@ $functions = array(
'capabilities' => '',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'core_table_dynamic_fetch' => [
'classname' => 'core_table\external\dynamic\fetch',
'methodname' => 'execute',
'description' => 'Fetch a dynamic table view raw html',
'type' => 'read',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
);
$services = array(
+2
View File
@@ -0,0 +1,2 @@
define ("core_table/dynamic",["exports","core_table/local/dynamic/repository","core_table/local/dynamic/selectors"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=a.setSortOrder=a.setFilters=a.updateTable=a.refreshTableContent=void 0;c=function(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}(c);function d(a){return g(a)||f(a)||e()}function e(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function f(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function g(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b<a.length;b++){c[b]=a[b]}return c}}var h=!1,i=function(a){if(!a){throw new Error("The table specified is not a dynamic table and cannot be updated")}if(!a.matches(c.table.region)){throw new Error("The table specified is not a dynamic table and cannot be updated")}return!0},j=function(a){return JSON.parse(a.dataset.tableFilters)},k=function(a){var c=j(a);return(0,b.fetch)(a.dataset.tableHandler,a.dataset.tableUniqueid,{sortBy:a.dataset.tableSortBy,sortOrder:a.dataset.tableSortOrder,joinType:c.jointype,filters:c.filters}).then(function(b){var c=document.createElement("div");c.innerHTML=b.html;a.replaceWith.apply(a,d(c.childNodes));return b})};a.refreshTableContent=k;var l=function(a){var b=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{},c=b.sortBy,d=void 0===c?null:c,e=b.sortOrder,f=void 0===e?null:e,g=b.filters,h=void 0===g?null:g,j=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!0;i(a);if(d&&f){a.dataset.tableSortBy=d;a.dataset.tableSortOrder=f}if(h){a.dataset.tableFilters=JSON.stringify(h)}if(j){return k(a)}else{return Promise.resolve()}};a.updateTable=l;a.setFilters=function setFilters(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!0;return l(a,{filters:b},c)};var m=function(a,b,c){var d=3<arguments.length&&arguments[3]!==void 0?arguments[3]:!0;return l(a,{sortBy:b,sortOrder:c},d)};a.setSortOrder=m;var n=function(){if(h){return}h=!0;document.addEventListener("click",function(a){var b=a.target.closest(c.table.region);if(!b){return}var d=a.target.closest(c.table.links.sortableColumn);if(d){a.preventDefault();m(b,d.dataset.sortby,d.dataset.sortorder)}})};a.init=n});
//# sourceMappingURL=dynamic.min.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
define ("core_table/local/dynamic/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.fetch=void 0;a.fetch=function fetch(a,c){var d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:{},e=d.sortBy,f=void 0===e?null:e,g=d.sortOrder,h=void 0===g?null:g,i=d.joinType,j=void 0===i?null:i,k=d.filters,l=void 0===k?{}:k;return(0,b.call)([{methodname:"core_table_dynamic_fetch",args:{handler:a,uniqueid:c,sortby:f,sortorder:h,jointype:j,filters:l}}])[0]}});
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/dynamic/repository.js"],"names":["fetch","handler","uniqueid","sortBy","sortOrder","joinType","filters","methodname","args","sortby","sortorder","jointype"],"mappings":"yKAmCqB,QAARA,CAAAA,KAAQ,CAACC,CAAD,CAAUC,CAAV,CAMhB,8DADG,EACH,KALGC,MAKH,CALGA,CAKH,YALY,IAKZ,OAJGC,SAIH,CAJGA,CAIH,YAJe,IAIf,OAHGC,QAGH,CAHGA,CAGH,YAHc,IAGd,OAFGC,OAEH,CAFGA,CAEH,YAFa,EAEb,GACD,MAAO,WAAU,CAAC,CACdC,UAAU,2BADI,CAEdC,IAAI,CAAE,CACFP,OAAO,CAAPA,CADE,CAEFC,QAAQ,CAARA,CAFE,CAGFO,MAAM,CAAEN,CAHN,CAIFO,SAAS,CAAEN,CAJT,CAKFO,QAAQ,CAAEN,CALR,CAMFC,OAAO,CAAPA,CANE,CAFQ,CAAD,CAAV,EAUH,CAVG,CAWV,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle calendar ajax actions.\n *\n * @module core_calendar/repository\n * @class repository\n * @package core_calendar\n * @copyright 2017 Simey Lameze <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Fetch table view.\n *\n * @method fetch\n * @param {String} handler The name of the handler\n * @param {String} uniqueid The unique id of the table\n * @param {Number} params parameters to request table\n * @return {Promise} Resolved with requested table view\n */\nexport const fetch = (handler, uniqueid, {\n sortBy = null,\n sortOrder = null,\n joinType = null,\n filters = {}\n } = {}\n) => {\n return fetchMany([{\n methodname: `core_table_dynamic_fetch`,\n args: {\n handler,\n uniqueid,\n sortby: sortBy,\n sortorder: sortOrder,\n jointype: joinType,\n filters,\n },\n }])[0];\n};\n"],"file":"repository.min.js"}
+2
View File
@@ -0,0 +1,2 @@
define ("core_table/local/dynamic/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={table:{region:"[data-region=\"core_table/dynamic\"]",links:{sortableColumn:"a[data-sortable=\"1\"]"}}};return a.default});
//# sourceMappingURL=selectors.min.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/dynamic/selectors.js"],"names":["table","region","links","sortableColumn"],"mappings":"8JAuBe,CACXA,KAAK,CAAE,CACHC,MAAM,CAAE,sCADL,CAEHC,KAAK,CAAE,CACHC,cAAc,CAAE,wBADb,CAFJ,CADI,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Dynamic table selectors.\n *\n * @module core_table/selectors\n * @package core_table\n * @copyright 2020 Simey Lameze <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n table: {\n region: '[data-region=\"core_table/dynamic\"]',\n links: {\n sortableColumn: 'a[data-sortable=\"1\"]',\n },\n },\n};\n"],"file":"selectors.min.js"}
+160
View File
@@ -0,0 +1,160 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to handle dynamic table features.
*
* @module core_table/dynamic
* @package core_table
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {fetch as fetchTableData} from 'core_table/local/dynamic/repository';
import * as Selectors from 'core_table/local/dynamic/selectors';
let watching = false;
/**
* Ensure that a table is a dynamic table.
*
* @param {HTMLElement} tableRoot
* @returns {Bool}
*/
const checkTableIsDynamic = tableRoot => {
if (!tableRoot) {
// The table is not a dynamic table.
throw new Error("The table specified is not a dynamic table and cannot be updated");
}
if (!tableRoot.matches(Selectors.table.region)) {
// The table is not a dynamic table.
throw new Error("The table specified is not a dynamic table and cannot be updated");
}
return true;
};
/**
* Get the filterset data from a known dynamic table.
*
* @param {HTMLElement} tableRoot
* @returns {Object}
*/
const getFiltersetFromTable = tableRoot => {
return JSON.parse(tableRoot.dataset.tableFilters);
};
/**
* Update the specified table based on its current values.
*
* @param {HTMLElement} tableRoot
* @returns {Promise}
*/
export const refreshTableContent = tableRoot => {
const filterset = getFiltersetFromTable(tableRoot);
return fetchTableData(
tableRoot.dataset.tableHandler,
tableRoot.dataset.tableUniqueid,
{
sortBy: tableRoot.dataset.tableSortBy,
sortOrder: tableRoot.dataset.tableSortOrder,
joinType: filterset.jointype,
filters: filterset.filters,
}
)
.then(data => {
const placeholder = document.createElement('div');
placeholder.innerHTML = data.html;
tableRoot.replaceWith(...placeholder.childNodes);
return data;
});
};
export const updateTable = (tableRoot, {
sortBy = null,
sortOrder = null,
filters = null,
} = {}, refreshContent = true) => {
checkTableIsDynamic(tableRoot);
// Update sort fields.
if (sortBy && sortOrder) {
tableRoot.dataset.tableSortBy = sortBy;
tableRoot.dataset.tableSortOrder = sortOrder;
}
// Update filters.
if (filters) {
tableRoot.dataset.tableFilters = JSON.stringify(filters);
}
// Refresh.
if (refreshContent) {
return refreshTableContent(tableRoot);
} else {
return Promise.resolve();
}
};
/**
* Update the specified table using the new filters.
*
* @param {HTMLElement} tableRoot
* @param {Object} filters
* @param {Bool} refreshContent
* @returns {Promise}
*/
export const setFilters = (tableRoot, filters, refreshContent = true) =>
updateTable(tableRoot, {filters}, refreshContent);
/**
* Update the sort order.
*
* @param {HTMLElement} tableRoot
* @param {String} sortBy
* @param {Number} sortOrder
* @param {Bool} refreshContent
* @returns {Promise}
*/
export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
/**
* Set up listeners to handle table updates.
*/
export const init = () => {
if (watching) {
// Already watching.
return;
}
watching = true;
document.addEventListener('click', e => {
const tableRoot = e.target.closest(Selectors.table.region);
if (!tableRoot) {
return;
}
const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
if (sortableLink) {
e.preventDefault();
setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder);
}
});
};
@@ -0,0 +1,54 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle calendar ajax actions.
*
* @module core_calendar/repository
* @class repository
* @package core_calendar
* @copyright 2017 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
/**
* Fetch table view.
*
* @method fetch
* @param {String} handler The name of the handler
* @param {String} uniqueid The unique id of the table
* @param {Number} params parameters to request table
* @return {Promise} Resolved with requested table view
*/
export const fetch = (handler, uniqueid, {
sortBy = null,
sortOrder = null,
joinType = null,
filters = {}
} = {}
) => {
return fetchMany([{
methodname: `core_table_dynamic_fetch`,
args: {
handler,
uniqueid,
sortby: sortBy,
sortorder: sortOrder,
jointype: joinType,
filters,
},
}])[0];
};
@@ -0,0 +1,31 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Dynamic table selectors.
*
* @module core_table/selectors
* @package core_table
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
table: {
region: '[data-region="core_table/dynamic"]',
links: {
sortableColumn: 'a[data-sortable="1"]',
},
},
};
+18 -1
View File
@@ -30,6 +30,7 @@ namespace core_table;
defined('MOODLE_INTERNAL') || die();
use moodle_url;
use context;
use core_table\local\filter\filterset;
/**
@@ -57,7 +58,7 @@ interface dynamic {
*
* @return moodle_url
*/
public static function get_base_url(): moodle_url;
public function get_base_url(): moodle_url;
/**
* Set the filterset filters build table object.
@@ -66,4 +67,20 @@ interface dynamic {
* @return void
*/
public function set_filterset(filterset $filterset): void;
/**
* Get the currently defined filterset.
*
* @return filterset
*/
public function get_filterset(): ?filterset;
/**
* Get the context of the current table.
*
* Note: This function should not be called until after the filterset has been provided.
*
* @return context
*/
public function get_context(): ?context;
}
+170
View File
@@ -0,0 +1,170 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Table external API.
*
* @package core_table
* @category external
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_table\external\dynamic;
use external_api;
use external_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use moodle_url;
/**
* Core table external functions.
*
* @package core_table
* @category external
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fetch extends external_api {
/**
* Describes the parameters for fetching the table html.
*
* @return external_function_parameters
* @since Moodle 3.9
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters ([
'handler' => new external_value(
// Note: We do not have a PARAM_CLASSNAME which would have been ideal.
PARAM_RAW,
'Handler',
VALUE_REQUIRED
),
'uniqueid' => new external_value(
PARAM_ALPHANUMEXT,
'Unique ID for the container',
VALUE_REQUIRED
),
'sortby' => new external_value(
PARAM_ALPHANUMEXT,
'The name of a sortable column',
VALUE_REQUIRED
),
'sortorder' => new external_value(
PARAM_ALPHANUMEXT,
'The sort order',
VALUE_REQUIRED
),
'filters' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_ALPHANUM, 'Name of the filter', VALUE_REQUIRED),
'jointype' => new external_value(PARAM_INT, 'Type of join for filter values', VALUE_REQUIRED),
'values' => new external_multiple_structure(
new external_value(PARAM_RAW, 'Filter value'),
'The value to filter on',
VALUE_REQUIRED
)
]),
'The filters that will be applied in the request',
VALUE_OPTIONAL
),
'jointype' => new external_value(PARAM_INT, 'Type of join to join all filters together', VALUE_REQUIRED),
]);
}
/**
* External function to fetch a table view.
*
* @param string $handler Dynamic table class name.
* @param string $uniqueid Unique ID for the container.
* @param string $sortby The name of a sortable column.
* @param string $sortorder The sort order.
* @param array $filters The filters that will be applied in the request.
* @param string $jointype The join type.
*
* @return array
*/
public static function execute(string $handler, string $uniqueid, string $sortby, string $sortorder,
array $filters = [], string $jointype = null) {
global $PAGE;
if (!class_exists($handler) || !is_subclass_of($handler, \core_table\dynamic::class)) {
throw new \UnexpectedValueException('Unknown table handler, or table handler does not support dynamic updating.');
}
[
'handler' => $handler,
'uniqueid' => $uniqueid,
'sortby' => $sortby,
'sortorder' => $sortorder,
'filters' => $filters,
'jointype' => $jointype,
] = self::validate_parameters(self::execute_parameters(), [
'handler' => $handler,
'uniqueid' => $uniqueid,
'sortby' => $sortby,
'sortorder' => $sortorder,
'filters' => $filters,
'jointype' => $jointype,
]);
$filterset = new \core_user\table\participants_filterset();
foreach ($filters as $rawfilter) {
$filterset->add_filter_from_params(
$rawfilter['name'],
$rawfilter['jointype'],
$rawfilter['values']
);
}
$instance = new $handler($uniqueid);
$instance->set_filterset($filterset);
$instance->set_sorting($sortby, $sortorder);
$context = $instance->get_context();
self::validate_context($context);
$PAGE->set_url($instance->get_base_url());
ob_start();
$instance->out(20, true);
$participanttablehtml = ob_get_contents();
ob_end_clean();
return [
'html' => $participanttablehtml,
'warnings' => []
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 3.9
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'html' => new external_value(PARAM_RAW, 'The raw html of the requested table.'),
'warnings' => new external_warnings()
]);
}
}
+15 -1
View File
@@ -28,6 +28,7 @@ declare(strict_types=1);
namespace core_table\local\filter;
use Countable;
use JsonSerializable;
use InvalidArgumentException;
use Iterator;
@@ -38,7 +39,7 @@ use Iterator;
* @copyright 2020 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter implements Countable, Iterator {
class filter implements Countable, Iterator, JsonSerializable {
/** @var in The default filter type (ANY) */
const JOINTYPE_DEFAULT = 1;
@@ -251,4 +252,17 @@ class filter implements Countable, Iterator {
$this->sort_filter_values();
return $this->filtervalues;
}
/**
* Serialize filter.
*
* @return mixed|object
*/
public function jsonSerialize() {
return (object) [
'name' => $this->get_name(),
'jointype' => $this->get_join_type(),
'values' => $this->get_filter_values(),
];
}
}
+14 -1
View File
@@ -28,6 +28,7 @@ declare(strict_types=1);
namespace core_table\local\filter;
use InvalidArgumentException;
use JsonSerializable;
use UnexpectedValueException;
use moodle_exception;
@@ -38,7 +39,7 @@ use moodle_exception;
* @copyright 2020 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class filterset {
abstract class filterset implements JsonSerializable {
/** @var in The default filter type (ANY) */
const JOINTYPE_DEFAULT = 1;
@@ -285,4 +286,16 @@ abstract class filterset {
return $this->filtertypes;
}
/**
* Serialize filterset.
*
* @return mixed|object
*/
public function jsonSerialize() {
return (object) [
'jointype' => $this->get_join_type(),
'filters' => $this->get_filters(),
];
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_table\external\fetch;
*
* @package core_table
* @category test
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace core_table\external\dynamic;
use core_table\local\filter\filter;
use advanced_testcase;
/**
* Unit tests for core_table\external\fetch;
*
* @package core_table
* @category test
* @copyright 2020 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fetch_test extends advanced_testcase {
/**
* Setup before class.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->libdir}/externallib.php");
}
/**
* Test execute invalid handler.
*/
public function test_execute_invalid_handler(): void {
$this->resetAfterTest();
$this->expectException('UnexpectedValueException');
$this->expectExceptionMessage("Unknown table handler, or table handler does not support dynamic updating.");
// Tests that invalid users_participants_table class gets an exception.
fetch::execute("core_user\users_participants_table", "", "email", "4", [], "1");
}
/**
* Test execute invalid filter.
*/
public function test_execute_invalid_filter(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
// Filter with an invalid name.
$filter = [
[
'fullname' => 'courseid',
'jointype' => filter::JOINTYPE_ANY,
'values' => [(int)$course->id]
]
];
$this->expectException('invalid_parameter_exception');
$this->expectExceptionMessage("Invalid parameter value detected (filters => Invalid parameter value detected " .
"(Missing required key in single structure: name): Missing required key in single structure: name");
fetch::execute("core_user\participants_table",
"user-index-participants-{$course->id}", "firstname", "4", $filter, (string)filter::JOINTYPE_ANY);
}
/**
* Test execute fetch table.
*/
public function test_execute_fetch_table(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => '[email protected]']);
$user2 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => '[email protected]']);
$user3 = $this->getDataGenerator()->create_user(['email' => '[email protected]']);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher', ['email' => '[email protected]']);
$this->setUser($teacher);
$filter = [
[
'name' => 'courseid',
'jointype' => filter::JOINTYPE_ANY,
'values' => [(int)$course->id]
]
];
$participantstable = fetch::execute("core_user\participants_table",
"user-index-participants-{$course->id}", "firstname", "4", $filter, (string)filter::JOINTYPE_ANY);
$html = $participantstable['html'];
$this->assertStringContainsString($user1->email, $html);
$this->assertStringContainsString($user2->email, $html);
$this->assertStringContainsString($teacher->email, $html);
$this->assertStringNotContainsString($user3->email, $html);
}
}
+105 -34
View File
@@ -84,6 +84,17 @@ class flexible_table {
private $persistent = false;
var $is_collapsible = false;
var $is_sortable = false;
/**
* @var string The field name to sort by.
*/
protected $sortby;
/**
* @var string $sortorder The direction for sorting.
*/
protected $sortorder;
var $use_pages = false;
var $use_initials = false;
@@ -513,35 +524,7 @@ class flexible_table {
}
}
if (($sortcol = optional_param($this->request[TABLE_VAR_SORT], '', PARAM_ALPHANUMEXT)) &&
$this->is_sortable($sortcol) && empty($this->prefs['collapse'][$sortcol]) &&
(isset($this->columns[$sortcol]) || in_array($sortcol, get_all_user_name_fields())
&& isset($this->columns['fullname']))) {
$sortdir = optional_param($this->request[TABLE_VAR_DIR], $this->sort_default_order, PARAM_INT);
if (array_key_exists($sortcol, $this->prefs['sortby'])) {
// This key already exists somewhere. Change its sortorder and bring it to the top.
$sortorder = $this->prefs['sortby'][$sortcol] = $sortdir;
unset($this->prefs['sortby'][$sortcol]);
$this->prefs['sortby'] = array_merge(array($sortcol => $sortorder), $this->prefs['sortby']);
} else {
// Key doesn't exist, so just add it to the beginning of the array, ascending order
$this->prefs['sortby'] = array_merge(array($sortcol => $sortdir), $this->prefs['sortby']);
}
// Finally, make sure that no more than $this->maxsortkeys are present into the array
$this->prefs['sortby'] = array_slice($this->prefs['sortby'], 0, $this->maxsortkeys);
}
// MDL-35375 - If a default order is defined and it is not in the current list of order by columns, add it at the end.
// This prevents results from being returned in a random order if the only order by column contains equal values.
if (!empty($this->sort_default_column)) {
if (!array_key_exists($this->sort_default_column, $this->prefs['sortby'])) {
$defaultsort = array($this->sort_default_column => $this->sort_default_order);
$this->prefs['sortby'] = array_merge($this->prefs['sortby'], $defaultsort);
}
}
$this->set_sorting_preferences();
$ilast = optional_param($this->request[TABLE_VAR_ILAST], null, PARAM_RAW);
if (!is_null($ilast) && ($ilast ==='' || strpos(get_string('alphabet', 'langconfig'), $ilast) !== false)) {
@@ -1158,7 +1141,8 @@ class flexible_table {
* This function is not part of the public api.
*/
function finish_html() {
global $OUTPUT;
global $OUTPUT, $PAGE;
if (!$this->started_output) {
//no data has been added to the table.
$this->print_nothing_to_display();
@@ -1187,6 +1171,13 @@ class flexible_table {
$pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
echo $OUTPUT->render($pagingbar);
}
// Dynamic Table content.
if (is_a($this, \core_table\dynamic::class)) {
echo html_writer::end_tag('div');
$PAGE->requires->js_call_amd('core_table/dynamic', 'init');
}
}
}
@@ -1322,6 +1313,57 @@ class flexible_table {
echo html_writer::end_tag('thead');
}
/**
* Calculate the preferences for sort order based on user-supplied values and get params.
*/
protected function set_sorting_preferences(): void {
$sortorder = $this->sortorder;
$sortby = $this->sortby;
if ($sortorder === null || $sortby === null) {
$sortorder = optional_param($this->request[TABLE_VAR_DIR], $this->sort_default_order, PARAM_INT);
$sortby = optional_param($this->request[TABLE_VAR_SORT], '', PARAM_ALPHANUMEXT);
}
$isvalidsort = $sortby && $this->is_sortable($sortby);
$isvalidsort = $isvalidsort && empty($this->prefs['collapse'][$sortby]);
$isrealcolumn = isset($this->columns[$sortby]);
$isfullnamefield = isset($this->columns['fullname']) && in_array($sortby, get_all_user_name_fields());
if ($isvalidsort && ($isrealcolumn || $isfullnamefield)) {
if (array_key_exists($sortby, $this->prefs['sortby'])) {
// This key already exists somewhere. Change its sortorder and bring it to the top.
$sortorder = $this->prefs['sortby'][$sortby] = $sortorder;
unset($this->prefs['sortby'][$sortby]);
$this->prefs['sortby'] = array_merge(array($sortby => $sortorder), $this->prefs['sortby']);
} else {
// Key doesn't exist, so just add it to the beginning of the array, ascending order.
$this->prefs['sortby'] = array_merge(array($sortby => $sortorder), $this->prefs['sortby']);
}
// Finally, make sure that no more than $this->maxsortkeys are present into the array.
$this->prefs['sortby'] = array_slice($this->prefs['sortby'], 0, $this->maxsortkeys);
}
// If a default order is defined and it is not in the current list of order by columns, add it at the end.
// This prevents results from being returned in a random order if the only order by column contains equal values.
if (!empty($this->sort_default_column) && !array_key_exists($this->sort_default_column, $this->prefs['sortby'])) {
$defaultsort = array($this->sort_default_column => $this->sort_default_order);
$this->prefs['sortby'] = array_merge($this->prefs['sortby'], $defaultsort);
}
}
/**
* Set the preferred table sorting attributes.
*
* @param string $sortby The field to sort by.
* @param int $sortorder The sort order.
*/
public function set_sorting(string $sortby, int $sortorder): void {
$this->sortby = $sortby;
$this->sortorder = $sortorder;
}
/**
* Generate the HTML for the sort icon. This is a helper method used by {@link sort_link()}.
* @param bool $isprimary whether an icon is needed (it is only needed for the primary sort column.)
@@ -1380,8 +1422,27 @@ class flexible_table {
return html_writer::link($this->baseurl->out(false, $params),
$text . get_accesshide(get_string('sortby') . ' ' .
$text . ' ' . $this->sort_order_name($isprimary, $order))) . ' ' .
$this->sort_icon($isprimary, $order);
$text . ' ' . $this->sort_order_name($isprimary, $order)),
[
'data-sortable' => $this->is_sortable($column),
'data-sortby' => $column,
'data-sortorder' => $sortorder,
]) . ' ' . $this->sort_icon($isprimary, $order);
}
/**
* Return sorting attributes values.
*
* @return array
*/
protected function get_sort_order(): array {
$sortbys = $this->prefs['sortby'];
$sortby = key($sortbys);
return [
'sortby' => $sortby,
'sortorder' => $sortbys[$sortby],
];
}
/**
@@ -1390,6 +1451,18 @@ class flexible_table {
function start_html() {
global $OUTPUT;
if (is_a($this, \core_table\dynamic::class)) {
$sortdata = $this->get_sort_order();
echo html_writer::start_tag('div', [
'data-region' => 'core_table/dynamic',
'data-table-handler' => get_class($this),
'data-table-uniqueid' => $this->uniqueid,
'data-table-filters' => json_encode($this->get_filterset()),
'data-table-sort-by' => $sortdata['sortby'],
'data-table-sort-order' => $sortdata['sortorder'],
]);
}
// Render button to allow user to reset table preferences.
echo $this->render_reset_button();
@@ -1460,10 +1533,8 @@ class flexible_table {
* @return bool
*/
protected function can_be_reset() {
// Loop through preferences and make sure they are empty or set to the default value.
foreach ($this->prefs as $prefname => $prefval) {
if ($prefname === 'sortby' and !empty($this->sort_default_column)) {
// Check if the actual sorting differs from the default one.
if (empty($prefval) or $prefval !== array($this->sort_default_column => $this->sort_default_order)) {
+132 -116
View File
@@ -135,6 +135,9 @@ class participants_table extends \table_sql implements dynamic_table {
/** @var \stdClass[] $viewableroles */
private $viewableroles;
/** @var moodle_url $baseurl The base URL for the report. */
public $baseurl;
/**
* Render the participants table.
*
@@ -143,7 +146,91 @@ class participants_table extends \table_sql implements dynamic_table {
* @param string $downloadhelpbutton
*/
public function out($pagesize, $useinitialsbar, $downloadhelpbutton = '') {
global $PAGE;
global $CFG, $OUTPUT, $PAGE;
// Define the headers and columns.
$headers = [];
$columns = [];
$bulkoperations = has_capability('moodle/course:bulkmessaging', $this->context);
if ($bulkoperations) {
$mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
'id' => 'select-all-participants',
'name' => 'select-all-participants',
'label' => $this->selectall ? get_string('deselectall') : get_string('selectall'),
'labelclasses' => 'sr-only',
'classes' => 'm-1',
'checked' => $this->selectall
]);
$headers[] = $OUTPUT->render($mastercheckbox);
$columns[] = 'select';
}
$headers[] = get_string('fullname');
$columns[] = 'fullname';
$extrafields = get_extra_user_fields($this->context);
foreach ($extrafields as $field) {
$headers[] = get_user_field_name($field);
$columns[] = $field;
}
$headers[] = get_string('roles');
$columns[] = 'roles';
// Get the list of fields we have to hide.
$hiddenfields = array();
if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
// Add column for groups if the user can view them.
$canseegroups = !isset($hiddenfields['groups']);
if ($canseegroups) {
$headers[] = get_string('groups');
$columns[] = 'groups';
}
// Do not show the columns if it exists in the hiddenfields array.
if (!isset($hiddenfields['lastaccess'])) {
if ($this->courseid == SITEID) {
$headers[] = get_string('lastsiteaccess');
} else {
$headers[] = get_string('lastcourseaccess');
}
$columns[] = 'lastaccess';
}
$canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
if ($canreviewenrol && $this->courseid != SITEID) {
$columns[] = 'status';
$headers[] = get_string('participationstatus', 'enrol');
$this->no_sorting('status');
};
$this->define_columns($columns);
$this->define_headers($headers);
// Make this table sorted by last name by default.
$this->sortable(true, 'lastname');
$this->no_sorting('select');
$this->no_sorting('roles');
if ($canseegroups) {
$this->no_sorting('groups');
}
$this->set_attribute('id', 'participants');
$this->countries = get_string_manager()->get_list_of_countries(true);
$this->extrafields = $extrafields;
if ($canseegroups) {
$this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
}
$this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
$this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
$this->profileroles = get_profile_roles($this->context);
$this->viewableroles = get_viewable_roles($this->context);
parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
@@ -401,141 +488,46 @@ class participants_table extends \table_sql implements dynamic_table {
* @param filterset $filterset The filterset object to get the filters from.
*/
public function set_filterset(filterset $filterset): void {
global $CFG, $OUTPUT;
// Store the filterset for later.
$this->filterset = $filterset;
$courseid = $filterset->get_filter('courseid')->current();
// Get the context.
$this->courseid = $filterset->get_filter('courseid')->current();
$this->course = get_course($this->courseid);
$this->context = \context_course::instance($this->courseid, MUST_EXIST);
// Process the filterset.
$currentgroup = null;
$this->currentgroup = null;
if ($filterset->has_filter('groups')) {
$currentgroup = $filterset->get_filter('groups')->current();
$this->currentgroup = $filterset->get_filter('groups')->current();
}
$contextid = null;
if ($filterset->has_filter('contextid')) {
$contextid = $filterset->get_filter('contextid')->current();
}
$roleid = null;
$this->roleid = null;
if ($filterset->has_filter('roles')) {
$roleid = $filterset->get_filter('roles')->current();
$this->roleid = $filterset->get_filter('roles')->current();
}
$enrolid = null;
$this->enrolid = null;
if ($filterset->has_filter('enrolments')) {
$enrolid = $filterset->get_filter('enrolments')->current();
$this->enrolid = $filterset->get_filter('enrolments')->current();
}
$status = -1;
$this->status = -1;
if ($filterset->has_filter('status')) {
$status = $filterset->get_filter('status')->current();
$this->status = $filterset->get_filter('status')->current();
}
$accesssince = null;
$this->accesssince = null;
if ($filterset->has_filter('accesssince')) {
$accesssince = $filterset->get_filter('accesssince')->current();
$this->accesssince = $filterset->get_filter('accesssince')->current();
}
$keywords = null;
$this->search = null;
if ($filterset->has_filter('keywords')) {
$this->search = $filterset->get_filter('keywords')->get_filter_values();
}
// Get the context.
$this->course = get_course($courseid);
$context = \context_course::instance($courseid, MUST_EXIST);
$this->context = $context;
// Define the headers and columns.
$headers = [];
$columns = [];
$bulkoperations = has_capability('moodle/course:bulkmessaging', $context);
if ($bulkoperations) {
$mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
'id' => 'select-all-participants',
'name' => 'select-all-participants',
'label' => $this->selectall ? get_string('deselectall') : get_string('selectall'),
'labelclasses' => 'sr-only',
'classes' => 'm-1',
'checked' => $this->selectall
]);
$headers[] = $OUTPUT->render($mastercheckbox);
$columns[] = 'select';
}
$headers[] = get_string('fullname');
$columns[] = 'fullname';
$extrafields = get_extra_user_fields($context);
foreach ($extrafields as $field) {
$headers[] = get_user_field_name($field);
$columns[] = $field;
}
$headers[] = get_string('roles');
$columns[] = 'roles';
// Get the list of fields we have to hide.
$hiddenfields = array();
if (!has_capability('moodle/course:viewhiddenuserfields', $context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
// Add column for groups if the user can view them.
$canseegroups = !isset($hiddenfields['groups']);
if ($canseegroups) {
$headers[] = get_string('groups');
$columns[] = 'groups';
}
// Do not show the columns if it exists in the hiddenfields array.
if (!isset($hiddenfields['lastaccess'])) {
if ($courseid == SITEID) {
$headers[] = get_string('lastsiteaccess');
} else {
$headers[] = get_string('lastcourseaccess');
}
$columns[] = 'lastaccess';
}
$canreviewenrol = has_capability('moodle/course:enrolreview', $context);
if ($canreviewenrol && $courseid != SITEID) {
$columns[] = 'status';
$headers[] = get_string('participationstatus', 'enrol');
$this->no_sorting('status');
};
$this->define_columns($columns);
$this->define_headers($headers);
// Make this table sorted by last name by default.
$this->sortable(true, 'lastname');
$this->no_sorting('select');
$this->no_sorting('roles');
if ($canseegroups) {
$this->no_sorting('groups');
}
$this->set_attribute('id', 'participants');
// Set the variables we need to use later.
$this->currentgroup = $currentgroup;
$this->accesssince = $accesssince;
$this->roleid = $roleid;
$this->enrolid = $enrolid;
$this->status = $status;
$this->countries = get_string_manager()->get_list_of_countries(true);
$this->extrafields = $extrafields;
$this->context = $context;
if ($canseegroups) {
$this->groups = groups_get_all_groups($courseid, 0, 0, 'g.*', true);
}
$this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
$this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
$this->profileroles = get_profile_roles($this->context);
$this->viewableroles = get_viewable_roles($this->context);
$this->define_baseurl($this->get_base_url());
}
/**
@@ -552,7 +544,31 @@ class participants_table extends \table_sql implements dynamic_table {
*
* @return moodle_url
*/
public static function get_base_url(): moodle_url {
return new moodle_url('');
public function get_base_url(): moodle_url {
if ($this->baseurl === null) {
return new moodle_url('/user/index.php', ['id' => $this->courseid]);
}
return $this->baseurl;
}
/**
* Get the context of the current table.
*
* Note: This function should not be called until after the filterset has been provided.
*
* @return context
*/
public function get_context(): ?context {
return $this->context;
}
/**
* Get the currently defined filterset.
*
* @return filterset
*/
public function get_filterset(): ?filterset {
return $this->filterset;
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020032700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2020032700.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9dev (Build: 20200327)'; // Human-friendly version name