diff --git a/mod/data/amd/build/templateseditor.min.js b/mod/data/amd/build/templateseditor.min.js index b6295cea7d1d3..9da187ddee3df 100644 --- a/mod/data/amd/build/templateseditor.min.js +++ b/mod/data/amd/build/templateseditor.min.js @@ -6,6 +6,6 @@ define("mod_data/templateseditor",["exports","core/str","core/notification","cor * @copyright 2021 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const selectors_toggleTemplateEditor='input[name="useeditor"]';_exports.init=(d,mode)=>{((d,mode)=>{document.querySelector(selectors_toggleTemplateEditor).addEventListener("click",(async event=>{event.preventDefault(),event.target.checked?(0,_notification.confirm)((0,_str.get_string)("confirmation","admin"),(0,_str.get_string)("enabletemplateeditorcheck","mod_data"),(0,_str.get_string)("yes","core"),(0,_str.get_string)("no","core"),(()=>{window.location=(0,_url.relativeUrl)("/mod/data/templates.php",{d:d,mode:mode,useeditor:!0})})):window.location=(0,_url.relativeUrl)("/mod/data/templates.php",{d:d,mode:mode,useeditor:!1})}))})(d,mode)}})); +const selectors_toggleTemplateEditor='input[name="useeditor"]';_exports.init=(d,mode)=>{((d,mode)=>{const toggleTemplateEditor=document.querySelector(selectors_toggleTemplateEditor);toggleTemplateEditor&&toggleTemplateEditor.addEventListener("click",(async event=>{event.preventDefault(),event.target.checked?(0,_notification.confirm)((0,_str.get_string)("confirmation","admin"),(0,_str.get_string)("enabletemplateeditorcheck","mod_data"),(0,_str.get_string)("yes","core"),(0,_str.get_string)("no","core"),(()=>{window.location=(0,_url.relativeUrl)("/mod/data/templates.php",{d:d,mode:mode,useeditor:!0})})):window.location=(0,_url.relativeUrl)("/mod/data/templates.php",{d:d,mode:mode,useeditor:!1})}))})(d,mode)}})); //# sourceMappingURL=templateseditor.min.js.map \ No newline at end of file diff --git a/mod/data/amd/build/templateseditor.min.js.map b/mod/data/amd/build/templateseditor.min.js.map index e7dfa86e106ba..9a229818db54e 100644 --- a/mod/data/amd/build/templateseditor.min.js.map +++ b/mod/data/amd/build/templateseditor.min.js.map @@ -1 +1 @@ -{"version":3,"file":"templateseditor.min.js","sources":["../src/templateseditor.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 .\n\n/**\n * Javascript module to control the template editor.\n *\n * @module mod_data/templateseditor\n * @copyright 2021 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport {confirm as confirmDialogue} from 'core/notification';\nimport {relativeUrl} from 'core/url';\n\n/**\n * Template editor constants.\n */\nconst selectors = {\n toggleTemplateEditor: 'input[name=\"useeditor\"]',\n};\n\n/**\n * Register event listeners for the module.\n *\n * @param {int} d The database ID\n * @param {string} mode The template mode\n */\nconst registerEventListeners = (d, mode) => {\n const toggleTemplateEditor = document.querySelector(selectors.toggleTemplateEditor);\n\n toggleTemplateEditor.addEventListener('click', async(event) => {\n event.preventDefault();\n // Whether the event action attempts to enable or disable the template editor.\n const enableTemplateEditor = event.target.checked;\n\n if (enableTemplateEditor) {\n // Display a confirmation dialog before enabling the template editor.\n confirmDialogue(\n getString('confirmation', 'admin'),\n getString('enabletemplateeditorcheck', 'mod_data'),\n getString('yes', 'core'),\n getString('no', 'core'),\n () => {\n window.location = relativeUrl('/mod/data/templates.php', {d: d, mode: mode, useeditor: true});\n }\n );\n } else {\n window.location = relativeUrl('/mod/data/templates.php', {d: d, mode: mode, useeditor: false});\n }\n });\n};\n\n/**\n * Initialize the module.\n *\n * @param {int} d The database ID\n * @param {string} mode The template mode\n */\nexport const init = (d, mode) => {\n registerEventListeners(d, mode);\n};\n"],"names":["selectors","d","mode","document","querySelector","addEventListener","async","event","preventDefault","target","checked","window","location","useeditor","registerEventListeners"],"mappings":";;;;;;;;MA8BMA,+BACoB,wCAwCN,CAACC,EAAGC,QA/BO,EAACD,EAAGC,QACFC,SAASC,cAAcJ,gCAE/BK,iBAAiB,SAASC,MAAAA,QAC3CC,MAAMC,iBAEuBD,MAAME,OAAOC,mCAKlC,mBAAU,eAAgB,UAC1B,mBAAU,4BAA6B,aACvC,mBAAU,MAAO,SACjB,mBAAU,KAAM,SAChB,KACIC,OAAOC,UAAW,oBAAY,0BAA2B,CAACX,EAAGA,EAAGC,KAAMA,KAAMW,WAAW,OAI/FF,OAAOC,UAAW,oBAAY,0BAA2B,CAACX,EAAGA,EAAGC,KAAMA,KAAMW,WAAW,QAY/FC,CAAuBb,EAAGC"} \ No newline at end of file +{"version":3,"file":"templateseditor.min.js","sources":["../src/templateseditor.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 .\n\n/**\n * Javascript module to control the template editor.\n *\n * @module mod_data/templateseditor\n * @copyright 2021 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport {confirm as confirmDialogue} from 'core/notification';\nimport {relativeUrl} from 'core/url';\n\n/**\n * Template editor constants.\n */\nconst selectors = {\n toggleTemplateEditor: 'input[name=\"useeditor\"]',\n};\n\n/**\n * Register event listeners for the module.\n *\n * @param {int} d The database ID\n * @param {string} mode The template mode\n */\nconst registerEventListeners = (d, mode) => {\n const toggleTemplateEditor = document.querySelector(selectors.toggleTemplateEditor);\n\n if (!toggleTemplateEditor) {\n return;\n }\n\n toggleTemplateEditor.addEventListener('click', async(event) => {\n event.preventDefault();\n // Whether the event action attempts to enable or disable the template editor.\n const enableTemplateEditor = event.target.checked;\n\n if (enableTemplateEditor) {\n // Display a confirmation dialog before enabling the template editor.\n confirmDialogue(\n getString('confirmation', 'admin'),\n getString('enabletemplateeditorcheck', 'mod_data'),\n getString('yes', 'core'),\n getString('no', 'core'),\n () => {\n window.location = relativeUrl('/mod/data/templates.php', {d: d, mode: mode, useeditor: true});\n }\n );\n } else {\n window.location = relativeUrl('/mod/data/templates.php', {d: d, mode: mode, useeditor: false});\n }\n });\n};\n\n/**\n * Initialize the module.\n *\n * @param {int} d The database ID\n * @param {string} mode The template mode\n */\nexport const init = (d, mode) => {\n registerEventListeners(d, mode);\n};\n"],"names":["selectors","d","mode","toggleTemplateEditor","document","querySelector","addEventListener","async","event","preventDefault","target","checked","window","location","useeditor","registerEventListeners"],"mappings":";;;;;;;;MA8BMA,+BACoB,wCA4CN,CAACC,EAAGC,QAnCO,EAACD,EAAGC,cACzBC,qBAAuBC,SAASC,cAAcL,gCAE/CG,sBAILA,qBAAqBG,iBAAiB,SAASC,MAAAA,QAC3CC,MAAMC,iBAEuBD,MAAME,OAAOC,mCAKlC,mBAAU,eAAgB,UAC1B,mBAAU,4BAA6B,aACvC,mBAAU,MAAO,SACjB,mBAAU,KAAM,SAChB,KACIC,OAAOC,UAAW,oBAAY,0BAA2B,CAACZ,EAAGA,EAAGC,KAAMA,KAAMY,WAAW,OAI/FF,OAAOC,UAAW,oBAAY,0BAA2B,CAACZ,EAAGA,EAAGC,KAAMA,KAAMY,WAAW,QAY/FC,CAAuBd,EAAGC"} \ No newline at end of file diff --git a/mod/data/amd/src/templateseditor.js b/mod/data/amd/src/templateseditor.js index a7eece0f7db81..2675eda9563cc 100644 --- a/mod/data/amd/src/templateseditor.js +++ b/mod/data/amd/src/templateseditor.js @@ -41,6 +41,10 @@ const selectors = { const registerEventListeners = (d, mode) => { const toggleTemplateEditor = document.querySelector(selectors.toggleTemplateEditor); + if (!toggleTemplateEditor) { + return; + } + toggleTemplateEditor.addEventListener('click', async(event) => { event.preventDefault(); // Whether the event action attempts to enable or disable the template editor. diff --git a/mod/data/classes/manager.php b/mod/data/classes/manager.php index ac9f776a75a61..85a98f0afa211 100644 --- a/mod/data/classes/manager.php +++ b/mod/data/classes/manager.php @@ -16,11 +16,13 @@ namespace mod_data; -use context_module; use cm_info; +use context_module; use completion_info; +use data_field_base; use mod_data\event\course_module_viewed; use mod_data\event\template_viewed; +use mod_data\event\template_updated; use stdClass; /** @@ -38,6 +40,23 @@ class manager { /** Pluginname name. */ const PLUGIN = 'mod_data'; + /** Template list with their files required to save the information of a preset. */ + const TEMPLATES_LIST = [ + 'listtemplate' => 'listtemplate.html', + 'singletemplate' => 'singletemplate.html', + 'asearchtemplate' => 'asearchtemplate.html', + 'addtemplate' => 'addtemplate.html', + 'rsstemplate' => 'rsstemplate.html', + 'csstemplate' => 'csstemplate.css', + 'jstemplate' => 'jstemplate.js', + 'listtemplateheader' => 'listtemplateheader.html', + 'listtemplatefooter' => 'listtemplatefooter.html', + 'rsstitletemplate' => 'rsstitletemplate.html', + ]; + + /** @var string plugin path. */ + private $path; + /** @var stdClass course_module record. */ private $instance; @@ -47,6 +66,11 @@ class manager { /** @var cm_info course_modules record. */ private $cm; + /** @var array the current data_fields records. + * Do not acces this attribute directly, use $this->get_field_records instead + */ + private $_fieldrecords = null; + /** * Class contructor. * @@ -54,10 +78,12 @@ class manager { * @param stdClass $instance activity instance object. */ public function __construct(cm_info $cm, stdClass $instance) { + global $CFG; $this->cm = $cm; $this->instance = $instance; $this->context = context_module::instance($cm->id); $this->instance->cmidnumber = $cm->idnumber; + $this->path = $CFG->dirroot . '/mod/' . self::MODULE; } /** @@ -167,4 +193,114 @@ public function set_template_viewed() { $event->add_record_snapshot(self::MODULE, $this->instance); $event->trigger(); } + + /** + * Return the database fields. + * + * @return data_field_base[] the field instances. + */ + public function get_fields(): array { + $result = []; + $fieldrecords = $this->get_field_records(); + foreach ($fieldrecords as $fieldrecord) { + $result[$fieldrecord->id] = $this->get_field($fieldrecord); + } + return $result; + } + + /** + * Return the field records (the current data_fields records). + * + * @return stdClass[] an array of records + */ + public function get_field_records() { + global $DB; + if ($this->_fieldrecords === null) { + $this->_fieldrecords = $DB->get_records('data_fields', ['dataid' => $this->instance->id]); + } + return $this->_fieldrecords; + } + + /** + * Return a specific field instance from a field record. + * + * @param stdClass $fieldrecord the fieldrecord to convert + * @return data_field_base the data field class instance + */ + public function get_field(stdClass $fieldrecord): data_field_base { + $filepath = "{$this->path}/field/{$fieldrecord->type}/field.class.php"; + $classname = "data_field_{$fieldrecord->type}"; + if (!file_exists($filepath) || !class_exists($classname)) { + return new data_field_base($fieldrecord, $this->instance, $this->cm); + } + require_once($filepath); + $newfield = new $classname($fieldrecord, $this->instance, $this->cm); + return $newfield; + } + + /** + * Return a specific template. + * + * NOTE: this method returns a default template if the module template is empty. + * However, it won't update the template database field. + * + * @param string $templatename + * @param array $options extra display options array + * @return template the template instance + */ + public function get_template(string $templatename, array $options = []): template { + if ($templatename == 'single') { + $templatename == 'singletemplate'; + } + $instance = $this->instance; + $templatestr = $instance->{$templatename} ?? ''; + if (empty($templatestr)) { + $templatestr = data_generate_default_template($instance, $templatename, 0, false, false); + } + // Some templates have extra options. + if ($templatename == 'singletemplate') { + $options['comments'] = true; + $options['ratings'] = true; + } + return new template($this, $templatestr, $options); + } + + /** + * Update the database templates. + * + * @param stdClass $newtemplates an object with all the new templates + * @return bool if updated successfully. + */ + public function update_templates(stdClass $newtemplates) { + global $DB; + $record = (object)[ + 'id' => $this->instance->id, + ]; + foreach (self::TEMPLATES_LIST as $templatename => $templatefile) { + if (!isset($newtemplates->{$templatename})) { + continue; + } + $record->{$templatename} = $newtemplates->{$templatename}; + } + + // The add entry form cannot repeat tags. + if (isset($record->addtemplate) && !data_tags_check($this->instance->id, $record->addtemplate)) { + return false; + } + + $DB->update_record(self::MODULE, $record); + $this->instance = $DB->get_record(self::MODULE, ['id' => $this->cm->instance], '*', MUST_EXIST); + + // Trigger an event for saving the templates. + $event = template_updated::create(array( + 'context' => $this->context, + 'courseid' => $this->cm->course, + 'other' => array( + 'dataid' => $this->instance->id, + ) + )); + $event->trigger(); + + return true; + } } diff --git a/mod/data/classes/output/template_editor.php b/mod/data/classes/output/template_editor.php new file mode 100644 index 0000000000000..de0a177665e95 --- /dev/null +++ b/mod/data/classes/output/template_editor.php @@ -0,0 +1,191 @@ +. + +namespace mod_data\output; + +use templatable; +use renderable; +use mod_data\manager; +use moodle_url; +use texteditor; + +/** + * Renderable class for template editor. + * + * @package mod_data + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class template_editor implements templatable, renderable { + + /** @var manager manager instance. */ + private $manager; + + /** @var string the template name. */ + private $templatename; + + /** + * The class constructor. + * + * @param manager $manager the activity instance manager + * @param string $templatename the template to edit + */ + public function __construct(manager $manager, string $templatename) { + $this->manager = $manager; + $this->templatename = $templatename; + } + + /** + * Export the data for the mustache template. + * + * @param \renderer_base $output renderer to be used to render the action bar elements. + * @return array + */ + public function export_for_template(\renderer_base $output): array { + $instance = $this->manager->get_instance(); + $cm = $this->manager->get_coursemodule(); + + $data = [ + 'title' => get_string('header' . $this->templatename, 'data'), + 'sesskey' => sesskey(), + 'disableeditor' => true, + 'url' => new moodle_url('/mod/data/templates.php', ['id' => $cm->id, 'mode' => $this->templatename]), + ]; + + // Determine whether to use HTML editors. + if (($this->templatename === 'csstemplate') || ($this->templatename === 'jstemplate')) { + $usehtmleditor = false; + } else { + $usehtmleditor = data_get_config($instance, "editor_{$this->templatename}", true); + } + $data['usehtmleditor'] = $usehtmleditor; + + $tools = new template_editor_tools($this->manager, $this->templatename); + $data['toolbar'] = $tools->export_for_template($output); + $data['editors'] = $this->get_editors_data($usehtmleditor); + + // Some templates cannot enable the wysiwyg editor. + if ($this->templatename == 'csstemplate' || $this->templatename == 'jstemplate') { + $data['disableeditor'] = false; + } + return $data; + } + + /** + * Get the editors data. + * + * @param bool $usehtmleditor if the user wants wysiwyg editor or not + * @return array editors data + */ + private function get_editors_data(bool $usehtmleditor): array { + global $PAGE; + + $result = []; + $instance = $this->manager->get_instance(); + + // Setup editor. + editors_head_setup(); + $PAGE->requires->js_call_amd( + 'mod_data/templateseditor', + 'init', + ['d' => $instance->id, 'mode' => $this->templatename] + ); + + if ($usehtmleditor) { + $format = FORMAT_HTML; + } else { + $format = FORMAT_PLAIN; + } + + $editor = editors_get_preferred_editor($format); + + // Add editors. + if ($this->templatename == 'listtemplate') { + $result[] = $this->generate_editor_data( + $editor, + 'header', + 'listtemplateheader', + $instance->listtemplateheader + ); + $maineditorname = 'multientry'; + } else { + $maineditorname = $this->templatename; + } + + $value = $instance->{$this->templatename} ?? ''; + $result[] = $this->generate_editor_data( + $editor, + $maineditorname, + $this->templatename, + $value + ); + + if ($this->templatename == 'listtemplate') { + $result[] = $this->generate_editor_data( + $editor, + 'footer', + 'listtemplatefooter', + $instance->listtemplatefooter + ); + } + + if ($this->templatename == 'rsstemplate') { + $result[] = $this->generate_editor_data( + $editor, + 'rsstitletemplate', + 'rsstitletemplate', + $instance->rsstitletemplate + ); + } + + return $result; + } + + /** + * Generate a single editor data. + * + * @param texteditor $editor the edtitor object + * @param string $name the editor name + * @param string $fieldname the field name + * @param string|null $value the current value + * @return array the editor data + */ + private function generate_editor_data( + texteditor $editor, + string $name, + string $fieldname, + ?string $value + ): array { + $options = [ + 'trusttext' => false, + 'forcehttps' => false, + 'subdirs' => false, + 'maxfiles' => 0, + 'maxbytes' => 0, + 'changeformat' => 0, + 'noclean' => false, + ]; + + $result = [ + 'name' => get_string($name, 'data'), + 'fieldname' => $fieldname, + 'value' => $value, + ]; + $editor->set_text($value); + $editor->use_editor($fieldname, $options); + return $result; + } +} diff --git a/mod/data/classes/output/template_editor_tools.php b/mod/data/classes/output/template_editor_tools.php new file mode 100644 index 0000000000000..e9afd1e28283c --- /dev/null +++ b/mod/data/classes/output/template_editor_tools.php @@ -0,0 +1,199 @@ +. + +namespace mod_data\output; + +use templatable; +use renderable; +use core_tag_tag; +use mod_data\manager; +use moodle_url; + +/** + * Renderable class for template editor tools. + * + * @package mod_data + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class template_editor_tools implements templatable, renderable { + + /** @var manager manager instance. */ + private $manager; + + /** @var string the template name. */ + private $templatename; + + /** + * The class constructor. + * + * @param manager $manager the activity instance manager + * @param string $templatename the template to edit + */ + public function __construct(manager $manager, string $templatename) { + $this->manager = $manager; + $this->templatename = $templatename; + } + + /** + * Export the data for the mustache template. + * + * @param \renderer_base $output renderer to be used to render the action bar elements. + * @return array + */ + public function export_for_template(\renderer_base $output): array { + $tools = [ + $this->get_field_tags($this->templatename), + $this->get_field_id_tags($this->templatename), + $this->get_action_tags($this->templatename), + $this->get_other_tags($this->templatename), + ]; + $tools = array_filter($tools, static function ($value) { + return !empty($value['tags']); + }); + return [ + 'toolshelp' => $output->help_icon('availabletags', 'data'), + 'hastools' => !empty($tools), + 'tools' => array_values($tools), + ]; + } + + /** + * Return the field template tags. + * + * @param string $templatename the template name + * @return array|null array of tags. + */ + protected function get_field_tags(string $templatename): array { + $name = get_string('fields', 'data'); + if ($templatename == 'csstemplate' || $templatename == 'jstemplate') { + return $this->get_optgroup_data($name, []); + } + $taglist = []; + $fields = $this->manager->get_fields(); + foreach ($fields as $field) { + $fieldname = $field->get_name(); + $taglist["[[$fieldname]]"] = $fieldname; + } + return $this->get_optgroup_data($name, $taglist); + } + + /** + * Return the field IDs template tags. + * + * @param string $templatename the template name + * @return array|null array of tags. + */ + protected function get_field_id_tags(string $templatename): array { + $name = get_string('fieldids', 'data'); + if ($templatename != 'addtemplate') { + return $this->get_optgroup_data($name, []); + } + $taglist = []; + // Field IDs. + $fields = $this->manager->get_fields(); + foreach ($fields as $field) { + $fieldname = $field->get_name(); + $taglist["[[$fieldname#id]]"] = "$fieldname id"; + } + return $this->get_optgroup_data($name, $taglist); + } + + /** + * Return the field action tags. + * + * @param string $templatename the template name + * @return array|null array of tags. + */ + protected function get_action_tags(string $templatename = null): array { + $name = get_string('actions'); + if ($templatename == 'addtemplate' || $templatename == 'asearchtemplate') { + return $this->get_optgroup_data($name, []); + } + $taglist = [ + '##edit##' => get_string('edit', 'data'), + '##delete##' => get_string('delete', 'data'), + '##approve##' => get_string('approve', 'data'), + '##disapprove##' => get_string('disapprove', 'data'), + ]; + if ($templatename != 'rsstemplate') { + $taglist['##export##'] = get_string('export', 'data'); + } + if ($templatename != 'singletemplate') { + $taglist['##more##'] = get_string('more', 'data'); + $taglist['##moreurl##'] = get_string('moreurl', 'data'); + $taglist['##delcheck##'] = get_string('delcheck', 'data'); + } + return $this->get_optgroup_data($name, $taglist); + } + + /** + * Return the available other tags + * + * @param string $templatename the template name + * @return array associative array of tags => tag name + */ + protected function get_other_tags(string $templatename = null): array { + $name = get_string('other', 'data'); + $taglist = []; + if ($templatename == 'asearchtemplate') { + $taglist['##firstname##'] = get_string('firstname'); + $taglist['##lastname##'] = get_string('lastname'); + return $this->get_optgroup_data($name, $taglist); + } + if (core_tag_tag::is_enabled('mod_data', 'data_records')) { + $taglist['##tags##'] = get_string('tags'); + } + if ($templatename == 'addtemplate') { + return $this->get_optgroup_data($name, $taglist); + } + $taglist['##timeadded##'] = get_string('timeadded', 'data'); + $taglist['##timemodified##'] = get_string('timemodified', 'data'); + $taglist['##user##'] = get_string('user'); + $taglist['##userpicture##'] = get_string('userpic'); + $taglist['##approvalstatus##'] = get_string('approvalstatus', 'data'); + $taglist['##id##'] = get_string('id', 'data'); + + if ($templatename == 'singletemplate') { + return $this->get_optgroup_data($name, $taglist); + } + + $taglist['##comments##'] = get_string('comments', 'data'); + + return $this->get_optgroup_data($name, $taglist); + } + + /** + * Generate a valid optgroup data. + * + * @param string $name the optgroup name + * @param array $taglist the indexed array of taglists ($tag => $tagname) + * @return array of optgroup data + */ + protected function get_optgroup_data (string $name, array $taglist): array { + $tags = []; + foreach ($taglist as $tag => $tagname) { + $tags[] = [ + 'tag' => "$tag", + 'tagname' => $tagname . ' - ' . $tag, + ]; + } + return [ + 'name' => $name, + 'tags' => $tags, + ]; + } +} diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index 69350f11b8f73..d2ce2a79dda78 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -52,7 +52,6 @@ $string['availabletodate'] = 'Available to'; $string['availabletodatevalidation'] = 'The available to date cannot be before the available from date.'; $string['blank'] = 'Blank'; -$string['buttons'] = 'Actions'; $string['bynameondate'] = 'by {$a->name} - {$a->date}'; $string['calendarend'] = '{$a} closes'; $string['calendarstart'] = '{$a} opens'; @@ -203,6 +202,7 @@ $string['headerrsstemplate'] = 'Defines appearance of entries in RSS feeds'; $string['headersingletemplate'] = 'Defines browsing interface for a single entry'; $string['checkbox'] = 'Checkbox'; +$string['id'] = 'Entry ID'; $string['chooseexportfields'] = 'Choose the fields you wish to export'; $string['chooseexportformat'] = 'Choose the format you wish to export to'; $string['chooseorupload'] = 'Choose file'; @@ -432,3 +432,6 @@ // Deprecated since Moodle 3.11. $string['unsupportedexport'] = '({$a->fieldtype}) cannot be exported.'; + +// Deprecated since Moodle 4.1. +$string['buttons'] = 'Actions'; diff --git a/mod/data/lang/en/deprecated.txt b/mod/data/lang/en/deprecated.txt index 0c6891c296032..3faf76f565d4a 100644 --- a/mod/data/lang/en/deprecated.txt +++ b/mod/data/lang/en/deprecated.txt @@ -1 +1,2 @@ unsupportedexport,mod_data +buttons,mod_data diff --git a/mod/data/lib.php b/mod/data/lib.php index d227b6630e057..2ce029267130e 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -21,6 +21,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_data\manager; + defined('MOODLE_INTERNAL') || die(); // Some constants @@ -136,6 +138,15 @@ function __construct($field=0, $data=0, $cm=0) { // Field or data or both, eac $this->context = context_module::instance($this->cm->id); } + /** + * Return the field type name. + * + * @return string the filed type. + */ + public function get_name(): string { + return $this->field->name; + } + /** * This field just sets up a default field object @@ -898,7 +909,7 @@ function data_get_field_new($type, $data) { * @param object $field * @param object $data * @param object $cm - * @return object + * @return data_field_base */ function data_get_field($field, $data, $cm=null) { global $CFG; diff --git a/mod/data/templates.php b/mod/data/templates.php index d34564ef959b3..67a11ebc8c5f3 100644 --- a/mod/data/templates.php +++ b/mod/data/templates.php @@ -36,16 +36,16 @@ $url = new moodle_url('/mod/data/templates.php'); if ($id) { - $url->param('id', $id); list($course, $cm) = get_course_and_cm_from_cmid($id, manager::MODULE); $manager = manager::create_from_coursemodule($cm); + $url->param('d', $cm->instance); } else { // We must have $d. + $instance = $DB->get_record('data', ['id' => $d], '*', MUST_EXIST); + $manager = manager::create_from_instance($instance); $url->param('d', $d); - $data = $DB->get_record('data', ['id' => $d], '*', MUST_EXIST); - $manager = manager::create_from_instance($data); } -$data = $manager->get_instance(); +$instance = $manager->get_instance(); $cm = $manager->get_coursemodule(); $context = $manager->get_context(); $course = get_course($cm->course); @@ -54,319 +54,65 @@ $PAGE->set_url($url); require_login($course, false, $cm); - require_capability('mod/data:managetemplates', $context); -if ($useeditor !== null) { - // The useeditor param was set. Update the value for this template. - data_set_config($data, "editor_{$mode}", !!$useeditor); -} - -if (!$DB->count_records('data_fields', array('dataid'=>$data->id))) { // Brand new database! - redirect($CFG->wwwroot.'/mod/data/field.php?d='.$data->id); // Redirect to field entry +// Check if it is an empty database. +if (count($manager->get_field_records()) == 0) { + redirect($CFG->wwwroot.'/mod/data/field.php?d='.$instance->id); } $manager->set_template_viewed(); -/// Print the page header - -$strdata = get_string('modulenameplural','data'); - -// For the javascript for inserting template tags: initialise the default textarea to -// 'edit_template' - it is always present in all different possible views. - -if ($mode == 'singletemplate') { - $PAGE->navbar->add(get_string($mode,'data')); +if ($useeditor !== null) { + // The useeditor param was set. Update the value for this template. + data_set_config($instance, "editor_{$mode}", !!$useeditor); } $PAGE->requires->js('/mod/data/data.js'); -$PAGE->set_title($data->name); +$PAGE->set_title($instance->name); $PAGE->set_heading($course->fullname); $PAGE->set_pagelayout('admin'); $PAGE->force_settings_menu(true); $PAGE->activityheader->disable(); +$PAGE->add_body_class('limitedwidth'); echo $OUTPUT->header(); -$actionbar = new \mod_data\output\action_bar($data->id, $url); +$actionbar = new \mod_data\output\action_bar($instance->id, $url); echo $actionbar->get_templates_action_bar(); echo $OUTPUT->heading(get_string($mode, 'data'), 2, 'mb-4'); -/// Processing submitted data, i.e updating form. -$resettemplate = false; - -if (($mytemplate = data_submitted()) && confirm_sesskey()) { - $newtemplate = new stdClass(); - $newtemplate->id = $data->id; - $newtemplate->{$mode} = $mytemplate->template; - - if (!empty($mytemplate->defaultform)) { +if (($formdata = data_submitted()) && confirm_sesskey()) { + if (!empty($formdata->defaultform)) { // Reset the template to default, but don't save yet. - $resettemplate = true; - $data->{$mode} = data_generate_default_template($data, $mode, 0, false, false); + $instance->{$mode} = data_generate_default_template($instance, $mode, 0, false, false); if ($mode == 'listtemplate') { - $data->listtemplateheader = ''; - $data->listtemplatefooter = ''; + $instance->listtemplateheader = ''; + $instance->listtemplatefooter = ''; } } else { - if (isset($mytemplate->listtemplateheader)){ - $newtemplate->listtemplateheader = $mytemplate->listtemplateheader; - } - if (isset($mytemplate->listtemplatefooter)){ - $newtemplate->listtemplatefooter = $mytemplate->listtemplatefooter; - } - if (isset($mytemplate->rsstitletemplate)){ - $newtemplate->rsstitletemplate = $mytemplate->rsstitletemplate; - } - - // Check for multiple tags, only need to check for add template. - if ($mode != 'addtemplate' or data_tags_check($data->id, $newtemplate->{$mode})) { - $DB->update_record('data', $newtemplate); + if ($manager->update_templates($formdata)) { + // Reload instance. + $instance = $manager->get_instance(); echo $OUTPUT->notification(get_string('templatesaved', 'data'), 'notifysuccess'); - - // Trigger an event for saving the templates. - $event = \mod_data\event\template_updated::create(array( - 'context' => $context, - 'courseid' => $course->id, - 'other' => array( - 'dataid' => $data->id, - ) - )); - $event->trigger(); } } -} else { - echo '
'.get_string('header'.$mode,'data').'
'; } /// If everything is empty then generate some defaults -if (empty($data->addtemplate) and empty($data->singletemplate) and - empty($data->listtemplate) and empty($data->rsstemplate)) { - data_generate_default_template($data, 'singletemplate'); - data_generate_default_template($data, 'listtemplate'); - data_generate_default_template($data, 'addtemplate'); - data_generate_default_template($data, 'asearchtemplate'); //Template for advanced searches. - data_generate_default_template($data, 'rsstemplate'); -} - -editors_head_setup(); - -// Determine whether to use HTML editors. -if (($mode === 'csstemplate') || ($mode === 'jstemplate')) { - // The CSS and JS templates aren't HTML. - $usehtmleditor = false; -} else { - $usehtmleditor = data_get_config($data, "editor_{$mode}", true); -} - -if ($usehtmleditor) { - $format = FORMAT_HTML; -} else { - $format = FORMAT_PLAIN; -} - -$editor = editors_get_preferred_editor($format); -$strformats = format_text_menu(); -$formats = $editor->get_supported_formats(); -foreach ($formats as $fid) { - $formats[$fid] = $strformats[$fid]; -} -$options = array(); -$options['trusttext'] = false; -$options['forcehttps'] = false; -$options['subdirs'] = false; -$options['maxfiles'] = 0; -$options['maxbytes'] = 0; -$options['changeformat'] = 0; -$options['noclean'] = false; - -echo '
'; -echo '
'; -echo ''; -// Print button to autogen all forms, if all templates are empty - -if (!$resettemplate) { - // Only reload if we are not resetting the template to default. - $data = $DB->get_record('data', array('id'=>$d)); -} -echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide'); -echo ''; - -if ($mode == 'listtemplate'){ - // Print the list template header. - echo ''; - echo ''; - echo ''; - echo ''; -} - -// Print the main template. - -echo ''; - -echo ''; -echo ''; - -if ($mode == 'listtemplate'){ - echo ''; - echo ''; - echo ''; - echo ''; -} else if ($mode == 'rsstemplate') { - echo ''; - echo ''; - echo ''; - echo ''; -} - -echo '
 '; - echo '
'; - - $field = 'listtemplateheader'; - $editor->set_text($data->listtemplateheader); - $editor->use_editor($field, $options); - echo '
'; - - echo '
'; -if ($mode != 'csstemplate' and $mode != 'jstemplate') { - // Add all the available fields for this data. - echo ''; - echo $OUTPUT->help_icon('availabletags', 'data'); - echo '
'; - - echo '
'; - echo ''; - echo '
'; -} -echo '
'; -if ($mode == 'listtemplate'){ - echo '
'; -} else { - echo '
'; -} - -$field = 'template'; -$editor->set_text($data->{$mode}); -$editor->use_editor($field, $options); -echo '
'; -echo ''; -echo '
'; -echo '
 '; - echo '
'; - - $field = 'listtemplatefooter'; - $editor->set_text($data->listtemplatefooter); - $editor->use_editor($field, $options); - echo '
'; - echo ''; - echo '
'; - echo '
 '; - echo '
'; - echo ''; - echo '
'; - - $field = 'rsstitletemplate'; - $editor->set_text($data->rsstitletemplate); - $editor->use_editor($field, $options); - echo '
'; - echo ''; - echo '
'; - echo '
'; -echo html_writer::start_div('container-fluid mt-4'); -echo html_writer::start_div('row'); - -$resettemplatebutton = html_writer::empty_tag('input', ['type' => 'submit', 'name' => 'defaultform', - 'class' => 'btn btn-secondary', 'value' => get_string('resettemplate', 'data')]); -$savetemplatebutton = html_writer::empty_tag('input', ['type' => 'submit', 'class' => 'btn btn-primary ml-2', - 'value' => get_string('savetemplate', 'data')]); - -echo html_writer::div($resettemplatebutton . $savetemplatebutton); - -if ($mode != 'csstemplate' and $mode != 'jstemplate') { - // Output the toggle template editor element. - $toggletemplateeditor = html_writer::checkbox('useeditor', 1, $usehtmleditor, - get_string('editorenable', 'data'), null, ['class' => 'pl-2']); - echo html_writer::div($toggletemplateeditor, 'ml-auto'); - $PAGE->requires->js_call_amd('mod_data/templateseditor', 'init', ['d' => $d, 'mode' => $mode]); +if (empty($instance->addtemplate) && empty($instance->singletemplate) && + empty($instance->listtemplate) && empty($instance->rsstemplate)) { + data_generate_default_template($instance, 'singletemplate'); + data_generate_default_template($instance, 'listtemplate'); + data_generate_default_template($instance, 'addtemplate'); + data_generate_default_template($instance, 'asearchtemplate'); + data_generate_default_template($instance, 'rsstemplate'); } -echo html_writer::end_div(); -echo html_writer::end_div(); -echo $OUTPUT->box_end(); -echo '
'; -echo '
'; +$renderer = $PAGE->get_renderer('mod_data'); +$templateeditor = new \mod_data\output\template_editor($manager, $mode); +echo $renderer->render($templateeditor); /// Finish the page echo $OUTPUT->footer(); diff --git a/mod/data/templates/template_editor.mustache b/mod/data/templates/template_editor.mustache new file mode 100644 index 0000000000000..e4e4461f8f221 --- /dev/null +++ b/mod/data/templates/template_editor.mustache @@ -0,0 +1,123 @@ +{{! + 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 . +}} +{{! + @template mod_data/template_editor + + Template editor in the database activity. + + Example context (json): + { + "title": "Defines browsing interface for multiple entries", + "sesskey": "XXXXX", + "disableeditor": true, + "url": {}, + "usehtmleditor": true, + "toolbar": { + "toolshelp": "Available tags help", + "hastools": true, + "tools": [ + { + "name": "Fields", + "tags": [ + { + "tag": "[[Checkme]]", + "tagname": "Checkme - [[Checkme]]" + }, + { + "tag": "[[Description]]", + "tagname": "Description - [[Description]]" + }, + { + "tag": "[[Name]]", + "tagname": "Name - [[Name]]" + } + ] + } + ] + }, + "editors": [ + { + "name": "Header", + "fieldname": "listtemplateheader", + "value": "" + }, + { + "name": "Repeated entry", + "fieldname": "listtemplate", + "value": "Template content" + }, + { + "name": "Footer", + "fieldname": "listtemplatefooter", + "value": "" + } + ] + } +}} +
{{title}}
+
+ +
+ {{#toolbar}} + {{> mod_data/template_editor_tools }} + {{/toolbar}} +
+ {{#editors}} +
+
+ +
+
+ +
+
+ {{/editors}} +
+
+
+
+
+ + +
+ {{#disableeditor}} +
+ + +
+ {{/disableeditor}} +
+
+
diff --git a/mod/data/templates/template_editor_tools.mustache b/mod/data/templates/template_editor_tools.mustache new file mode 100644 index 0000000000000..5b65f171d95c5 --- /dev/null +++ b/mod/data/templates/template_editor_tools.mustache @@ -0,0 +1,134 @@ +{{! + 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 . +}} +{{! + @template mod_data/template_editor_tools + + Tag tool box for the template editor in the mod_data. + + Example context (json): + { + "toolshelp": "Available tags", + "hastools": true, + "tools": [ + { + "name": "Fields", + "tags": [ + { + "tag": "[[Checkme]]", + "tagname": "Checkme - [[Checkme]]" + }, + { + "tag": "[[Description]]", + "tagname": "Description - [[Description]]" + }, + { + "tag": "[[Name]]", + "tagname": "Name - [[Name]]" + } + ] + }, + { + "name": "Actions", + "tags": [ + { + "tag": "##edit##", + "tagname": "Edit - ##edit##" + }, + { + "tag": "##delete##", + "tagname": "Delete - ##delete##" + }, + { + "tag": "##approve##", + "tagname": "Approve - ##approve##" + }, + { + "tag": "##disapprove##", + "tagname": "Undo approval - ##disapprove##" + }, + { + "tag": "##export##", + "tagname": "Export - ##export##" + }, + { + "tag": "##more##", + "tagname": "More - ##more##" + }, + { + "tag": "##moreurl##", + "tagname": "More URL - ##moreurl##" + }, + { + "tag": "##delcheck##", + "tagname": "Bulk delete checkbox - ##delcheck##" + } + ] + }, + { + "name": "Other", + "tags": [ + { + "tag": "##tags##", + "tagname": "Tags - ##tags##" + }, + { + "tag": "##timeadded##", + "tagname": "Time added - ##timeadded##" + }, + { + "tag": "##timemodified##", + "tagname": "Time modified - ##timemodified##" + }, + { + "tag": "##user##", + "tagname": "User - ##user##" + }, + { + "tag": "##userpicture##", + "tagname": "User picture - ##userpicture##" + }, + { + "tag": "##approvalstatus##", + "tagname": "Approval status - ##approvalstatus##" + }, + { + "tag": "##id##", + "tagname": "Entry ID - ##id##" + }, + { + "tag": "##comments##", + "tagname": "Comments - ##comments##" + } + ] + } + ] + } +}} +{{#hastools}} +
+ + {{{toolshelp}}} +
+ +
+
+{{/hastools}} diff --git a/mod/data/tests/behat/edit_templates.feature b/mod/data/tests/behat/edit_templates.feature new file mode 100644 index 0000000000000..32757d1d62e67 --- /dev/null +++ b/mod/data/tests/behat/edit_templates.feature @@ -0,0 +1,115 @@ +@mod @mod_data +Feature: Users can edit the database templates + In order to use custom templates for entries + As a teacher + I need to edit the templates html + + 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 | Test database name | Database intro | C1 | data1 | + And the following "mod_data > fields" exist: + | database | type | name | description | + | data1 | text | field1 | Test field description | + | data1 | text | field2 | Test field 2 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 | field2 | + | data1 | Student entry 1 | Some content 1 | + And I am on the "Test database name" "data activity" page logged in as teacher1 + And I navigate to "Templates" in current page administration + + @javascript + Scenario: Edit list template + Given I set the following fields to these values: + | Header | New header! | + | Repeated entry | [[field1]] and [[field2]]! | + | Footer | New footer! | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + Then I should see "New header!" + And I should see "Student entry 1 and Some content 1!" + And I should see "New footer!" + + @javascript + Scenario: Edit single template + Given I set the field "Templates tertiary navigation" to "Single template" + And I set the following fields to these values: + | Single template | [[field1]] and [[field2]] details! | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + And I set the field "View mode tertiary navigation" to "Single view" + Then I should see "Student entry 1 and Some content 1 details!" + + @javascript + Scenario: Edit add entry template + Given I set the field "Templates tertiary navigation" to "Add entry template" + And I set the following fields to these values: + | Add entry template | [[field1]] [[field2]] Form extra! | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + And I click on "Add entry" "button" + Then I should see "Form extra!" + + @javascript + Scenario: Edit advanced search template + Given I set the field "Templates tertiary navigation" to "Advanced search template" + And I set the following fields to these values: + | Advanced search template | New advanced search template! | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + And I click on "Advanced search" "checkbox" + Then I should see "New advanced search template!" + + @javascript + Scenario: Edit without the wysiwyg editor + Given I click on "Enable editor" "checkbox" + And I set the following fields to these values: + | Repeated entry | NopeYep! | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + Then I should not see "Nope" + And I should see "Yep!" + + @javascript + Scenario: Edit CSS teamplate + Given I click on "Enable editor" "checkbox" + And I set the following fields to these values: + | Repeated entry | NopeYep! | + And I click on "Save template" "button" + And I set the field "Templates tertiary navigation" to "CSS template" + And I set the following fields to these values: + | CSS template | .hideme {display: none;} | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + Then I should not see "Nope" + And I should see "Yep!" + + @javascript + Scenario: Edit Javascript template + Given I click on "Enable editor" "checkbox" + And I set the following fields to these values: + | Repeated entry | NopeYep! | + And I click on "Save template" "button" + And I set the field "Templates tertiary navigation" to "Javascript template" + And I set the following fields to these values: + | Javascript template | window.onload = () => document.querySelector('#hideme').style.display = 'none'; | + And I click on "Save template" "button" + When I navigate to "Database" in current page administration + Then I should not see "Nope" + And I should see "Yep!"