MDL-82715 customfield_number: Add automatically populated providers.

Added number of activity provider and also hooks for plugins.
This commit is contained in:
Ilya Tregubov 2024-09-16 08:42:43 +08:00
parent 09e56f2d1a
commit 8b5b91475e
20 changed files with 1259 additions and 14 deletions

View 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

View 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

View File

@ -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"}

View 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);
});
}

View File

@ -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());

View 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)'),
]);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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;
}
}

View 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 [];
}
}

View 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();
}
}
}
}
}

View 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,
],
];

View 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' => '*',
],
];

View File

@ -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';

View 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}}

View File

@ -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"

View 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];
}
}

View File

@ -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);
}
}

View File

@ -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;