From bc7caa3ac82f3cbc1571ab18189028a4ba5ffe5a Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Thu, 6 Oct 2022 14:03:55 +0200 Subject: [PATCH 1/2] MDL-75140 mod_data: Skip mapping form if there is no field created --- .../importer/preset_existing_importer.php | 79 ++++ .../local/importer/preset_importer.php | 348 ++++++++++++++++++ .../local/importer/preset_upload_importer.php | 55 +++ mod/data/classes/output/action_bar.php | 12 - mod/data/field.php | 86 ++--- mod/data/lang/en/data.php | 2 +- mod/data/lang/en/deprecated.txt | 1 + mod/data/lib.php | 30 +- mod/data/preset.php | 26 +- mod/data/renderer.php | 51 ++- mod/data/upgrade.txt | 5 + mod/data/version.php | 2 +- 12 files changed, 605 insertions(+), 92 deletions(-) create mode 100644 mod/data/classes/local/importer/preset_existing_importer.php create mode 100644 mod/data/classes/local/importer/preset_importer.php create mode 100644 mod/data/classes/local/importer/preset_upload_importer.php diff --git a/mod/data/classes/local/importer/preset_existing_importer.php b/mod/data/classes/local/importer/preset_existing_importer.php new file mode 100644 index 00000000000..92b21c90af3 --- /dev/null +++ b/mod/data/classes/local/importer/preset_existing_importer.php @@ -0,0 +1,79 @@ +. + +namespace mod_data\local\importer; + +use mod_data\manager; + +/** + * Data preset importer for existing presets + * @package mod_data + * @copyright 2022 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_existing_importer extends preset_importer { + + /** @var int user id. */ + protected $userid; + + /** @var string fullname of the preset. */ + private $fullname; + + /** + * Constructor + * + * @param manager $manager + * @param string $fullname + */ + public function __construct(manager $manager, string $fullname) { + global $USER; + + list($userid, $shortname) = explode('/', $fullname, 2); + $context = $manager->get_context(); + if ($userid && + ($userid != $USER->id) && + !has_capability('mod/data:manageuserpresets', $context) && + !has_capability('mod/data:viewalluserpresets', $context) + ) { + throw new \coding_exception('Invalid preset provided'); + } + + $this->userid = $userid; + $this->fullname = $fullname; + $cm = $manager->get_coursemodule(); + $course = $cm->get_course(); + $filepath = data_preset_path($course, $userid, $shortname); + parent::__construct($manager, $filepath); + } + + /** + * Returns user ID + * + * @return int|string userid or empty string + */ + public function get_userid() { + return $this->userid; + } + + /** + * Returns the information we need to build the importer selector. + * + * @return array Value and name for the preset importer selector + */ + public function get_preset_selector(): array { + return ['name' => 'fullname', 'value' => $this->get_userid().'/'.$this->get_directory()]; + } +} diff --git a/mod/data/classes/local/importer/preset_importer.php b/mod/data/classes/local/importer/preset_importer.php new file mode 100644 index 00000000000..5b898290845 --- /dev/null +++ b/mod/data/classes/local/importer/preset_importer.php @@ -0,0 +1,348 @@ +. + +namespace mod_data\local\importer; + +use mod_data\manager; +use mod_data\preset; +use stdClass; + +/** + * Abstract class used for data preset importers + * + * @package mod_data + * @copyright 2022 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class preset_importer { + + /** @var manager manager instance. */ + private $manager; + + /** @var string directory where to find the preset. */ + protected $directory; + + /** + * Constructor + * + * @param manager $manager + * @param string $directory + */ + public function __construct(manager $manager, string $directory) { + $this->manager = $manager; + $this->directory = $directory; + } + + /** + * Returns the name of the directory the preset is located in + * + * @return string + */ + public function get_directory(): string { + return basename($this->directory); + } + + /** + * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage + * + * @param \file_storage|null $filestorage . Should be null if using a conventional directory + * @param \stored_file|null $fileobj the directory to look in. null if using a conventional directory + * @param string|null $dir the directory to look in. null if using the Moodle file storage + * @param string $filename the name of the file we want + * @return string|null the contents of the file or null if the file doesn't exist. + */ + public function get_file_contents( + ?\file_storage &$filestorage, + ?\stored_file &$fileobj, + ?string $dir, + string $filename + ): ?string { + if (empty($filestorage) || empty($fileobj)) { + if (substr($dir, -1) != '/') { + $dir .= '/'; + } + if (file_exists($dir.$filename)) { + return file_get_contents($dir.$filename); + } else { + return null; + } + } else { + if ($filestorage->file_exists( + DATA_PRESET_CONTEXT, + DATA_PRESET_COMPONENT, + DATA_PRESET_FILEAREA, + 0, + $fileobj->get_filepath(), + $filename) + ) { + $file = $filestorage->get_file( + DATA_PRESET_CONTEXT, + DATA_PRESET_COMPONENT, + DATA_PRESET_FILEAREA, + 0, + $fileobj->get_filepath(), + $filename + ); + return $file->get_content(); + } else { + return null; + } + } + } + + /** + * Gets the preset settings + * + * @return stdClass Settings to be imported. + */ + public function get_preset_settings(): stdClass { + global $CFG; + require_once($CFG->libdir.'/xmlize.php'); + + $fs = null; + $fileobj = null; + if (!preset::is_directory_a_preset($this->directory)) { + // Maybe the user requested a preset stored in the Moodle file storage. + + $fs = get_file_storage(); + $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA); + + // Preset name to find will be the final element of the directory. + $explodeddirectory = explode('/', $this->directory); + $presettofind = end($explodeddirectory); + + // Now go through the available files available and see if we can find it. + foreach ($files as $file) { + if (($file->is_directory() && $file->get_filepath() == '/') || !$file->is_directory()) { + continue; + } + $presetname = trim($file->get_filepath(), '/'); + if ($presetname == $presettofind) { + $this->directory = $presetname; + $fileobj = $file; + } + } + + if (empty($fileobj)) { + throw new \moodle_exception('invalidpreset', 'data', '', $this->directory); + } + } + + $allowedsettings = [ + 'intro', + 'comments', + 'requiredentries', + 'requiredentriestoview', + 'maxentries', + 'rssarticles', + 'approval', + 'defaultsortdir', + 'defaultsort' + ]; + + $module = $this->manager->get_instance(); + $result = new stdClass; + $result->settings = new stdClass; + $result->importfields = []; + $result->currentfields = $this->manager->get_field_records(); + + // Grab XML. + $presetxml = $this->get_file_contents($fs, $fileobj, $this->directory, 'preset.xml'); + $parsedxml = xmlize($presetxml, 0); + + // First, do settings. Put in user friendly array. + $settingsarray = $parsedxml['preset']['#']['settings'][0]['#']; + $result->settings = new StdClass(); + foreach ($settingsarray as $setting => $value) { + if (!is_array($value) || !in_array($setting, $allowedsettings)) { + // Unsupported setting. + continue; + } + $result->settings->$setting = $value[0]['#']; + } + + // 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)) { + continue; + } + $fieldstoimport->$param = $value[0]['#']; + } + $fieldstoimport->dataid = $module->id; + $fieldstoimport->type = clean_param($fieldstoimport->type, PARAM_ALPHA); + $result->importfields[] = $fieldstoimport; + } + // 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( + $fs, + $fileobj, + $this->directory, + $templatefile + ); + } + + $result->settings->instance = $module->id; + return $result; + } + + /** + * Import the preset into the given database module + * + * @param bool $overwritesettings Whether to overwrite activity settings or not. + * @return bool Wether the importing has been successful. + */ + public function import(bool $overwritesettings): bool { + global $DB; + + $params = $this->get_preset_settings(); + $settings = $params->settings; + $newfields = $params->importfields; + $currentfields = $params->currentfields; + $preservedfields = []; + $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; + } + } + unset($fieldobject->field->similarfield); + $fieldobject->update_field(); + unset($fieldobject); + } else { + /* Make a new field */ + include_once("field/$newfield->type/field.class.php"); + + if (!isset($newfield->description)) { + $newfield->description = ''; + } + $classname = 'data_field_'.$newfield->type; + $fieldclass = new $classname($newfield, $module); + $fieldclass->insert_field(); + unset($fieldclass); + } + } + } + + // Get rid of all old unused data. + if (!empty($preservedfields)) { + foreach ($currentfields as $cid => $currentfield) { + if (!array_key_exists($cid, $preservedfields)) { + // Data not used anymore so wipe! + print "Deleting field $currentfield->name
"; + + $id = $currentfield->id; + // Why delete existing data records and related comments/ratings?? + $DB->delete_records('data_content', ['fieldid' => $id]); + $DB->delete_records('data_fields', ['id' => $id]); + } + } + } + + // Handle special settings here. + if (!empty($settings->defaultsort)) { + if (is_numeric($settings->defaultsort)) { + // Old broken value. + $settings->defaultsort = 0; + } else { + $settings->defaultsort = (int)$DB->get_field( + 'data_fields', + 'id', + ['dataid' => $module->id, 'name' => $settings->defaultsort] + ); + } + } else { + $settings->defaultsort = 0; + } + + // Do we want to overwrite all current database settings? + if ($overwritesettings) { + // All supported settings. + $overwrite = array_keys((array)$settings); + } else { + // Only templates and sorting. + $overwrite = ['singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter', + 'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate', + 'asearchtemplate', 'defaultsortdir', 'defaultsort']; + } + + // Now overwrite current data settings. + foreach ($module as $prop => $unused) { + if (in_array($prop, $overwrite)) { + $module->$prop = $settings->$prop; + } + } + + data_update_instance($module); + + return $this->cleanup(); + } + + /** + * Any clean up routines should go here + * + * @return bool Wether the preset has been successfully cleaned up. + */ + public function cleanup(): bool { + return true; + } + + /** + * Check if the importing process needs fields mapping. + * + * @return bool True if the current database needs to map the fields imported. + */ + public function needs_mapping(): bool { + return $this->manager->has_fields(); + } + + /** + * Returns the information we need to build the importer selector. + * + * @return array Value and name for the preset importer selector + */ + public function get_preset_selector(): array { + return ['name' => 'directory', 'value' => $this->get_directory()]; + } +} diff --git a/mod/data/classes/local/importer/preset_upload_importer.php b/mod/data/classes/local/importer/preset_upload_importer.php new file mode 100644 index 00000000000..67081f5ab52 --- /dev/null +++ b/mod/data/classes/local/importer/preset_upload_importer.php @@ -0,0 +1,55 @@ +. + +namespace mod_data\local\importer; + +use mod_data\manager; + +/** + * Data preset importer for uploaded presets + * + * @package mod_data + * @copyright 2022 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_upload_importer extends preset_importer { + + /** + * Constructor + * + * @param manager $manager + * @param string $filepath + */ + public function __construct(manager $manager, string $filepath) { + if (is_file($filepath)) { + $fp = get_file_packer(); + if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) { + fulldelete($filepath); + } + $filepath .= '_extracted'; + } + parent::__construct($manager, $filepath); + } + + /** + * Clean uploaded files up + * + * @return bool Wether the preset has been successfully cleaned up. + */ + public function cleanup(): bool { + return fulldelete($this->directory); + } +} diff --git a/mod/data/classes/output/action_bar.php b/mod/data/classes/output/action_bar.php index 3d0aa65ba99..e4ca9bbe392 100644 --- a/mod/data/classes/output/action_bar.php +++ b/mod/data/classes/output/action_bar.php @@ -59,23 +59,11 @@ class action_bar { global $PAGE, $DB; $createfieldlink = new moodle_url('/mod/data/field.php', ['d' => $this->id]); - $importlink = new moodle_url('/mod/data/field.php', ['d' => $this->id, 'mode' => 'import']); - $presetslink = new moodle_url('/mod/data/field.php', ['d' => $this->id, 'mode' => 'usepreset']); - $menu = [ $createfieldlink->out(false) => get_string('managefields', 'mod_data'), - $importlink->out(false) => get_string('importpreset', 'mod_data'), - $presetslink->out(false) => get_string('usestandard', 'mod_data'), ]; $selected = $createfieldlink->out(false); - $mode = $this->currenturl->get_param('mode'); - - if ($mode == 'import') { - $selected = $importlink->out(false); - } else if ($mode === 'usepreset') { - $selected = $presetslink->out(false); - } $urlselect = new url_select($menu, $selected, null, 'fieldactionselect'); $urlselect->set_label(get_string('fieldsnavigation', 'mod_data'), ['class' => 'sr-only']); diff --git a/mod/data/field.php b/mod/data/field.php index 65dbc07c912..156c753d7a2 100644 --- a/mod/data/field.php +++ b/mod/data/field.php @@ -24,6 +24,8 @@ */ use mod_data\manager; +use mod_data\local\importer\preset_existing_importer; +use mod_data\local\importer\preset_upload_importer; require_once('../../config.php'); require_once('lib.php'); @@ -119,21 +121,6 @@ $data->instance = $cm->instance; ***********************************/ $renderer = $manager->get_renderer(); -if ($formimportzip->is_cancelled()) { - redirect(new moodle_url('/mod/data/field.php', ['d' => $data->id])); -} else if ($formdata = $formimportzip->get_data()) { - $fieldactionbar = $actionbar->get_fields_action_bar(); - data_print_header($course, $cm, $data, false, $fieldactionbar); - echo $OUTPUT->heading(get_string('importpreset', 'data'), 2, 'mb-4'); - $file = new stdClass; - $file->name = $formimportzip->get_new_filename('importfile'); - $file->path = $formimportzip->save_temp_file('importfile'); - $importer = new data_preset_upload_importer($course, $cm, $data, $file->path); - echo $renderer->import_setting_mappings($data, $importer); - echo $OUTPUT->footer(); - exit(0); -} - if ($action == 'finishimport' && confirm_sesskey()) { data_print_header($course, $cm, $data, false); $overwritesettings = optional_param('overwritesettings', false, PARAM_BOOL); @@ -143,26 +130,28 @@ if ($action == 'finishimport' && confirm_sesskey()) { if (!file_exists($presetdir) || !is_dir($presetdir)) { throw new moodle_exception('cannotimport', 'error'); } - $importer = new data_preset_upload_importer($course, $cm, $data, $presetdir); + $importer = new preset_upload_importer($manager, $presetdir); } else { - $importer = new data_preset_existing_importer($course, $cm, $data, $fullname); + $importer = new preset_existing_importer($manager, $fullname); } - $importer->import($overwritesettings); - $strimportsuccess = get_string('importsuccess', 'data'); - $straddentries = get_string('addentries', 'data'); - $strtodatabase = get_string('todatabase', 'data'); + if ($importer->needs_mapping()) { + $importer->import($overwritesettings); + $strimportsuccess = get_string('importsuccess', 'data'); + $straddentries = get_string('addentries', 'data'); + $strtodatabase = get_string('todatabase', 'data'); - if (!$DB->get_records('data_records', array('dataid' => $data->id))) { - echo $OUTPUT->notification("$strimportsuccess $straddentries $strtodatabase", - 'notifysuccess'); - } else { - echo $OUTPUT->notification("$strimportsuccess", 'notifysuccess'); + if (!$DB->get_records('data_records', array('dataid' => $data->id))) { + echo $OUTPUT->notification("$strimportsuccess $straddentries $strtodatabase", + 'notifysuccess'); + } else { + echo $OUTPUT->notification("$strimportsuccess", 'notifysuccess'); + } + + echo $OUTPUT->continue_button(new moodle_url('/mod/data/field.php', ['d' => $data->id])); + echo $OUTPUT->footer(); + exit; } - - echo $OUTPUT->continue_button(new moodle_url('/mod/data/field.php', ['d' => $data->id])); - echo $OUTPUT->footer(); - exit; } switch ($mode) { @@ -295,34 +284,23 @@ switch ($mode) { } break; - case 'import': - $PAGE->navbar->add(get_string('importpreset', 'data')); - $fieldactionbar = $actionbar->get_fields_action_bar(); - data_print_header($course, $cm, $data, false, $fieldactionbar); - - echo $OUTPUT->heading(get_string('importpreset', 'data'), 2, 'mb-4'); - echo $formimportzip->display(); - echo $OUTPUT->footer(); - exit; - case 'usepreset': + $importer = new preset_existing_importer($manager, $fullname); + if (!$importer->needs_mapping()) { + $backurl = new moodle_url('/mod/data/field.php', ['id' => $cm->id]); + if ($importer->import(false)) { + \core\notification::success(get_string('importsuccess', 'mod_data')); + } else { + \core\notification::error(get_string('presetapplied', 'mod_data')); + } + redirect($backurl); + } $PAGE->navbar->add(get_string('usestandard', 'data')); $fieldactionbar = $actionbar->get_fields_action_bar(); data_print_header($course, $cm, $data, false, $fieldactionbar); - - if ($action === 'select') { - if (!empty($fullname)) { - echo $OUTPUT->heading(get_string('usestandard', 'data'), 2, 'mb-4'); - $importer = new data_preset_existing_importer($course, $cm, $data, $fullname); - echo $renderer->import_setting_mappings($data, $importer); - } - } else { - echo $OUTPUT->heading(get_string('presets', 'data'), 2, 'mb-4'); - $presets = $manager->get_available_presets(); - $presetsdata = new \mod_data\output\presets($data->id, $presets, - new \moodle_url('/mod/data/field.php')); - echo $renderer->render_presets($presetsdata); - } + 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(); exit; diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index 6502d218200..07a7e80b807 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -53,7 +53,6 @@ Fields have the format [[fieldname]]. All other tags have the format ##sometag## Only the tags that are in the "Available tags" list may be used for the current template.'; $string['availabletodate'] = 'Available to'; $string['availabletodatevalidation'] = 'The available to date cannot be before the available from date.'; -$string['blank'] = 'Blank'; $string['bynameondate'] = 'by {$a->name} - {$a->date}'; $string['calendarend'] = '{$a} closes'; $string['calendarstart'] = '{$a} opens'; @@ -452,3 +451,4 @@ $string['unsupportedexport'] = '({$a->fieldtype}) cannot be exported.'; $string['buttons'] = 'Actions'; $string['nolisttemplate'] = 'List template is not yet defined'; $string['nosingletemplate'] = 'Single template is not yet defined'; +$string['blank'] = 'Blank'; diff --git a/mod/data/lang/en/deprecated.txt b/mod/data/lang/en/deprecated.txt index e3ff3c52742..97fed034dc5 100644 --- a/mod/data/lang/en/deprecated.txt +++ b/mod/data/lang/en/deprecated.txt @@ -2,3 +2,4 @@ unsupportedexport,mod_data buttons,mod_data nosingletemplate,mod_data nolisttemplate,mod_data +blank,mod_data diff --git a/mod/data/lib.php b/mod/data/lib.php index d1375cdbe51..32348b77e30 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -1121,7 +1121,9 @@ function data_update_instance($data) { require_once($CFG->dirroot.'/mod/data/locallib.php'); $data->timemodified = time(); - $data->id = $data->instance; + if (!empty($data->instance)) { + $data->id = $data->instance; + } if (empty($data->assessed)) { $data->assessed = 0; @@ -2271,6 +2273,9 @@ function is_directory_a_preset($directory) { /** * Abstract class used for data preset importers + * + * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. + * @todo MDL-75189 Final deprecation in Moodle 4.5. */ abstract class data_preset_importer { @@ -2288,6 +2293,11 @@ abstract class data_preset_importer { * @param string $directory */ public function __construct($course, $cm, $module, $directory) { + debugging( + 'data_preset_importer is deprecated. Please use mod\\data\\local\\importer\\preset_importer instead', + DEBUG_DEVELOPER + ); + $this->course = $course; $this->cm = $cm; $this->module = $module; @@ -2552,10 +2562,19 @@ abstract class data_preset_importer { /** * Data preset importer for uploaded presets + * + * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. + * @todo MDL-75189 Final deprecation in Moodle 4.5. */ class data_preset_upload_importer extends data_preset_importer { public function __construct($course, $cm, $module, $filepath) { global $USER; + + debugging( + 'data_preset_upload_importer is deprecated. Please use mod\\data\\local\\importer\\preset_upload_importer instead', + DEBUG_DEVELOPER + ); + if (is_file($filepath)) { $fp = get_file_packer(); if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) { @@ -2572,11 +2591,20 @@ class data_preset_upload_importer extends data_preset_importer { /** * Data preset importer for existing presets + * + * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more. + * @todo MDL-75189 Final deprecation in Moodle 4.5. */ class data_preset_existing_importer extends data_preset_importer { protected $userid; public function __construct($course, $cm, $module, $fullname) { global $USER; + + debugging( + 'data_preset_existing_importer is deprecated. Please use mod\\data\\local\\importer\\preset_existing_importer instead', + DEBUG_DEVELOPER + ); + list($userid, $shortname) = explode('/', $fullname, 2); $context = context_module::instance($cm->id); if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) { diff --git a/mod/data/preset.php b/mod/data/preset.php index b97b84570cf..8cc1de89130 100644 --- a/mod/data/preset.php +++ b/mod/data/preset.php @@ -32,6 +32,8 @@ use mod_data\manager; use mod_data\preset; use mod_data\output\action_bar; use mod_data\output\preset_preview; +use mod_data\local\importer\preset_upload_importer; +use mod_data\local\importer\preset_existing_importer; require_once('../../config.php'); require_once($CFG->dirroot.'/mod/data/lib.php'); @@ -139,19 +141,25 @@ if ($action === 'preview') { exit(0); } -echo $OUTPUT->header(); - if ($formdata = $formimportzip->get_data()) { - echo $OUTPUT->heading(get_string('importpreset', 'data'), 2, 'mb-4'); $file = new stdClass; $file->name = $formimportzip->get_new_filename('importfile'); $file->path = $formimportzip->save_temp_file('importfile'); - $importer = new data_preset_upload_importer($course, $cm, $data, $file->path); - echo $renderer->import_setting_mappings($data, $importer); - echo $OUTPUT->footer(); - exit(0); + $importer = new preset_upload_importer($manager, $file->path); + if ($importer->needs_mapping()) { + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('importpreset', 'data'), 2, 'mb-4'); + echo $renderer->importing_preset($data, $importer); + echo $OUTPUT->footer(); + exit(0); + } + + $importer->import(false); + redirect(new moodle_url('/mod/data/field.php', ['id' => $cm->id])); } +echo $OUTPUT->header(); + if ($action === 'finishimport') { $fullname = optional_param('fullname', '' , PARAM_PATH); // The directory the preset is in. // Find out preset owner userid and shortname. @@ -169,9 +177,9 @@ if ($action === 'finishimport') { if (!file_exists($presetdir) || !is_dir($presetdir)) { throw new \moodle_exception('cannotimport'); } - $importer = new data_preset_upload_importer($course, $cm, $data, $presetdir); + $importer = new preset_upload_importer($manager, $presetdir); } else { - $importer = new data_preset_existing_importer($course, $cm, $data, $fullname); + $importer = new preset_existing_importer($manager, $fullname); } $importer->import($overwritesettings); $strimportsuccess = get_string('importsuccess', 'data'); diff --git a/mod/data/renderer.php b/mod/data/renderer.php index 4ef29b05f62..70dbbbcf631 100644 --- a/mod/data/renderer.php +++ b/mod/data/renderer.php @@ -1,35 +1,58 @@ get_directory(); + return $this->importing_preset($datamodule, new preset_existing_importer($manager, $fullname)); + } + + /** + * Importing a preset on a database module. + * + * @param stdClass $datamodule Database module to import to. + * @param \mod_data\local\importer\preset_importer $importer Importer instance to use for the importing. + * + * @return string + */ + public function importing_preset(stdClass $datamodule, \mod_data\local\importer\preset_importer $importer): string { - $strblank = get_string('blank', 'data'); $strcontinue = get_string('continue'); $strwarning = get_string('mappingwarning', 'data'); $strfieldmappings = get_string('fieldmappings', 'data'); - $strnew = get_string('new'); - $params = $importer->get_preset_settings(); - $settings = $params->settings; $newfields = $params->importfields; $currentfields = $params->currentfields; - $html = html_writer::start_tag('div', array('class'=>'presetmapping')); - $html .= html_writer::start_tag('form', array('method'=>'post', 'action'=>'')); + $html = html_writer::start_tag('div', ['class'=>'presetmapping']); + $html .= html_writer::start_tag('form', ['method'=>'post', 'action'=>'']); $html .= html_writer::start_tag('div'); - $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'action', 'value'=>'finishimport')); - $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey())); - $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'d', 'value'=>$datamodule->id)); + $html .= html_writer::empty_tag('input', ['type'=>'hidden', 'name'=>'action', 'value'=>'finishimport']); + $html .= html_writer::empty_tag('input', ['type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()]); + $html .= html_writer::empty_tag('input', ['type'=>'hidden', 'name'=>'d', 'value'=>$datamodule->id]); - if ($importer instanceof data_preset_existing_importer) { - $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'fullname', 'value'=>$importer->get_userid().'/'.$importer->get_directory())); - } else { - $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'directory', 'value'=>$importer->get_directory())); - } + $inputselector = $importer->get_preset_selector(); + $html .= html_writer::empty_tag( + 'input', + ['type'=>'hidden', 'name'=> $inputselector['name'], 'value' => $inputselector['value']] + ); if (!empty($newfields)) { $html .= $this->output->heading_with_help($strfieldmappings, 'fieldmappings', 'data', '', '', 3); diff --git a/mod/data/upgrade.txt b/mod/data/upgrade.txt index 1241338012c..fc941e61835 100644 --- a/mod/data/upgrade.txt +++ b/mod/data/upgrade.txt @@ -17,6 +17,11 @@ information provided here is intended especially for developers. * data_user_can_add_entry() function returns false for any user if there is no field created on the database. * From now on, the data_generate_default_template method will always return a string with the template content or an empty string when there is no content available. +* The following classes have been deprecated from lib.php because they have been moved to use manager class: + - data_preset_importer + - data_preset_existing_importer + - data_preset_upload_importer +* import_setting_mappings() function has been deprecated. Use importing_preset() instead. === 3.7 === * External functions get_entries, get_entry and search_entries now return an additional field "tags" containing the entry tags. diff --git a/mod/data/version.php b/mod/data/version.php index e49fdbfad58..bfad973c615 100644 --- a/mod/data/version.php +++ b/mod/data/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022082601; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2022100600; // 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; From 80a627294a1065167c8ebc56724af86a78e93fec Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Thu, 6 Oct 2022 14:03:35 +0200 Subject: [PATCH 2/2] MDL-75140 mod_data: Tests to cover skipping mapping workflow --- .../tests/behat/imagegallery_preset.feature | 2 - .../tests/behat/journal_preset.feature | 2 - .../tests/behat/proposals_preset.feature | 2 - .../tests/behat/resources_preset.feature | 2 - mod/data/tests/behat/import_presets.feature | 121 ++++++++++++++++++ mod/data/tests/behat/preview_preset.feature | 9 +- .../tests/fixtures/image_gallery_preset.zip | Bin 0 -> 3067 bytes mod/data/tests/preset_importer_test.php | 94 ++++++++++++++ 8 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 mod/data/tests/behat/import_presets.feature create mode 100644 mod/data/tests/fixtures/image_gallery_preset.zip create mode 100644 mod/data/tests/preset_importer_test.php diff --git a/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature b/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature index a050dca2194..09ec21ee46a 100644 --- a/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature +++ b/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature @@ -23,8 +23,6 @@ Feature: Users can use the Image gallery preset And I follow "Presets" And I click on "fullname" "radio" in the "Image gallery" "table_row" And I click on "Use preset" "button" - And I click on "Continue" "button" - And I click on "Continue" "button" And the following "mod_data > entries" exist: | database | user | title | description | image | | data1 | student1 | First image | This is the description text for image 1 | first.png | diff --git a/mod/data/preset/journal/tests/behat/journal_preset.feature b/mod/data/preset/journal/tests/behat/journal_preset.feature index d138c6ba13b..1e7aa582293 100644 --- a/mod/data/preset/journal/tests/behat/journal_preset.feature +++ b/mod/data/preset/journal/tests/behat/journal_preset.feature @@ -23,8 +23,6 @@ Feature: Users can use the Journal preset And I follow "Presets" And I click on "fullname" "radio" in the "Journal" "table_row" And I click on "Use preset" "button" - And I click on "Continue" "button" - And I click on "Continue" "button" And the following "mod_data > entries" exist: | database | user | Title | Content | | data1 | student1 | Reflection created by student | This is the content for the entry 1 | diff --git a/mod/data/preset/proposals/tests/behat/proposals_preset.feature b/mod/data/preset/proposals/tests/behat/proposals_preset.feature index d14c498175d..cad72a7acf6 100644 --- a/mod/data/preset/proposals/tests/behat/proposals_preset.feature +++ b/mod/data/preset/proposals/tests/behat/proposals_preset.feature @@ -23,8 +23,6 @@ Feature: Users can use the Proposals preset And I follow "Presets" And I click on "fullname" "radio" in the "Proposals" "table_row" And I click on "Use preset" "button" - And I click on "Continue" "button" - And I click on "Continue" "button" And the following "mod_data > entries" exist: | database | user | Title | Summary | Content | Status | | data1 | student1 | Project created by student | Summary 1 | Content for entry 1 | Pending | diff --git a/mod/data/preset/resources/tests/behat/resources_preset.feature b/mod/data/preset/resources/tests/behat/resources_preset.feature index c288acae6c9..884057ad3d7 100644 --- a/mod/data/preset/resources/tests/behat/resources_preset.feature +++ b/mod/data/preset/resources/tests/behat/resources_preset.feature @@ -23,8 +23,6 @@ Feature: Users can use the Resources preset And I follow "Presets" And I click on "fullname" "radio" in the "Resources" "table_row" And I click on "Use preset" "button" - And I click on "Continue" "button" - And I click on "Continue" "button" And the following "mod_data > entries" exist: | database | user | Title | Description | Type | Author | Web link | Cover | | data1 | student1 | My favourite book | Book content | Type1 | The book author | http://myfavouritebook.cat | first.png | diff --git a/mod/data/tests/behat/import_presets.feature b/mod/data/tests/behat/import_presets.feature new file mode 100644 index 00000000000..b57e7ea5145 --- /dev/null +++ b/mod/data/tests/behat/import_presets.feature @@ -0,0 +1,121 @@ +@mod @mod_data @javascript @_file_upload +Feature: Users can import presets + In order to use presets + As a user + I need to import and apply presets from zip files + + 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 | 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 "Import" "link" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "button" + Then I should not see "Field mappings" + And I should see "Image" in the "image" "table_row" + + Scenario: Teacher can import from preset 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 "Import" "link" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "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 preset page on a database with entries + And the following "mod_data > fields" exist: + | database | type | name | description | + | data1 | text | field1 | Test field description | + And the following "mod_data > templates" exist: + | database | name | + | data1 | singletemplate | + | data1 | listtemplate | + | data1 | addtemplate | + | data1 | asearchtemplate | + | data1 | rsstemplate | + And the following "mod_data > entries" exist: + | database | field1 | + | 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 "Import" "link" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "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 "Import" "button" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "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 "Import" "button" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "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 a database with entries + And the following "mod_data > fields" exist: + | database | type | name | description | + | data1 | text | field1 | Test field description | + And the following "mod_data > templates" exist: + | database | name | + | data1 | singletemplate | + | data1 | listtemplate | + | data1 | addtemplate | + | data1 | asearchtemplate | + | data1 | rsstemplate | + And the following "mod_data > entries" exist: + | database | field1 | + | 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 "Import" "button" + And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Choose file" filemanager + When I click on "Save" "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 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 "Choose file" filemanager + When I click on "Save" "button" + Then I should not see "Field mappings" + And I should see "Image" in the "image" "table_row" diff --git a/mod/data/tests/behat/preview_preset.feature b/mod/data/tests/behat/preview_preset.feature index 3b7e576ecdf..489d4973099 100644 --- a/mod/data/tests/behat/preview_preset.feature +++ b/mod/data/tests/behat/preview_preset.feature @@ -28,7 +28,6 @@ Feature: Users can preview presets And I click on "Import" "button" And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Choose file" filemanager And I click on "Save" "button" - And I click on "Continue" "button" And I follow "Templates" And I click on "Save as preset" "button" And I set the field "Name" to "Saved preset by teacher1" @@ -75,7 +74,6 @@ Feature: Users can preview presets And I click on "Import" "button" And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Choose file" filemanager And I click on "Save" "button" - And I click on "Continue" "button" And I follow "Templates" And I click on "Save as preset" "button" And I set the field "Name" to "Saved preset by teacher1" @@ -141,8 +139,7 @@ Feature: Users can preview presets Given I follow "Presets" And I click on "Image gallery" "link" When I click on "Use preset" "button" - Then I should see "Field mappings" - And I should see "image" + Then I should see "image" And I should see "title" @javascript @_file_upload @@ -151,7 +148,6 @@ Feature: Users can preview presets And I click on "Import" "button" And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Choose file" filemanager And I click on "Save" "button" - And I click on "Continue" "button" And I follow "Templates" And I click on "Save as preset" "button" And I set the field "Name" to "Saved preset by teacher1" @@ -160,5 +156,4 @@ Feature: Users can preview presets When I follow "Presets" And I click on "Saved preset by teacher1" "link" And I click on "Use preset" "button" - Then I should see "Field mappings" - And I should see "My URL field" + Then I should see "My URL field" diff --git a/mod/data/tests/fixtures/image_gallery_preset.zip b/mod/data/tests/fixtures/image_gallery_preset.zip new file mode 100644 index 0000000000000000000000000000000000000000..b11e53698b36b87e3899ac0af087679cbfb254ec GIT binary patch literal 3067 zcmZ`*3pmql8(&!Fm_uHaq{wIsL$CHa6cO8O)=&=lYB6SWC>tRvrxcS@vdUov=#tg=rtgaEHL|c)97@P?=c@Douosi`}&aVkkZk5k~8xPvTSwEwZ|Wm&(+BY zCtF0SaBI2g7)XO5$1v7(s4|gklJlwc8E8|`OVgg52N*_#x13Z`duuH7n(h=XT+VgB0++zp+_5{wC<951?Cw74LQ=TnA< z?h~E?6p0CgK=Qy$^Y-)dA@OO;Bn(p;)MUcB2hW*dv_b-fW4>Drh!&Kde@W^-KD@N3 z`EPJ}&Q(t2U-ifiSNo(;2jL*9*MeJxe==Y;ZE2`-m56lqz1I*c3iZ>73%WSEHk| zi~O!wjqjmI{hcV?GTT}^9My@xOc}qmg-sR@SyK4X2lIb;`v5tSPH`%?;}6b! z>~o_GASl5=+LsRlkwzj0xRd#!lY607yiQN@EZ5A6Jy8>loWRQ<8YQ=t3iVkbpx?x4RgQh+O3fb@gIrkJ@RpWLEB?TAR5mcapS_) zG_4vaU0VN&d0k*fd9d&fwY{;0`u5Wt>_w&dUgMirpA?k$mH7KW`|cOlbQJ9_`1Z+P z!gQamAynKR5pW3laKGseIyOo_Ss+C51`TbwjXn$R=Q42 z#*%vqJcmY?XKE^pCy!e>7so;BwpuX4 zb`_hHm@{_Co8;pgDo#^f!0K|ELS_tiWF9JrcG$OaW^Q^xVMDjG(D9F+-Ub^?7K~5s ze@XbR#g)4My6k2dw)W|t+0s|hCy_30@}cF~9%@r(z~9pkc2;KbOL)rELA(MaR09&$ z0kium3BY?`5`tQ|Q8T`i=v^$$1h$_^B zlVPFUxbzTE^|PeW;ZaC-pj5mI&v#_uBURZMXK zlg%;Nat=GGHw-FZqgeI?IwK}{NR8M}(T82{_i#%>=E*&Gypn&i>^!n5i@3QTTpzdd z)NJJE1^kiJ(@Zy8#4}dHsU>}H_h4E(GjGS@5lMK_52yjrv<*Sf-O>0$ZEsB`sXh)9=m;^oDW{Z9}shdN+SgbMu!LpBn1S7!b>TImg`_n zjnY%9T7+2;4LPi!uGDGik+mBcZYPwC&+S`M)7*BRL;Cbkmz`&jS$OHS+Lg}v>hyV2 zU%7~Uk9k{`mV(Q**ZOf)c_U$9HZG>INGCX?odC|*a)ec!SZ$J{_zL1>QUt4Wz{`BL zJYV6)J*;n%TqBNVp~dw-g!h^q-EFqDUin(eH*9f)Ltms#RGv%9qYevhm;94=E{MD* z>V|}P)t48ims|<5CI}YJv0&ST5YfpBAw-|wE03=ecbppe`s|AytSF`{3>t+yvu;5$ zI1AI$H%4nV_Q+S+r-B*sir0H|FX7!t%O{1)+vH}?y$NbktfaV$jx{Gy>`=wC>&L3c z29t-aX@(;+d5TChm2vV+HyjCJtD;$h$WgkimC-@-*^rK8(c4vD>OF*ql&RHVj9JF5 z2<8cS!zZN!h;C*1t1oURwzV}rIFy?9R${dBLW#dBsEp*U|*_vtPz7Pf0T^@q#d;0s+f30t#>){<_ zCjQUfe_~}~xgaqBw>%VpvD_rQ=>mr4>bmluo7+FwpC*5+N^5u%a2Q1ZD-;K?#{x(+ z5?v?Q*C&C{jjPv_4C_3|LOn>i>#nAe8jk4=P0h`lRqhXG)IShUsD+uy?OUtPxFWq6LrOVx?v(s7T9!M7)!|Lf=R}q^o!KYnb7hke<4@b zZI6{x4;0gyaCJ33ayREBJfD_y-MHm%Y+tNClfn+WK(?m5YKDYbh=)#erzG}AWlasA zYE8=#4VVcd!Q55Ny^;niJ*Fk@bIL+a7X2Cc$5U-5=~A0*!Iwfbw!Hn$_!NKDola{S zb6ukL4plQ3legR38YpzW1=G^l2~~7oPwmgIQ%?gGy+&xg@c+Ln0g+t!aUzzd)rV>2 z7zx4!FC;&~AP^^F2zdR?^+b?B(6o63^FVTcXW@bjf=0<>XcYQi+Z03#`V!w>`uwo@M12`tcD1)+jP&x6jc<. + +namespace mod_data; + +use mod_data\local\importer\preset_existing_importer; +use mod_data\local\importer\preset_upload_importer; + +/** + * Preset importer tests class for mod_data. + * + * @package mod_data + * @category test + * @copyright 2022 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \mod_data\local\importer\preset_importer + */ +class preset_importer_test extends \advanced_testcase { + + /** + * Test for needs_mapping method. + * + * @covers ::needs_mapping + */ + public function test_needs_mapping() { + global $CFG, $USER; + + $this->resetAfterTest(); + $this->setAdminUser(); + + // 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); + + $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'); + + $fixturepath = $CFG->dirroot . '/mod/data/tests/fixtures/image_gallery_preset.zip'; + + // 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()); + + // 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()); + } +}