Merge branch 'MDL-86065' of https://github.com/dravek/moodle
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
issueNumber: MDL-86065
|
||||
notes:
|
||||
core_customfield:
|
||||
- message: >-
|
||||
Added parameters 'component', 'area' and 'itemid' to the
|
||||
`api::get_instance_fields_data()` and `api::get_instances_fields_data()` methods.
|
||||
Added a new field 'shared' to the customfield_category DB table.
|
||||
Added 'component', 'area' and 'itemid' fields to the customfield_data DB table.
|
||||
Modified the customfield_data DB table unique index to include the new fields.
|
||||
|
||||
type: changed
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Custom fields related settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/** @var admin_root $ADMIN */
|
||||
$ADMIN->add(
|
||||
'root',
|
||||
new admin_category(
|
||||
'customfield',
|
||||
new lang_string('customfields', 'core_customfield')
|
||||
)
|
||||
);
|
||||
|
||||
$ADMIN->add(
|
||||
'customfield',
|
||||
new admin_externalpage(
|
||||
'sharedcustomfields',
|
||||
new lang_string('sharedcustomfields', 'core_customfield'),
|
||||
new moodle_url("/customfield/customfield.php")
|
||||
)
|
||||
);
|
||||
@@ -170,7 +170,7 @@ class content_handler extends \core_customfield\handler {
|
||||
$courseid = $task->get_courseid();
|
||||
$context = $this->get_instance_context($courseid);
|
||||
$editablefields = $this->get_editable_fields($courseid);
|
||||
$records = api::get_instance_fields_data($editablefields, $courseid);
|
||||
$records = $this->get_instance_fields_data($editablefields, $courseid);
|
||||
$target = $task->get_target();
|
||||
$override = ($target != \backup::TARGET_CURRENT_ADDING && $target != \backup::TARGET_EXISTING_ADDING);
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace core_course\customfield;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
use core_customfield\api;
|
||||
use core_customfield\field_controller;
|
||||
|
||||
/**
|
||||
@@ -120,7 +119,7 @@ class course_handler extends \core_customfield\handler {
|
||||
* @return bool true if the current can edit custom fields, false otherwise
|
||||
*/
|
||||
public function can_view(field_controller $field, int $instanceid): bool {
|
||||
$visibility = $field->get_configdata_property('visibility');
|
||||
$visibility = $field->get_configdata_property('visibility') ?? self::VISIBLETOALL;
|
||||
if ($visibility == self::NOTVISIBLE) {
|
||||
return false;
|
||||
} else if ($visibility == self::VISIBLETOTEACHERS) {
|
||||
@@ -222,7 +221,7 @@ class course_handler extends \core_customfield\handler {
|
||||
$courseid = $task->get_courseid();
|
||||
$context = $this->get_instance_context($courseid);
|
||||
$editablefields = $this->get_editable_fields($courseid);
|
||||
$records = api::get_instance_fields_data($editablefields, $courseid);
|
||||
$records = $this->get_instance_fields_data($editablefields, $courseid);
|
||||
$target = $task->get_target();
|
||||
$override = ($target != \backup::TARGET_CURRENT_ADDING && $target != \backup::TARGET_EXISTING_ADDING);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
define("core_customfield/repository/toggle_shared",["exports","core/ajax"],(function(_exports,_ajax){var obj;
|
||||
/**
|
||||
* Module to handle toggle shared category AJAX requests
|
||||
*
|
||||
* @module core_customfield/repository/toggle_shared
|
||||
* @copyright 2025 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.toggleCategory=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.toggleCategory=(categoryId,component,area,itemid,state)=>{const request={methodname:"core_customfield_toggle_shared",args:{categoryid:categoryId,component:component,area:area,itemid:itemid,state:state}};return _ajax.default.call([request])[0]}}));
|
||||
|
||||
//# sourceMappingURL=toggle_shared.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"toggle_shared.min.js","sources":["../../src/repository/toggle_shared.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 * Module to handle toggle shared category AJAX requests\n *\n * @module core_customfield/repository/toggle_shared\n * @copyright 2025 David Carrillo <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Toggle shared category enabled\n *\n * @method\n * @param {Number} categoryId\n * @param {String} component\n * @param {String} area\n * @param {Number} itemid\n * @param {Boolean} state\n * @return {Promise}\n */\nexport const toggleCategory = (categoryId, component, area, itemid, state) => {\n const request = {\n methodname: 'core_customfield_toggle_shared',\n args: {categoryid: categoryId, component: component, area: area, itemid: itemid, state: state}\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["categoryId","component","area","itemid","state","request","methodname","args","categoryid","Ajax","call"],"mappings":";;;;;;;wKAoC8B,CAACA,WAAYC,UAAWC,KAAMC,OAAQC,eAC1DC,QAAU,CACZC,WAAY,iCACZC,KAAM,CAACC,WAAYR,WAAYC,UAAWA,UAAWC,KAAMA,KAAMC,OAAQA,OAAQC,MAAOA,eAGrFK,cAAKC,KAAK,CAACL,UAAU"}
|
||||
@@ -0,0 +1,3 @@
|
||||
define("core_customfield/toggle_shared",["exports","core/inplace_editable","core/notification","core/pending","core_customfield/repository/toggle_shared"],(function(_exports,_inplace_editable,_notification,_pending,_toggle_shared){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);let initialized=!1;_exports.init=()=>{initialized||(document.addEventListener("click",(event=>{const sharedToggle=event.target.closest('[data-action="shared-toggle"]');if(sharedToggle){const pendingPromise=new _pending.default("core_customfield/shared:toggle"),categoryId=sharedToggle.dataset.id,component=sharedToggle.dataset.component,area=sharedToggle.dataset.area,itemid=sharedToggle.dataset.itemid,sharedStateToggle=+!Number(sharedToggle.dataset.state);(0,_toggle_shared.toggleCategory)(categoryId,component,area,itemid,sharedStateToggle).then((()=>(sharedToggle.dataset.state=sharedStateToggle,pendingPromise.resolve()))).catch(_notification.default.exception)}})),initialized=!0)}}));
|
||||
|
||||
//# sourceMappingURL=toggle_shared.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"toggle_shared.min.js","sources":["../src/toggle_shared.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 * Custom fields shared category toggle\n *\n * @module core_customfield/toggle_shared\n * @copyright 2025 David Carrillo <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {toggleCategory} from 'core_customfield/repository/toggle_shared';\n\nlet initialized = false;\n\n/**\n * Initialise toggle\n */\nexport const init = () => {\n if (initialized) {\n // We already added the event listeners (can be called multiple times by mustache template).\n return;\n }\n\n document.addEventListener('click', event => {\n // Toggle shared category.\n const sharedToggle = event.target.closest('[data-action=\"shared-toggle\"]');\n if (sharedToggle) {\n const pendingPromise = new Pending('core_customfield/shared:toggle');\n const categoryId = sharedToggle.dataset.id;\n const component = sharedToggle.dataset.component;\n const area = sharedToggle.dataset.area;\n const itemid = sharedToggle.dataset.itemid;\n const sharedStateToggle = +!Number(sharedToggle.dataset.state);\n\n toggleCategory(categoryId, component, area, itemid, sharedStateToggle)\n .then(() => {\n sharedToggle.dataset.state = sharedStateToggle;\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }\n });\n\n initialized = true;\n};\n"],"names":["initialized","document","addEventListener","event","sharedToggle","target","closest","pendingPromise","Pending","categoryId","dataset","id","component","area","itemid","sharedStateToggle","Number","state","then","resolve","catch","Notification","exception"],"mappings":"weA8BIA,aAAc,gBAKE,KACZA,cAKJC,SAASC,iBAAiB,SAASC,cAEzBC,aAAeD,MAAME,OAAOC,QAAQ,oCACtCF,aAAc,OACRG,eAAiB,IAAIC,iBAAQ,kCAC7BC,WAAaL,aAAaM,QAAQC,GAClCC,UAAYR,aAAaM,QAAQE,UACjCC,KAAOT,aAAaM,QAAQG,KAC5BC,OAASV,aAAaM,QAAQI,OAC9BC,oBAAsBC,OAAOZ,aAAaM,QAAQO,yCAEzCR,WAAYG,UAAWC,KAAMC,OAAQC,mBAC/CG,MAAK,KACFd,aAAaM,QAAQO,MAAQF,kBACtBR,eAAeY,aAEzBC,MAAMC,sBAAaC,eAIhCtB,aAAc"}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Module to handle toggle shared category AJAX requests
|
||||
*
|
||||
* @module core_customfield/repository/toggle_shared
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Toggle shared category enabled
|
||||
*
|
||||
* @method
|
||||
* @param {Number} categoryId
|
||||
* @param {String} component
|
||||
* @param {String} area
|
||||
* @param {Number} itemid
|
||||
* @param {Boolean} state
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const toggleCategory = (categoryId, component, area, itemid, state) => {
|
||||
const request = {
|
||||
methodname: 'core_customfield_toggle_shared',
|
||||
args: {categoryid: categoryId, component: component, area: area, itemid: itemid, state: state}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Custom fields shared category toggle
|
||||
*
|
||||
* @module core_customfield/toggle_shared
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import 'core/inplace_editable';
|
||||
import Notification from 'core/notification';
|
||||
import Pending from 'core/pending';
|
||||
import {toggleCategory} from 'core_customfield/repository/toggle_shared';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* Initialise toggle
|
||||
*/
|
||||
export const init = () => {
|
||||
if (initialized) {
|
||||
// We already added the event listeners (can be called multiple times by mustache template).
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
// Toggle shared category.
|
||||
const sharedToggle = event.target.closest('[data-action="shared-toggle"]');
|
||||
if (sharedToggle) {
|
||||
const pendingPromise = new Pending('core_customfield/shared:toggle');
|
||||
const categoryId = sharedToggle.dataset.id;
|
||||
const component = sharedToggle.dataset.component;
|
||||
const area = sharedToggle.dataset.area;
|
||||
const itemid = sharedToggle.dataset.itemid;
|
||||
const sharedStateToggle = +!Number(sharedToggle.dataset.state);
|
||||
|
||||
toggleCategory(categoryId, component, area, itemid, sharedStateToggle)
|
||||
.then(() => {
|
||||
sharedToggle.dataset.state = sharedStateToggle;
|
||||
return pendingPromise.resolve();
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
};
|
||||
@@ -49,13 +49,23 @@ class api {
|
||||
* @param field_controller[] $fields list of fields indexed by field id
|
||||
* @param int $instanceid
|
||||
* @param bool $adddefaults
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @return data_controller[] array of data_controller objects indexed by fieldid. All fields are present,
|
||||
* some data_controller objects may have 'id', some not
|
||||
* If ($adddefaults): All fieldids are present, some data_controller objects may have 'id', some not.
|
||||
* If (!$adddefaults): Only fieldids with data are present, all data_controller objects have 'id'.
|
||||
*/
|
||||
public static function get_instance_fields_data(array $fields, int $instanceid, bool $adddefaults = true): array {
|
||||
return self::get_instances_fields_data($fields, [$instanceid], $adddefaults)[$instanceid];
|
||||
public static function get_instance_fields_data(
|
||||
array $fields,
|
||||
int $instanceid,
|
||||
bool $adddefaults = true,
|
||||
string $component = '',
|
||||
string $area = '',
|
||||
int $itemid = 0
|
||||
): array {
|
||||
return self::get_instances_fields_data($fields, [$instanceid], $adddefaults, $component, $area, $itemid)[$instanceid];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,12 +74,22 @@ class api {
|
||||
* @param field_controller[] $fields list of fields indexed by field id
|
||||
* @param int[] $instanceids
|
||||
* @param bool $adddefaults
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @return data_controller[][] 2-dimension array, first index is instanceid, second index is fieldid.
|
||||
* If ($adddefaults): All instanceids and all fieldids are present, some data_controller objects may have 'id', some not.
|
||||
* If (!$adddefaults): All instanceids are present but only fieldids with data are present, all
|
||||
* data_controller objects have 'id'.
|
||||
*/
|
||||
public static function get_instances_fields_data(array $fields, array $instanceids, bool $adddefaults = true): array {
|
||||
public static function get_instances_fields_data(
|
||||
array $fields,
|
||||
array $instanceids,
|
||||
bool $adddefaults = true,
|
||||
string $component = '',
|
||||
string $area = '',
|
||||
int $itemid = 0
|
||||
): array {
|
||||
global $DB;
|
||||
|
||||
// Create the results array where instances and fields order is the same as in the input arrays.
|
||||
@@ -82,10 +102,18 @@ class api {
|
||||
// Retrieve all existing data.
|
||||
list($sqlfields, $params) = $DB->get_in_or_equal(array_keys($fields), SQL_PARAMS_NAMED, 'fld');
|
||||
list($sqlinstances, $iparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED, 'ins');
|
||||
$sqlplugin = '';
|
||||
if (!empty($component) && !empty($area)) {
|
||||
// If component, area and itemid are provided, filter by them.
|
||||
$params['component'] = $component;
|
||||
$params['area'] = $area;
|
||||
$params['itemid'] = $itemid;
|
||||
$sqlplugin = "AND d.component = :component AND d.area = :area AND d.itemid = :itemid";
|
||||
}
|
||||
$sql = "SELECT d.*
|
||||
FROM {customfield_field} f
|
||||
JOIN {customfield_data} d ON (f.id = d.fieldid AND d.instanceid {$sqlinstances})
|
||||
WHERE f.id {$sqlfields}";
|
||||
WHERE f.id {$sqlfields} $sqlplugin";
|
||||
$fieldsdata = $DB->get_recordset_sql($sql, $params + $iparams);
|
||||
foreach ($fieldsdata as $data) {
|
||||
$result[$data->instanceid][$data->fieldid] = data_controller::create(0, $data, $fields[$data->fieldid]);
|
||||
@@ -439,4 +467,24 @@ class api {
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a shared category is enabled for the given entity
|
||||
*
|
||||
* @param int $categoryid
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_shared_category_enabled(int $categoryid, string $component, string $area, int $itemid): bool {
|
||||
$sql = "categoryid = :categoryid AND component = :component AND area = :area AND itemid = :itemid";
|
||||
$params = [
|
||||
'categoryid' => $categoryid,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
];
|
||||
return shared::record_exists_select($sql, $params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ class category extends persistent {
|
||||
'default' => -1,
|
||||
'null' => NULL_ALLOWED,
|
||||
],
|
||||
'shared' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
'default' => false,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,15 @@ class category_controller {
|
||||
/** @var handler */
|
||||
protected $handler;
|
||||
|
||||
/** @var ?string */
|
||||
protected $component = null;
|
||||
|
||||
/** @var ?string */
|
||||
protected $area = null;
|
||||
|
||||
/** @var ?int */
|
||||
protected $itemid = null;
|
||||
|
||||
/**
|
||||
* category constructor.
|
||||
*
|
||||
@@ -107,6 +116,8 @@ class category_controller {
|
||||
}
|
||||
$record->itemid = $handler->get_itemid();
|
||||
}
|
||||
// Check if the category is shared.
|
||||
$record->shared = $record->component === 'core_customfield' && $record->area === 'shared';
|
||||
$category = new self(0, $record);
|
||||
if (!$category->get('contextid')) {
|
||||
// If contextid was not present in the record we can find it out from the handler.
|
||||
@@ -115,6 +126,10 @@ class category_controller {
|
||||
}
|
||||
if ($handler) {
|
||||
$category->set_handler($handler);
|
||||
|
||||
$category->set_original_component($category->get_handler()->get_component());
|
||||
$category->set_original_area($category->get_handler()->get_area());
|
||||
$category->set_original_itemid($category->get_handler()->get_itemid());
|
||||
}
|
||||
return $category;
|
||||
}
|
||||
@@ -183,7 +198,12 @@ class category_controller {
|
||||
public function get_handler(): handler {
|
||||
if ($this->handler === null) {
|
||||
$this->handler = handler::get_handler($this->get('component'), $this->get('area'), $this->get('itemid'));
|
||||
|
||||
$this->set_original_component($this->handler->get_component());
|
||||
$this->set_original_area($this->handler->get_area());
|
||||
$this->set_original_itemid($this->handler->get_itemid());
|
||||
}
|
||||
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
@@ -208,6 +228,67 @@ class category_controller {
|
||||
throw new \coding_exception('Context of the handler does not match the one from the record');
|
||||
}
|
||||
$this->handler = $handler;
|
||||
|
||||
$this->component = $handler->get_component();
|
||||
$this->area = $handler->get_area();
|
||||
$this->itemid = $handler->get_itemid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original component.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_original_component(): ?string {
|
||||
return $this->component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original area.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_original_area(): ?string {
|
||||
return $this->area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original itemid.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_original_itemid(): ?int {
|
||||
return $this->itemid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original component.
|
||||
*
|
||||
* @param string $component
|
||||
* @return void
|
||||
*/
|
||||
public function set_original_component(string $component): void {
|
||||
$this->component = $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original area.
|
||||
*
|
||||
* @param string $area
|
||||
* @return void
|
||||
*/
|
||||
public function set_original_area(string $area): void {
|
||||
$this->area = $area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original itemid.
|
||||
*
|
||||
* @param int $itemid
|
||||
* @return void
|
||||
*/
|
||||
public function set_original_itemid(int $itemid): void {
|
||||
$this->itemid = $itemid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_customfield\customfield;
|
||||
|
||||
use core\context;
|
||||
use core\context\system;
|
||||
use core\exception\coding_exception;
|
||||
use core\url;
|
||||
use core_customfield\field_controller;
|
||||
|
||||
/**
|
||||
* Shared handler for custom fields
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class shared_handler extends \core_customfield\handler {
|
||||
/**
|
||||
* @var shared_handler|null
|
||||
*/
|
||||
protected static ?shared_handler $singleton = null;
|
||||
|
||||
/**
|
||||
* Returns a singleton
|
||||
*
|
||||
* @param int $itemid
|
||||
* @return self
|
||||
*/
|
||||
public static function create(int $itemid = 0): self {
|
||||
if (static::$singleton === null) {
|
||||
self::$singleton = new static($itemid);
|
||||
}
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run reset code after unit tests to reset the singleton usage.
|
||||
*/
|
||||
public static function reset_caches(): void {
|
||||
if (!PHPUNIT_TEST) {
|
||||
throw new coding_exception('This feature is only intended for use in unit tests');
|
||||
}
|
||||
|
||||
static::$singleton = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current user can configure custom fields on this component.
|
||||
*
|
||||
* @return bool true if the current user can configure custom fields, false otherwise
|
||||
*/
|
||||
public function can_configure(): bool {
|
||||
return has_capability('moodle/customfield:configureshared', $this->get_configuration_context());
|
||||
}
|
||||
|
||||
/**
|
||||
* The current user can edit custom fields on the given report.
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @param int $instanceid id of the report to test edit permission
|
||||
* @return bool true if the current user can edit custom fields, false otherwise
|
||||
*/
|
||||
public function can_edit(field_controller $field, int $instanceid = 0): bool {
|
||||
return has_capability('moodle/customfield:configureshared', $this->get_configuration_context());
|
||||
}
|
||||
|
||||
/**
|
||||
* The current user can view custom fields on the given report.
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @param int $instanceid id of the report to test edit permission
|
||||
* @return bool true if the current user can edit custom fields, false otherwise
|
||||
*/
|
||||
public function can_view(field_controller $field, int $instanceid): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context that should be used for new categories created by this handler
|
||||
*
|
||||
* @return context the context for configuration
|
||||
*/
|
||||
public function get_configuration_context(): context {
|
||||
return system::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* URL for configuration of the fields on this handler.
|
||||
*
|
||||
* @return url The URL to configure custom fields for this component
|
||||
*/
|
||||
public function get_configuration_url(): url {
|
||||
return new url('/customfield/customfield.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context for the data associated with the given instanceid.
|
||||
*
|
||||
* @param int $instanceid id of the record to get the context for
|
||||
* @return context the context for the given record
|
||||
*/
|
||||
public function get_instance_context(int $instanceid = 0): context {
|
||||
return system::instance();
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,18 @@ class data extends persistent {
|
||||
'type' => PARAM_INT,
|
||||
'optional' => false,
|
||||
'null' => NULL_NOT_ALLOWED
|
||||
]
|
||||
],
|
||||
'component' => [
|
||||
'type' => PARAM_COMPONENT,
|
||||
],
|
||||
'area' => [
|
||||
'type' => PARAM_COMPONENT,
|
||||
],
|
||||
'itemid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,18 @@ abstract class data_controller {
|
||||
if (!class_exists($customfieldtype) || !is_subclass_of($customfieldtype, self::class)) {
|
||||
throw new \moodle_exception('errorfieldtypenotfound', 'core_customfield', '', s($type));
|
||||
}
|
||||
|
||||
$category = $field->get_category();
|
||||
$record->component = $category->get_original_component();
|
||||
$record->area = $category->get_original_area();
|
||||
$record->itemid = $category->get_original_itemid();
|
||||
|
||||
if (!$record->component || !$record->area) {
|
||||
$record->component = $field->get_handler()->get_component();
|
||||
$record->area = $field->get_handler()->get_area();
|
||||
$record->itemid = $field->get_handler()->get_itemid();
|
||||
}
|
||||
|
||||
$datacontroller = new $customfieldtype(0, $record);
|
||||
$datacontroller->field = $field;
|
||||
return $datacontroller;
|
||||
@@ -210,6 +222,14 @@ abstract class data_controller {
|
||||
|
||||
$this->data->set($datafield, $datafieldvalue);
|
||||
$this->data->set('value', $value);
|
||||
|
||||
// Set component, area and itemid from the handler.
|
||||
$category = $this->field->get_category();
|
||||
$this->data->set_many([
|
||||
'component' => $category->get_original_component(),
|
||||
'area' => $category->get_original_area(),
|
||||
'itemid' => $category->get_original_itemid(),
|
||||
]);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_customfield\event;
|
||||
|
||||
use context;
|
||||
use core_customfield\shared;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Custom field shared category usage disabled event class.
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class shared_category_usage_disabled extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Initialise the event data.
|
||||
*/
|
||||
protected function init(): void {
|
||||
$this->data['objecttable'] = 'customfield_shared';
|
||||
$this->data['crud'] = 'd';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from a shared object
|
||||
*
|
||||
* @param shared $shared
|
||||
* @param context $context
|
||||
* @return category_updated
|
||||
*/
|
||||
public static function create_from_object(shared $shared, context $context): shared_category_usage_disabled {
|
||||
$eventparams = [
|
||||
'objectid' => $shared->get('id'),
|
||||
'context' => $context,
|
||||
'other' => [
|
||||
'categoryid' => $shared->get('categoryid'),
|
||||
'component' => $shared->get('component'),
|
||||
'area' => $shared->get('area'),
|
||||
'itemid' => $shared->get('itemid'),
|
||||
],
|
||||
];
|
||||
$event = self::create($eventparams);
|
||||
$event->add_record_snapshot($event->objecttable, $shared->to_record());
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns localised general event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('eventsharedcategoryusagedisabled', 'core_customfield');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-localised description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return "The user with ID '$this->userid' disabled usage of shared category with ID '{$this->other['categoryid']}'" .
|
||||
" in '{$this->other['component']}/{$this->other['area']}'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_customfield\event;
|
||||
|
||||
use context;
|
||||
use core_customfield\shared;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Custom field shared category usage enabled event class.
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class shared_category_usage_enabled extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Initialise the event data.
|
||||
*/
|
||||
protected function init(): void {
|
||||
$this->data['objecttable'] = 'customfield_shared';
|
||||
$this->data['crud'] = 'c';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from a shared object
|
||||
*
|
||||
* @param shared $shared
|
||||
* @param context $context
|
||||
* @return category_updated
|
||||
*/
|
||||
public static function create_from_object(shared $shared, context $context): shared_category_usage_enabled {
|
||||
$eventparams = [
|
||||
'objectid' => $shared->get('id'),
|
||||
'context' => $context,
|
||||
'other' => [
|
||||
'categoryid' => $shared->get('categoryid'),
|
||||
'component' => $shared->get('component'),
|
||||
'area' => $shared->get('area'),
|
||||
'itemid' => $shared->get('itemid'),
|
||||
],
|
||||
];
|
||||
$event = self::create($eventparams);
|
||||
$event->add_record_snapshot($event->objecttable, $shared->to_record());
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns localised general event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('eventsharedcategoryusageenabled', 'core_customfield');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-localised description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return "The user with ID '$this->userid' enabled usage of shared category with ID '{$this->other['categoryid']}'" .
|
||||
" in '{$this->other['component']}/{$this->other['area']}'";
|
||||
}
|
||||
}
|
||||
+116
@@ -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 core_customfield\external;
|
||||
|
||||
use core_customfield\event\shared_category_usage_disabled;
|
||||
use core_customfield\event\shared_category_usage_enabled;
|
||||
use core_customfield\handler;
|
||||
use core_customfield\shared;
|
||||
use core_external\external_api;
|
||||
use core_external\external_value;
|
||||
use core_external\external_function_parameters;
|
||||
|
||||
/**
|
||||
* External method for toggling shared categories
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class toggle_shared_category extends external_api {
|
||||
/**
|
||||
* External method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category ID'),
|
||||
'component' => new external_value(PARAM_COMPONENT, 'Component'),
|
||||
'area' => new external_value(PARAM_AREA, 'Area'),
|
||||
'itemid' => new external_value(PARAM_INT, 'Item ID'),
|
||||
'state' => new external_value(PARAM_BOOL, 'New state'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External method execution
|
||||
*
|
||||
* @param int $categoryid
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @param bool $state
|
||||
* @return bool
|
||||
*/
|
||||
public static function execute(int $categoryid, string $component, string $area, int $itemid, bool $state): bool {
|
||||
[
|
||||
'categoryid' => $categoryid,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
'state' => $state,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'categoryid' => $categoryid,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
'state' => $state,
|
||||
]);
|
||||
|
||||
// Validate context.
|
||||
$context = \core\context\system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
$handler = handler::get_handler($component, $area, $itemid);
|
||||
if (!$handler->can_configure()) {
|
||||
throw new \moodle_exception('nopermissions', 'error', '', get_string('customfield:configureshared', 'core_role'));
|
||||
}
|
||||
|
||||
global $DB;
|
||||
|
||||
$params = [
|
||||
'categoryid' => $categoryid,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
];
|
||||
|
||||
if ($state) {
|
||||
$record = new shared(0, (object) $params);
|
||||
$record->create();
|
||||
shared_category_usage_enabled::create_from_object($record, $handler->get_configuration_context())->trigger();
|
||||
} else {
|
||||
$record = shared::get_record($params);
|
||||
$DB->delete_records('customfield_shared', $params);
|
||||
shared_category_usage_disabled::create_from_object($record, $handler->get_configuration_context())->trigger();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* External method return value
|
||||
*
|
||||
* @return external_value
|
||||
*/
|
||||
public static function execute_returns(): external_value {
|
||||
return new external_value(PARAM_BOOL);
|
||||
}
|
||||
}
|
||||
@@ -227,10 +227,41 @@ abstract class handler {
|
||||
*/
|
||||
protected function validate_category(category_controller $category): category_controller {
|
||||
$categories = $this->get_categories_with_fields();
|
||||
if (!array_key_exists($category->get('id'), $categories)) {
|
||||
$category = $this->get_category_from_array(
|
||||
$categories,
|
||||
$category->get('id'),
|
||||
$this->get_component(),
|
||||
$this->get_area(),
|
||||
$this->get_itemid()
|
||||
);
|
||||
if ($category === null) {
|
||||
throw new \moodle_exception('categorynotfound', 'core_customfield');
|
||||
}
|
||||
return $categories[$category->get('id')];
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a category_controller from an array of categories matching the given identifiers.
|
||||
*
|
||||
* @param array $categories Array of category_controller objects.
|
||||
* @param int $categoryid The ID of the category to find.
|
||||
* @param string $component The component name.
|
||||
* @param string $area The area name.
|
||||
* @param int $itemid The item ID.
|
||||
* @return category_controller|null
|
||||
*/
|
||||
public function get_category_from_array(
|
||||
array $categories,
|
||||
int $categoryid,
|
||||
string $component,
|
||||
string $area,
|
||||
int $itemid
|
||||
): ?category_controller {
|
||||
$category = array_filter($categories, fn($category) => $category->get('id') === $categoryid &&
|
||||
$category->get_original_component() === $component &&
|
||||
$category->get_original_area() === $area &&
|
||||
$category->get_original_itemid() === $itemid);
|
||||
return $category ? reset($category) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,11 +272,16 @@ abstract class handler {
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
protected function validate_field(field_controller $field): field_controller {
|
||||
if (!array_key_exists($field->get('categoryid'), $this->get_categories_with_fields())) {
|
||||
throw new \moodle_exception('fieldnotfound', 'core_customfield');
|
||||
}
|
||||
$category = $this->get_categories_with_fields()[$field->get('categoryid')];
|
||||
if (!array_key_exists($field->get('id'), $category->get_fields())) {
|
||||
$categories = $this->get_categories_with_fields();
|
||||
$category = $this->get_category_from_array(
|
||||
$categories,
|
||||
$field->get('categoryid'),
|
||||
$this->get_component(),
|
||||
$this->get_area(),
|
||||
$this->get_itemid()
|
||||
);
|
||||
|
||||
if (!$category || !array_key_exists($field->get('id'), $category->get_fields())) {
|
||||
throw new \moodle_exception('fieldnotfound', 'core_customfield');
|
||||
}
|
||||
return $category->get_fields()[$field->get('id')];
|
||||
@@ -375,7 +411,26 @@ abstract class handler {
|
||||
*/
|
||||
public function get_instance_data(int $instanceid, bool $returnall = false): array {
|
||||
$fields = $returnall ? $this->get_fields() : $this->get_visible_fields($instanceid);
|
||||
return api::get_instance_fields_data($fields, $instanceid);
|
||||
return $this->get_instance_fields_data($fields, $instanceid);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given instance and list of fields fields retrieves data associated with them using the current entity context
|
||||
*
|
||||
* @param field_controller[] $fields Array of field_controller objects.
|
||||
* @param int $instanceid The instance ID.
|
||||
* @param bool $adddefaults Whether to add default values for fields without data.
|
||||
* @return data_controller[] Array of data_controller objects indexed by fieldid.
|
||||
*/
|
||||
public function get_instance_fields_data(array $fields, int $instanceid, bool $adddefaults = true): array {
|
||||
return api::get_instance_fields_data(
|
||||
$fields,
|
||||
$instanceid,
|
||||
$adddefaults,
|
||||
$this->get_component(),
|
||||
$this->get_area(),
|
||||
$this->get_itemid()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,15 +519,43 @@ abstract class handler {
|
||||
/**
|
||||
* Returns array of categories, each of them contains a list of fields definitions.
|
||||
*
|
||||
* @param bool $ismanagementpage Whether we are on the management page to show all shared categories or not.
|
||||
* @return category_controller[]
|
||||
*/
|
||||
public function get_categories_with_fields(): array {
|
||||
public function get_categories_with_fields(bool $ismanagementpage = false): array {
|
||||
if ($this->categories === null) {
|
||||
$sharedcategories = [];
|
||||
$this->categories = api::get_categories_with_fields($this->get_component(), $this->get_area(), $this->get_itemid());
|
||||
// Avoid duplication when we are in the shared custom fields page.
|
||||
if ($this->get_component() !== 'core_customfield' && $this->get_area() !== 'shared') {
|
||||
$sharedcategories = api::get_categories_with_fields('core_customfield', 'shared', 0);
|
||||
// Filter only by enabled shared categories.
|
||||
if (!$ismanagementpage) {
|
||||
$sharedcategories = array_filter($sharedcategories, function (category_controller $cc) {
|
||||
return api::is_shared_category_enabled(
|
||||
$cc->get('id'),
|
||||
$this->get_component(),
|
||||
$this->get_area(),
|
||||
$this->get_itemid()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->categories = array_merge($this->categories, $sharedcategories);
|
||||
}
|
||||
$handler = $this;
|
||||
array_walk($this->categories, function(category_controller $c) use ($handler) {
|
||||
$c->set_handler($handler);
|
||||
array_walk($this->categories, function (category_controller $cc) use ($handler) {
|
||||
if ($cc->get('area') === 'shared') {
|
||||
$sharedhandler = \core_customfield\customfield\shared_handler::create();
|
||||
$cc->set_handler($sharedhandler);
|
||||
} else {
|
||||
// Set the handler for the category.
|
||||
$cc->set_handler($handler);
|
||||
}
|
||||
|
||||
$cc->set_original_component($handler->get_component());
|
||||
$cc->set_original_area($handler->get_area());
|
||||
$cc->set_original_itemid($handler->get_itemid());
|
||||
});
|
||||
return $this->categories;
|
||||
}
|
||||
@@ -566,7 +649,7 @@ abstract class handler {
|
||||
*/
|
||||
public function instance_form_definition_after_data(\MoodleQuickForm $mform, int $instanceid = 0) {
|
||||
$editablefields = $this->get_editable_fields($instanceid);
|
||||
$fields = api::get_instance_fields_data($editablefields, $instanceid);
|
||||
$fields = $this->get_instance_fields_data($editablefields, $instanceid);
|
||||
|
||||
foreach ($fields as $formfield) {
|
||||
$formfield->instance_form_definition_after_data($mform);
|
||||
@@ -587,7 +670,7 @@ abstract class handler {
|
||||
*/
|
||||
public function instance_form_before_set_data(stdClass $instance) {
|
||||
$instanceid = !empty($instance->id) ? $instance->id : 0;
|
||||
$fields = api::get_instance_fields_data($this->get_editable_fields($instanceid), $instanceid);
|
||||
$fields = $this->get_instance_fields_data($this->get_editable_fields($instanceid), $instanceid);
|
||||
|
||||
foreach ($fields as $formfield) {
|
||||
$formfield->instance_form_before_set_data($instance);
|
||||
@@ -616,7 +699,7 @@ abstract class handler {
|
||||
return;
|
||||
}
|
||||
$editablefields = $this->get_editable_fields($isnewinstance ? 0 : $instance->id);
|
||||
$fields = api::get_instance_fields_data($editablefields, $instance->id);
|
||||
$fields = $this->get_instance_fields_data($editablefields, $instance->id);
|
||||
foreach ($fields as $data) {
|
||||
if (!$data->get('id')) {
|
||||
$data->set('contextid', $this->get_instance_context($instance->id)->id);
|
||||
@@ -643,7 +726,7 @@ abstract class handler {
|
||||
public function instance_form_validation(array $data, array $files) {
|
||||
$instanceid = empty($data['id']) ? 0 : $data['id'];
|
||||
$editablefields = $this->get_editable_fields($instanceid);
|
||||
$fields = api::get_instance_fields_data($editablefields, $instanceid);
|
||||
$fields = $this->get_instance_fields_data($editablefields, $instanceid);
|
||||
$errors = [];
|
||||
foreach ($fields as $formfield) {
|
||||
$errors += $formfield->instance_form_validation($data, $files);
|
||||
@@ -670,7 +753,7 @@ abstract class handler {
|
||||
?string $headerlangidentifier = null, ?string $headerlangcomponent = null) {
|
||||
|
||||
$editablefields = $this->get_editable_fields($instanceid);
|
||||
$fieldswithdata = api::get_instance_fields_data($editablefields, $instanceid);
|
||||
$fieldswithdata = $this->get_instance_fields_data($editablefields, $instanceid);
|
||||
$lastcategoryid = null;
|
||||
foreach ($fieldswithdata as $data) {
|
||||
$categoryid = $data->get_field()->get_category()->get('id');
|
||||
@@ -819,7 +902,7 @@ abstract class handler {
|
||||
* @param int $instanceid
|
||||
*/
|
||||
public function delete_instance(int $instanceid) {
|
||||
$fielddata = api::get_instance_fields_data($this->get_fields(), $instanceid, false);
|
||||
$fielddata = $this->get_instance_fields_data($this->get_fields(), $instanceid, false);
|
||||
foreach ($fielddata as $data) {
|
||||
$data->delete();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace core_customfield\output;
|
||||
|
||||
use core_customfield\api;
|
||||
use core_customfield\handler;
|
||||
use core_customfield\shared;
|
||||
use renderable;
|
||||
use templatable;
|
||||
|
||||
@@ -65,6 +66,7 @@ class management implements renderable, templatable {
|
||||
* @return array|object|\stdClass
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output) {
|
||||
global $DB;
|
||||
$data = new \stdClass();
|
||||
|
||||
$fieldtypes = $this->handler->get_available_field_types();
|
||||
@@ -73,17 +75,48 @@ class management implements renderable, templatable {
|
||||
$data->area = $this->handler->get_area();
|
||||
$data->itemid = $this->handler->get_itemid();
|
||||
$data->usescategories = $this->handler->uses_categories();
|
||||
$categories = $this->handler->get_categories_with_fields();
|
||||
$categories = $this->handler->get_categories_with_fields(true);
|
||||
|
||||
// Get all enabled shared categories at once.
|
||||
$sharedcategoriesenabled = shared::get_records([
|
||||
'component' => $data->component,
|
||||
'area' => $data->area,
|
||||
'itemid' => $data->itemid,
|
||||
]);
|
||||
|
||||
$categoriesarray = array();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
|
||||
$canedit = $data->component === $category->get('component') && $data->area === $category->get('area');
|
||||
|
||||
$categoryarray = array();
|
||||
$categoryarray['id'] = $category->get('id');
|
||||
$categoryarray['nameeditable'] = $output->render(api::get_category_inplace_editable($category, true));
|
||||
$categoryarray['nameeditable'] = $canedit ? $output->render(api::get_category_inplace_editable($category, true)) :
|
||||
$category->get_formatted_name();
|
||||
$categoryarray['movetitle'] = get_string('movecategory', 'core_customfield',
|
||||
$category->get_formatted_name());
|
||||
$categoryarray['canedit'] = $canedit;
|
||||
|
||||
$toggleenabled = (bool) array_filter(
|
||||
$sharedcategoriesenabled,
|
||||
fn($record) => $record->get('categoryid') === $category->get('id')
|
||||
);
|
||||
$attributes = [
|
||||
['name' => 'data-id', 'value' => $category->get('id')],
|
||||
['name' => 'data-action', 'value' => 'shared-toggle'],
|
||||
['name' => 'data-state', 'value' => $toggleenabled],
|
||||
['name' => 'data-component', 'value' => $data->component],
|
||||
['name' => 'data-area', 'value' => $data->area],
|
||||
['name' => 'data-itemid', 'value' => $data->itemid],
|
||||
];
|
||||
$categoryarray['toggle'] = $output->render_from_template('core/toggle', [
|
||||
'id' => 'shared-toggle-' . $category->get('id'),
|
||||
'checked' => $toggleenabled,
|
||||
'extraattributes' => $attributes,
|
||||
'label' => get_string('enableplugin', 'core_admin', $category->get_formatted_name()),
|
||||
'labelclasses' => 'visually-hidden',
|
||||
]);
|
||||
|
||||
$categoryarray['fields'] = array();
|
||||
|
||||
@@ -95,22 +128,31 @@ class management implements renderable, templatable {
|
||||
$fieldarray['name'] = $fieldname;
|
||||
$fieldarray['shortname'] = $field->get('shortname');
|
||||
$fieldarray['movetitle'] = get_string('movefield', 'core_customfield', $fieldname);
|
||||
$categoryarray['canedit'] = $canedit;
|
||||
|
||||
$categoryarray['fields'][] = $fieldarray;
|
||||
}
|
||||
|
||||
$menu = new \action_menu();
|
||||
$menu->set_menu_trigger(get_string('createnewcustomfield', 'core_customfield'));
|
||||
if ($canedit) {
|
||||
$menu = new \action_menu();
|
||||
$menu->set_menu_trigger(get_string('createnewcustomfield', 'core_customfield'));
|
||||
|
||||
foreach ($fieldtypes as $type => $fieldname) {
|
||||
$action = new \action_menu_link_secondary(new \moodle_url('#'), null, $fieldname,
|
||||
['data-role' => 'addfield', 'data-categoryid' => $category->get('id'), 'data-type' => $type,
|
||||
'data-typename' => $fieldname]);
|
||||
$menu->add($action);
|
||||
foreach ($fieldtypes as $type => $fieldname) {
|
||||
$params = [
|
||||
'data-role' => 'addfield',
|
||||
'data-categoryid' => $category->get('id'),
|
||||
'data-type' => $type,
|
||||
'data-typename' => $fieldname,
|
||||
];
|
||||
$action = new \action_menu_link_secondary(new \core\url('#'), null, $fieldname, $params);
|
||||
$menu->add($action);
|
||||
}
|
||||
$menu->attributes['class'] .= ' float-start me-1';
|
||||
|
||||
$categoryarray['addfieldmenu'] = $output->render($menu);
|
||||
} else {
|
||||
$categoryarray['addfieldmenu'] = '';
|
||||
}
|
||||
$menu->attributes['class'] .= ' float-start me-1';
|
||||
|
||||
$categoryarray['addfieldmenu'] = $output->render($menu);
|
||||
|
||||
$categoriesarray[] = $categoryarray;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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 core_customfield;
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* Customfield shared persistent class
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class shared extends persistent {
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'customfield_shared';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties(): array {
|
||||
return [
|
||||
'categoryid' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'component' => [
|
||||
'type' => PARAM_COMPONENT,
|
||||
],
|
||||
'area' => [
|
||||
'type' => PARAM_COMPONENT,
|
||||
],
|
||||
'itemid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Manage shared custom fields
|
||||
*
|
||||
* @package core_customfield
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
admin_externalpage_setup('sharedcustomfields');
|
||||
|
||||
/** @var \core_customfield\output\renderer $output */
|
||||
$output = $PAGE->get_renderer('core_customfield');
|
||||
$handler = \core_customfield\customfield\shared_handler::create();
|
||||
$outputpage = new \core_customfield\output\management($handler);
|
||||
|
||||
echo $output->header(),
|
||||
$output->heading(new lang_string('sharedcustomfields', 'core_customfield')),
|
||||
$output->render($outputpage),
|
||||
$output->footer();
|
||||
@@ -120,6 +120,7 @@ class core_customfield_external extends external_api {
|
||||
'id' => new external_value(PARAM_INT, 'id'),
|
||||
'nameeditable' => new external_value(PARAM_RAW, 'inplace editable name'),
|
||||
'addfieldmenu' => new external_value(PARAM_RAW, 'addfieldmenu'),
|
||||
'canedit' => new external_value(PARAM_BOOL, 'can edit'),
|
||||
'fields' => new external_multiple_structure(
|
||||
new external_single_structure(
|
||||
array(
|
||||
@@ -130,6 +131,7 @@ class core_customfield_external extends external_api {
|
||||
)
|
||||
)
|
||||
, '', VALUE_OPTIONAL),
|
||||
'toggle' => new external_value(PARAM_RAW, 'toggle'),
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -81,7 +81,7 @@ class recalculate extends external_api {
|
||||
|
||||
$provider->recalculate($instanceid);
|
||||
|
||||
$data = \core_customfield\api::get_instance_fields_data(
|
||||
$data = $handler->get_instance_fields_data(
|
||||
[$fieldid => $field], $instanceid)[$fieldid];
|
||||
|
||||
return ['value' => $data->export_value()];
|
||||
|
||||
@@ -129,7 +129,7 @@ class nofactivities extends provider_base {
|
||||
}
|
||||
} else if (empty($record->dataid) || (int)$record->decvalue != $value) {
|
||||
// Stored value is out of date.
|
||||
$data = \core_customfield\api::get_instance_fields_data(
|
||||
$data = $this->field->get_handler()->get_instance_fields_data(
|
||||
[$fieldid => $this->field], (int)$record->id)[$fieldid];
|
||||
$data->set('contextid', context_course::instance($record->id)->id);
|
||||
$data->set('decvalue', $value);
|
||||
|
||||
+7
-1
@@ -156,7 +156,13 @@ final class nofactivities_test extends advanced_testcase {
|
||||
],
|
||||
]);
|
||||
$getdata = fn(\customfield_number\field_controller $field): \customfield_number\data_controller =>
|
||||
\core_customfield\api::get_instance_fields_data([$field->get('id') => $field], (int)$course1->id)[$field->get('id')];
|
||||
\core_customfield\api::get_instance_fields_data(
|
||||
[$field->get('id') => $field],
|
||||
(int)$course1->id,
|
||||
true,
|
||||
'core_course',
|
||||
'course'
|
||||
)[$field->get('id')];
|
||||
|
||||
// Recalculate the value of the field and assert it is set to 1 (one activity in the course).
|
||||
(new \customfield_number\task\cron())->execute();
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
{ "id": "0",
|
||||
"nameeditable": "Other fields",
|
||||
"addfieldmenu": "Add field",
|
||||
"canedit": true,
|
||||
"fields": [
|
||||
{ "id": 0, "name": "Field name", "shortname": "shortname", "type": "Text" },
|
||||
{ "id": 0, "name": "Another field", "shortname": "checkme", "type": "Checkbox" }
|
||||
@@ -50,6 +51,7 @@
|
||||
{ "id": "00",
|
||||
"nameeditable": "Empty category",
|
||||
"addfieldmenu": "Add field",
|
||||
"canedit": true,
|
||||
"fields": [] }
|
||||
],
|
||||
"singleselect" : "select"
|
||||
@@ -71,19 +73,26 @@
|
||||
{{{nocategories}}}
|
||||
{{/categories}}
|
||||
|
||||
<div class="categorieslist">
|
||||
{{#categories}}
|
||||
{{#canedit}}<div class="categorieslist">{{/canedit}}
|
||||
<div data-category-id="{{id}}" id="category-{{id}}" class="mt-2">
|
||||
<div class="row justify-content-between align-items-end">
|
||||
<div class="col-6 categoryinstance">
|
||||
<div class="col-6 categoryinstance d-flex align-items-center gap-3">
|
||||
{{#usescategories}}
|
||||
<h3>
|
||||
<span class="movecategory icon-size-3">{{> core/drag_handle}}</span>
|
||||
{{#canedit}}<span class="movecategory icon-size-3">{{> core/drag_handle}}</span>{{/canedit}}
|
||||
{{{nameeditable}}}
|
||||
{{#canedit}}
|
||||
<a class="icon-size-3" href="#" data-role="deletecategory" data-id="{{id}}">
|
||||
{{#pix}} t/delete, core, {{#str}} delete, moodle {{/str}} {{/pix}}
|
||||
</a>
|
||||
{{/canedit}}
|
||||
</h3>
|
||||
{{^canedit}}
|
||||
<div data-bs-toggle="tooltip" data-bs-placement="top" title="{{#str}} sharedcategoryhelptext, customfield {{/str}}">
|
||||
{{{toggle}}}
|
||||
</div>
|
||||
{{/canedit}}
|
||||
{{/usescategories}}
|
||||
</div>
|
||||
<div class="col-auto text-end">
|
||||
@@ -91,7 +100,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table class="table generaltable fieldslist">
|
||||
<table class="table generaltable {{#canedit}}fieldslist{{/canedit}}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="col-5">{{#str}} customfield, core_customfield {{/str}}</th>
|
||||
@@ -103,14 +112,16 @@
|
||||
<tbody>
|
||||
{{#fields}}
|
||||
<tr data-field-name="{{name}}" data-field-id="{{id}}" class="field">
|
||||
<td class="col-5"><span class="movefield">{{> core/drag_handle}}</span>{{{name}}}</td>
|
||||
<td class="col-5">{{#canedit}}<span class="movefield">{{> core/drag_handle}}</span>{{/canedit}}{{{name}}}</td>
|
||||
<td class="col-3">{{{shortname}}}</td>
|
||||
<td class="col-2">{{{type}}}</td>
|
||||
<td class="col-2 text-end">
|
||||
{{#canedit}}
|
||||
<a href="#" data-role="editfield" data-name="{{name}}" data-id="{{id}}">{{#pix}}
|
||||
t/edit, core, {{#str}} edit, moodle {{/str}} {{/pix}}</a>
|
||||
<a href="#" data-id="{{id}}" data-role="deletefield">{{#pix}}
|
||||
t/delete, core, {{#str}} delete, moodle {{/str}} {{/pix}}</a>
|
||||
{{/canedit}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/fields}}
|
||||
@@ -121,12 +132,13 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{#canedit}}</div>{{/canedit}}
|
||||
{{/categories}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#js}}
|
||||
require(['core_customfield/form'], function(s) {
|
||||
require(['core_customfield/form', 'core_customfield/toggle_shared'], function(s, toggle) {
|
||||
s.init();
|
||||
toggle.init();
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -181,7 +181,14 @@ final class api_test extends \advanced_testcase {
|
||||
|
||||
$handler = \core_course\customfield\course_handler::create();
|
||||
$c1id = $handler->create_category();
|
||||
$c1 = $handler->get_categories_with_fields()[$c1id];
|
||||
$categories = $handler->get_categories_with_fields();
|
||||
$c1 = $handler->get_category_from_array(
|
||||
$categories,
|
||||
$c1id,
|
||||
$handler->get_component(),
|
||||
$handler->get_area(),
|
||||
$handler->get_itemid()
|
||||
);
|
||||
$this->assertEquals('Other fields', $c1->get('name'));
|
||||
$this->assertEquals($handler->get_component(), $c1->get('component'));
|
||||
$this->assertEquals($handler->get_area(), $c1->get('area'));
|
||||
@@ -191,8 +198,21 @@ final class api_test extends \advanced_testcase {
|
||||
// Generate more categories and make sure they have different names.
|
||||
$c2id = $handler->create_category();
|
||||
$c3id = $handler->create_category();
|
||||
$c2 = $handler->get_categories_with_fields()[$c2id];
|
||||
$c3 = $handler->get_categories_with_fields()[$c3id];
|
||||
$categories = $handler->get_categories_with_fields();
|
||||
$c2 = $handler->get_category_from_array(
|
||||
$categories,
|
||||
$c2id,
|
||||
$handler->get_component(),
|
||||
$handler->get_area(),
|
||||
$handler->get_itemid()
|
||||
);
|
||||
$c3 = $handler->get_category_from_array(
|
||||
$categories,
|
||||
$c3id,
|
||||
$handler->get_component(),
|
||||
$handler->get_area(),
|
||||
$handler->get_itemid()
|
||||
);
|
||||
$this->assertEquals('Other fields 1', $c2->get('name'));
|
||||
$this->assertEquals('Other fields 2', $c3->get('name'));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
@core @core_course @core_customfield @javascript
|
||||
Feature: Create shared categories and fields
|
||||
In order to have shared custom fields
|
||||
As a manager
|
||||
I need to create, edit, remove and sort shared custom field's categories
|
||||
|
||||
Scenario: Create and delete a category for shared custom fields
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Custom fields > Shared custom fields" in site administration
|
||||
And I press "Add a new category"
|
||||
And I wait until the page is ready
|
||||
Then I should see "Other fields" in the "#customfield_catlist" "css_element"
|
||||
And I click on "[data-role='deletecategory']" "css_element"
|
||||
And I click on "Yes" "button" in the "Confirm" "dialogue"
|
||||
And I wait until the page is ready
|
||||
And I wait until "Other fields" "text" does not exist
|
||||
|
||||
Scenario: Shared customfields are displayed in other entities
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| My shared category | core_customfield | shared | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Shared field 1 | My shared category | text | f1 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Default settings > Course custom fields" in site administration
|
||||
Then I should see "My shared category" in the "#customfield_catlist" "css_element"
|
||||
And I should see "Shared field 1" in the "#customfield_catlist" "css_element"
|
||||
And I navigate to "Users > Accounts > Cohort custom fields" in site administration
|
||||
And I should see "My shared category" in the "#customfield_catlist" "css_element"
|
||||
And I should see "Shared field 1" in the "#customfield_catlist" "css_element"
|
||||
|
||||
Scenario: Shared custom fields cannot be reordered, edited or deleted from other entities
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| My shared category | core_customfield | shared | 0 |
|
||||
| My course category | core_course | course | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Shared field 1 | My shared category | text | f1 |
|
||||
| Course field 1 | My course category | text | f2 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Default settings > Course custom fields" in site administration
|
||||
# Check that the delete category link exists for course categories but not for shared categories.
|
||||
Then "Delete" "link" should exist in the ".//div[contains(@class, 'categoryinstance')][.//span[@data-value='My course category']]" "xpath_element"
|
||||
And "Delete" "link" should not exist in the ".//div[contains(@class,'categoryinstance')]/h3[normalize-space(text())='My shared category']" "xpath_element"
|
||||
# Check that the inplaceeditable exists for course categories but not for shared categories.
|
||||
And "//div[contains(@class,'categoryinstance') and contains(.,'My course category') and .//span[contains(@class,'inplaceeditable')]]" "xpath_element" should exist
|
||||
And "//div[contains(@class,'categoryinstance') and contains(.,'My shared category') and .//span[contains(@class,'inplaceeditable')]]" "xpath_element" should not exist
|
||||
# Check that the move category option exists for course categories but not for shared categories.
|
||||
And "//span[contains(@class,'movecategory')][.//span[@title='Move \"My course category\"']]" "xpath_element" should exist
|
||||
And "//span[contains(@class,'movecategory')][.//span[@title='Move \"My shared category\"']]" "xpath_element" should not exist
|
||||
# Check that the move field option exists for course fields but not for shared fields.
|
||||
And "//tr[@data-field-name='Course field 1']//span[@title='Move \"Course field 1\"']" "xpath_element" should exist
|
||||
And "//tr[@data-field-name='Shared field 1']//span[@title='Move \"Shared field 1\"']" "xpath_element" should not exist
|
||||
|
||||
Scenario: Select which shared custom fields categories are used in the course entity
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| My shared category 1 | core_customfield | shared | 0 |
|
||||
| My shared category 2 | core_customfield | shared | 0 |
|
||||
| My course category | core_course | course | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Shared field 1 | My shared category 1 | text | f1 |
|
||||
| Shared field 2 | My shared category 2 | text | f2 |
|
||||
| Course field 1 | My course category | text | f3 |
|
||||
And the following "courses" exist:
|
||||
| shortname | fullname |
|
||||
| C1 | Course 1 |
|
||||
And I log in as "admin"
|
||||
When I am on the "C1" "Course" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
Then I should see "My course category"
|
||||
And I should not see "My shared category 1"
|
||||
And I should not see "My shared category 2"
|
||||
And I navigate to "Courses > Default settings > Course custom fields" in site administration
|
||||
And I toggle the "Enable My shared category 1" admin switch "on"
|
||||
And I am on the "C1" "Course" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I should see "My course category"
|
||||
And I should see "My shared category 1"
|
||||
And I should not see "My shared category 2"
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_customfield\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_customfield\shared;
|
||||
use core_customfield_generator;
|
||||
|
||||
/**
|
||||
* Unit tests for custom field toggle_shared_category external method
|
||||
*
|
||||
* @package core_customfield
|
||||
* @covers \core_customfield\external\toggle_shared_category
|
||||
* @copyright 2025 David Carrillo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class toggle_shared_category_test extends \core_external\tests\externallib_testcase {
|
||||
/**
|
||||
* Test execute
|
||||
*/
|
||||
public function test_execute(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
/** @var core_customfield_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
$sharedcategory = $generator->create_category(['component' => 'core_customfield', 'area' => 'shared']);
|
||||
$generator->create_field([
|
||||
'categoryid' => $sharedcategory->get('id'),
|
||||
'name' => 'My shared field',
|
||||
'shortname' => 'mysharedfield',
|
||||
'type' => 'text',
|
||||
]);
|
||||
|
||||
$this->assertEmpty(shared::get_records());
|
||||
|
||||
$result = toggle_shared_category::execute(
|
||||
$sharedcategory->get('id'),
|
||||
'core_course',
|
||||
'course',
|
||||
0,
|
||||
true
|
||||
);
|
||||
$result = external_api::clean_returnvalue(toggle_shared_category::execute_returns(), $result);
|
||||
$this->assertTrue($result);
|
||||
$records = shared::get_records();
|
||||
$record = reset($records);
|
||||
$this->assertEquals($sharedcategory->get('id'), $record->get('categoryid'));
|
||||
$this->assertEquals('core_course', $record->get('component'));
|
||||
$this->assertEquals('course', $record->get('area'));
|
||||
$this->assertEquals(0, $record->get('itemid'));
|
||||
|
||||
$result = toggle_shared_category::execute(
|
||||
$sharedcategory->get('id'),
|
||||
'core_course',
|
||||
'course',
|
||||
0,
|
||||
false
|
||||
);
|
||||
$result = external_api::clean_returnvalue(toggle_shared_category::execute_returns(), $result);
|
||||
$this->assertTrue($result);
|
||||
$this->assertEmpty(shared::get_records());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execute with no permission
|
||||
*/
|
||||
public function test_execute_no_permission(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
/** @var core_customfield_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
$sharedcategory = $generator->create_category(['component' => 'core_customfield', 'area' => 'shared']);
|
||||
$generator->create_field([
|
||||
'categoryid' => $sharedcategory->get('id'),
|
||||
'name' => 'My shared field',
|
||||
'shortname' => 'mysharedfield',
|
||||
'type' => 'text',
|
||||
]);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
$this->expectException(\moodle_exception::class);
|
||||
$str = "Sorry, but you do not currently have permissions to do that (Configure shared custom fields).";
|
||||
$this->expectExceptionMessage($str);
|
||||
toggle_shared_category::execute(
|
||||
$sharedcategory->get('id'),
|
||||
'core_course',
|
||||
'course',
|
||||
0,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,14 @@ class core_customfield_generator extends component_generator_base {
|
||||
|
||||
$handler = \core_customfield\handler::get_handler($record->component, $record->area, $record->itemid);
|
||||
$categoryid = $handler->create_category($record->name);
|
||||
return $handler->get_categories_with_fields()[$categoryid];
|
||||
$categories = $handler->get_categories_with_fields();
|
||||
return $handler->get_category_from_array(
|
||||
$categories,
|
||||
$categoryid,
|
||||
$handler->get_component(),
|
||||
$handler->get_area(),
|
||||
$handler->get_itemid()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +146,15 @@ class core_customfield_generator extends component_generator_base {
|
||||
|
||||
$field = field_controller::create(0, (object)['type' => $record->type], $category);
|
||||
$handler->save_field_configuration($field, $record);
|
||||
return $handler->get_categories_with_fields()[$field->get('categoryid')]->get_fields()[$field->get('id')];
|
||||
$categories = $handler->get_categories_with_fields();
|
||||
$category = $handler->get_category_from_array(
|
||||
$categories,
|
||||
$field->get('categoryid'),
|
||||
$handler->get_component(),
|
||||
$handler->get_area(),
|
||||
$handler->get_itemid()
|
||||
);
|
||||
return $category->get_fields()[$field->get('id')];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -169,7 +169,7 @@ class group_handler extends handler {
|
||||
$instanceid = $data['groupid'];
|
||||
$context = $this->get_instance_context($instanceid);
|
||||
$editablefields = $this->get_editable_fields($instanceid);
|
||||
$records = api::get_instance_fields_data($editablefields, $instanceid);
|
||||
$records = $this->get_instance_fields_data($editablefields, $instanceid);
|
||||
|
||||
foreach ($records as $d) {
|
||||
$field = $d->get_field();
|
||||
|
||||
@@ -170,7 +170,7 @@ class grouping_handler extends handler {
|
||||
$instanceid = $data['groupingid'];
|
||||
$context = $this->get_instance_context($instanceid);
|
||||
$editablefields = $this->get_editable_fields($instanceid);
|
||||
$records = api::get_instance_fields_data($editablefields, $instanceid);
|
||||
$records = $this->get_instance_fields_data($editablefields, $instanceid);
|
||||
|
||||
foreach ($records as $d) {
|
||||
$field = $d->get_field();
|
||||
|
||||
@@ -50,6 +50,8 @@ $string['eventcategoryupdated'] = 'Custom field category updated';
|
||||
$string['eventfieldcreated'] = 'Custom field created';
|
||||
$string['eventfielddeleted'] = 'Custom field deleted';
|
||||
$string['eventfieldupdated'] = 'Custom field updated';
|
||||
$string['eventsharedcategoryusagedisabled'] = 'Shared category usage disabled';
|
||||
$string['eventsharedcategoryusageenabled'] = 'Shared category usage enabled';
|
||||
$string['fieldname'] = 'Name';
|
||||
$string['fieldnotfound'] = 'Field not found';
|
||||
$string['fieldshortname'] = 'Short name';
|
||||
@@ -85,6 +87,8 @@ $string['privacy:metadata:customfield_data:valueformat'] = 'The format of the va
|
||||
$string['privacy:metadata:customfield_data:valuetrust'] = 'The trust flag of the value, when it is text';
|
||||
$string['privacy:metadata:customfieldpluginsummary'] = 'Fields for various components';
|
||||
$string['privacy:metadata:filepurpose'] = 'File attached to the custom field data';
|
||||
$string['sharedcategoryhelptext'] = 'When enabled, the custom fields in this category will be used';
|
||||
$string['sharedcustomfields'] = 'Shared custom fields';
|
||||
$string['shortname'] = 'Short name';
|
||||
$string['shortname_help'] = 'The short name must be unique and can only contain alphanumeric lowercase characters and underscores (_). It is not displayed anywhere on the site, but may be used for synchronisation with external systems or in web services.';
|
||||
$string['showdate'] = 'Show date';
|
||||
|
||||
@@ -302,6 +302,7 @@ $string['checkpermissionsin'] = 'Check permissions in {$a}';
|
||||
$string['checksystempermissionsfor'] = 'Check system permissions for {$a->fullname}';
|
||||
$string['checkuserspermissionshere'] = 'Check permissions for {$a->fullname} has in this {$a->contextlevel}';
|
||||
$string['chooseroletoassign'] = 'Please choose a role to assign';
|
||||
$string['customfield:configureshared'] = 'Configure shared custom fields';
|
||||
$string['inactiveformorethan'] = 'inactive for more than {$a->timeperiod}';
|
||||
$string['ingroup'] = 'in the group "{$a->group}"';
|
||||
$string['inherit'] = 'Inherit';
|
||||
|
||||
@@ -2849,4 +2849,12 @@ $capabilities = array(
|
||||
'manager' => CAP_ALLOW,
|
||||
],
|
||||
],
|
||||
|
||||
// Allow users to configure shared custom fields.
|
||||
'moodle/customfield:configureshared' => [
|
||||
'captype' => 'write',
|
||||
'riskbitmap' => RISK_PERSONAL,
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -4292,6 +4292,7 @@
|
||||
<FIELD NAME="area" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="shared" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@@ -4338,6 +4339,9 @@
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="area" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@@ -4345,12 +4349,32 @@
|
||||
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="instanceid-fieldid" UNIQUE="true" FIELDS="instanceid, fieldid"/>
|
||||
<INDEX NAME="instanceid-fieldid-component-area-itemid" UNIQUE="true" FIELDS="instanceid, fieldid, component, area, itemid"/>
|
||||
<INDEX NAME="fieldid-intvalue" UNIQUE="false" FIELDS="fieldid, intvalue"/>
|
||||
<INDEX NAME="fieldid-shortcharvalue" UNIQUE="false" FIELDS="fieldid, shortcharvalue"/>
|
||||
<INDEX NAME="fieldid-decvalue" UNIQUE="false" FIELDS="fieldid, decvalue"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="customfield_shared" COMMENT="core_customfield shared category table">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="area" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="customfield_category" REFFIELDS="id"/>
|
||||
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="categoryid-component-area-itemid" UNIQUE="true" FIELDS="categoryid, component, area, itemid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="h5p_libraries" COMMENT="Stores information about libraries used by H5P content.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Primary Key: The id of the library"/>
|
||||
|
||||
@@ -2927,6 +2927,13 @@ $functions = array(
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_customfield_toggle_shared' => [
|
||||
'classname' => core_customfield\external\toggle_shared_category::class,
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Toggle shared category state',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_h5p_get_trusted_h5p_file' => [
|
||||
'classname' => 'core_h5p\external',
|
||||
'methodname' => 'get_trusted_h5p_file',
|
||||
|
||||
@@ -2190,5 +2190,110 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2025090200.02);
|
||||
}
|
||||
|
||||
if ($oldversion < 2025091600.01) {
|
||||
// Define field shared to be added to customfield_category.
|
||||
$table = new xmldb_table('customfield_category');
|
||||
$field = new xmldb_field('shared', XMLDB_TYPE_INTEGER, '1', null, null, null, '0', 'contextid');
|
||||
|
||||
// Conditionally launch add field shared.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field component to be added to customfield_data.
|
||||
$table = new xmldb_table('customfield_data');
|
||||
$field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'contextid');
|
||||
|
||||
// Conditionally launch add field component.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field area to be added to customfield_data.
|
||||
$table = new xmldb_table('customfield_data');
|
||||
$field = new xmldb_field('area', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'component');
|
||||
|
||||
// Conditionally launch add field area.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field itemid to be added to customfield_data.
|
||||
$table = new xmldb_table('customfield_data');
|
||||
$field = new xmldb_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'area');
|
||||
|
||||
// Conditionally launch add field itemid.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define index instanceid-fieldid (unique) to be dropped form customfield_data.
|
||||
$table = new xmldb_table('customfield_data');
|
||||
$index = new xmldb_index('instanceid-fieldid', XMLDB_INDEX_UNIQUE, ['instanceid', 'fieldid']);
|
||||
|
||||
// Conditionally launch drop index instanceid-fieldid.
|
||||
if ($dbman->index_exists($table, $index)) {
|
||||
$dbman->drop_index($table, $index);
|
||||
}
|
||||
|
||||
// Define index instanceid-fieldid-component-area-itemid (unique) to be added to customfield_data.
|
||||
$table = new xmldb_table('customfield_data');
|
||||
$index = new xmldb_index(
|
||||
'instanceid-fieldid-component-area-itemid',
|
||||
XMLDB_INDEX_UNIQUE,
|
||||
['instanceid', 'fieldid', 'component', 'area', 'itemid']
|
||||
);
|
||||
|
||||
// Conditionally launch add index instanceid-fieldid-component-area-itemid.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Populate component, area and itemid for each record in customfield_data table.
|
||||
$sql = "SELECT d.id, c.component, c.area, c.itemid
|
||||
FROM {customfield_data} d
|
||||
JOIN {customfield_field} f ON d.fieldid = f.id
|
||||
JOIN {customfield_category} c ON f.categoryid = c.id";
|
||||
$records = $DB->get_records_sql($sql);
|
||||
|
||||
foreach ($records as $r) {
|
||||
$DB->update_record('customfield_data', (object)[
|
||||
'id' => $r->id,
|
||||
'component' => $r->component,
|
||||
'area' => $r->area,
|
||||
'itemid' => $r->itemid,
|
||||
]);
|
||||
}
|
||||
|
||||
// Define table customfield_shared to be created.
|
||||
$table = new xmldb_table('customfield_shared');
|
||||
|
||||
// Adding fields to table customfield_shared.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('categoryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('area', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table customfield_shared.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('categoryid', XMLDB_KEY_FOREIGN, ['categoryid'], 'customfield_category', ['id']);
|
||||
$table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
|
||||
|
||||
// Adding indexes to table customfield_shared.
|
||||
$table->add_index('categoryid-component-area-itemid', XMLDB_INDEX_UNIQUE, ['categoryid', 'component', 'area', 'itemid']);
|
||||
|
||||
// Conditionally launch create table for customfield_shared.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2025091600.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -276,6 +276,9 @@ class phpunit_util extends testing_util {
|
||||
if (class_exists('\core_reportbuilder\customfield\report_handler')) {
|
||||
\core_reportbuilder\customfield\report_handler::reset_caches();
|
||||
}
|
||||
if (class_exists('\core_customfield\customfield\shared_handler')) {
|
||||
\core_customfield\customfield\shared_handler::reset_caches();
|
||||
}
|
||||
|
||||
// Clear static cache within restore.
|
||||
if (class_exists('restore_section_structure_step')) {
|
||||
|
||||
@@ -208,7 +208,7 @@ class question_handler extends \core_customfield\handler {
|
||||
*/
|
||||
public function get_field_data(\core_customfield\field_controller $field, int $instanceid): \core_customfield\data_controller {
|
||||
$fields = [$field->get('id') => $field];
|
||||
$fieldsdata = api::get_instance_fields_data($fields, $instanceid);
|
||||
$fieldsdata = $this->get_instance_fields_data($fields, $instanceid);
|
||||
return $fieldsdata[$field->get('id')];
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ class question_handler extends \core_customfield\handler {
|
||||
public function restore_instance_data_from_backup(\restore_task $task, array $data) {
|
||||
|
||||
$editablefields = $this->get_editable_fields($data['newquestion']);
|
||||
$records = api::get_instance_fields_data($editablefields, $data['newquestion']);
|
||||
$records = $this->get_instance_fields_data($editablefields, $data['newquestion']);
|
||||
$target = $task->get_target();
|
||||
$override = ($target != \backup::TARGET_CURRENT_ADDING && $target != \backup::TARGET_EXISTING_ADDING);
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2025091600.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2025091600.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '5.1dev+ (Build: 20250916)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user