Merge branch 'MDL-75492-master' of https://github.com/aanabit/moodle

This commit is contained in:
Sara Arjona 2022-11-16 10:30:47 +01:00
commit 8e535a2346
34 changed files with 1682 additions and 217 deletions

View File

@ -0,0 +1,10 @@
define("mod_data/importmappingdialogue",["exports","core/notification","core/ajax","core/url","core/templates","core/modal_factory","core/prefetch","core/str"],(function(_exports,_notification,_ajax,_url,_templates,_modal_factory,_prefetch,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Javascript module for deleting a database as a preset.
*
* @module mod_data/importmappingdialogue
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_ajax=_interopRequireDefault(_ajax),_url=_interopRequireDefault(_url),_templates=_interopRequireDefault(_templates),_modal_factory=_interopRequireDefault(_modal_factory),(0,_prefetch.prefetchStrings)("mod_data",["mapping:dialogtitle:usepreset"]);const selectors_selectPresetButton='input[name="selectpreset"]';_exports.init=()=>{registerEventListeners()};const registerEventListeners=()=>{document.addEventListener("click",(event=>{const usepreset=event.target.closest(selectors_selectPresetButton);usepreset&&(event.preventDefault(),showMappingDialogue(usepreset))}))},showMappingDialogue=usepreset=>{const presetName=usepreset.dataset.presetname,cmId=usepreset.dataset.cmid;getMappingInformation(cmId,presetName).then((result=>(result.data&&result.data.needsmapping?buildModal({title:(0,_str.get_string)("mapping:dialogtitle:usepreset","mod_data",result.data.presetname),body:_templates.default.render("mod_data/fields_mapping_body",result.data),footer:_templates.default.render("mod_data/fields_mapping_footer",getMappingButtons(cmId,presetName)),large:!0}):window.location.href=_url.default.relativeUrl("mod/data/field.php",{id:cmId,mode:"usepreset",fullname:presetName},!1),!0))).catch(_notification.default.exception)},buildModal=params=>_modal_factory.default.create({...params,type:_modal_factory.default.types.DEFAULT}).then((modal=>(modal.show(),modal.showFooter(),modal.registerCloseOnCancel(),modal))).catch(_notification.default.exception),getMappingButtons=(cmId,presetName)=>{const data={};return data.mapfieldsbutton=_url.default.relativeUrl("mod/data/field.php",{id:cmId,fullname:presetName,mode:"usepreset",action:"select"},!1),data.applybutton=_url.default.relativeUrl("mod/data/field.php",{id:cmId,fullname:presetName,mode:"usepreset",action:"notmapping"},!1),data},getMappingInformation=(cmId,presetName)=>{const request={methodname:"mod_data_get_mapping_information",args:{cmid:cmId,importedpreset:presetName}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=importmappingdialogue.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,6 @@ define("mod_data/selectpreset",["exports"],(function(_exports){Object.defineProp
* @copyright 2021 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const selectors_presetRadioButton='input[name="fullname"]',selectors_selectPresetButton='input[name="selectpreset"]',selectors_selectedPresetRadioButton='input[name="fullname"]:checked';_exports.init=()=>{const radioButton=document.querySelectorAll(selectors_presetRadioButton);disableUsePresetButton(),radioButton.forEach((elem=>{elem.addEventListener("change",(function(event){event.preventDefault(),disableUsePresetButton()}))}))};const disableUsePresetButton=()=>{let selectPresetButton=document.querySelector(selectors_selectPresetButton);document.querySelectorAll(selectors_selectedPresetRadioButton).length>0?(selectPresetButton.removeAttribute("disabled"),selectPresetButton.classList.remove("btn-secondary"),selectPresetButton.classList.add("btn-primary")):(selectPresetButton.setAttribute("disabled",!0),selectPresetButton.classList.remove("btn-primary"),selectPresetButton.classList.add("btn-secondary"))}}));
const selectors_presetRadioButton='input[name="fullname"]',selectors_selectPresetButton='input[name="selectpreset"]',selectors_selectedPresetRadioButton='input[name="fullname"]:checked';_exports.init=()=>{const radioButton=document.querySelectorAll(selectors_presetRadioButton);disableUsePresetButton(),radioButton.forEach((elem=>{elem.addEventListener("change",(function(event){event.preventDefault(),disableUsePresetButton()}))}))};const disableUsePresetButton=()=>{let selectPresetButton=document.querySelector(selectors_selectPresetButton);const selectedRadioButton=document.querySelector(selectors_selectedPresetRadioButton);selectedRadioButton?(selectPresetButton.removeAttribute("disabled"),selectPresetButton.classList.remove("btn-secondary"),selectPresetButton.classList.add("btn-primary"),selectPresetButton.setAttribute("data-presetname",selectedRadioButton.getAttribute("value")),selectPresetButton.setAttribute("data-cmid",selectedRadioButton.getAttribute("data-cmid"))):(selectPresetButton.setAttribute("disabled",!0),selectPresetButton.classList.remove("btn-primary"),selectPresetButton.classList.add("btn-secondary"),selectPresetButton.removeAttribute("data-presetname"),selectPresetButton.removeAttribute("data-cmid"))}}));
//# sourceMappingURL=selectpreset.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"selectpreset.min.js","sources":["../src/selectpreset.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 * Javascript module to control the form responsible for selecting a preset.\n *\n * @module mod_data/selectpreset\n * @copyright 2021 Mihail Geshoski <mihail@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst selectors = {\n presetRadioButton: 'input[name=\"fullname\"]',\n selectPresetButton: 'input[name=\"selectpreset\"]',\n selectedPresetRadioButton: 'input[name=\"fullname\"]:checked',\n};\n\n/**\n * Initialize module.\n */\nexport const init = () => {\n const radioButton = document.querySelectorAll(selectors.presetRadioButton);\n\n // Initialize the \"Use a preset\" button properly.\n disableUsePresetButton();\n\n radioButton.forEach((elem) => {\n elem.addEventListener('change', function(event) {\n event.preventDefault();\n // Enable the \"Use a preset\" button when any of the radio buttons in the presets list is checked.\n disableUsePresetButton();\n });\n });\n\n};\n\n/**\n * Decide whether to disable or not the \"Use a preset\" button.\n * When there is no preset selected, the button should be displayed disabled; otherwise, it will appear enabled as a primary button.\n *\n * @method\n * @private\n */\nconst disableUsePresetButton = () => {\n let selectPresetButton = document.querySelector(selectors.selectPresetButton);\n const selectedRadioButton = document.querySelectorAll(selectors.selectedPresetRadioButton);\n\n if (selectedRadioButton.length > 0) {\n // There is one preset selected, so the button should be enabled.\n selectPresetButton.removeAttribute('disabled');\n selectPresetButton.classList.remove('btn-secondary');\n selectPresetButton.classList.add('btn-primary');\n } else {\n // There is no any preset selected, so the button should be disabled.\n selectPresetButton.setAttribute('disabled', true);\n selectPresetButton.classList.remove('btn-primary');\n selectPresetButton.classList.add('btn-secondary');\n }\n};\n"],"names":["selectors","radioButton","document","querySelectorAll","disableUsePresetButton","forEach","elem","addEventListener","event","preventDefault","selectPresetButton","querySelector","length","removeAttribute","classList","remove","add","setAttribute"],"mappings":";;;;;;;;MAuBMA,4BACiB,yBADjBA,6BAEkB,6BAFlBA,oCAGyB,+CAMX,WACVC,YAAcC,SAASC,iBAAiBH,6BAG9CI,yBAEAH,YAAYI,SAASC,OACjBA,KAAKC,iBAAiB,UAAU,SAASC,OACrCA,MAAMC,iBAENL,sCAaNA,uBAAyB,SACvBM,mBAAqBR,SAASS,cAAcX,8BACpBE,SAASC,iBAAiBH,qCAE9BY,OAAS,GAE7BF,mBAAmBG,gBAAgB,YACnCH,mBAAmBI,UAAUC,OAAO,iBACpCL,mBAAmBI,UAAUE,IAAI,iBAGjCN,mBAAmBO,aAAa,YAAY,GAC5CP,mBAAmBI,UAAUC,OAAO,eACpCL,mBAAmBI,UAAUE,IAAI"}
{"version":3,"file":"selectpreset.min.js","sources":["../src/selectpreset.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 * Javascript module to control the form responsible for selecting a preset.\n *\n * @module mod_data/selectpreset\n * @copyright 2021 Mihail Geshoski <mihail@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst selectors = {\n presetRadioButton: 'input[name=\"fullname\"]',\n selectPresetButton: 'input[name=\"selectpreset\"]',\n selectedPresetRadioButton: 'input[name=\"fullname\"]:checked',\n};\n\n/**\n * Initialize module.\n */\nexport const init = () => {\n const radioButton = document.querySelectorAll(selectors.presetRadioButton);\n\n // Initialize the \"Use a preset\" button properly.\n disableUsePresetButton();\n\n radioButton.forEach((elem) => {\n elem.addEventListener('change', function(event) {\n event.preventDefault();\n // Enable the \"Use a preset\" button when any of the radio buttons in the presets list is checked.\n disableUsePresetButton();\n });\n });\n\n};\n\n/**\n * Decide whether to disable or not the \"Use a preset\" button.\n * When there is no preset selected, the button should be displayed disabled; otherwise, it will appear enabled as a primary button.\n *\n * @method\n * @private\n */\nconst disableUsePresetButton = () => {\n let selectPresetButton = document.querySelector(selectors.selectPresetButton);\n const selectedRadioButton = document.querySelector(selectors.selectedPresetRadioButton);\n\n if (selectedRadioButton) {\n // There is one preset selected, so the button should be enabled.\n selectPresetButton.removeAttribute('disabled');\n selectPresetButton.classList.remove('btn-secondary');\n selectPresetButton.classList.add('btn-primary');\n selectPresetButton.setAttribute('data-presetname', selectedRadioButton.getAttribute('value'));\n selectPresetButton.setAttribute('data-cmid', selectedRadioButton.getAttribute('data-cmid'));\n } else {\n // There is no any preset selected, so the button should be disabled.\n selectPresetButton.setAttribute('disabled', true);\n selectPresetButton.classList.remove('btn-primary');\n selectPresetButton.classList.add('btn-secondary');\n selectPresetButton.removeAttribute('data-presetname');\n selectPresetButton.removeAttribute('data-cmid');\n }\n};\n"],"names":["selectors","radioButton","document","querySelectorAll","disableUsePresetButton","forEach","elem","addEventListener","event","preventDefault","selectPresetButton","querySelector","selectedRadioButton","removeAttribute","classList","remove","add","setAttribute","getAttribute"],"mappings":";;;;;;;;MAuBMA,4BACiB,yBADjBA,6BAEkB,6BAFlBA,oCAGyB,+CAMX,WACVC,YAAcC,SAASC,iBAAiBH,6BAG9CI,yBAEAH,YAAYI,SAASC,OACjBA,KAAKC,iBAAiB,UAAU,SAASC,OACrCA,MAAMC,iBAENL,sCAaNA,uBAAyB,SACvBM,mBAAqBR,SAASS,cAAcX,oCAC1CY,oBAAsBV,SAASS,cAAcX,qCAE/CY,qBAEAF,mBAAmBG,gBAAgB,YACnCH,mBAAmBI,UAAUC,OAAO,iBACpCL,mBAAmBI,UAAUE,IAAI,eACjCN,mBAAmBO,aAAa,kBAAmBL,oBAAoBM,aAAa,UACpFR,mBAAmBO,aAAa,YAAaL,oBAAoBM,aAAa,gBAG9ER,mBAAmBO,aAAa,YAAY,GAC5CP,mBAAmBI,UAAUC,OAAO,eACpCL,mBAAmBI,UAAUE,IAAI,iBACjCN,mBAAmBG,gBAAgB,mBACnCH,mBAAmBG,gBAAgB"}

View File

@ -0,0 +1,164 @@
// 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/>.
/**
* Javascript module for deleting a database as a preset.
*
* @module mod_data/importmappingdialogue
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Notification from 'core/notification';
import Ajax from 'core/ajax';
import Url from 'core/url';
import Templates from 'core/templates';
import ModalFactory from 'core/modal_factory';
import {prefetchStrings} from 'core/prefetch';
import {get_string as getString} from 'core/str';
// Load global strings.
prefetchStrings('mod_data', ['mapping:dialogtitle:usepreset']);
const selectors = {
selectPresetButton: 'input[name="selectpreset"]',
};
/**
* Initialize module
*/
export const init = () => {
registerEventListeners();
};
/**
* Register events.
*/
const registerEventListeners = () => {
document.addEventListener('click', (event) => {
const usepreset = event.target.closest(selectors.selectPresetButton);
if (usepreset) {
event.preventDefault();
showMappingDialogue(usepreset);
}
});
};
/**
* Show the confirmation modal for uploading a preset.
*
* @param {HTMLElement} usepreset the preset to import.
*/
const showMappingDialogue = (usepreset) => {
const presetName = usepreset.dataset.presetname;
const cmId = usepreset.dataset.cmid;
getMappingInformation(cmId, presetName).then((result) => {
if (result.data && result.data.needsmapping) {
buildModal({
title: getString('mapping:dialogtitle:usepreset', 'mod_data', result.data.presetname),
body: Templates.render('mod_data/fields_mapping_body', result.data),
footer: Templates.render('mod_data/fields_mapping_footer', getMappingButtons(cmId, presetName)),
large: true,
});
} else {
window.location.href = Url.relativeUrl(
'mod/data/field.php',
{
id: cmId,
mode: 'usepreset',
fullname: presetName,
},
false
);
}
return true;
}).catch(Notification.exception);
};
/**
* Given an object we want to build a modal ready to show
*
* @method buildModal
* @param {Object} params the modal params
* @param {Promise} params.title
* @param {Promise} params.body
* @param {Promise} params.footer
* @return {Object} The modal ready to display immediately and render body in later.
*/
const buildModal = (params) => {
return ModalFactory.create({
...params,
type: ModalFactory.types.DEFAULT,
}).then(modal => {
modal.show();
modal.showFooter();
modal.registerCloseOnCancel();
return modal;
}).catch(Notification.exception);
};
/**
* Add buttons to render on mapping modal.
*
* @param {int} cmId The id of the current database activity.
* @param {string} presetName The preset name to delete.
* @return {array} Same data with buttons.
*/
const getMappingButtons = (cmId, presetName) => {
const data = {};
data.mapfieldsbutton = Url.relativeUrl(
'mod/data/field.php',
{
id: cmId,
fullname: presetName,
mode: 'usepreset',
action: 'select',
},
false
);
data.applybutton = Url.relativeUrl(
'mod/data/field.php',
{
id: cmId,
fullname: presetName,
mode: 'usepreset',
action: 'notmapping'
},
false
);
return data;
};
/**
* Check whether we should show the mapping dialogue or not.
*
* @param {int} cmId The id of the current database activity.
* @param {string} presetName The preset name to delete.
* @return {promise} Resolved with the result and warnings of deleting a preset.
*/
const getMappingInformation = (cmId, presetName) => {
const request = {
methodname: 'mod_data_get_mapping_information',
args: {
cmid: cmId,
importedpreset: presetName,
}
};
return Ajax.call([request])[0];
};

View File

@ -55,17 +55,21 @@ export const init = () => {
*/
const disableUsePresetButton = () => {
let selectPresetButton = document.querySelector(selectors.selectPresetButton);
const selectedRadioButton = document.querySelectorAll(selectors.selectedPresetRadioButton);
const selectedRadioButton = document.querySelector(selectors.selectedPresetRadioButton);
if (selectedRadioButton.length > 0) {
if (selectedRadioButton) {
// There is one preset selected, so the button should be enabled.
selectPresetButton.removeAttribute('disabled');
selectPresetButton.classList.remove('btn-secondary');
selectPresetButton.classList.add('btn-primary');
selectPresetButton.setAttribute('data-presetname', selectedRadioButton.getAttribute('value'));
selectPresetButton.setAttribute('data-cmid', selectedRadioButton.getAttribute('data-cmid'));
} else {
// There is no any preset selected, so the button should be disabled.
selectPresetButton.setAttribute('disabled', true);
selectPresetButton.classList.remove('btn-primary');
selectPresetButton.classList.add('btn-secondary');
selectPresetButton.removeAttribute('data-presetname');
selectPresetButton.removeAttribute('data-cmid');
}
};

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/>.
namespace mod_data\external;
use core\notification;
use mod_data\local\importer\preset_importer;
use mod_data\manager;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/externallib.php');
/**
* This is the external method for deleting a saved preset.
*
* @package mod_data
* @since Moodle 4.1
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_mapping_information extends \external_api {
/**
* Parameters.
*
* @return \external_function_parameters
*/
public static function execute_parameters(): \external_function_parameters {
return new \external_function_parameters([
'cmid' => new \external_value(PARAM_INT, 'Id of the data activity', VALUE_REQUIRED),
'importedpreset' => new \external_value(PARAM_TEXT, 'Preset to be imported'),
]);
}
/**
* Get importing information for the given database course module.
*
* @param int $cmid Id of the course module where to import the preset.
* @param string $importedpreset Plugin or saved preset to be imported.
* @return array Information needed to decide whether to show the dialogue or not.
*/
public static function execute(int $cmid, string $importedpreset): array {
$params = self::validate_parameters(
self::execute_parameters(),
['cmid' => $cmid, 'importedpreset' => $importedpreset]
);
try {
// Let's get the manager.
list($course, $cm) = get_course_and_cm_from_cmid($params['cmid'], manager::MODULE);
$manager = manager::create_from_coursemodule($cm);
$importer = preset_importer::create_from_plugin_or_directory($manager, $params['importedpreset']);
$result['data'] = $importer->get_mapping_information();
} catch (\moodle_exception $e) {
$result['warnings'][] = [
'item' => $importedpreset,
'warningcode' => 'exception',
'message' => $e->getMessage()
];
notification::error($e->getMessage());
}
return $result;
}
/**
* Return.
*
* @return \external_single_structure
*/
public static function execute_returns(): \external_single_structure {
return new \external_single_structure([
'data' => new \external_single_structure([
'needsmapping' => new \external_value(PARAM_BOOL, 'Whether the importing needs mapping or not'),
'presetname' => new \external_value(PARAM_TEXT, 'Name of the applied preset'),
'fieldstocreate' => new \external_value(PARAM_TEXT, 'List of field names to create'),
'fieldstoremove' => new \external_value(PARAM_TEXT, 'List of field names to remove'),
], 'Information to import if everything went fine', VALUE_OPTIONAL),
'warnings' => new \external_warnings(),
]);
}
}

View File

@ -16,6 +16,7 @@
namespace mod_data\local\importer;
use core\notification;
use mod_data\manager;
use mod_data\preset;
use stdClass;
@ -36,6 +37,18 @@ abstract class preset_importer {
/** @var string directory where to find the preset. */
protected $directory;
/** @var array fields to remove. */
public $fieldstoremove;
/** @var array fields to update. */
public $fieldstoupdate;
/** @var array fields to create. */
public $fieldstocreate;
/** @var array settings to be imported. */
public $settings;
/**
* Constructor
*
@ -45,6 +58,9 @@ abstract class preset_importer {
public function __construct(manager $manager, string $directory) {
$this->manager = $manager;
$this->directory = $directory;
// Read the preset and saved result.
$this->settings = $this->get_preset_settings();
}
/**
@ -176,22 +192,33 @@ abstract class preset_importer {
}
// Now work out fields to user friendly array.
$fieldsarray = $parsedxml['preset']['#']['field'];
foreach ($fieldsarray as $field) {
if (!is_array($field)) {
continue;
}
$fieldstoimport = new StdClass();
foreach ($field['#'] as $param => $value) {
if (!is_array($value)) {
if (
array_key_exists('preset', $parsedxml) &&
array_key_exists('#', $parsedxml['preset']) &&
array_key_exists('field', $parsedxml['preset']['#'])) {
$fieldsarray = $parsedxml['preset']['#']['field'];
foreach ($fieldsarray as $field) {
if (!is_array($field)) {
continue;
}
$fieldstoimport->$param = $value[0]['#'];
$fieldstoimport = new StdClass();
foreach ($field['#'] as $param => $value) {
if (!is_array($value)) {
continue;
}
$fieldstoimport->$param = $value[0]['#'];
}
$fieldstoimport->dataid = $module->id;
$fieldstoimport->type = clean_param($fieldstoimport->type, PARAM_ALPHA);
$result->importfields[] = $fieldstoimport;
}
$fieldstoimport->dataid = $module->id;
$fieldstoimport->type = clean_param($fieldstoimport->type, PARAM_ALPHA);
$result->importfields[] = $fieldstoimport;
}
// Calculate default mapping.
if (is_null($this->fieldstoremove) && is_null($this->fieldstocreate) && is_null($this->fieldstoupdate)) {
$this->set_affected_fields($result->importfields, $result->currentfields);
}
// Now add the HTML templates to the settings array so we can update d.
foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) {
$result->settings->$templatename = $this->get_file_contents(
@ -215,63 +242,44 @@ abstract class preset_importer {
public function import(bool $overwritesettings): bool {
global $DB, $OUTPUT;
$params = $this->get_preset_settings();
$settings = $params->settings;
$newfields = $params->importfields;
$currentfields = $params->currentfields;
$preservedfields = [];
$settings = $this->settings->settings;
$currentfields = $this->settings->currentfields;
$missingfieldtypes = [];
$module = $this->manager->get_instance();
// Maps fields and makes new ones.
if (!empty($newfields)) {
// We require an injective mapping, and need to know what to protect.
foreach ($newfields as $newid => $newfield) {
$cid = optional_param("field_$newid", -1, PARAM_INT);
if ($cid == -1) {
continue;
}
if (array_key_exists($cid, $preservedfields)) {
throw new \moodle_exception('notinjectivemap', 'data');
} else {
$preservedfields[$cid] = true;
}
}
foreach ($newfields as $newid => $newfield) {
$cid = optional_param("field_$newid", -1, PARAM_INT);
/* A mapping. Just need to change field params. Data kept. */
if ($cid != -1 && isset($currentfields[$cid])) {
$fieldobject = data_get_field_from_id($currentfields[$cid]->id, $module);
foreach ($newfield as $param => $value) {
if ($param != "id") {
$fieldobject->field->$param = $value;
}
foreach ($this->fieldstoupdate as $currentid => $updatable) {
if ($currentid != -1 && isset($currentfields[$currentid])) {
$fieldobject = data_get_field_from_id($currentfields[$currentid]->id, $module);
foreach ($updatable as $param => $value) {
if ($param != "id") {
$fieldobject->field->$param = $value;
}
unset($fieldobject->field->similarfield);
$fieldobject->update_field();
unset($fieldobject);
} else {
/* Make a new field */
$filepath = "field/$newfield->type/field.class.php";
if (!file_exists($filepath)) {
$missingfieldtypes[] = $newfield->name;
continue;
}
include_once($filepath);
if (!isset($newfield->description)) {
$newfield->description = '';
}
$classname = 'data_field_'.$newfield->type;
$fieldclass = new $classname($newfield, $module);
$fieldclass->insert_field();
unset($fieldclass);
}
unset($fieldobject->field->similarfield);
$fieldobject->update_field();
unset($fieldobject);
}
if (!empty($missingfieldtypes)) {
echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes));
}
foreach ($this->fieldstocreate as $newfield) {
/* Make a new field */
$filepath = "field/$newfield->type/field.class.php";
if (!file_exists($filepath)) {
$missingfieldtypes[] = $newfield->name;
continue;
}
include_once($filepath);
if (!isset($newfield->description)) {
$newfield->description = '';
}
$classname = 'data_field_' . $newfield->type;
$fieldclass = new $classname($newfield, $module);
$fieldclass->insert_field();
unset($fieldclass);
}
if (!empty($missingfieldtypes)) {
echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes));
}
// Get rid of all old unused data.
@ -328,6 +336,59 @@ abstract class preset_importer {
return $this->cleanup();
}
/**
* Returns information about the fields needs to be removed, updated or created.
*
* @param array $newfields Array of new fields to be applied.
* @param array $currentfields Array of current fields on database activity.
* @return void
*/
public function set_affected_fields(array $newfields = [], array $currentfields = []): void {
$fieldstoremove = [];
$fieldstocreate = [];
$preservedfields = [];
// Maps fields and makes new ones.
if (!empty($newfields)) {
// We require an injective mapping, and need to know what to protect.
foreach ($newfields as $newid => $newfield) {
$preservedfieldid = optional_param("field_$newid", -1, PARAM_INT);
if (array_key_exists($preservedfieldid, $preservedfields)) {
throw new \moodle_exception('notinjectivemap', 'data');
}
if ($preservedfieldid == -1) {
// Let's check if there is any field with same type and name that we could map to.
foreach ($currentfields as $currentid => $currentfield) {
if (($currentfield->type == $newfield->type) &&
($currentfield->name == $newfield->name) && !array_key_exists($currentid, $preservedfields)) {
// We found a possible default map.
$preservedfieldid = $currentid;
$preservedfields[$currentid] = $newfield;
}
}
}
if ($preservedfieldid == -1) {
// We need to create a new field.
$fieldstocreate[] = $newfield;
} else {
$preservedfields[$preservedfieldid] = $newfield;
}
}
}
foreach ($currentfields as $currentid => $currentfield) {
if (!array_key_exists($currentid, $preservedfields)) {
$fieldstoremove[] = $currentfield;
}
}
$this->fieldstocreate = $fieldstocreate;
$this->fieldstoremove = $fieldstoremove;
$this->fieldstoupdate = $preservedfields;
}
/**
* Any clean up routines should go here
*
@ -343,7 +404,10 @@ abstract class preset_importer {
* @return bool True if the current database needs to map the fields imported.
*/
public function needs_mapping(): bool {
return $this->manager->has_fields();
if (!$this->manager->has_fields()) {
return false;
}
return (!empty($this->fieldstocreate) || !empty($this->fieldstoremove));
}
/**
@ -365,16 +429,14 @@ abstract class preset_importer {
* @return void
*/
public function finish_import_process(bool $overwritesettings, stdClass $instance): void {
global $DB;
$this->import($overwritesettings);
$strimportsuccess = get_string('importsuccess', 'data');
$straddentries = get_string('addentries', 'data');
$strtodatabase = get_string('todatabase', 'data');
if (!$DB->get_records('data_records', ['dataid' => $instance->id])) {
\core\notification::success("$strimportsuccess <a href='edit.php?d=$instance->id'>$straddentries</a> $strtodatabase");
$result = $this->import($overwritesettings);
if ($result) {
notification::success(get_string('importsuccess', 'mod_data'));
} else {
\core\notification::success($strimportsuccess);
notification::error(get_string('cannotapplypreset', 'mod_data'));
}
$backurl = new \moodle_url('/mod/data/field.php', ['d' => $instance->id]);
redirect($backurl);
}
/**
@ -385,17 +447,64 @@ abstract class preset_importer {
* @throws \moodle_exception when the file provided as parameter (POST or GET) does not exist
*/
public static function create_from_parameters(manager $manager): preset_importer {
global $CFG;
$fullname = optional_param('fullname', '', PARAM_PATH); // Directory the preset is in.
if (!$fullname) {
$presetdir = $CFG->tempdir . '/forms/' . required_param('directory', PARAM_FILE);
if (!file_exists($presetdir) || !is_dir($presetdir)) {
throw new \moodle_exception('cannotimport');
}
$importer = new preset_upload_importer($manager, $presetdir);
} else {
$importer = new preset_existing_importer($manager, $fullname);
$fullname = required_param('directory', PARAM_FILE);
}
return $importer;
return self::create_from_plugin_or_directory($manager, $fullname);
}
/**
* Get the right importer instance from the provided parameters (POST or GET)
*
* @param manager $manager the current database manager
* @param string $pluginordirectory The plugin name or directory to create the importer from.
* @return preset_importer the relevant preset_importer instance
*/
public static function create_from_plugin_or_directory(manager $manager, string $pluginordirectory): preset_importer {
global $CFG;
if (!$pluginordirectory) {
throw new \moodle_exception('emptypresetname', 'mod_data');
}
try {
$presetdir = $CFG->tempdir . '/forms/' . $pluginordirectory;
if (file_exists($presetdir) && is_dir($presetdir)) {
return new preset_upload_importer($manager, $presetdir);
} else {
return new preset_existing_importer($manager, $pluginordirectory);
}
} catch (\moodle_exception $e) {
throw new \moodle_exception('errorpresetnotfound', 'mod_data', '', $pluginordirectory);
}
}
/**
* Get the information needed to decide the modal
*
* @return array An array with all the information to decide the mapping
*/
public function get_mapping_information(): array {
return [
'needsmapping' => $this->needs_mapping(),
'presetname' => preset::get_name_from_plugin($this->get_directory()),
'fieldstocreate' => $this->get_field_names($this->fieldstocreate),
'fieldstoremove' => $this->get_field_names($this->fieldstoremove),
];
}
/**
* Returns a list of the fields
*
* @param array $fields Array of fields to get name from.
* @return string A string listing the names of the fields.
*/
public function get_field_names(array $fields): string {
$fieldnames = array_map(function($field) {
return $field->name;
}, $fields);
return implode(', ', $fieldnames);
}
}

View File

@ -82,6 +82,21 @@ class action_bar {
return $renderer->render_fields_action_bar($fieldsactionbar);
}
/**
* Generate the output for the action bar in the field mappings page.
*
* @return string The HTML code for the action bar.
*/
public function get_fields_mapping_action_bar(): string {
global $PAGE;
$renderer = $PAGE->get_renderer('mod_data');
$fieldsactionbar = new fields_mappings_action_bar($this->id);
$data = $fieldsactionbar->export_for_template($renderer);
return $renderer->render_from_template('mod_data/fields_action_bar', $data);
}
/**
* Generate the output for the create a new field action menu.
*

View File

@ -66,6 +66,7 @@ class fields_action_bar implements templatable, renderable {
$data = [
'd' => $this->id,
'tertiarytitle' => get_string('managefields', 'mod_data'),
];
if ($this->fieldselect) {

View File

@ -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 mod_data\output;
use templatable;
use renderable;
/**
* Renderable class for the action bar elements in the fields mapping page in the database activity.
*
* @package mod_data
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fields_mappings_action_bar implements templatable, renderable {
/** @var int $id The database module id. */
private $id;
/**
* The class constructor.
*
* @param int $id The database module id
*/
public function __construct(int $id) {
$this->id = $id;
}
/**
* Export the data for the mustache template.
*
* @param \renderer_base $output The renderer to be used to render the action bar elements.
* @return array
*/
public function export_for_template(\renderer_base $output): array {
return [
'tertiarytitle' => get_string('fieldmappings', 'mod_data'),
'hasback' => true,
'backtitle' => get_string('back'),
'backurl' => new \moodle_url('/mod/data/preset.php', ['d' => $this->id]),
];
}
}

View File

@ -83,7 +83,7 @@ class preset_preview implements templatable, renderable {
* @return array
*/
public function export_for_template(\renderer_base $output): array {
$instance = $this->manager->get_instance();
$coursemodule = $this->manager->get_coursemodule();
$preset = $this->preset;
// Get fields for preview.
@ -107,7 +107,7 @@ class preset_preview implements templatable, renderable {
}
return [
'd' => $instance->id,
'cmid' => $coursemodule->id,
'description' => $preset->description ?? '',
'preview' => $content,
'formactionurl' => $useurl->out(),

View File

@ -102,6 +102,7 @@ class presets implements templatable, renderable {
$fullname = $preset->get_fullname();
$id = $this->manager->get_instance()->id;
$cmid = $this->manager->get_coursemodule()->id;
$previewurl = new moodle_url(
'/mod/data/preset.php',
['d' => $id, 'fullname' => $fullname, 'action' => 'preview']
@ -109,6 +110,7 @@ class presets implements templatable, renderable {
$presets[] = [
'id' => $id,
'cmid' => $cmid,
'name' => $preset->name,
'url' => $previewurl->out(),
'shortname' => $preset->shortname,

View File

@ -576,7 +576,8 @@ class preset {
* @return string The plugin preset name to display.
*/
public static function get_name_from_plugin(string $pluginname): string {
if ($pos = strpos($pluginname, '/')) {
$pos = strpos($pluginname, '/');
if ($pos !== false) {
$pluginname = substr($pluginname, $pos + 1);
}
if (!strpos(trim($pluginname), ' ') && get_string_manager()->string_exists('modulename', 'datapreset_'.$pluginname)) {

View File

@ -122,4 +122,11 @@ $functions = array(
'ajax' => true,
'capabilities' => 'mod/data:manageuserpresets',
),
'mod_data_get_mapping_information' => array(
'classname' => 'mod_data\external\get_mapping_information',
'description' => 'Get importing information',
'type' => 'read',
'ajax' => true,
'capabilities' => 'mod/data:managetemplates',
),
);

View File

@ -108,13 +108,9 @@ $data->instance = $cm->instance;
$renderer = $manager->get_renderer();
if ($action == 'finishimport' && confirm_sesskey()) {
data_print_header($course, $cm, $data, false);
$overwritesettings = optional_param('overwritesettings', false, PARAM_BOOL);
$importer = preset_importer::create_from_parameters($manager);
$importer->finish_import_process($overwritesettings, $data);
echo $OUTPUT->continue_button(new moodle_url('/mod/data/field.php', ['d' => $data->id]));
echo $OUTPUT->footer();
exit;
}
switch ($mode) {
@ -255,19 +251,18 @@ switch ($mode) {
case 'usepreset':
$importer = preset_importer::create_from_parameters($manager);
if (!$importer->needs_mapping()) {
if (!$importer->needs_mapping() || $action == 'notmapping') {
$backurl = new moodle_url('/mod/data/field.php', ['id' => $cm->id]);
if ($importer->import(false)) {
notification::success(get_string('importsuccess', 'mod_data'));
} else {
notification::error(get_string('presetapplied', 'mod_data'));
notification::error(get_string('cannotapplypreset', 'mod_data'));
}
redirect($backurl);
}
$PAGE->navbar->add(get_string('usestandard', 'data'));
$fieldactionbar = $actionbar->get_fields_action_bar();
$fieldactionbar = $actionbar->get_fields_mapping_action_bar();
data_print_header($course, $cm, $data, false, $fieldactionbar);
echo $OUTPUT->heading(get_string('usestandard', 'data'), 2, 'mb-4');
$importer = new preset_existing_importer($manager, $fullname);
echo $renderer->importing_preset($data, $importer);
echo $OUTPUT->footer();

View File

@ -27,7 +27,6 @@ $string['action'] = 'Action';
$string['actionsmenu'] = 'Actions menu';
$string['add'] = 'Add entry';
$string['addcomment'] = 'Add comment';
$string['addentries'] = 'Add entries';
$string['addtemplate'] = 'Add entry template';
$string['advancedsearch'] = 'Advanced search';
$string['allowcomments'] = 'Allow comments on entries';
@ -61,6 +60,7 @@ $string['cannotadd'] = 'Can not add entries!';
$string['cannotdeletepreset'] = 'Error deleting a preset!';
$string['cannotoverwritepreset'] = 'Error overwriting preset';
$string['cannotunziptopreset'] = 'Cannot unzip to the preset directory';
$string['cannotapplypreset'] = 'Error applying the preset.';
$string['closebeforeopen'] = 'You have specified an end date before the start date.';
$string['columns'] = 'columns';
$string['comment'] = 'Comment';
@ -146,6 +146,7 @@ $string['eventrecordupdated'] = 'Record updated';
$string['eventtemplateupdated'] = 'Template updated';
$string['eventtemplateviewed'] = 'Templates viewed';
$string['fileencoding'] = 'Encoding';
$string['emptypresetname'] = 'Preset name or file cannot be empty';
$string['entries'] = 'Entries';
$string['entrieslefttoadd'] = 'You must add {$a->entriesleft} more entry/entries in order to complete this activity';
$string['entrieslefttoaddtoview'] = 'You must add {$a->entrieslefttoview} more entry/entries before you can view other participants\' entries.';
@ -155,6 +156,7 @@ $string['errormustbeteacher'] = 'You need to be a teacher to use this page!';
$string['errorpresetexists'] = 'A preset with this name already exists.';
$string['errorpresetexistsbutnotoverwrite'] = 'A preset with this name already exists. Choose a different name.';
$string['errormustsupplyvalue'] = 'You must supply a value here.';
$string['errorpresetnotfound'] = 'Preset with name {$a} not found.';
$string['example'] = 'Database module example';
$string['excel'] = 'Excel';
$string['export'] = 'Export';
@ -177,7 +179,7 @@ $string['fieldheight'] = 'Height';
$string['fieldheightlistview'] = 'Height in list view';
$string['fieldheightsingleview'] = 'Height in single view';
$string['fieldids'] = 'Field ids';
$string['fieldmappings'] = 'Field mappings';
$string['fieldmappings'] = 'Fields mappings';
$string['fieldmappings_help'] = 'This menu allows you to keep the data from the existing database. To preserve the data in a field, you must map it to a new field, where the data will appear. Any field can also be left blank, with no information copied into it. Any old field not mapped to a new one will be lost and all its data removed.
You can only map fields of the same type, so each drop-down menu will have different fields in it. Also, you must be careful not to try and map one old field to more than one new field.';
$string['fieldname'] = 'Field name';
@ -219,7 +221,7 @@ $string['expired'] = 'Sorry, this activity closed on {$a} and is no longer avail
$string['importentries'] = 'Import entries';
$string['importpreset'] = 'Import preset';
$string['importapreset'] = 'Import a preset';
$string['importsuccess'] = 'The preset has been successfully applied.';
$string['importsuccess'] = 'Preset applied.';
$string['importpresetmissingcapability'] = 'You don\'t have permission to import a preset.';
$string['includeapproval'] = 'Include approval status';
$string['includetags'] = 'Include tags';
@ -266,11 +268,18 @@ $string['listview'] = 'List view';
$string['listtemplate'] = 'List view template';
$string['longitude'] = 'Longitude';
$string['manageapproved'] = 'Allow editing of approved entries';
$string['managefields'] = 'Manage fields';
$string['manageapproved_help'] = 'If disabled, approved entries are no longer editable or deletable by the user who added them. This setting has no effect unless \'Approval required\' is enabled.';
$string['managefields'] = 'Manage fields';
$string['mapexistingfield'] = 'Map to {$a}';
$string['mapnewfield'] = 'Create a new field';
$string['mappingwarning'] = 'All old fields not mapped to a new field will be lost and all data in that field will be removed.';
$string['mapping:fieldstocreate'] = 'Fields to be created: {$a}';
$string['mapping:fieldstodelete'] = 'Existing fields to be deleted: {$a}';
$string['mapping:warningmessagedeleteandcreate'] = 'If fields to be deleted are of the same type as fields to be created, you may be able to map them in Map fields.';
$string['mapping:warningmessagedelete'] = 'If fields to be deleted are of the same type as new fields in the preset you\'re applying, you may be able to map them in Map fields.';
$string['mapping:mapfields'] = 'Map fields';
$string['mapping:applypresets'] = 'Apply preset';
$string['mapping:dialogtitle:usepreset'] = 'Apply preset {$a}?';
$string['maxentries'] = 'Maximum number of entries';
$string['maxentries_help'] = 'The maximum number of entries a student is allowed to submit for this activity.';
$string['maxsize'] = 'Maximum size';
@ -330,7 +339,7 @@ $string['optionalfilename'] = 'Filename (optional)';
$string['other'] = 'Other';
$string['overwrite'] = 'Overwrite';
$string['overrwritedesc'] = 'Replace existing preset with this name and overwrite its contents';
$string['overwritesettings'] = 'Overwrite current settings';
$string['overwritesettings'] = 'Overwrite current settings such as comments, ratings, etc.';
$string['page-mod-data-x'] = 'Any database activity module page';
$string['pagesize'] = 'Entries per page';
$string['participants'] = 'Participants';
@ -434,7 +443,6 @@ $string['text'] = 'Text';
$string['textarea'] = 'Text area';
$string['timeadded'] = 'Time added';
$string['timemodified'] = 'Time modified';
$string['todatabase'] = 'to this database.';
$string['type'] = 'Field type';
$string['undefinedprocessactionmethod'] = 'No action method defined in Data_Preset to handle action "{$a}".';
$string['unsupportedfields'] = 'Unsupported fields';
@ -474,3 +482,5 @@ $string['pleaseaddsome'] = 'Please create some below or <a href="{$a}">choose a
$string['blank'] = 'Blank';
$string['savetemplate'] = 'Save template';
$string['addedby'] = 'Added by';
$string['addentries'] = 'Add entries';
$string['todatabase'] = 'to this database.';

View File

@ -6,3 +6,6 @@ pleaseaddsome,mod_data
blank,mod_data
savetemplate,mod_data
addedby,mod_data
addentries,mod_data
todatabase,mod_data

View File

@ -119,7 +119,7 @@ if ($action == 'importzip') {
$importer = new preset_upload_importer($manager, $CFG->tempdir . $filepath);
if ($importer->needs_mapping()) {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('importpreset', 'data'), 2, 'mb-4');
echo $OUTPUT->heading(get_string('fieldmappings', 'data'), 2, 'mb-4');
echo $renderer->importing_preset($data, $importer);
echo $OUTPUT->footer();
exit(0);
@ -151,8 +151,6 @@ if ($action === 'preview') {
exit(0);
}
echo $OUTPUT->header();
if ($action === 'finishimport') {
if (!confirm_sesskey()) {
throw new moodle_exception('invalidsesskey');
@ -160,12 +158,10 @@ if ($action === 'finishimport') {
$overwritesettings = optional_param('overwritesettings', false, PARAM_BOOL);
$importer = preset_importer::create_from_parameters($manager);
$importer->finish_import_process($overwritesettings, $data);
echo $OUTPUT->continue_button(new moodle_url('/mod/data/preset.php', ['d' => $data->id]));
echo $OUTPUT->footer();
exit(0);
}
echo $OUTPUT->header();
$actionbar = new \mod_data\output\action_bar($data->id, $url);
echo $actionbar->get_presets_action_bar();
$presets = new \mod_data\output\presets($manager, $presets, new \moodle_url('/mod/data/field.php'), true);

View File

@ -57,9 +57,8 @@ class mod_data_renderer extends plugin_renderer_base {
public function importing_preset(stdClass $datamodule, \mod_data\local\importer\preset_importer $importer): string {
$strwarning = get_string('mappingwarning', 'data');
$strfieldmappings = get_string('fieldmappings', 'data');
$params = $importer->get_preset_settings();
$params = $importer->settings;
$newfields = $params->importfields;
$currentfields = $params->currentfields;
@ -77,8 +76,6 @@ class mod_data_renderer extends plugin_renderer_base {
);
if (!empty($newfields)) {
$html .= $this->output->heading_with_help($strfieldmappings, 'fieldmappings', 'data', '', '', 3);
$table = new html_table();
$table->data = array();
@ -94,10 +91,18 @@ class mod_data_renderer extends plugin_renderer_base {
continue;
}
if ($currentfield->name == $newfield->name) {
$row[1] .= html_writer::tag('option', get_string('mapexistingfield', 'data', $currentfield->name), array('value'=>$cid, 'selected'=>'selected'));
$selected=true;
$row[1] .= html_writer::tag(
'option',
get_string('mapexistingfield', 'data', $currentfield->name),
['value' => $cid, 'selected' => 'selected']
);
$selected = true;
} else {
$row[1] .= html_writer::tag('option', get_string('mapexistingfield', 'data', $currentfield->name), array('value'=>$cid));
$row[1] .= html_writer::tag(
'option',
get_string('mapexistingfield', 'data', $currentfield->name),
['value' => $cid]
);
}
}
@ -117,13 +122,13 @@ class mod_data_renderer extends plugin_renderer_base {
}
$html .= html_writer::start_tag('div', array('class'=>'overwritesettings'));
$html .= html_writer::tag('label', get_string('overwritesettings', 'data'), array('for' => 'overwritesettings'));
$attrs = array('type' => 'checkbox', 'name' => 'overwritesettings', 'id' => 'overwritesettings', 'class' => 'ml-1');
$attrs = ['type' => 'checkbox', 'name' => 'overwritesettings', 'id' => 'overwritesettings', 'class' => 'mr-2'];
$html .= html_writer::empty_tag('input', $attrs);
$html .= html_writer::tag('label', get_string('overwritesettings', 'data'), ['for' => 'overwritesettings']);
$html .= html_writer::end_tag('div');
$actionbuttons = html_writer::start_div();
$cancelurl = new moodle_url('/mod/data/preset.php', ['d' => $datamodule->id]);
$cancelurl = new moodle_url('/mod/data/field.php', ['d' => $datamodule->id]);
$actionbuttons .= html_writer::tag('a', get_string('cancel') , [
'href' => $cancelurl->out(false),
'class' => 'btn btn-secondary mr-2',

View File

@ -198,6 +198,7 @@
.entriesactions .dropdown-toggle:hover,
.entriesactions .show > .btn-secondary.dropdown-toggle,
#page-mod-data-view .whitebutton .btn-secondary,
#page-mod-data-preset .whitebutton .btn-secondary,
#page-mod-data-field- .whitebutton .btn-secondary,
#page-mod-data-templates .whitebutton .btn-secondary {
background: white;

View File

@ -22,7 +22,7 @@
Example context (json):
{
"d": 1,
"tertiarytitle": "Manage fields",
"fieldselect": {
"id": "single_select_test",
"name": "fieldselector",
@ -43,15 +43,34 @@
],
"disabled": false,
"title": null
}
},
"hasback" : true,
"backurl": "https://moodlesite.com/mod/data/preset.php",
"backtitle": "Back"
}
}}
<div class="container-fluid tertiary-navigation">
<div class="row">
<div class="col">
<h2>{{#str}}managefields, mod_data{{/str}}</h2>
<div class="d-flex">
{{#hasback}}
<div class="navitem mb-0 backbutton border-right">
<a href="{{backurl}}" role="button" title="{{backtitle}}" class="d-flex align-items-center">{{!
}}{{#pix}} i/previous, moodle {{/pix}}{{!
}}</a>
</div>
{{/hasback}}
{{#urlselect}}
<div class="navitem mb-0">
{{>core/url_select}}
</div>
{{/urlselect}}
{{#tertiarytitle}}
<div class="navitem mb-0 title d-flex align-items-center">
<h2>{{tertiarytitle}}</h2>
</div>
{{/tertiarytitle}}
</div>
<div class="col-auto">
<div class="ml-sm-auto d-flex">
{{#fieldselect}}
<div class='navitem mb-0 fieldadd'>
{{>core/action_menu}}

View File

@ -0,0 +1,48 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_data/field_mapping_modal
Display a modal to decide on field mapping
Example context (json):
{
"presetname": "Image gallery",
"fieldstocreate": "A, B, C",
"fieldstodelete": "A, B, C",
"cancelbutton": "",
"mapfieldsbutton": "",
"applybutton": ""
}
}}
<div class="p-2">
{{#fieldstocreate}}
<div class="my-2"><span>{{#str}}mapping:fieldstocreate, mod_data, {{.}}{{/str}}</span></div>
{{/fieldstocreate}}
{{#fieldstoremove}}
<div class="my-2">
<span>{{#str}}mapping:fieldstodelete, mod_data, {{.}}{{/str}}</span>
</div>
{{#fieldstocreate}}
<div class="my-2">
{{#str}}mapping:warningmessagedeleteandcreate, mod_data{{/str}}
</div>
{{/fieldstocreate}}
{{^fieldstocreate}}
<div class="my-2">
{{#str}}mapping:warningmessagedelete, mod_data{{/str}}
</div>
{{/fieldstocreate}}
{{/fieldstoremove}}
</div>

View File

@ -0,0 +1,33 @@
{{!
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 mod_data/field_mapping_modal
Display a modal to decide on field mapping
Example context (json):
{
"mapfieldsbutton": "",
"applybutton": ""
}
}}
<button class="btn" data-action="cancel">
{{#str}} cancel {{/str}}
</button>
<a href="{{mapfieldsbutton}}" class="btn btn-secondary" role="button" data-action="mapfields">
{{#str}} mapping:mapfields, mod_data {{/str}}
</a>
<a href="{{applybutton}}" class="btn btn-primary" role="button" data-action="applypreset">
{{#str}} mapping:applypresets, mod_data {{/str}}
</a>

View File

@ -19,7 +19,7 @@
Example context (json):
{
"formactionurl": "#",
"d": "42",
"cmid": "42",
"description": "Preset description",
"preview": "<p>The preset preview HTML</p>",
"userid": "0",
@ -34,11 +34,18 @@
{{< core/sticky_footer }}
{{$ stickycontent }}
<form method="post" action="{{formactionurl}}">
<input type="hidden" name="d" value="{{d}}">
<input type="hidden" name="cmid" value="{{cmid}}">
<input type="hidden" name="mode" value="usepreset">
<input type="hidden" name="action" value="select">
<input type="hidden" name="fullname" value="{{userid}}/{{shortname}}">
<input type="submit" name="selectpreset" value="{{#str}}usepreset, mod_data{{/str}}" class="btn btn-primary">
<input type="submit" name="selectpreset" value="{{#str}}usepreset, mod_data{{/str}}" class="btn btn-primary"
data-cmid="{{cmid}}" data-presetname="{{userid}}/{{shortname}}">
</form>
{{/ stickycontent }}
{{/ core/sticky_footer }}
{{#js}}
require(['mod_data/importmappingdialogue'], function(importPreset) {
importPreset.init();
});
{{/js}}

View File

@ -28,6 +28,7 @@
"presets": [
{
"id": 1,
"cmid": 1,
"name": "Image gallery",
"shortname": "imagegallery",
"fullname": "Image gallery",
@ -38,6 +39,7 @@
},
{
"id": 2,
"cmid": 2,
"name": "Preset saved manually",
"shortname": "Preset saved manually",
"fullname": "Preset saved manually (admin)",
@ -68,7 +70,7 @@
{{#presets}}
<tr>
<td class="p-4 border-right">
<input type="radio" name="fullname" value="{{userid}}/{{shortname}}" />
<input type="radio" name="fullname" value="{{userid}}/{{shortname}}" data-cmid="{{cmid}}" />
</td>
<td class="p-4">
<a href="{{{url}}}">{{fullname}}</a>
@ -104,8 +106,10 @@
selectPreset.init();
});
require(['mod_data/editpreset'], function(editPreset) {
editPreset.init();
});
require(['mod_data/importmappingdialogue'], function(importPreset) {
importPreset.init();
});
{{/js}}

View File

@ -313,10 +313,10 @@ Feature: Users can view and manage data presets
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
Then I click on "Import preset and apply" "button" in the ".modal-dialog" "css_element"
Then I should see "The preset has been successfully applied."
Then I should see "Preset applied"
# I am on the field page.
And I should see "Manage fields"
Then I should see "The preset has been successfully applied."
Then I should see "Preset applied"
Examples:
| user |

View File

@ -17,19 +17,14 @@ Feature: Users can import presets
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Mountain landscapes | n | C1 | data1 |
And the following "mod_data > presets" exist:
| database | name | description |
| data1 | Saved preset 1 | The preset1 has description |
| data1 | Saved preset 2 | |
Scenario: Teacher can import from preset page on an empty database
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
Then I should not see "Fields mappings"
And I should see "Image" in the "image" "table_row"
Scenario: Teacher can import from preset page on a database with fields
@ -38,11 +33,10 @@ Feature: Users can import presets
| data1 | text | Test field name | Test field description |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
Then I should see "Fields mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
@ -62,35 +56,10 @@ Feature: Users can import presets
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
Scenario: Teacher can import from field page on an empty database
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
And I should see "Image" in the "image" "table_row"
Scenario: Teacher can import from field page on a database with fields
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
Then I should see "Fields mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
@ -110,18 +79,85 @@ Feature: Users can import presets
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
Then I should see "Fields mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to field1"
And I click on "Continue" "button"
And I follow "Database"
And I should see "Student entry"
Scenario: Teacher can import from zero state page on an empty database
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I click on "Import a preset" "button"
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
Then I should not see "Fields mappings"
And I should see "Image" in the "image" "table_row"
Scenario: Importing a preset could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Importing a preset could create map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
# Let's map a field that is not mapped by default
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Importing same preset twice doesn't show mapping dialogue
# Importing a preset on an empty database doesn't show the mapping dialogue, so we add a field for the database
# not to be empty.
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I should see "Fields mappings"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
And I click on "Import preset and apply" "button"
Then I should not see "Fields mappings"
And I should see "Preset applied"

View File

@ -161,5 +161,5 @@ Feature: Users can preview presets
When I follow "Presets"
And I click on "Saved preset by teacher1" "link"
And I click on "Use this preset" "button"
Then I should see "Field mappings"
Then I should see "Preset applied"
And I should see "My URL field"

View File

@ -0,0 +1,240 @@
@mod @mod_data @javascript
Feature: Users can use predefined presets
In order to use presets
As a user
I need to select an existing preset
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Mountain landscapes | introduction... | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
Scenario: Using a preset on a non empty database could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
And I should see "Fields mappings"
When I click on "Continue" "button"
And I should see "Preset applied"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Using a preset on a non-empty database could show the option to map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
# Let's map a field that is not mapped by default
And I should see "Apply preset Image gallery"
And I should see "Fields to be created: image, title, description"
And I should see "Existing fields to be deleted: Test field name, oldtitle"
When I click on "Map fields" "button"
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Teacher can use a preset from presets page on a database with existing entries
# Creating an entry to test use a preset feature with databases with entries.
Given the following "mod_data > entries" exist:
| database | Test field name |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And the "Use this preset" "button" should be enabled
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
Then I should see "Fields mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Fields"
And I should see "title"
And I follow "Database"
And I should see "Student entry 1"
Scenario: Using same preset twice doesn't show mapping dialogue and applies the preset directly
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
When I click on "Use this preset" "button"
And I should see "Apply preset"
And I click on "Map fields" "button"
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
Then I should not see "Apply preset Image gallery"
And I should see "Preset applied"
Scenario: Using a preset from preset preview page on a non empty database could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
When I click on "Apply preset" "button"
And I should see "Preset applied"
And I follow "Fields"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Using a preset from preset preview page on a non-empty database could show the option to map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
# Let's map a field that is not mapped by default
And I should see "Apply preset Image gallery"
And I should see "Fields to be created: image, title, description"
And I should see "Existing fields to be deleted: Test field name, oldtitle"
When I click on "Map fields" "button"
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Teacher can use a preset from preset preview page on a database with existing entries
# Creating an entry to test use a preset feature with databases with entries.
Given the following "mod_data > entries" exist:
| database | Test field name |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
And the "Use this preset" "button" should be enabled
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
Then I should see "Fields mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Fields"
And I should see "title"
And I follow "Database"
And I should see "Student entry 1"
Scenario: Using same preset twice from preset preview page doesn't show mapping dialogue and applies the preset
directly
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
And I should see "Fields mappings"
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
Then I should not see "Fields mappings"
And I should see "Preset applied"
Scenario: Apply preset dialogue should show helpful information to the user
Given the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Sea landscapes | introduction... | C1 | data2 |
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | text | title |
And I am on the "Sea landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be created only.
Then I should see "Fields to be created: image, description"
And I should not see "If fields to be deleted are of the same type as fields to be created"
And I should not see "If fields to be deleted are of the same type as new fields in the preset"
And I click on "Cancel" "button" in the "Apply preset Image gallery?" "dialogue"
And I follow "Presets"
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | number | number |
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be created and fields to be deleted.
And I should see "Fields to be created: image, description"
And I should see "Existing fields to be deleted: number"
And I should see "If fields to be deleted are of the same type as fields to be created"
And I should not see "If fields to be deleted are of the same type as new fields in the preset"
And I click on "Cancel" "button" in the "Apply preset Image gallery?" "dialogue"
And I follow "Presets"
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | textarea | description |
| data2 | picture | image |
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be deleted only.
And I should see "Existing fields to be deleted: number"
And I should not see "If fields to be deleted are of the same type as fields to be created"
And I should see "If fields to be deleted are of the same type as new fields in the preset"

View File

@ -54,7 +54,7 @@ Feature: Zero state page (no fields created)
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
Then I click on "Import preset and apply" "button" in the ".modal-dialog" "css_element"
And I should see "Manage fields"
Then I should see "The preset has been successfully applied."
Then I should see "Preset applied"
@javascript
Scenario: Teacher can use a preset from zero state page on an empty database
@ -63,5 +63,5 @@ Feature: Zero state page (no fields created)
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And the "Use this preset" "button" should be enabled
Then I click on "Use this preset" "button"
And I should not see "Field mappings"
And I should not see "Fields mappings"
And I should see "Image" in the "image" "table_row"

View File

@ -0,0 +1,235 @@
<?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 mod_data\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use external_api;
use mod_data\manager;
/**
* External function tests class for get_mapping_information.
*
* @package mod_data
* @category external
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\external\get_mapping_information
*/
class get_mapping_information_test extends \advanced_testcase {
/**
* Data provider for test_get_mapping_information().
*
* @return array[]
*/
public function get_mapping_information_provider(): array {
// Image gallery preset is: ['title' => 'text', 'description' => 'textarea', 'image' => 'picture'];
$titlefield = new \stdClass();
$titlefield->name = 'title';
$titlefield->type = 'text';
$descfield = new \stdClass();
$descfield->name = 'description';
$descfield->type = 'textarea';
$imagefield = new \stdClass();
$imagefield->name = 'image';
$imagefield->type = 'picture';
$difffield = new \stdClass();
$difffield->name = 'title';
$difffield->type = 'textarea';
$newfield = new \stdClass();
$newfield->name = 'number';
$newfield->type = 'number';
return [
'Empty database / Empty importer' => [
'currentfields' => [],
'newfields' => [],
'pluginname' => '',
'fieldstocreate' => '',
'fieldstoremove' => '',
],
'Empty database / Importer with fields' => [
'currentfields' => [],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'image, title, description',
'fieldstoremove' => '',
],
'Database with fields / Empty importer' => [
'currentfields' => [$imagefield, $titlefield, $descfield],
'newfields' => [],
'pluginname' => '',
'fieldstocreate' => '',
'fieldstoremove' => 'image, title, description',
],
'Same fields' => [
'currentfields' => [$imagefield, $titlefield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => '',
'fieldstoremove' => '',
],
'Fields to create' => [
'currentfields' => [$titlefield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'image',
'fieldstoremove' => '',
],
'Fields to remove' => [
'currentfields' => [$imagefield, $titlefield, $descfield, $difffield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => '',
'fieldstoremove' => 'title',
],
'Fields to update' => [
'currentfields' => [$imagefield, $difffield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'title',
'fieldstoremove' => 'title',
],
'Fields to create, remove and update' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $newfield],
'pluginname' => '',
'fieldstocreate' => 'number',
'fieldstoremove' => 'image, title',
],
];
}
/**
* Test for get_mapping_information method.
*
* @dataProvider get_mapping_information_provider
* @covers ::execute
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param string $fieldstocreate Expected fields on $fieldstocreate.
* @param string $fieldstoremove Expected fields on $fieldstoremove.
*/
public function test_get_mapping_information(
array $currentfields,
array $newfields,
string $pluginname,
string $fieldstocreate,
string $fieldstoremove
) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$module = $manager->get_coursemodule();
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$result = get_mapping_information::execute($module->id, $USER->id . '/' . $saved->name);
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertEquals($result['data']['fieldstocreate'], $fieldstocreate);
$this->assertEquals($result['data']['fieldstoremove'], $fieldstoremove);
// Create presets and importers.
if ($pluginname) {
$result = get_mapping_information::execute($module->id, '/' . $pluginname);;
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertEquals($result['data']['fieldstoremove'], $fieldstoremove);
$this->assertEquals($result['data']['fieldstocreate'], $fieldstocreate);
}
}
/**
* Test for get_mapping_information method for wrong presets.
*
* @covers ::execute
*
*/
public function test_get_mapping_information_for_wrong_preset() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
$module = $manager->get_coursemodule();
// We get warnings with empty preset name.
$result = get_mapping_information::execute($module->id, '');
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertFalse(array_key_exists('data', $result));
$this->assertTrue(array_key_exists('warnings', $result));
// We get warnings with non-existing preset name.
$result = get_mapping_information::execute($module->id, $USER->id . '/Non-existing');
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertFalse(array_key_exists('data', $result));
$this->assertTrue(array_key_exists('warnings', $result));
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($activity, $record);
// We get no warning with the right preset.
$result = get_mapping_information::execute($module->id, $USER->id . '/' . $saved->name);
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertTrue(array_key_exists('data', $result));
$this->assertFalse(array_key_exists('warnings', $result));
}
}

View File

@ -17,7 +17,7 @@
namespace mod_data;
use mod_data\local\importer\preset_existing_importer;
use mod_data\local\importer\preset_upload_importer;
use mod_data\local\importer\preset_importer;
/**
* Preset importer tests class for mod_data.
@ -30,65 +30,430 @@ use mod_data\local\importer\preset_upload_importer;
*/
class preset_importer_test extends \advanced_testcase {
/**
* Data provider for build providers for test_needs_mapping and test_set_affected_fields.
*
* @return array[]
*/
public function preset_importer_provider(): array {
// Image gallery preset is: ['title' => 'text', 'description' => 'textarea', 'image' => 'picture'];
$titlefield = new \stdClass();
$titlefield->name = 'title';
$titlefield->type = 'text';
$descfield = new \stdClass();
$descfield->name = 'description';
$descfield->type = 'textarea';
$imagefield = new \stdClass();
$imagefield->name = 'image';
$imagefield->type = 'picture';
$difffield = new \stdClass();
$difffield->name = 'title';
$difffield->type = 'textarea';
$newfield = new \stdClass();
$newfield->name = 'number';
$newfield->type = 'number';
return [
'Empty database / Empty importer' => [
'currentfields' => [],
'newfields' => [],
'pluginname' => '',
],
'Empty database / Importer with fields' => [
'currentfields' => [],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Database with fields / Empty importer' => [
'currentfields' => [$titlefield, $descfield, $imagefield],
'newfields' => [],
'pluginname' => '',
],
'Same fields' => [
'currentfields' => [$titlefield, $descfield, $imagefield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to create' => [
'currentfields' => [$titlefield, $descfield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to remove' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to update' => [
'currentfields' => [$difffield, $descfield, $imagefield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to create, remove and update' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $newfield],
'pluginname' => '',
],
];
}
/**
* Data provider for needs_mapping().
*
* @return array[]
*/
public function needs_mapping_provider(): array {
$basedprovider = $this->preset_importer_provider();
$basedprovider['Empty database / Empty importer']['needsmapping'] = false;
$basedprovider['Empty database / Importer with fields']['needsmapping'] = false;
$basedprovider['Database with fields / Empty importer']['needsmapping'] = true;
$basedprovider['Same fields']['needsmapping'] = false;
$basedprovider['Fields to create']['needsmapping'] = true;
$basedprovider['Fields to remove']['needsmapping'] = true;
$basedprovider['Fields to update']['needsmapping'] = true;
$basedprovider['Fields to create, remove and update']['needsmapping'] = true;
return $basedprovider;
}
/**
* Test for needs_mapping method.
*
* @dataProvider needs_mapping_provider
* @covers ::needs_mapping
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param bool $expectedresult Expected exception.
*/
public function test_needs_mapping() {
global $CFG, $USER;
public function test_needs_mapping(
array $currentfields,
array $newfields,
string $pluginname,
bool $expectedresult
) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals($savedimporter->needs_mapping(), $expectedresult);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$this->assertEquals($pluginimporter->needs_mapping(), $expectedresult);
}
}
/**
* Data provider for test_set_affected_fields().
*
* @return array[]
*/
public function set_affected_provider(): array {
$basedprovider = $this->preset_importer_provider();
$basedprovider['Empty database / Empty importer']['fieldstocreate'] = 0;
$basedprovider['Empty database / Empty importer']['fieldstoremove'] = 0;
$basedprovider['Empty database / Empty importer']['fieldstoupdate'] = 0;
$basedprovider['Empty database / Importer with fields']['fieldstocreate'] = 3;
$basedprovider['Empty database / Importer with fields']['fieldstoremove'] = 0;
$basedprovider['Empty database / Importer with fields']['fieldstoupdate'] = 0;
$basedprovider['Database with fields / Empty importer']['fieldstocreate'] = 0;
$basedprovider['Database with fields / Empty importer']['fieldstoremove'] = 3;
$basedprovider['Database with fields / Empty importer']['fieldstoupdate'] = 0;
$basedprovider['Same fields']['fieldstocreate'] = 0;
$basedprovider['Same fields']['fieldstoremove'] = 0;
$basedprovider['Same fields']['fieldstoupdate'] = 3;
$basedprovider['Fields to create']['fieldstocreate'] = 1;
$basedprovider['Fields to create']['fieldstoremove'] = 0;
$basedprovider['Fields to create']['fieldstoupdate'] = 2;
$basedprovider['Fields to remove']['fieldstocreate'] = 0;
$basedprovider['Fields to remove']['fieldstoremove'] = 1;
$basedprovider['Fields to remove']['fieldstoupdate'] = 3;
$basedprovider['Fields to update']['fieldstocreate'] = 1;
$basedprovider['Fields to update']['fieldstoremove'] = 1;
$basedprovider['Fields to update']['fieldstoupdate'] = 2;
$basedprovider['Fields to create, remove and update']['fieldstocreate'] = 1;
$basedprovider['Fields to create, remove and update']['fieldstoremove'] = 2;
$basedprovider['Fields to create, remove and update']['fieldstoupdate'] = 2;
return $basedprovider;
}
/**
* Test for set_affected_fields method.
*
* @dataProvider set_affected_provider
* @covers ::set_affected_fields
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param int $fieldstocreate Expected number of fields on $fieldstocreate.
* @param int $fieldstoremove Expected number of fields on $fieldstoremove.
* @param int $fieldstoupdate Expected number of fields on $fieldstoupdate.
*/
public function test_set_affected_fields(
array $currentfields,
array $newfields,
string $pluginname,
int $fieldstocreate,
int $fieldstoremove,
int $fieldstoupdate
) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals(count($savedimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($savedimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($savedimporter->fieldstoupdate), $fieldstoupdate);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$this->assertEquals(count($pluginimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($pluginimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($pluginimporter->fieldstoupdate), $fieldstoupdate);
}
}
/**
* Test for get_mapping_information method.
*
* @dataProvider set_affected_provider
* @covers ::get_mapping_information
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param int $fieldstocreate Expected number of fields on $fieldstocreate.
* @param int $fieldstoremove Expected number of fields on $fieldstoremove.
* @param int $fieldstoupdate Expected number of fields on $fieldstoupdate.
*/
public function test_get_mapping_information(
array $currentfields,
array $newfields,
string $pluginname,
int $fieldstocreate,
int $fieldstoremove,
int $fieldstoupdate
) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$information = $savedimporter->get_mapping_information();
$this->assertEquals($savedimporter->needs_mapping(), $information['needsmapping']);
$this->assertEquals(count($savedimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($savedimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($savedimporter->fieldstoupdate), $fieldstoupdate);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$information = $pluginimporter->get_mapping_information();
$this->assertEquals($pluginimporter->needs_mapping(), $information['needsmapping']);
$this->assertEquals(count($pluginimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($pluginimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($pluginimporter->fieldstoupdate), $fieldstoupdate);
}
}
/**
* Data provider for get_field_names().
*
* @return array[]
*/
public function get_field_names_provider(): array {
return [
'Empty list' => [
'fields' => [],
'expected' => '',
],
'List with one field' => [
'fields' => ['fieldname' => 'text'],
'expected' => 'fieldname',
],
'List of fields with same type' => [
'fields' => ['textfield' => 'text', 'other' => 'text'],
'expected' => 'textfield, other',
],
'List of fields with different type' => [
'fields' => ['textfield' => 'text', 'number' => 'number'],
'expected' => 'textfield, number',
],
];
}
/**
* Test for get_field_names method.
*
* @dataProvider get_field_names_provider
* @covers ::get_field_names
*
* @param array $fields List of fields to get the names from.
* @param string $expected The list of field names expected.
*/
public function test_get_field_names(array $fields, string $expected) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
foreach ($fields as $fieldname => $fieldtype) {
$newfield = new \stdClass();
$newfield->name = $fieldname;
$newfield->type = $fieldtype;
$createdfield = $plugingenerator->create_field($newfield, $presetactivity);
}
$manager = manager::create_from_instance($presetactivity);
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals($expected, $savedimporter->get_field_names($manager->get_field_records()));
}
/**
* Test for create_from_plugin_or_directory creation static method.
*
* @covers ::create_from_plugin_or_directory
*
*/
public function test_create_from_plugin_or_directory() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Create presets and importers.
$pluginname = 'imagegallery';
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($activity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$saved = $plugingenerator->create_preset($presetactivity, $record);
$fixturepath = $CFG->dirroot . '/mod/data/tests/fixtures/image_gallery_preset.zip';
// A plugin preset returns an instance of preset_existing_importer.
$preset = preset_importer::create_from_plugin_or_directory($manager, '/imagegallery');
$this->assertInstanceOf('\mod_data\local\importer\preset_existing_importer', $preset);
// Create a storage file.
$draftid = file_get_unused_draft_itemid();
$filerecord = [
'component' => 'user',
'filearea' => 'draft',
'contextid' => \context_user::instance($USER->id)->id,
'itemid' => $draftid,
'filename' => 'image_gallery_preset.zip',
'filepath' => '/'
];
$fs = get_file_storage();
$file = $fs->create_file_from_pathname($filerecord, $fixturepath);
$uploadedimporter = new preset_upload_importer($manager, $file->get_filepath());
// A saved preset returns an instance of preset_existing_importer.
$preset = preset_importer::create_from_plugin_or_directory($manager, $USER->id . '/Testing preset name');
$this->assertInstanceOf('\mod_data\local\importer\preset_existing_importer', $preset);
// Needs mapping returns false for empty databases.
$this->assertFalse($pluginimporter->needs_mapping());
$this->assertFalse($savedimporter->needs_mapping());
$this->assertFalse($uploadedimporter->needs_mapping());
// Add a field to the database.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'field1';
$fieldrecord->type = 'text';
$plugingenerator->create_field($fieldrecord, $activity);
// Needs mapping returns true for non-empty databases.
$this->assertTrue($pluginimporter->needs_mapping());
$this->assertTrue($savedimporter->needs_mapping());
$this->assertTrue($uploadedimporter->needs_mapping());
// An empty preset name throws an exception.
$this->expectException('moodle_exception');
try {
preset_importer::create_from_plugin_or_directory($manager, '');
} finally {
// A non-existing preset name throws an exception.
$this->expectException('moodle_exception');
preset_importer::create_from_plugin_or_directory($manager, $USER->id . '/Non-existing');
}
}
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2022100600; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2022110600; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2022041200; // Requires this Moodle version.
$plugin->component = 'mod_data'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;