mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
MDL-82715 customfield_number: Add automatically populated providers.
Added number of activity provider and also hooks for plugins.
This commit is contained in:
parent
09e56f2d1a
commit
8b5b91475e
28
.upgradenotes/MDL-82715-2024091706584681.yml
Normal file
28
.upgradenotes/MDL-82715-2024091706584681.yml
Normal file
@ -0,0 +1,28 @@
|
||||
issueNumber: MDL-82715
|
||||
notes:
|
||||
customfield_number:
|
||||
- message: >+
|
||||
New 'customfield_number\hook\add_custom_providers' hook has been added.
|
||||
|
||||
It allows automatic calculation of number course custom field.
|
||||
|
||||
|
||||
Added new class
|
||||
'\customfield_number\local\numberproviders\nofactivities'
|
||||
|
||||
that allows to automatically calculate number of activities of a given
|
||||
|
||||
type in a given course.
|
||||
|
||||
|
||||
Added new webservice customfield_number_recalculate_value to recalculate
|
||||
|
||||
a value of number course custom field.
|
||||
|
||||
|
||||
Added 'customfield_number\task\cron' cron task that recalculates
|
||||
|
||||
automatically calculated number course custom fields.
|
||||
|
||||
|
||||
type: improved
|
11
customfield/field/number/amd/build/recalculate.min.js
vendored
Normal file
11
customfield/field/number/amd/build/recalculate.min.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
define("customfield_number/recalculate",["exports","core/ajax","core/notification","core/loadingicon","core/pending"],(function(_exports,_ajax,_notification,_loadingicon,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Allows to recalculate a single value on demand
|
||||
*
|
||||
* @module customfield_number/recalculate
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){if(initialised)return;initialised=!0,document.addEventListener("click",(e=>{const target=e.target.closest(SELECTORS_wrapper+" "+SELECTORS_link);if(!target)return;const el=target.closest(SELECTORS_wrapper).querySelector(SELECTORS_value);if(!el)return;e.preventDefault();const fieldid=target.dataset.fieldid,instanceid=target.dataset.instanceid,pendingPromise=new _pending.default("recalculate_customfield_number");(0,_loadingicon.addIconToContainer)(el).then((()=>_ajax.default.call([{methodname:"customfield_number_recalculate_value",args:{fieldid:fieldid,instanceid:instanceid}}])[0])).then((data=>{el.innerHTML=data.value,pendingPromise.resolve()})).catch(_notification.default.exception)}))},_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const SELECTORS_wrapper='[data-fieldtype="wrapper"]',SELECTORS_value='[data-fieldtype="value"]',SELECTORS_link='[data-fieldtype="link"]';let initialised=!1}));
|
||||
|
||||
//# sourceMappingURL=recalculate.min.js.map
|
@ -0,0 +1 @@
|
||||
{"version":3,"file":"recalculate.min.js","sources":["../src/recalculate.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Allows to recalculate a single value on demand\n *\n * @module customfield_number/recalculate\n * @author 2024 Marina Glancy\n * @copyright 2024 Moodle Pty Ltd <support@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Pending from 'core/pending';\n\nconst SELECTORS = {\n wrapper: '[data-fieldtype=\"wrapper\"]',\n value: '[data-fieldtype=\"value\"]',\n link: '[data-fieldtype=\"link\"]',\n};\n\nlet initialised = false;\n\n/**\n * Init\n */\nexport function init() {\n if (initialised) {\n return;\n }\n\n initialised = true;\n\n document.addEventListener('click', (e) => {\n const target = e.target.closest(SELECTORS.wrapper + \" \" + SELECTORS.link);\n if (!target) {\n return;\n }\n const el = target.closest(SELECTORS.wrapper).querySelector(SELECTORS.value);\n if (!el) {\n return;\n }\n e.preventDefault();\n const fieldid = target.dataset.fieldid;\n const instanceid = target.dataset.instanceid;\n\n const pendingPromise = new Pending('recalculate_customfield_number');\n addIconToContainer(el).then(() => {\n return Ajax.call([{\n methodname: 'customfield_number_recalculate_value',\n args: {fieldid, instanceid}\n }])[0];\n }).then((data) => {\n el.innerHTML = data.value;\n pendingPromise.resolve();\n return;\n }).catch(Notification.exception);\n });\n}\n"],"names":["initialised","document","addEventListener","e","target","closest","SELECTORS","el","querySelector","preventDefault","fieldid","dataset","instanceid","pendingPromise","Pending","then","Ajax","call","methodname","args","data","innerHTML","value","resolve","catch","Notification","exception"],"mappings":";;;;;;;;yFAyCQA,mBAIJA,aAAc,EAEdC,SAASC,iBAAiB,SAAUC,UAC1BC,OAASD,EAAEC,OAAOC,QAAQC,kBAAoB,IAAMA,oBACrDF,oBAGCG,GAAKH,OAAOC,QAAQC,mBAAmBE,cAAcF,qBACtDC,UAGLJ,EAAEM,uBACIC,QAAUN,OAAOO,QAAQD,QACzBE,WAAaR,OAAOO,QAAQC,WAE5BC,eAAiB,IAAIC,iBAAQ,sEAChBP,IAAIQ,MAAK,IACjBC,cAAKC,KAAK,CAAC,CACdC,WAAY,uCACZC,KAAM,CAACT,QAAAA,QAASE,WAAAA,eAChB,KACLG,MAAMK,OACLb,GAAGc,UAAYD,KAAKE,MACpBT,eAAeU,aAEhBC,MAAMC,sBAAaC,uJAzCxBpB,kBACO,6BADPA,gBAEK,2BAFLA,eAGI,8BAGNN,aAAc"}
|
73
customfield/field/number/amd/src/recalculate.js
Normal file
73
customfield/field/number/amd/src/recalculate.js
Normal file
@ -0,0 +1,73 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Allows to recalculate a single value on demand
|
||||
*
|
||||
* @module customfield_number/recalculate
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
import Notification from 'core/notification';
|
||||
import {addIconToContainer} from 'core/loadingicon';
|
||||
import Pending from 'core/pending';
|
||||
|
||||
const SELECTORS = {
|
||||
wrapper: '[data-fieldtype="wrapper"]',
|
||||
value: '[data-fieldtype="value"]',
|
||||
link: '[data-fieldtype="link"]',
|
||||
};
|
||||
|
||||
let initialised = false;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
export function init() {
|
||||
if (initialised) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialised = true;
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest(SELECTORS.wrapper + " " + SELECTORS.link);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const el = target.closest(SELECTORS.wrapper).querySelector(SELECTORS.value);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
const fieldid = target.dataset.fieldid;
|
||||
const instanceid = target.dataset.instanceid;
|
||||
|
||||
const pendingPromise = new Pending('recalculate_customfield_number');
|
||||
addIconToContainer(el).then(() => {
|
||||
return Ajax.call([{
|
||||
methodname: 'customfield_number_recalculate_value',
|
||||
args: {fieldid, instanceid}
|
||||
}])[0];
|
||||
}).then((data) => {
|
||||
el.innerHTML = data.value;
|
||||
pendingPromise.resolve();
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
}
|
@ -44,7 +44,19 @@ class data_controller extends \core_customfield\data_controller {
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function instance_form_definition(MoodleQuickForm $mform): void {
|
||||
global $OUTPUT;
|
||||
|
||||
$field = $this->get_field();
|
||||
$elementname = $this->get_form_element_name();
|
||||
if (!$field->is_editable()) {
|
||||
// Display the value as static text.
|
||||
$instanceid = (int)$this->get('instanceid');
|
||||
$data = ['value' => $this->export_value(), 'fieldid' => $field->get('id'), 'instanceid' => $instanceid];
|
||||
$value = $OUTPUT->render_from_template('customfield_number/staticvalue', $data);
|
||||
$mform->addElement('static', $elementname . '_static', $this->get_field()->get_formatted_name(),
|
||||
$value);
|
||||
return;
|
||||
}
|
||||
|
||||
$mform->addElement('float', $elementname, $this->get_field()->get_formatted_name());
|
||||
if (!$this->get('id')) {
|
||||
@ -63,8 +75,11 @@ class data_controller extends \core_customfield\data_controller {
|
||||
$errors = parent::instance_form_validation($data, $files);
|
||||
|
||||
$elementname = $this->get_form_element_name();
|
||||
$elementvalue = $data[$elementname];
|
||||
|
||||
$elementvalue = '';
|
||||
// Providers calculate values automatically, so nothing to validate.
|
||||
if (!provider_base::instance($this->get_field())) {
|
||||
$elementvalue = $data[$elementname];
|
||||
}
|
||||
$minimumvalue = $this->get_field()->get_configdata_property('minimumvalue') ?? '';
|
||||
$maximumvalue = $this->get_field()->get_configdata_property('maximumvalue') ?? '';
|
||||
|
||||
@ -107,6 +122,10 @@ class data_controller extends \core_customfield\data_controller {
|
||||
* @return float|null
|
||||
*/
|
||||
public function get_default_value(): ?float {
|
||||
// If a provider is available, use its default value.
|
||||
if ($provider = provider_base::instance($this->get_field())) {
|
||||
return $provider->get_default_value();
|
||||
}
|
||||
$defaultvalue = $this->get_field()->get_configdata_property('defaultvalue');
|
||||
if ($this->is_empty($defaultvalue)) {
|
||||
return null;
|
||||
@ -117,9 +136,9 @@ class data_controller extends \core_customfield\data_controller {
|
||||
/**
|
||||
* Returns value in a human-readable format
|
||||
*
|
||||
* @return string|null
|
||||
* @return string|float|null
|
||||
*/
|
||||
public function export_value(): ?string {
|
||||
public function export_value(): string|float|null {
|
||||
/** @var field_controller $field */
|
||||
$field = $this->get_field();
|
||||
return $field->prepare_field_for_display($this->get_value(), $this->get_context());
|
||||
|
97
customfield/field/number/classes/external/recalculate.php
vendored
Normal file
97
customfield/field/number/classes/external/recalculate.php
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number\external;
|
||||
|
||||
use core\exception\invalid_parameter_exception;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_api;
|
||||
use core_external\external_value;
|
||||
use customfield_number\provider_base;
|
||||
|
||||
/**
|
||||
* Implementation of web service customfield_number_recalculate_value
|
||||
*
|
||||
* @package customfield_number
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class recalculate extends external_api {
|
||||
|
||||
/**
|
||||
* Describes the parameters for customfield_number_recalculate_value
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'fieldid' => new external_value(PARAM_INT, 'Field id', VALUE_REQUIRED),
|
||||
'instanceid' => new external_value(PARAM_INT, 'Instance id', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of web service customfield_number_recalculate_value
|
||||
*
|
||||
* @param int $fieldid
|
||||
* @param int $instanceid
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(int $fieldid, int $instanceid): array {
|
||||
// Parameter validation.
|
||||
['fieldid' => $fieldid, 'instanceid' => $instanceid] = self::validate_parameters(
|
||||
self::execute_parameters(),
|
||||
['fieldid' => $fieldid, 'instanceid' => $instanceid]
|
||||
);
|
||||
|
||||
// Access validation.
|
||||
$context = \context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
$field = \core_customfield\field_controller::create($fieldid);
|
||||
$provider = provider_base::instance($field);
|
||||
if (!$provider) {
|
||||
throw new invalid_parameter_exception('Invalid parameter');
|
||||
}
|
||||
|
||||
$handler = $field->get_handler();
|
||||
if (!$handler->can_edit($field, $instanceid)) {
|
||||
throw new \moodle_exception('nopermissions', '', '', get_string('update'));
|
||||
}
|
||||
|
||||
$provider->recalculate($instanceid);
|
||||
|
||||
$data = \core_customfield\api::get_instance_fields_data(
|
||||
[$fieldid => $field], $instanceid)[$fieldid];
|
||||
|
||||
return ['value' => $data->export_value()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure for customfield_number_recalculate_value
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'value' => new external_value(PARAM_RAW, 'Recalculated value (prepared for display)'),
|
||||
]);
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ use MoodleQuickForm;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class field_controller extends \core_customfield\field_controller {
|
||||
|
||||
/**
|
||||
* Add form elements for editing the custom field definition
|
||||
*
|
||||
@ -41,6 +40,15 @@ class field_controller extends \core_customfield\field_controller {
|
||||
$mform->addElement('header', 'specificsettings', get_string('specificsettings', 'customfield_number'));
|
||||
$mform->setExpanded('specificsettings');
|
||||
|
||||
$providers = provider_base::get_all_providers($this);
|
||||
if (count($providers) > 0) {
|
||||
$this->add_field_type_select($mform, $providers);
|
||||
// Add form config elements for each provider.
|
||||
foreach ($providers as $provider) {
|
||||
$provider->config_form_definition($mform);
|
||||
}
|
||||
}
|
||||
|
||||
// Default value.
|
||||
$mform->addElement('float', 'configdata[defaultvalue]', get_string('defaultvalue', 'core_customfield'));
|
||||
if ($this->get_configdata_property('defaultvalue') === null) {
|
||||
@ -68,7 +76,7 @@ class field_controller extends \core_customfield\field_controller {
|
||||
|
||||
// Display format settings.
|
||||
// TODO: Change this after MDL-82996 fixed.
|
||||
$randelname = 'str_' . random_string();
|
||||
$randelname = 'str_display_format';
|
||||
$mform->addGroup([], $randelname, html_writer::tag('h4', get_string('headerdisplaysettings', 'customfield_number')));
|
||||
|
||||
// Display template.
|
||||
@ -90,6 +98,22 @@ class field_controller extends \core_customfield\field_controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds selector to provider for field population.
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
* @param array $providers
|
||||
*/
|
||||
protected function add_field_type_select(\MoodleQuickForm $mform, array $providers): void {
|
||||
$autooptions = [];
|
||||
foreach ($providers as $provider) {
|
||||
$autooptions[get_class($provider)] = $provider->get_name();
|
||||
}
|
||||
$options = [get_string('genericfield', 'customfield_number')];
|
||||
$options = array_merge($options, $autooptions);
|
||||
$mform->addElement('select', 'configdata[fieldtype]', get_string('fieldtype', 'customfield_number'), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the data on the field configuration form
|
||||
*
|
||||
@ -110,6 +134,11 @@ class field_controller extends \core_customfield\field_controller {
|
||||
$minimumvalue = $data['configdata']['minimumvalue'] ?? '';
|
||||
$maximumvalue = $data['configdata']['maximumvalue'] ?? '';
|
||||
|
||||
foreach (provider_base::get_all_providers($this) as $provider) {
|
||||
if (array_key_exists('fieldtype', $data["configdata"]) && $data["configdata"]["fieldtype"] == get_class($provider)) {
|
||||
$errors = array_merge($errors, $provider->config_form_validation($data, $files));
|
||||
}
|
||||
}
|
||||
// Early exit if neither maximum/minimum are specified.
|
||||
if ($minimumvalue === '' && $maximumvalue === '') {
|
||||
return $errors;
|
||||
@ -140,9 +169,9 @@ class field_controller extends \core_customfield\field_controller {
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param context|null $context
|
||||
* @return string|null
|
||||
* @return string|float|null
|
||||
*/
|
||||
public function prepare_field_for_display(mixed $value, ?context $context = null): ?string {
|
||||
public function prepare_field_for_display(mixed $value, ?context $context = null): string|null|float {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
@ -154,12 +183,26 @@ class field_controller extends \core_customfield\field_controller {
|
||||
}
|
||||
} else {
|
||||
// Let's format the value.
|
||||
$value = format_float((float) $value, $decimalplaces);
|
||||
|
||||
// Apply the display format.
|
||||
$format = $this->get_configdata_property('display') ?? '{value}';
|
||||
$value = str_replace('{value}', $value, $format);
|
||||
$provider = provider_base::instance($this);
|
||||
if ($provider) {
|
||||
$value = $provider->prepare_export_value($value, $context);
|
||||
} else {
|
||||
$value = format_float((float)$value, $decimalplaces);
|
||||
// Apply the display format.
|
||||
$format = $this->get_configdata_property('display') ?? '{value}';
|
||||
$value = str_replace('{value}', $value, $format);
|
||||
}
|
||||
}
|
||||
return format_string($value, true, ['context' => $context ?? system::instance()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the value of this field be manually editable in the edit forms
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_editable(): bool {
|
||||
return $this->get_configdata_property('fieldtype') == false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number\hook;
|
||||
|
||||
use customfield_number\provider_base;
|
||||
use customfield_number\field_controller;
|
||||
|
||||
/**
|
||||
* Hook for adding custom providers to the provider_base.
|
||||
*
|
||||
* @package customfield_number
|
||||
* @copyright 2024 Ilya Tregubov <ilya.tregubov@proton.me>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\core\attribute\label('This hook allows adding custom providers to calculate custom field automatically like price for course')]
|
||||
class add_custom_providers {
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $providers = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param field_controller $field the custom field controller
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var field_controller the custom field controller */
|
||||
public readonly field_controller $field,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a provider to the hook.
|
||||
*
|
||||
* @param provider_base $provider
|
||||
*/
|
||||
public function add_provider(provider_base $provider): void {
|
||||
$this->providers[] = $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of providers added through the hook.
|
||||
*
|
||||
* @return provider_base[]
|
||||
*/
|
||||
public function get_providers(): array {
|
||||
return $this->providers;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number\local\numberproviders;
|
||||
|
||||
use context_course;
|
||||
use core_plugin_manager;
|
||||
use customfield_number\data_controller;
|
||||
use customfield_number\provider_base;
|
||||
use MoodleQuickForm;
|
||||
|
||||
/**
|
||||
* Class nofactivities to calculate number of activities in the course.
|
||||
*
|
||||
* @package customfield_number
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class nofactivities extends provider_base {
|
||||
|
||||
/**
|
||||
* Provider name
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return get_string('nofactivities', 'customfield_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provider is available for the current field.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available(): bool {
|
||||
return $this->field->get_handler()->get_component() === 'core_course' &&
|
||||
$this->field->get_handler()->get_area() === 'course';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add autocomplete field for selecting activity type.
|
||||
* Also add checkbox to display the field when the number of activities is zero.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
*/
|
||||
public function config_form_definition(MoodleQuickForm $mform): void {
|
||||
$options = [];
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('mod');
|
||||
foreach ($plugins as $plugin) {
|
||||
$options[$plugin->name] = $plugin->displayname;
|
||||
}
|
||||
|
||||
// Define the label for the autocomplete element.
|
||||
$valuelabel = get_string('activitytypes', 'customfield_number');
|
||||
// Add autocomplete element.
|
||||
$mform->addElement('autocomplete', 'configdata[activitytypes]', $valuelabel, $options, ['multiple' => true])
|
||||
->setHiddenLabel(true);
|
||||
$mform->hideIf('configdata[activitytypes]', 'configdata[fieldtype]', 'ne', get_class($this));
|
||||
$mform->hideIf('configdata[decimalplaces]', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
$mform->hideIf('configdata[display]', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
$mform->hideIf('str_display_format', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
$mform->hideIf('configdata[defaultvalue]', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
$mform->hideIf('configdata[minimumvalue]', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
$mform->hideIf('configdata[maximumvalue]', 'configdata[fieldtype]', 'eq', get_class($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate the number of activities in the course.
|
||||
*
|
||||
* @param int|null $instanceid
|
||||
*/
|
||||
public function recalculate(?int $instanceid = null): void {
|
||||
global $DB;
|
||||
$types = $this->field->get_configdata_property('activitytypes');
|
||||
$displaywhenzero = $this->field->get_configdata_property('displaywhenzero');
|
||||
if (!empty($types)) {
|
||||
// Prepare the SQL for non-empty types.
|
||||
[$sqlin, $params] = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Subquery to select all modules of selected types.
|
||||
$cmsql = "SELECT m.id
|
||||
FROM {modules} m
|
||||
WHERE m.name $sqlin
|
||||
AND m.visible = 1";
|
||||
|
||||
$where = '';
|
||||
if ($instanceid) {
|
||||
$where = "AND c.id = :courseid ";
|
||||
$params['courseid'] = $instanceid;
|
||||
}
|
||||
|
||||
// Number of activities is stored in database. So we count the number and check if it matches the stored value.
|
||||
// We update value in database if it doesn't match counted value.
|
||||
$sql = "SELECT c.id, COUNT(cm.id) AS cnt, d.id AS dataid, d.decvalue
|
||||
FROM {course} c
|
||||
LEFT JOIN {customfield_data} d
|
||||
ON d.fieldid = :fieldid
|
||||
AND d.instanceid = c.id
|
||||
LEFT JOIN {course_modules} cm
|
||||
ON cm.course = c.id
|
||||
AND cm.visible = 1
|
||||
AND cm.deletioninprogress = 0
|
||||
AND cm.module IN ($cmsql)
|
||||
WHERE c.id <> :siteid $where
|
||||
GROUP BY c.id, d.id, d.decvalue
|
||||
";
|
||||
$params['fieldid'] = $fieldid = $this->field->get('id');
|
||||
$records = $DB->get_records_sql($sql, $params + ['siteid' => SITEID]);
|
||||
foreach ($records as $record) {
|
||||
$value = (int)$record->cnt;
|
||||
if (!isset($displaywhenzero) && !$value) {
|
||||
// Do not display the field when the number of activities is zero.
|
||||
if ($record->dataid) {
|
||||
(new data_controller((int)$record->dataid, (object)['id' => $record->dataid]))->delete();
|
||||
}
|
||||
} else if (empty($record->dataid) || (int)$record->decvalue != $value) {
|
||||
// Stored value is out of date.
|
||||
$data = \core_customfield\api::get_instance_fields_data(
|
||||
[$fieldid => $this->field], (int)$record->id)[$fieldid];
|
||||
$data->set('contextid', context_course::instance($record->id)->id);
|
||||
$data->set('decvalue', $value);
|
||||
$data->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the data on the field configuration form for number of activities provider.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array associative array of error messages
|
||||
*/
|
||||
public function config_form_validation(array $data, array $files = []): array {
|
||||
$errors = [];
|
||||
if (empty($data['configdata']['activitytypes'])) {
|
||||
$errors['configdata[activitytypes]'] = get_string('err_required', 'form');
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preparation for export for number of activities provider.
|
||||
*
|
||||
* @param mixed $value String or float
|
||||
* @param \context|null $context |null $context Context
|
||||
* @return ?string
|
||||
*/
|
||||
public function prepare_export_value(mixed $value, ?\context $context = null): ?string {
|
||||
if (trim((string)$value) === '') {
|
||||
return null;
|
||||
} else {
|
||||
return format_float((float)$value, 0);
|
||||
}
|
||||
}
|
||||
}
|
44
customfield/field/number/classes/missing_provider.php
Normal file
44
customfield/field/number/classes/missing_provider.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number;
|
||||
|
||||
/**
|
||||
* Class missing_provider
|
||||
*
|
||||
* @package customfield_number
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class missing_provider extends provider_base {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return get_string('invalidprovider', 'customfield_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function is_available(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
147
customfield/field/number/classes/provider_base.php
Normal file
147
customfield/field/number/classes/provider_base.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number;
|
||||
|
||||
use context;
|
||||
use MoodleQuickForm;
|
||||
|
||||
/**
|
||||
* Class provider_base
|
||||
*
|
||||
* @package customfield_number
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class provider_base {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param field_controller $field A field controller.
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var field_controller the custom field controller */
|
||||
protected field_controller $field,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider name
|
||||
*/
|
||||
abstract public function get_name(): string;
|
||||
|
||||
/**
|
||||
* If provide is available for the current field.
|
||||
*/
|
||||
abstract public function is_available(): bool;
|
||||
|
||||
/**
|
||||
* Add provider specific fields for form.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
*/
|
||||
public function config_form_definition(MoodleQuickForm $mform): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate field value.
|
||||
*
|
||||
* @param int|null $instanceid
|
||||
*/
|
||||
public function recalculate(?int $instanceid = null): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value if there is no value in the database (or there is a null)
|
||||
*
|
||||
* Usually returns either null or 0
|
||||
*
|
||||
* @return null|float
|
||||
*/
|
||||
public function get_default_value(): ?float {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider specific value preparation for export.
|
||||
*
|
||||
* @param mixed $value String or float
|
||||
* @param context|null $context Context
|
||||
* @return ?string
|
||||
*/
|
||||
public function prepare_export_value(mixed $value, ?\context $context = null): ?string {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new provider instance.
|
||||
*
|
||||
* @param field_controller $field Field
|
||||
*/
|
||||
final public static function instance(\core_customfield\field_controller $field): ?self {
|
||||
if ($field->get('type') !== 'number' || !($field instanceof field_controller)) {
|
||||
return null;
|
||||
}
|
||||
$classname = $field->get_configdata_property('fieldtype');
|
||||
if (!$classname) {
|
||||
return null;
|
||||
}
|
||||
if (!class_exists($classname) || !is_a($classname, self::class, true)) {
|
||||
return new missing_provider($field);
|
||||
}
|
||||
return new $classname($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of applicable automatic providers for this field
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @return provider_base[]
|
||||
*/
|
||||
final public static function get_all_providers(field_controller $field): array {
|
||||
/** @var provider_base[] $allproviders */
|
||||
$allproviders = [
|
||||
new \customfield_number\local\numberproviders\nofactivities($field),
|
||||
];
|
||||
|
||||
// Custom providers.
|
||||
$hook = new \customfield_number\hook\add_custom_providers($field);
|
||||
|
||||
// Dispatch the hook and collect custom providers.
|
||||
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||
|
||||
$allproviders = array_merge($allproviders, $hook->get_providers());
|
||||
|
||||
return array_filter($allproviders, fn($p) => $p->is_available());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the data on the field configuration form
|
||||
*
|
||||
* Providers can override it
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array associative array of error messages
|
||||
*/
|
||||
public function config_form_validation(array $data, array $files = []): array {
|
||||
return [];
|
||||
}
|
||||
}
|
75
customfield/field/number/classes/task/cron.php
Normal file
75
customfield/field/number/classes/task/cron.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace customfield_number\task;
|
||||
|
||||
use core\task\scheduled_task;
|
||||
use core_customfield\category_controller;
|
||||
use core_customfield\field_controller;
|
||||
use customfield_number\provider_base;
|
||||
|
||||
/**
|
||||
* Scheduled task for customfield_number to recalculate automatically populated fields.
|
||||
*
|
||||
* @package customfield_number
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cron extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the task (shown to admins)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return get_string('crontaskname', 'customfield_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate automatically populated number fields.
|
||||
*
|
||||
* Throw exceptions on errors (the job will be retried).
|
||||
*/
|
||||
public function execute(): void {
|
||||
global $DB;
|
||||
// Get all number custom fields.
|
||||
$sql = "SELECT f.*, c.component, c.area, c.itemid, c.contextid
|
||||
FROM {customfield_field} f
|
||||
JOIN {customfield_category} c ON f.categoryid = c.id
|
||||
WHERE f.type = ?";
|
||||
$res = $DB->get_records_sql($sql, ['number']);
|
||||
foreach ($res as $row) {
|
||||
$cat = (object)[
|
||||
'id' => $row->categoryid,
|
||||
'component' => $row->component,
|
||||
'area' => $row->area,
|
||||
'itemid' => $row->itemid,
|
||||
'contextid' => $row->contextid,
|
||||
];
|
||||
unset($row->component, $row->area, $row->itemid, $row->contextid);
|
||||
$category = category_controller::create(0, $cat);
|
||||
// Create an instance of field controller for each field and recalculate the value if field provider is available.
|
||||
$field = field_controller::create(0, $row, $category);
|
||||
if ($provider = provider_base::instance($field)) {
|
||||
if ($provider->is_available()) {
|
||||
$provider->recalculate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
customfield/field/number/db/services.php
Normal file
38
customfield/field/number/db/services.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* External functions and service declaration for Number
|
||||
*
|
||||
* Documentation: {@link https://moodledev.io/docs/apis/subsystems/external/description}
|
||||
*
|
||||
* @package customfield_number
|
||||
* @category webservice
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$functions = [
|
||||
'customfield_number_recalculate_value' => [
|
||||
'classname' => customfield_number\external\recalculate::class,
|
||||
'description' => 'This web service is used to recalculate the value of automatically populated number custom field.',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
];
|
40
customfield/field/number/db/tasks.php
Normal file
40
customfield/field/number/db/tasks.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Scheduled task definitions for Number
|
||||
*
|
||||
* Documentation: {@link https://moodledev.io/docs/apis/subsystems/task/scheduled}
|
||||
*
|
||||
* @package customfield_number
|
||||
* @category task
|
||||
* @author 2024 Marina Glancy
|
||||
* @copyright 2024 Moodle Pty Ltd <support@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$tasks = [
|
||||
[
|
||||
'classname' => customfield_number\task\cron::class,
|
||||
'minute' => 'R',
|
||||
'hour' => '2',
|
||||
'day' => '*',
|
||||
'month' => '*',
|
||||
'dayofweek' => '*',
|
||||
],
|
||||
];
|
@ -24,6 +24,9 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$string['activitytypes'] = 'Activity types';
|
||||
$string['automaticallypopulated'] = 'Automatically populated';
|
||||
$string['crontaskname'] = 'Populating automatic number custom fields';
|
||||
$string['decimalplaces'] = 'Decimal places';
|
||||
$string['defaultvalueconfigerror'] = 'Default value must be between minimum and maximum';
|
||||
$string['display'] = 'Display template';
|
||||
@ -37,12 +40,18 @@ $string['displaywhenzero'] = 'Display when zero';
|
||||
$string['displaywhenzero_help'] = 'How to display the field value when the value is "0". For example, in case of a price you can display the word "Free" but in case of the duration you may want to leave it empty since it means that the duration was not estimated.
|
||||
|
||||
Leave empty if you do not want to display anything at all when the value is set to "0".';
|
||||
$string['fieldtype'] = 'Field type';
|
||||
$string['genericfield'] = 'Generic field for any numeric data';
|
||||
$string['headerdisplaysettings'] = 'Display format';
|
||||
$string['invalidprovider'] = 'Invalid provider';
|
||||
$string['manualinput'] = 'Manual input';
|
||||
$string['maximumvalue'] = 'Maximum value';
|
||||
$string['maximumvalueerror'] = 'Value must be less than or equal to {$a}';
|
||||
$string['minimumvalue'] = 'Minimum value';
|
||||
$string['minimumvalueconfigerror'] = 'Minimum value must be less than maximum';
|
||||
$string['minimumvalueerror'] = 'Value must be greater than or equal to {$a}';
|
||||
$string['missingrequired'] = 'Missing instanceid or fieldid';
|
||||
$string['nofactivities'] = 'Number of activities in the course';
|
||||
$string['pluginname'] = 'Number';
|
||||
$string['privacy:metadata'] = 'The number custom field plugin does not store any personal data';
|
||||
$string['specificsettings'] = 'Number field settings';
|
||||
|
39
customfield/field/number/templates/staticvalue.mustache
Normal file
39
customfield/field/number/templates/staticvalue.mustache
Normal file
@ -0,0 +1,39 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template customfield_number/staticvalue
|
||||
|
||||
Automatically calculated value for a number field.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"value": 5,
|
||||
"instanceid": 4,
|
||||
"fieldid": 5
|
||||
}
|
||||
}}
|
||||
<div class="customfield_number-recalculate-wrapper d-flex mt-2" data-fieldtype="wrapper">
|
||||
<div class="customfield_number-recalculate-value" data-fieldtype="value">{{{value}}}</div>
|
||||
{{#instanceid}}
|
||||
<div class="customfield_number-recalculate-link ms-2">
|
||||
<a href="#" data-customfield_number-action="recalculate" data-fieldtype="link"
|
||||
data-fieldid="{{fieldid}}" data-instanceid="{{instanceid}}">{{#pix}}t/reload, core{{/pix}}{{#str}} update, core {{/str}}</a>
|
||||
</div>
|
||||
{{/instanceid}}
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['customfield_number/recalculate'], function(Recalculate) {
|
||||
Recalculate.init();
|
||||
});
|
||||
{{/js}}
|
@ -91,3 +91,108 @@ Feature: Managers can manage course custom fields number
|
||||
| template | whenzero | fieldvalue | expectedvalue |
|
||||
| $ {value} | 0 | 150 | $ 150.00 |
|
||||
| {value} | Free | 0 | Free |
|
||||
|
||||
Scenario: Automatically populated field should hide some field form elements
|
||||
Given I click on "Add a new custom field" "link"
|
||||
And I click on "Number" "link"
|
||||
And I should see "Default value"
|
||||
And I should see "Minimum value"
|
||||
And I should see "Maximum value"
|
||||
And I should see "Decimal places"
|
||||
And I should see "Display format"
|
||||
And I should see "Display template"
|
||||
And I should see "Display when zero"
|
||||
And I should see "Display template"
|
||||
And I should not see "No selection"
|
||||
|
||||
When I set the following fields to these values:
|
||||
| Name | Number field |
|
||||
| Field type | Number of activities in the course |
|
||||
Then I should not see "Default value"
|
||||
And I should not see "Minimum value"
|
||||
And I should not see "Maximum value"
|
||||
And I should not see "Decimal places"
|
||||
And I should not see "Display format"
|
||||
And I should not see "Display template"
|
||||
And I should see "Display when zero"
|
||||
And I should not see "Display when empty"
|
||||
And I should see "No selection"
|
||||
And I open the autocomplete suggestions list
|
||||
And "Assignment" "autocomplete_suggestions" should exist
|
||||
|
||||
Scenario: Automatically populated field should be displayed in course settings
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Test course1 | C1 | 0 |
|
||||
| Test course2 | C2 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | 1 | teacher@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher | C1 | editingteacher |
|
||||
| teacher | C2 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section | visible |
|
||||
| assign | Assignment1 | Test description | C1 | assign1 | 1 | 0 |
|
||||
| assign | Assignment2 | Test description | C1 | assign2 | 1 | 1 |
|
||||
| assign | Assignment3 | Test description | C1 | assign3 | 1 | 1 |
|
||||
| quiz | Quiz1 | Test description | C1 | quiz1 | 1 | 0 |
|
||||
| quiz | Quiz2 | Test description | C1 | quiz2 | 1 | 1 |
|
||||
| forum | Forum1 | Test description | C1 | forum1 | 1 | 0 |
|
||||
| forum | Forum2 | Test description | C1 | forum2 | 1 | 1 |
|
||||
| forum | Forum3 | Test description | C1 | forum3 | 1 | 1 |
|
||||
| forum | Forum4 | Test description | C1 | forum4 | 1 | 1 |
|
||||
| quiz | QuizC2 | Test description | C2 | quizC2 | 1 | 1 |
|
||||
Given I click on "Add a new custom field" "link"
|
||||
And I click on "Number" "link"
|
||||
When I set the following fields to these values:
|
||||
| Name | Number field |
|
||||
| Short name | numberfield |
|
||||
| Field type | Number of activities in the course |
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
|
||||
And I should see "You must supply a value here." in the "Adding a new Number" "dialogue"
|
||||
And I set the following fields to these values:
|
||||
| Activity types | Assignment, Forum |
|
||||
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
|
||||
And I log in as "teacher"
|
||||
And I am on "C1" course homepage
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I click on "Update" "link"
|
||||
And I should see "5" in the ".customfield_number-recalculate-value" "css_element"
|
||||
And I add a assign activity to course "C1" section "1" and I fill the form with:
|
||||
| Assignment name | Assignment4 |
|
||||
| ID number | assign4 |
|
||||
| Description | Test description |
|
||||
And I run the scheduled task "\customfield_number\task\cron"
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I should see "6" in the ".customfield_number-recalculate-value" "css_element"
|
||||
And I add a quiz activity to course "C1" section "1" and I fill the form with:
|
||||
| Name | Quiz3 |
|
||||
| ID number | quiz3 |
|
||||
| Description | Test description |
|
||||
And I run the scheduled task "\customfield_number\task\cron"
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I should see "6" in the ".customfield_number-recalculate-value" "css_element"
|
||||
|
||||
And I am on "C2" course homepage
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I should see "0" in the ".customfield_number-recalculate-value" "css_element"
|
||||
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Default settings > Course custom fields" in site administration
|
||||
And I click on "Edit" "link" in the "Number field" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Display when zero | |
|
||||
And I click on "Save changes" "button" in the "Updating Number field" "dialogue"
|
||||
And I run the scheduled task "\customfield_number\task\cron"
|
||||
And I log in as "teacher"
|
||||
And I am on "C2" course homepage
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I should not see "0" in the ".customfield_number-recalculate-value" "css_element"
|
||||
|
121
customfield/field/number/tests/external/recalculate_test.php
vendored
Normal file
121
customfield/field/number/tests/external/recalculate_test.php
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number\external;
|
||||
|
||||
use core_customfield_generator;
|
||||
use core_external\external_api;
|
||||
use moodle_exception;
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the customfield_number\external\recalculate.
|
||||
*
|
||||
* @package customfield_number
|
||||
* @category external
|
||||
* @copyright 2024 Ilya Tregubov <ilya.tregubov@proton.me>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \customfield_number\external\recalculate
|
||||
*/
|
||||
final class recalculate_test extends \externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Tests when teacher can not edit locked field.
|
||||
*/
|
||||
public function test_execute_no_permission(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
[$course, $field] = $this->prepare_course();
|
||||
$configdata = [
|
||||
'fieldtype' => 'customfield_number\\local\\numberproviders\\nofactivities',
|
||||
'activitytypes' => ['assign', 'forum'],
|
||||
'locked' => 1,
|
||||
];
|
||||
$field->set('configdata', json_encode($configdata));
|
||||
$field->save();
|
||||
|
||||
$context = \context_course::instance($course->id);
|
||||
$roleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
|
||||
$this->unassignUserCapability('moodle/course:changelockedcustomfields', $context->id, $roleid);
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
$this->expectExceptionMessage(get_string('update'));
|
||||
recalculate::execute($field->get('id'), (int)$course->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when all data is valid.
|
||||
*/
|
||||
public function test_execute(): void {
|
||||
$this->resetAfterTest();
|
||||
[$course, $field] = $this->prepare_course();
|
||||
$result = recalculate::execute($field->get('id'), (int)$course->id);
|
||||
$result = external_api::clean_returnvalue(recalculate::execute_returns(), $result);
|
||||
$this->assertEquals(3.0, $result['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a course with number custom field.
|
||||
* @return array An array with the course object and field object.
|
||||
*/
|
||||
private function prepare_course(): array {
|
||||
global $DB;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Add teacher to a course.
|
||||
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
|
||||
|
||||
$this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'name' => 'Assign1', 'visible' => 1]);
|
||||
$this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'name' => 'Assign2', 'visible' => 1]);
|
||||
$this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'name' => 'Assign3', 'visible' => 0]);
|
||||
|
||||
$this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 'name' => 'Quiz1', 'visible' => 1]);
|
||||
$this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 'name' => 'Quiz2', 'visible' => 0]);
|
||||
|
||||
$this->getDataGenerator()->create_module('forum', ['course' => $course->id, 'name' => 'Forum1', 'visible' => 1]);
|
||||
|
||||
/** @var core_customfield_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
|
||||
$category = $generator->create_category();
|
||||
$field = $generator->create_field(
|
||||
[
|
||||
'categoryid' => $category->get('id'),
|
||||
'shortname' => 'myfield', 'type' => 'number',
|
||||
'configdata' => [
|
||||
'fieldtype' => 'customfield_number\\local\\numberproviders\\nofactivities',
|
||||
'activitytypes' => ['assign', 'forum'],
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->setUser($teacher);
|
||||
|
||||
return [$course, $field];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace customfield_number\local\numberproviders\nofactivities;
|
||||
|
||||
use advanced_testcase;
|
||||
use customfield_number\local\numberproviders\nofactivities;
|
||||
use customfield_number\provider_base;
|
||||
|
||||
/**
|
||||
* Tests for the number of activities
|
||||
*
|
||||
* @package customfield_number
|
||||
* @covers \customfield_number\local\numberproviders\nofactivities
|
||||
* @copyright 2024 Ilya Tregubov <ilya.tregubov@proton.me>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class nofactivities_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test that we can automatically calculate number of activities in courses.
|
||||
*/
|
||||
public function test_recalculate(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Add some activities to the courses.
|
||||
$assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
||||
$assign1 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
|
||||
$assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
|
||||
$assigngenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
|
||||
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quizgenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
|
||||
$quizgenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
|
||||
$quizgenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
|
||||
|
||||
$forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
$forumgenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
|
||||
$forumgenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
|
||||
$forumgenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
|
||||
|
||||
$assigngenerator->create_instance(['course' => $course2->id, 'visible' => 1]);
|
||||
$assigngenerator->create_instance(['course' => $course2->id, 'visible' => 1]);
|
||||
$assigngenerator->create_instance(['course' => $course2->id, 'visible' => 1]);
|
||||
|
||||
/** @var \core_customfield_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
|
||||
// Create a category and field.
|
||||
$category = $generator->create_category();
|
||||
$field = $generator->create_field([
|
||||
'categoryid' => $category->get('id'),
|
||||
'type' => 'number',
|
||||
'configdata' => [
|
||||
'fieldtype' => 'customfield_number\local\numberproviders\nofactivities',
|
||||
"activitytypes" => ["assign", "forum"],
|
||||
],
|
||||
]);
|
||||
|
||||
// Test if the provider has been added correctly.
|
||||
$providers = provider_base::get_all_providers($field);
|
||||
$this->assertNotEmpty($providers);
|
||||
$this->assertInstanceOf(nofactivities::class, $providers[0]);
|
||||
|
||||
// Calculate only in course1.
|
||||
$providers[0]->recalculate((int)$course1->id);
|
||||
$course1customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course1->id]);
|
||||
$course2customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course2->id]);
|
||||
|
||||
$this->assertEquals(4.0000, $course1customfield);
|
||||
$this->assertEquals(false, $course2customfield);
|
||||
|
||||
// Calculate in all courses.
|
||||
$providers[0]->recalculate();
|
||||
$course1customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course1->id]);
|
||||
$course2customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course2->id]);
|
||||
|
||||
$this->assertEquals(4.0000, $course1customfield);
|
||||
$this->assertEquals(3.0000, $course2customfield);
|
||||
|
||||
// Delete some assign module.
|
||||
$cm = get_coursemodule_from_instance('assign', $assign1->id);
|
||||
course_delete_module($cm->id);
|
||||
$providers[0]->recalculate((int)$course1->id);
|
||||
$course1customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course1->id]);
|
||||
// Module is marked as deleted.
|
||||
$this->assertEquals(3.0000, $course1customfield);
|
||||
|
||||
// Now, run the course module deletion adhoc task.
|
||||
\phpunit_util::run_all_adhoc_tasks();
|
||||
$providers[0]->recalculate((int)$course1->id);
|
||||
$course1customfield = $DB->get_field('customfield_data', 'decvalue', ['instanceid' => $course1->id]);
|
||||
$this->assertEquals(3.0000, $course1customfield);
|
||||
}
|
||||
}
|
@ -25,6 +25,6 @@
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->component = 'customfield_number';
|
||||
$plugin->version = 2024042200;
|
||||
$plugin->version = 2024042202;
|
||||
$plugin->requires = 2024041600;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
Loading…
x
Reference in New Issue
Block a user