diff --git a/mod/data/classes/form/save_as_preset.php b/mod/data/classes/form/save_as_preset.php index 526110c43a2..0f099afc4a7 100644 --- a/mod/data/classes/form/save_as_preset.php +++ b/mod/data/classes/form/save_as_preset.php @@ -194,7 +194,11 @@ class save_as_preset extends dynamic_form { if ($result) { // Add notification in the session to be shown when the page is reloaded on the JS side. - notification::success(get_string('savesuccess', 'mod_data')); + $previewurl = new moodle_url( + '/mod/data/preset.php', + ['id' => $cm->id, 'fullname' => $preset->get_fullname(), 'action' => 'preview'] + ); + notification::success(get_string('savesuccess', 'mod_data', (object)['url' => $previewurl->out()])); } } catch (\Exception $exception) { $errors[] = $exception->getMessage(); diff --git a/mod/data/classes/manager.php b/mod/data/classes/manager.php index 16f2dc9946d..c501d669fd9 100644 --- a/mod/data/classes/manager.php +++ b/mod/data/classes/manager.php @@ -38,6 +38,9 @@ class manager { /** Module name. */ const MODULE = 'data'; + /** The plugin name. */ + const PLUGINNAME = 'mod_data'; + /** Template list with their files required to save the information of a preset. */ const TEMPLATES_LIST = [ 'listtemplate' => 'listtemplate.html', @@ -53,7 +56,7 @@ class manager { ]; /** @var string plugin path. */ - private $path; + public $path; /** @var stdClass course_module record. */ private $instance; @@ -239,6 +242,7 @@ class manager { * @return data_field_base the data field class instance */ public function get_field(stdClass $fieldrecord): data_field_base { + global $CFG; // Some old field plugins require $CFG to be in the scope. $filepath = "{$this->path}/field/{$fieldrecord->type}/field.class.php"; $classname = "data_field_{$fieldrecord->type}"; if (!file_exists($filepath)) { @@ -323,6 +327,24 @@ class manager { return true; } + /** Check if the user can view a specific preset. + * + * @param preset $preset the preset instance. + * @param int $userid the user id to check ($USER->id if null). + * @return bool if the user can view the preset. + */ + public function can_view_preset (preset $preset, ?int $userid = null): bool { + global $USER; + if (!$userid) { + $userid = $USER->id; + } + $presetuserid = $preset->get_userid(); + if ($presetuserid && $presetuserid != $userid) { + return has_capability('mod/data:viewalluserpresets', $this->context, $userid); + } + return true; + } + /** * Returns an array of all the available presets. * diff --git a/mod/data/classes/output/action_bar.php b/mod/data/classes/output/action_bar.php index 52d5133694b..3d0aa65ba99 100644 --- a/mod/data/classes/output/action_bar.php +++ b/mod/data/classes/output/action_bar.php @@ -16,7 +16,9 @@ namespace mod_data\output; +use mod_data\manager; use moodle_url; +use url_select; /** * Class responsible for generating the action bar elements in the database module pages. @@ -75,7 +77,7 @@ class action_bar { $selected = $presetslink->out(false); } - $urlselect = new \url_select($menu, $selected, null, 'fieldactionselect'); + $urlselect = new url_select($menu, $selected, null, 'fieldactionselect'); $urlselect->set_label(get_string('fieldsnavigation', 'mod_data'), ['class' => 'sr-only']); $fieldselect = null; @@ -144,7 +146,7 @@ class action_bar { $activeurl = $viewsinglelink; } - $urlselect = new \url_select($menu, $activeurl->out(false), null, 'viewactionselect'); + $urlselect = new url_select($menu, $activeurl->out(false), null, 'viewactionselect'); $urlselect->set_label(get_string('viewnavigation', 'mod_data'), ['class' => 'sr-only']); $renderer = $PAGE->get_renderer('mod_data'); $viewactionbar = new view_action_bar($this->id, $urlselect, $hasentries); @@ -181,7 +183,7 @@ class action_bar { $jstemplatelink->out(false) => get_string('jstemplate', 'mod_data'), ]; - $urlselect = new \url_select($menu, $this->currenturl->out(false), null, 'templatesactionselect'); + $urlselect = new url_select($menu, $this->currenturl->out(false), null, 'templatesactionselect'); $urlselect->set_label(get_string('templatesnavigation', 'mod_data'), ['class' => 'sr-only']); $hasfields = $DB->record_exists('data_fields', ['dataid' => $this->id]); @@ -221,4 +223,46 @@ class action_bar { return $renderer->render_presets_action_bar($presetsactionbar); } + + /** + * Generate the output for the action selector in the presets preview page. + * + * @param manager $manager the manager instance + * @param string $fullname the preset fullname + * @param string $current the current template name + * @return string The HTML code for the action selector + */ + public function get_presets_preview_action_bar(manager $manager, string $fullname, string $current): string { + global $PAGE; + + $renderer = $PAGE->get_renderer(manager::PLUGINNAME); + + $cm = $manager->get_coursemodule(); + + $menu = []; + $selected = null; + foreach (['listtemplate', 'singletemplate'] as $templatename) { + $link = new moodle_url('/mod/data/preset.php', [ + 'd' => $this->id, + 'template' => $templatename, + 'fullname' => $fullname, + 'action' => 'preview', + ]); + $menu[$link->out(false)] = get_string($templatename, manager::PLUGINNAME); + if (!$selected || $templatename == $current) { + $selected = $link->out(false); + } + } + $urlselect = new url_select($menu, $selected, null); + $urlselect->set_label(get_string('templatesnavigation', manager::PLUGINNAME), ['class' => 'sr-only']); + + $data = [ + 'title' => get_string('preview', manager::PLUGINNAME), + 'hasback' => true, + 'backtitle' => get_string('back'), + 'backurl' => new moodle_url('/mod/data/preset.php', ['id' => $cm->id]), + 'extraurlselect' => $urlselect->export_for_template($renderer), + ]; + return $renderer->render_from_template('mod_data/action_bar', $data); + } } diff --git a/mod/data/classes/output/preset_preview.php b/mod/data/classes/output/preset_preview.php new file mode 100644 index 00000000000..a0f85a285c1 --- /dev/null +++ b/mod/data/classes/output/preset_preview.php @@ -0,0 +1,118 @@ +. + +namespace mod_data\output; + +use templatable; +use renderable; +use mod_data\manager; +use mod_data\preset; +use mod_data\template; +use moodle_page; +use moodle_url; + +/** + * Preset preview output class. + * + * @package mod_data + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_preview implements templatable, renderable { + + /** @var manager manager instance. */ + private $manager; + + /** @var preset the preset. */ + private $preset; + + /** @var string the template to preview. */ + private $templatename; + + /** + * The class constructor. + * + * @param manager $manager the activity instance manager + * @param preset $preset the preset + * @param string $templatename the templatename + */ + public function __construct(manager $manager, preset $preset, string $templatename) { + $this->manager = $manager; + $this->preset = $preset; + $this->templatename = $templatename; + } + + /** + * Add the preset CSS and JS to the page. + * + * @param moodle_page $page the current page instance + */ + public function prepare_page(moodle_page $page) { + $instance = $this->manager->get_instance(); + $preset = $this->preset; + // Add CSS and JS. + $csscontent = $preset->get_template_content('csstemplate'); + if (!empty($csscontent)) { + $url = new moodle_url('/mod/data/css.php', ['d' => $instance->id, 'preset' => $preset->get_fullname()]); + $page->requires->css($url); + } + $jscontent = $preset->get_template_content('jstemplate'); + if (!empty($jscontent)) { + $url = new moodle_url('/mod/data/js.php', ['d' => $instance->id, 'preset' => $preset->get_fullname()]); + $page->requires->js($url); + } + } + + /** + * 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(); + $preset = $this->preset; + + // Get fields for preview. + $count = ($this->templatename == 'listtemplate') ? 2 : 1; + $fields = $preset->get_fields(true); + $entries = $preset->get_sample_entries($count); + $templatecontent = $preset->get_template_content($this->templatename); + $useurl = new moodle_url('/mod/data/field.php'); + + // Generate preview content. + $options = ['templatename' => $this->templatename]; + if ($this->templatename == 'listtemplate') { + $options['showmore'] = true; + } + $parser = new template($this->manager, $templatecontent, $options, $fields); + $content = $parser->parse_entries($entries); + if ($this->templatename == 'listtemplate') { + $listtemplateheader = $preset->get_template_content('listtemplateheader'); + $listtemplatefooter = $preset->get_template_content('listtemplatefooter'); + $content = $listtemplateheader . $content . $listtemplatefooter; + } + + return [ + 'd' => $instance->id, + 'description' => $preset->description ?? '', + 'preview' => $content, + 'formactionurl' => $useurl->out(), + 'userid' => $preset->get_userid() ?? 0, + 'shortname' => $preset->shortname, + ]; + } +} diff --git a/mod/data/classes/output/presets.php b/mod/data/classes/output/presets.php index 9b8ce3f91c4..b9ce09f25f7 100644 --- a/mod/data/classes/output/presets.php +++ b/mod/data/classes/output/presets.php @@ -72,7 +72,7 @@ class presets implements templatable, renderable { $presets = $this->get_presets($output); return [ 'd' => $this->id, - 'formactionul' => $this->formactionurl->out(), + 'formactionurl' => $this->formactionurl->out(), 'showmanage' => $this->manage, 'presets' => $presets, ]; @@ -100,9 +100,16 @@ class presets implements templatable, renderable { } $actions = $this->get_preset_action_menu($output, $preset, $userid); + $fullname = "{$userid}/{$preset->shortname}"; + $previewurl = new moodle_url( + '/mod/data/preset.php', + ['d' => $this->id, 'fullname' => $fullname, 'action' => 'preview'] + ); + $presets[] = [ 'id' => $this->id, 'name' => $preset->name, + 'url' => $previewurl->out(), 'shortname' => $preset->shortname, 'fullname' => $presetname, 'description' => $preset->description, diff --git a/mod/data/classes/preset.php b/mod/data/classes/preset.php index 18020b7a103..334bacdb14c 100644 --- a/mod/data/classes/preset.php +++ b/mod/data/classes/preset.php @@ -18,6 +18,8 @@ namespace mod_data; use core_component; use invalid_parameter_exception; +use data_field_base; +use moodle_exception; use SimpleXMLElement; use stdClass; use stored_file; @@ -50,6 +52,12 @@ class preset { * haven't been saved yet. */ public $storedfile; + /** @var array|null the field sample instances. */ + private $fields = null; + + /** @var stdClass|null the preset.xml parsed information. */ + protected $xmlinfo = null; + /** * Class constructor. * @@ -151,6 +159,38 @@ class preset { return new self($manager, $isplugin, $presetname, $presetname, $description, $file); } + /** + * Create a preset instance from the preset fullname. + * + * The preset fullname is a concatenation of userid and pluginname|presetname used by most + * preset pages. Plugins uses userid zero while preset instances has the owner as identifier. + * + * This method will throw an exception if the preset instance has a different userid thant the one + * from the $fullname. However, it won't check the current user capabilities. + * + * @param manager $manager the current instance manager + * @param string $fullname the preset full name + * @return preset + */ + public static function create_from_fullname(manager $manager, string $fullname): self { + $parts = explode('/', $fullname, 2); + $userid = empty($parts[0]) ? 0 : (int)$parts[0]; + $shortname = empty($parts[1]) ? '' : $parts[1]; + + // Shortnames with userid zero are plugins. + if ($userid == 0) { + return static::create_from_plugin($manager, $shortname); + } + + $path = '/' . $shortname . '/'; + $file = static::get_file($path, '.'); + $result = static::create_from_storedfile($manager, $file); + if ($result->get_userid() != $userid) { + throw new moodle_exception('invalidpreset', manager::PLUGINNAME); + } + return $result; + } + /** * Save this preset. * @@ -303,16 +343,29 @@ class preset { /** * Return the preset author. * - * @return int|null + * Preset plugins do not have any user id. + * + * @return int|null the userid or null if it is a plugin */ public function get_userid(): ?int { if (!empty($this->storedfile)) { return $this->storedfile->get_userid(); } - return null; } + /** + * Return the preset fullname. + * + * Preset fullname is used mostly for urls. + * + * @return string the preset fullname + */ + public function get_fullname(): string { + $userid = $this->get_userid() ?? '0'; + return "{$userid}/{$this->shortname}"; + } + /** * Returns the preset path. * @@ -330,6 +383,175 @@ class preset { return '/' . $this->name . '/'; } + /** + * Return the field instances of the preset. + * + * @param bool $forpreview if the fields are only for preview + * @return data_field_base[] and array with field objects + */ + public function get_fields(bool $forpreview = false): array { + if ($this->fields !== null) { + return $this->fields; + } + // Parse the preset.xml file. + $this->load_preset_xml(); + if (empty($this->xmlinfo) || empty($this->xmlinfo->field)) { + $this->fields = []; + return $this->fields; + } + // Generate field instances. + $result = []; + foreach ($this->xmlinfo->field as $fieldinfo) { + $result[(string) $fieldinfo->name] = $this->get_field_instance($fieldinfo, count($result), $forpreview); + } + $this->fields = $result; + return $result; + } + + /** + * Convert a preset.xml field data into field instance. + * + * @param SimpleXMLElement $fieldinfo the field xml information + * @param int $id the field id to use + * @param bool $forpreview if the field should support preview + * @return data_field_base the field instance + */ + private function get_field_instance( + SimpleXMLElement $fieldinfo, + int $id = 0, + bool $forpreview = false + ): data_field_base { + global $CFG; // Some old field plugins require $CFG to be in the scope. + + $fieldrecord = $this->get_fake_field_record($fieldinfo, $id); + $instance = $this->manager->get_instance(); + $cm = $this->manager->get_coursemodule(); + + // Include the plugin. + $filepath = "{$this->manager->path}/field/{$fieldrecord->type}/field.class.php"; + if (file_exists($filepath)) { + require_once($filepath); + } + $classname = "data_field_{$fieldrecord->type}"; + $newfield = null; + if (class_exists($classname)) { + $newfield = new $classname($fieldrecord, $instance, $cm); + if ($forpreview && !$newfield->supports_preview()) { + $newfield = new data_field_base($fieldrecord, $instance, $cm); + } + } else { + $newfield = new data_field_base($fieldrecord, $instance, $cm); + } + if ($forpreview) { + $newfield->set_preview(true); + } + return $newfield; + } + + /** + * Generate a fake field record fomr the preset.xml field data. + * + * @param SimpleXMLElement $fieldinfo the field xml information + * @param int $id the field id to use + * @return stdClass the fake record + */ + private function get_fake_field_record(SimpleXMLElement $fieldinfo, int $id = 0): stdClass { + $instance = $this->manager->get_instance(); + // Generate stub record. + $fieldrecord = (object)[ + 'id' => $id, + 'dataid' => $instance->id, + 'type' => (string) $fieldinfo->type, + 'name' => (string) $fieldinfo->name, + 'description' => (string) $fieldinfo->description ?? '', + 'required' => (int) $fieldinfo->required ?? 0, + ]; + for ($i = 1; $i < 11; $i++) { + $name = "param{$i}"; + $fieldrecord->{$name} = null; + if (property_exists($fieldinfo, $name)) { + $fieldrecord->{$name} = (string) $fieldinfo->{$name}; + } + } + return $fieldrecord; + } + + /** + * Return sample entries to preview this preset. + * + * @param int $count the number of entries to generate. + * @return array of sample entries + */ + public function get_sample_entries(int $count = 1): array { + global $USER; + $fields = $this->get_fields(); + $instance = $this->manager->get_instance(); + $entries = []; + for ($current = 1; $current <= $count; $current++) { + $entry = (object)[ + 'id' => $current, + 'userid' => $USER->id, + 'groupid' => 0, + 'dataid' => $instance->id, + 'timecreated' => time(), + 'timemodified' => time(), + 'approved' => 1, + ]; + // Add all necessary user fields. + $userfieldsapi = \core_user\fields::for_userpic()->excluding('id'); + $fields = $userfieldsapi->get_required_fields(); + foreach ($fields as $field) { + $entry->{$field} = $USER->{$field}; + } + $entries[$current] = $entry; + } + return $entries; + } + + /** + * Load all the information from the preset.xml. + */ + protected function load_preset_xml() { + if (!empty($this->xmlinfo)) { + return; + } + // Load everything from the XML. + $presetxml = null; + if ($this->isplugin) { + $path = $this->manager->path . '/preset/' . $this->shortname . '/preset.xml'; + $presetxml = file_get_contents($path); + } else { + $presetxml = static::get_content_from_file($this->storedfile->get_filepath(), 'preset.xml'); + } + $this->xmlinfo = simplexml_load_string($presetxml); + } + + /** + * Return the template content from the preset. + * + * @param string $templatename the template name + * @return string the template content + */ + public function get_template_content(string $templatename): string { + $filename = "{$templatename}.html"; + if ($templatename == 'csstemplate') { + $filename = "{$templatename}.css"; + } + if ($templatename == 'jstemplate') { + $filename = "{$templatename}.js"; + } + if ($this->isplugin) { + $path = $this->manager->path . '/preset/' . $this->shortname . '/' . $filename; + $result = file_get_contents($path); + } else { + $result = static::get_content_from_file($this->storedfile->get_filepath(), $filename); + } + if (empty($result)) { + return ''; + } + return $result; + } + /** * Checks if a directory contains all the required files to define a preset. * diff --git a/mod/data/classes/template.php b/mod/data/classes/template.php index dc04a91a348..369aba2c315 100644 --- a/mod/data/classes/template.php +++ b/mod/data/classes/template.php @@ -76,6 +76,9 @@ class template { /** @var array All template tags (calculated in load_template_tags). */ protected $tags = []; + /** @var array The mod_data fields. */ + protected $fields = []; + /** * Class contructor. * @@ -84,8 +87,9 @@ class template { * @param manager $manager the current instance manager * @param string $templatecontent the template string to use * @param array $options an array of extra diplay options + * @param array $fields alternative array of fields (for preview presets) */ - public function __construct(manager $manager, string $templatecontent, array $options = []) { + public function __construct(manager $manager, string $templatecontent, array $options = [], array $fields = null) { $this->manager = $manager; $this->instance = $manager->get_instance(); $this->templatecontent = $templatecontent; @@ -93,6 +97,7 @@ class template { $context = $manager->get_context(); $this->canmanageentries = has_capability('mod/data:manageentries', $context); $this->icons = $this->get_icons(); + $this->fields = $fields ?? $manager->get_fields(); $this->add_options($options); $this->load_template_tags($templatecontent); } @@ -215,8 +220,7 @@ class template { */ private function get_fields_replacements(stdClass $entry): array { $result = []; - $fields = $this->manager->get_fields(); - foreach ($fields as $field) { + foreach ($this->fields as $field) { // Field value. $pattern = '[[' . $field->field->name . ']]'; $result[$pattern] = highlight( @@ -448,8 +452,7 @@ class template { ['id' => $cm->id, 'recordid' => $entry->id], 'mod_data' ); - $fields = $this->manager->get_fields(); - list($formats, $files) = data_portfolio_caller::formats($fields, $entry); + list($formats, $files) = data_portfolio_caller::formats($this->fields, $entry); $button->set_formats($formats); $result = $button->to_html(PORTFOLIO_ADD_ICON_LINK); if (is_null($result)) { diff --git a/mod/data/css.php b/mod/data/css.php index c87f9706c87..e8f41672391 100644 --- a/mod/data/css.php +++ b/mod/data/css.php @@ -1,5 +1,4 @@ set_url('/mod/data/css.php', array('d'=>$d)); +$lifetime = 600; // Seconds to cache this stylesheet. +$url = new moodle_url('/mod/data/css.php'); -if ($data = $DB->get_record('data', array('id'=>$d))) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT'); - header('Cache-control: max_age = '. $lifetime); - header('Pragma: '); - header('Content-type: text/css; charset=utf-8'); // Correct MIME type +$manager = null; +if ($id) { + list($course, $cm) = get_course_and_cm_from_cmid($id, manager::MODULE); + $manager = manager::create_from_coursemodule($cm); + $instance = $manager->get_instance(); +} else { + // We must have the database activity id. + $d = required_param('d', PARAM_INT); + $instance = $DB->get_record('data', ['id' => $d], '*', MUST_EXIST); + $manager = manager::create_from_instance($instance); +} +$url->param('d', $instance->id); - echo $data->csstemplate; -} \ No newline at end of file +// Get the content. +if ($presetfullname) { + $url->param('preset', $presetfullname); + $preset = preset::create_from_fullname($manager, $presetfullname); + $content = $preset->get_template_content('csstemplate'); + $lifetime = 60; // Preset preview does not need a long cache. +} else { + $content = $instance->csstemplate; +} + +$PAGE->set_url($url); + +header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); +header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT'); +header('Cache-control: max_age = '. $lifetime); +header('Pragma: '); +header('Content-type: text/css; charset=utf-8'); // Correct MIME type. + +echo $content; diff --git a/mod/data/field/checkbox/field.class.php b/mod/data/field/checkbox/field.class.php index 7c3a2cc1bb6..25f0d5d24cd 100644 --- a/mod/data/field/checkbox/field.class.php +++ b/mod/data/field/checkbox/field.class.php @@ -32,6 +32,26 @@ class data_field_checkbox extends data_field_base { */ protected static $priority = self::LOW_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + $selected = $options[$recordid % count($options)]; + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => $selected, + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $CFG, $DB, $OUTPUT; @@ -188,28 +208,24 @@ class data_field_checkbox extends data_field_base { } function display_browse_field($recordid, $template) { - global $DB; - - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - if (strval($content->content) === '') { - return false; - } - - $options = explode("\n",$this->field->param1); - $options = array_map('trim', $options); - - $contentArr = explode('##', $content->content); - $str = ''; - foreach ($contentArr as $line) { - if (!in_array($line, $options)) { - // hmm, looks like somebody edited the field definition - continue; - } - $str .= $line . "
\n"; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content || empty($content->content)) { + return ''; } - return false; + + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + + $contentarray = explode('##', $content->content); + $str = ''; + foreach ($contentarray as $line) { + if (!in_array($line, $options)) { + // Hmm, looks like somebody edited the field definition. + continue; + } + $str .= $line . "
\n"; + } + return $str; } function format_data_field_checkbox_content($content) { diff --git a/mod/data/field/date/field.class.php b/mod/data/field/date/field.class.php index c488f562cf9..758f307a53b 100644 --- a/mod/data/field/date/field.class.php +++ b/mod/data/field/date/field.class.php @@ -34,6 +34,23 @@ class data_field_date extends data_field_base { var $month = 0; var $year = 0; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => (string) time(), + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $DB, $OUTPUT; @@ -164,11 +181,11 @@ class data_field_date extends data_field_base { } function display_browse_field($recordid, $template) { - global $CFG, $DB; - - if ($content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - return userdate($content, get_string('strftimedate'), 0); + $content = $this->get_data_content($recordid); + if (!$content || empty($content->content)) { + return ''; } + return userdate($content->content, get_string('strftimedate'), 0); } function get_sort_sql($fieldname) { diff --git a/mod/data/field/file/field.class.php b/mod/data/field/file/field.class.php index 508613cca0f..19f372cc51d 100644 --- a/mod/data/field/file/field.class.php +++ b/mod/data/field/file/field.class.php @@ -25,6 +25,23 @@ class data_field_file extends data_field_base { var $type = 'file'; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => 'samplefile.csv', + 'content1' => 'samplefile.csv', + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $DB, $OUTPUT, $PAGE; @@ -111,7 +128,7 @@ class data_field_file extends data_field_base { function get_file($recordid, $content=null) { global $DB; if (empty($content)) { - if (!$content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { + if (!$content = $this->get_data_content($recordid)) { return null; } } @@ -124,28 +141,48 @@ class data_field_file extends data_field_base { } function display_browse_field($recordid, $template) { - global $CFG, $DB, $OUTPUT; + global $OUTPUT; - if (!$content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { + $content = $this->get_data_content($recordid); + + if (!$content || empty($content->content)) { return ''; } - if (empty($content->content)) { - return ''; + $file = null; + $url = ''; + $name = !empty($content->content1) ? $content->content1 : $content->content; + + if ($this->preview) { + $file = (object)[ + 'filename' => $content->content, + 'mimetype' => 'text/csv', + ]; + $name = $content->content; + } else { + $file = $this->get_file($recordid, $content); + if (!$file) { + return ''; + } + $fileurl = moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + ); + $url = $fileurl->out(); } - if (!$file = $this->get_file($recordid, $content)) { - return ''; - } + $icon = $OUTPUT->pix_icon( + file_file_icon($file), + get_mimetype_description($file), + 'moodle', + ['width' => 16, 'height' => 16] + ); - $name = empty($content->content1) ? $file->get_filename() : $content->content1; - $src = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_data/content/'.$content->id.'/'.$file->get_filename()); - $width = $this->field->param1 ? ' width = "'.s($this->field->param1).'" ':' '; - $height = $this->field->param2 ? ' height = "'.s($this->field->param2).'" ':' '; - - $str = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('width' => 16, 'height' => 16)). ' '. - ''.s($name).''; - return $str; + return $icon . ' ' . s($name) . ''; } diff --git a/mod/data/field/latlong/field.class.php b/mod/data/field/latlong/field.class.php index c5c9b34556a..8595d2c12f7 100644 --- a/mod/data/field/latlong/field.class.php +++ b/mod/data/field/latlong/field.class.php @@ -43,6 +43,23 @@ class data_field_latlong extends data_field_base { ); // Other map sources listed at http://kvaleberg.com/extensions/mapsources/index.php?params=51_30.4167_N_0_7.65_W_region:earth + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => 41.391205, + 'content1' => 2.163873, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $CFG, $DB, $OUTPUT; @@ -145,70 +162,79 @@ class data_field_latlong extends data_field_base { } function display_browse_field($recordid, $template) { - global $CFG, $DB; - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - $lat = $content->content; - if (strlen($lat) < 1) { - return false; - } - $long = $content->content1; - if (strlen($long) < 1) { - return false; - } - // We use format_float to display in the regional format. - if($lat < 0) { - $compasslat = format_float(-$lat, 4) . '°S'; - } else { - $compasslat = format_float($lat, 4) . '°N'; - } - if($long < 0) { - $compasslong = format_float(-$long, 4) . '°W'; - } else { - $compasslong = format_float($long, 4) . '°E'; - } + global $CFG; - // Now let's create the jump-to-services link - $servicesshown = explode(',', $this->field->param1); - - // These are the different things that can be magically inserted into URL schemes - $urlreplacements = array( - '@lat@'=> $lat, - '@long@'=> $long, - '@wwwroot@'=> $CFG->wwwroot, - '@contentid@'=> $content->id, - '@dataid@'=> $this->data->id, - '@courseid@'=> $this->data->course, - '@fieldid@'=> $content->fieldid, - '@recordid@'=> $content->recordid, - ); - - if(sizeof($servicesshown)==1 && $servicesshown[0]) { - $str = " $compasslat $compasslong"; - } elseif (sizeof($servicesshown)>1) { - $str = '
'; - $str .= "$compasslat, $compasslong\n"; - $str .= ""; - $str .= ''; - $str .= '
'; - } else { - $str = "$compasslat, $compasslong"; - } - - return $str; + $content = $this->get_data_content($recordid); + if (!$content) { + return ''; } - return false; + + $lat = $content->content; + if (strlen($lat) < 1) { + return ''; + } + $long = $content->content1; + if (strlen($long) < 1) { + return ''; + } + // We use format_float to display in the regional format. + if ($lat < 0) { + $compasslat = format_float(-$lat, 4) . '°S'; + } else { + $compasslat = format_float($lat, 4) . '°N'; + } + if ($long < 0) { + $compasslong = format_float(-$long, 4) . '°W'; + } else { + $compasslong = format_float($long, 4) . '°E'; + } + + // Now let's create the jump-to-services link. + $servicesshown = explode(',', $this->field->param1); + + // These are the different things that can be magically inserted into URL schemes. + $urlreplacements = array( + '@lat@' => $lat, + '@long@' => $long, + '@wwwroot@' => $CFG->wwwroot, + '@contentid@' => $content->id, + '@dataid@' => $this->data->id, + '@courseid@' => $this->data->course, + '@fieldid@' => $content->fieldid, + '@recordid@' => $content->recordid, + ); + + if (count($servicesshown) == 1 && $servicesshown[0]) { + $str = " $compasslat $compasslong"; + } else if (count($servicesshown) > 1) { + $str = '
'; + $str .= "$compasslat, $compasslong\n"; + $str .= ""; + $str .= ''; + $str .= '
'; + } else { + $str = "$compasslat, $compasslong"; + } + + return $str; } function update_content_import($recordid, $value, $name='') { diff --git a/mod/data/field/menu/field.class.php b/mod/data/field/menu/field.class.php index 1463648c2de..7c1fe73a041 100644 --- a/mod/data/field/menu/field.class.php +++ b/mod/data/field/menu/field.class.php @@ -32,6 +32,26 @@ class data_field_menu extends data_field_base { */ protected static $priority = self::HIGH_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + $selected = $options[$recordid % count($options)]; + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => $selected, + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $DB, $OUTPUT; diff --git a/mod/data/field/multimenu/field.class.php b/mod/data/field/multimenu/field.class.php index e97cba1429e..f8490569cf2 100644 --- a/mod/data/field/multimenu/field.class.php +++ b/mod/data/field/multimenu/field.class.php @@ -32,6 +32,27 @@ class data_field_multimenu extends data_field_base { * */ protected static $priority = self::LOW_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + $selected = $options[$recordid % count($options)]; + $selected .= '##' . $options[($recordid + 1) % count($options)]; + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => $selected, + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $DB, $OUTPUT; @@ -250,28 +271,23 @@ class data_field_multimenu extends data_field_base { function display_browse_field($recordid, $template) { - global $DB; - - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - if (strval($content->content) === '') { - return false; - } - - $options = explode("\n",$this->field->param1); - $options = array_map('trim', $options); - - $contentArr = explode('##', $content->content); - $str = ''; - foreach ($contentArr as $line) { - if (!in_array($line, $options)) { - // hmm, looks like somebody edited the field definition - continue; - } - $str .= $line . "
\n"; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content || empty($content->content)) { + return ''; } - return false; + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + + $contentarray = explode('##', $content->content); + $str = ''; + foreach ($contentarray as $line) { + if (!in_array($line, $options)) { + // Hmm, looks like somebody edited the field definition. + continue; + } + $str .= $line . "
\n"; + } + return $str; } /** diff --git a/mod/data/field/number/field.class.php b/mod/data/field/number/field.class.php index d1922b0a7ac..df75243ad8f 100644 --- a/mod/data/field/number/field.class.php +++ b/mod/data/field/number/field.class.php @@ -25,6 +25,23 @@ class data_field_number extends data_field_base { var $type = 'number'; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => 1233 + $recordid, + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function update_content($recordid, $value, $name='') { global $DB; @@ -46,27 +63,21 @@ class data_field_number extends data_field_base { } function display_browse_field($recordid, $template) { - global $DB; - - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - if (strlen($content->content) < 1) { - return false; - } - $number = $content->content; - $decimals = trim($this->field->param1); - // only apply number formatting if param1 contains an integer number >= 0: - if (preg_match("/^\d+$/", $decimals)) { - $decimals = $decimals * 1; - // removes leading zeros (eg. '007' -> '7'; '00' -> '0') - $str = format_float($number, $decimals, true); - // For debugging only: -# $str .= " ($decimals)"; - } else { - $str = $number; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content || empty($content->content)) { + return ''; } - return false; + $number = $content->content; + $decimals = trim($this->field->param1); + // Only apply number formatting if param1 contains an integer number >= 0. + if (preg_match("/^\d+$/", $decimals)) { + $decimals = $decimals * 1; + // Removes leading zeros (eg. '007' -> '7'; '00' -> '0'). + $str = format_float($number, $decimals, true); + } else { + $str = $number; + } + return $str; } function display_search_field($value = '') { @@ -125,5 +136,3 @@ class data_field_number extends data_field_base { return $configs; } } - - diff --git a/mod/data/field/picture/field.class.php b/mod/data/field/picture/field.class.php index a132afcb2f5..2a432d8fec4 100644 --- a/mod/data/field/picture/field.class.php +++ b/mod/data/field/picture/field.class.php @@ -27,6 +27,23 @@ class data_field_picture extends data_field_base { var $previewwidth = 50; var $previewheight = 50; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => 'datafield_picture/preview', + 'content1' => get_string('sample', 'datafield_picture'), + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $CFG, $DB, $OUTPUT, $USER, $PAGE; @@ -158,32 +175,43 @@ class data_field_picture extends data_field_base { } function display_browse_field($recordid, $template) { - global $CFG, $DB; + global $OUTPUT; - if (!$content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - return false; - } + $content = $this->get_data_content($recordid); - if (empty($content->content)) { + if (!$content || empty($content->content)) { return ''; } $alt = $content->content1; $title = $alt; - if ($template == 'listtemplate') { - $src = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_data/content/'.$content->id.'/'.'thumb_'.$content->content); - // no need to add width/height, because the thumb is resized properly - $str = ''.s($alt).''; + $width = $this->field->param1 ? ' width="' . s($this->field->param1) . '" ' : ' '; + $height = $this->field->param2 ? ' height="' . s($this->field->param2) . '" ' : ' '; - } else { - $src = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_data/content/'.$content->id.'/'.$content->content); - $width = $this->field->param1 ? ' width="'.s($this->field->param1).'" ':' '; - $height = $this->field->param2 ? ' height="'.s($this->field->param2).'" ':' '; - $str = ''.s($alt).''; + if ($this->preview) { + $imgurl = $OUTPUT->image_url('sample', 'datafield_picture'); + return '' . s($alt) . ''; } - return $str; + if ($template == 'listtemplate') { + $filename = 'thumb_' . $content->content; + // Thumbnails are already converted to the correct width and height. + $width = ''; + $height = ''; + $url = new moodle_url('/mod/data/view.php', ['d' => $this->field->dataid, 'rid' => $recordid]); + } else { + $filename = $content->content; + $url = null; + } + $imgurl = moodle_url::make_pluginfile_url($this->context->id, 'mod_data', 'content', $content->id, '/', $filename); + + if (!$url) { + $url = $imgurl; + } + $img = '' . s($alt) .
+            ''; + return '' . $img . ''; } function update_field() { diff --git a/mod/data/field/picture/lang/en/datafield_picture.php b/mod/data/field/picture/lang/en/datafield_picture.php index 77144a62087..20a9ff77567 100644 --- a/mod/data/field/picture/lang/en/datafield_picture.php +++ b/mod/data/field/picture/lang/en/datafield_picture.php @@ -27,3 +27,4 @@ $string['pluginname'] = 'Image'; $string['fieldtypelabel'] = 'Image'; $string['privacy:metadata'] = 'The Image field component doesn\'t store any personal data; it uses tables defined in mod_data.'; +$string['sample'] = 'Image description sample'; diff --git a/mod/data/field/picture/pix/sample.png b/mod/data/field/picture/pix/sample.png new file mode 100644 index 00000000000..53edc9bcdca Binary files /dev/null and b/mod/data/field/picture/pix/sample.png differ diff --git a/mod/data/field/picture/pix/sample.svg b/mod/data/field/picture/pix/sample.svg new file mode 100644 index 00000000000..81919faf5d9 --- /dev/null +++ b/mod/data/field/picture/pix/sample.svg @@ -0,0 +1,42 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/mod/data/field/radiobutton/field.class.php b/mod/data/field/radiobutton/field.class.php index d0cf7ccb0c0..61577cbe5e9 100644 --- a/mod/data/field/radiobutton/field.class.php +++ b/mod/data/field/radiobutton/field.class.php @@ -32,6 +32,26 @@ class data_field_radiobutton extends data_field_base { */ protected static $priority = self::HIGH_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + $options = explode("\n", $this->field->param1); + $options = array_map('trim', $options); + $selected = $options[$recordid % count($options)]; + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => $selected, + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $CFG, $DB, $OUTPUT; @@ -153,4 +173,3 @@ class data_field_radiobutton extends data_field_base { return $configs; } } - diff --git a/mod/data/field/text/field.class.php b/mod/data/field/text/field.class.php index 5cebe66c85f..da86094637e 100644 --- a/mod/data/field/text/field.class.php +++ b/mod/data/field/text/field.class.php @@ -32,6 +32,23 @@ class data_field_text extends data_field_base { */ protected static $priority = self::MAX_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => get_string('sample', 'datafield_text'), + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_search_field($value = '') { return '' . ' 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => get_string('sample', 'datafield_textarea'), + 'content1' => 1, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + /** * Returns options for embedded files * @@ -255,23 +272,26 @@ class data_field_textarea extends data_field_base { * @return bool|string */ function display_browse_field($recordid, $template) { - global $DB; - - if ($content = $DB->get_record('data_content', array('fieldid' => $this->field->id, 'recordid' => $recordid))) { - if (isset($content->content)) { - $options = new stdClass(); - if ($this->field->param1 == '1') { // We are autolinking this field, so disable linking within us - $options->filter = false; - } - $options->para = false; - $str = file_rewrite_pluginfile_urls($content->content, 'pluginfile.php', $this->context->id, 'mod_data', 'content', $content->id, $this->get_options()); - $str = format_text($str, $content->content1, $options); - } else { - $str = ''; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content || !isset($content->content)) { + return ''; } - return false; + $options = new stdClass(); + if ($this->field->param1 == '1') { // We are autolinking this field, so disable linking within us. + $options->filter = false; + } + $options->para = false; + $str = file_rewrite_pluginfile_urls( + $content->content, + 'pluginfile.php', + $this->context->id, + 'mod_data', + 'content', + $content->id, + $this->get_options() + ); + $str = format_text($str, $content->content1, $options); + return '
' . $str . '
'; } /** diff --git a/mod/data/field/textarea/lang/en/datafield_textarea.php b/mod/data/field/textarea/lang/en/datafield_textarea.php index 3d08ba8a654..afe70be9ed8 100644 --- a/mod/data/field/textarea/lang/en/datafield_textarea.php +++ b/mod/data/field/textarea/lang/en/datafield_textarea.php @@ -29,3 +29,4 @@ $string['maxbytes_desc'] = 'If set to zero will be unlimited by default'; $string['pluginname'] = 'Text area'; $string['fieldtypelabel'] = 'Text area'; $string['privacy:metadata'] = 'The Text area field component doesn\'t store any personal data; it uses tables defined in mod_data.'; +$string['sample'] = '

This is a text area. It lets users enter long form text which spans over multiple lines.

You can choose the size of your text areas in rows and columns.

The text in a text area can be formatted, for example in bold, and also include hyperlinks or images.

'; diff --git a/mod/data/field/url/field.class.php b/mod/data/field/url/field.class.php index 13a5ea0a2cf..94527d2a4d4 100644 --- a/mod/data/field/url/field.class.php +++ b/mod/data/field/url/field.class.php @@ -31,6 +31,23 @@ class data_field_url extends data_field_base { */ protected static $priority = self::MIN_PRIORITY; + public function supports_preview(): bool { + return true; + } + + public function get_data_content_preview(int $recordid): stdClass { + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => 'https://example.com', + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + function display_add_field($recordid = 0, $formdata = null) { global $CFG, $DB, $OUTPUT, $PAGE; @@ -138,38 +155,39 @@ class data_field_url extends data_field_base { } function display_browse_field($recordid, $template) { - global $DB; - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - $url = empty($content->content)? '':$content->content; - $text = empty($content->content1)? '':$content->content1; - if (empty($url) or ($url == 'http://')) { - return ''; - } - if (!empty($this->field->param2)) { - // param2 forces the text to something - $text = $this->field->param2; - } - if ($this->field->param1) { - // param1 defines whether we want to autolink the url. - $attributes = array(); - if ($this->field->param3) { - // param3 defines whether this URL should open in a new window. - $attributes['target'] = '_blank'; - $attributes['rel'] = 'noreferrer'; - } - - if (empty($text)) { - $text = $url; - } - - $str = html_writer::link($url, $text, $attributes); - } else { - $str = $url; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content) { + return ''; } - return false; + + $url = empty($content->content) ? '' : $content->content; + $text = empty($content->content1) ? '' : $content->content1; + if (empty($url) || ($url == 'http://')) { + return ''; + } + if (!empty($this->field->param2)) { + // Param2 forces the text to something. + $text = $this->field->param2; + } + if ($this->field->param1) { + // Param1 defines whether we want to autolink the url. + $attributes = ['class' => 'data-field-link']; + if ($this->field->param3) { + // Param3 defines whether this URL should open in a new window. + $attributes['target'] = '_blank'; + $attributes['rel'] = 'noreferrer'; + } + + if (empty($text)) { + $text = $url; + } + + $str = html_writer::link($url, $text, $attributes); + } else { + $str = $url; + } + return $str; } function update_content_import($recordid, $value, $name='') { diff --git a/mod/data/js.php b/mod/data/js.php index 96e48559755..c02a1f8ed46 100644 --- a/mod/data/js.php +++ b/mod/data/js.php @@ -1,5 +1,4 @@ set_url('/mod/data/js.php', array('d'=>$d)); +$lifetime = 600; // Seconds to cache this stylesheet. +$url = new moodle_url('/mod/data/js.php'); -$lifetime = 600; // Seconds to cache this stylesheet +$manager = null; +if ($id) { + list($course, $cm) = get_course_and_cm_from_cmid($id, manager::MODULE); + $manager = manager::create_from_coursemodule($cm); + $instance = $manager->get_instance(); +} else { + // We must have the database activity id. + $d = required_param('d', PARAM_INT); + $instance = $DB->get_record('data', ['id' => $d], '*', MUST_EXIST); + $manager = manager::create_from_instance($instance); +} +$url->param('d', $instance->id); -if ($data = $DB->get_record('data', array('id'=>$d))) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT'); - header('Cache-control: max_age = '. $lifetime); - header('Pragma: '); - header('Content-type: application/javascript; charset=utf-8'); // Correct MIME type. +// Get the content. +if ($presetfullname) { + $url->param('preset', $presetfullname); + $preset = preset::create_from_fullname($manager, $presetfullname); + $content = $preset->get_template_content('jstemplate'); + $lifetime = 60; // Preset preview does not need a long cache. +} else { + $content = $instance->jstemplate; +} - echo $data->jstemplate; -} \ No newline at end of file +$PAGE->set_url($url); + +header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); +header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT'); +header('Cache-control: max_age = '. $lifetime); +header('Pragma: '); +header('Content-type: application/javascript; charset=utf-8'); // Correct MIME type. + +echo $content; diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index d68331a87e6..60aebc1ea88 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -297,6 +297,7 @@ $string['nofieldindatabase'] = 'There are no fields defined for this database.'; $string['nolisttemplate'] = 'List template is not yet defined'; $string['nomatch'] = 'No matching entries found!'; $string['nomaximum'] = 'No maximum'; +$string['nopreviewavailable'] = 'No preview available for {$a}'; $string['norecords'] = 'No entries yet'; $string['nosingletemplate'] = 'Single template is not yet defined'; $string['notapproved'] = 'Entry is not approved yet.'; @@ -327,6 +328,7 @@ $string['presetinfo'] = 'Saving as a preset will publish this template. Other us $string['presetnotselected'] = 'No preset has been selected.'; $string['presets'] = 'Presets'; $string['presetshelp'] = 'Choose a preset to use as a starting point.'; +$string['preview'] = 'Preview'; $string['privacy:metadata:commentpurpose'] = 'Comments on database records'; $string['privacy:metadata:data_content'] = 'Represents one answer to one field in database activity module'; $string['privacy:metadata:data_content:fieldid'] = 'Field definition ID'; @@ -382,7 +384,7 @@ $string['saveaspreset_help'] = 'The save as preset feature publishes the templat $string['savedataaspreset'] = 'Save all fields and templates as preset'; $string['saveaspresetmissingcapability'] = 'The user does not have permission to save the database as a preset.'; $string['savesettings'] = 'Save settings'; -$string['savesuccess'] = 'Preset saved.'; +$string['savesuccess'] = 'Preset saved. Preview preset'; $string['savetemplate'] = 'Save template'; $string['search'] = 'Search'; $string['search:activity'] = 'Database - activity information'; diff --git a/mod/data/lib.php b/mod/data/lib.php index 5651a12e018..de9e52ef913 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -1,5 +1,4 @@ field->name; } + /** + * Return if the field type supports preview. + * + * Fields without a preview cannot be displayed in the preset preview. + * + * @return bool if the plugin supports preview. + */ + public function supports_preview(): bool { + return false; + } + + /** + * Generate a fake data_content for this field to be used in preset previews. + * + * Data plugins must override this method and support_preview in order to enable + * preset preview for this field. + * + * @param int $recordid the fake record id + * @return stdClass the fake record + */ + public function get_data_content_preview(int $recordid): stdClass { + $message = get_string('nopreviewavailable', 'mod_data', $this->field->name); + return (object)[ + 'id' => 0, + 'fieldid' => $this->field->id, + 'recordid' => $recordid, + 'content' => "$message", + 'content1' => null, + 'content2' => null, + 'content3' => null, + 'content4' => null, + ]; + } + + /** + * Set the field to preview mode. + * + * @param bool $preview the new preview value + */ + public function set_preview(bool $preview) { + $this->preview = $preview; + } + + /** + * Get the field preview value. + * + * @return bool + */ + public function get_preview(): bool { + return $this->preview; + } + /** * This field just sets up a default field object @@ -377,6 +431,23 @@ class data_field_base { // Base class for Database Field Types (see field/*/ echo $OUTPUT->box_end(); } + /** + * Return the data_content of the field, or generate it if it is in preview mode. + * + * @param int $recordid the record id + * @return stdClass|bool the record data or false if none + */ + protected function get_data_content(int $recordid) { + global $DB; + if ($this->preview) { + return $this->get_data_content_preview($recordid); + } + return $DB->get_record( + 'data_content', + ['fieldid' => $this->field->id, 'recordid' => $recordid] + ); + } + /** * Display the content of the field in browse mode * @@ -387,23 +458,18 @@ class data_field_base { // Base class for Database Field Types (see field/*/ */ function display_browse_field($recordid, $template) { global $DB; - - if ($content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) { - if (isset($content->content)) { - $options = new stdClass(); - if ($this->field->param1 == '1') { // We are autolinking this field, so disable linking within us - //$content->content = ''.$content->content.''; - //$content->content1 = FORMAT_HTML; - $options->filter=false; - } - $options->para = false; - $str = format_text($content->content, $content->content1, $options); - } else { - $str = ''; - } - return $str; + $content = $this->get_data_content($recordid); + if (!$content || !isset($content->content)) { + return ''; } - return false; + $options = new stdClass(); + if ($this->field->param1 == '1') { + // We are autolinking this field, so disable linking within us. + $options->filter = false; + } + $options->para = false; + $str = format_text($content->content, $content->content1, $options); + return $str; } /** diff --git a/mod/data/preset.php b/mod/data/preset.php index 794941ad529..879310dc773 100644 --- a/mod/data/preset.php +++ b/mod/data/preset.php @@ -30,6 +30,8 @@ use mod_data\manager; use mod_data\preset; +use mod_data\output\action_bar; +use mod_data\output\preset_preview; require_once('../../config.php'); require_once($CFG->dirroot.'/mod/data/lib.php'); @@ -54,7 +56,7 @@ if ($id) { $action = optional_param('action', 'view', PARAM_ALPHA); // The page action. $allowedactions = ['view', 'import', 'importzip', 'finishimport', - 'export']; + 'export', 'preview']; if (!in_array($action, $allowedactions)) { throw new moodle_exception('invalidaccess'); } @@ -112,6 +114,28 @@ if ($formimportzip->is_cancelled()) { redirect(new moodle_url('/mod/data/preset.php', ['d' => $data->id])); } +// Preset preview injects CSS and JS to the page and should be done before the page header. +if ($action === 'preview') { + $fullname = optional_param('fullname', '', PARAM_PATH); // The directory the preset is in. + $templatename = optional_param('template', 'listtemplate', PARAM_ALPHA); + // Find out preset owner userid and shortname. + $preset = preset::create_from_fullname($manager, $fullname); + // Validate if the user can view this preset. + if (!$manager->can_view_preset($preset)) { + throw new \moodle_exception('cannotaccesspresentsother', manager::PLUGINNAME); + } + $preview = new preset_preview($manager, $preset, $templatename); + $preview->prepare_page($PAGE); + // Print the preview screen. + echo $OUTPUT->header(); + $actionbar = new action_bar($data->id, $url); + echo $actionbar->get_presets_preview_action_bar($manager, $fullname, $templatename); + echo $OUTPUT->heading(ucfirst($preset->name), 2, 'mb-4'); + echo $renderer->render($preview); + echo $OUTPUT->footer(); + exit(0); +} + echo $OUTPUT->header(); if ($formdata = $formimportzip->get_data()) { diff --git a/mod/data/preset/proposals/listtemplate.html b/mod/data/preset/proposals/listtemplate.html index 857500d6117..1e8a5cb25ee 100644 --- a/mod/data/preset/proposals/listtemplate.html +++ b/mod/data/preset/proposals/listtemplate.html @@ -17,7 +17,7 @@
diff --git a/mod/data/preset_form.php b/mod/data/preset_form.php index 03ec52a9858..d0dd0e78de3 100644 --- a/mod/data/preset_form.php +++ b/mod/data/preset_form.php @@ -1,4 +1,26 @@ . + +/** + * Database module preset forms. + * + * @package mod_data + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden!'); diff --git a/mod/data/styles.css b/mod/data/styles.css index 70a9e18a8fe..6eae7625943 100644 --- a/mod/data/styles.css +++ b/mod/data/styles.css @@ -149,3 +149,19 @@ .entry-actionsmenu .dropdown-toggle::after { display: none; } + +/* Preset preview styles */ +#page-mod-data-preset .nopreview { + border: 1px solid var(--secondary); + padding: 0.2rem; + border-radius: 5px; +} +/* Disable links and buttons in the preset preview */ +.template-preview-content .preview-disabled, +.template-preview-content .action-icon, +.template-preview-content .dropdown-menu a, +.template-preview-content .data-field-link, +.template-preview-content .data-field-html a, +.template-preview-content .data-field-html button { + pointer-events: none; +} diff --git a/mod/data/templates/action_bar.mustache b/mod/data/templates/action_bar.mustache new file mode 100644 index 00000000000..70f94b45f33 --- /dev/null +++ b/mod/data/templates/action_bar.mustache @@ -0,0 +1,117 @@ +{{! + 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/action_bar + + General actions bar at the top of the pages in the database activity. + + Context variables required for this template: + * d - The database instance id. + * urlselect - The data object containing the required properties to render core/url_select. + * fieldselect - The data object containing the required properties to render core/single_select. + * saveaspreset - Whether to display the save as preset button (mod_data/save_as_preset). + * exportpreset - The data object containing the required properties to render core/single_button. + + Example context (json): + { + "d": 1, + "hasback": true, + "backurl": "https://example.com", + "backtitle": "Back", + "title": "Some title", + "urlselect": { + "id": "url_select_test", + "action": "https://example.com/post", + "formid": "url_select_form", + "sesskey": "sesskey", + "classes": "urlselect", + "label": "", + "helpicon": false, + "showbutton": null, + "options": [ + { + "name": "Some name", + "value": "/mod/data/someurl.php", + "selected": false + } + ], + "disabled": false, + "title": null + }, + "extraurlselect": { + "id": "url_select_test", + "action": "https://example.com/post", + "formid": "url_select_form", + "sesskey": "sesskey", + "classes": "urlselect", + "label": "", + "helpicon": false, + "showbutton": null, + "options": [ + { + "name": "Some name", + "value": "/mod/data/someurl.php", + "selected": false + } + ], + "disabled": false, + "title": null + }, + "buttons": { + "id": "single_button_test", + "method" : "post", + "formid": "single_button_form", + "url" : "https://example.com/post", + "primary" : false, + "tooltip" : null, + "label" : "Label", + "attributes": [] + } + } +}} +
+
+
+ {{#hasback}} + + {{/hasback}} + {{#urlselect}} + + {{/urlselect}} + {{#title}} + + {{/title}} +
+
+ {{#extraurlselect}} + + {{/extraurlselect}} + {{#buttons}} + + {{/buttons}} +
+
+
diff --git a/mod/data/templates/preset_preview.mustache b/mod/data/templates/preset_preview.mustache new file mode 100644 index 00000000000..1ef948d6c29 --- /dev/null +++ b/mod/data/templates/preset_preview.mustache @@ -0,0 +1,43 @@ +{{! + 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/preset_preview + + The preset preview. + + Example context (json): + { + "formactionurl": "#", + "d": "42", + "description": "Preset description", + "preview": "

The preset preview HTML

", + "userid": "0", + "shortname": "imagegallery" + } +}} +
+
+ {{description}} +
+
+ {{{preview}}} +
+
+
+ + + + + +
diff --git a/mod/data/templates/presets.mustache b/mod/data/templates/presets.mustache index 39f0553a04c..ccc9f5fcdd5 100644 --- a/mod/data/templates/presets.mustache +++ b/mod/data/templates/presets.mustache @@ -17,13 +17,13 @@ Form containing all database presets displayed within a table. Context variables required for this template: - * formactionul - The form action url. + * formactionurl - The form action url. * d - The database id. * presets - List of presets containing id, name, fullname, shortname and actions. Example context (json): { - "formactionul": "http://www.example.com", + "formactionurl": "http://www.example.com", "d": 1, "presets": [ { @@ -33,6 +33,7 @@ "fullname": "Image gallery", "description": "Use this preset to collect images", "userid": 0, + "url": "http://www.example.com", "actions": [] }, { @@ -41,6 +42,7 @@ "shortname": "Preset saved manually", "fullname": "Preset saved manually (admin)", "userid": 2, + "url": "http://www.example.com", "actions": [] } ] @@ -48,7 +50,7 @@ }} {{#str}}presetshelp, mod_data{{/str}} -
+ @@ -68,7 +70,9 @@ - {{fullname}} + + {{fullname}} + {{description}} {{#actions}} diff --git a/mod/data/templates/presets_action_bar.mustache b/mod/data/templates/presets_action_bar.mustache index f7a93a979f8..124aae6eaae 100644 --- a/mod/data/templates/presets_action_bar.mustache +++ b/mod/data/templates/presets_action_bar.mustache @@ -27,7 +27,7 @@ diff --git a/mod/data/tests/behat/data_presets.feature b/mod/data/tests/behat/data_presets.feature index a70c936c6dc..d0a24caf039 100644 --- a/mod/data/tests/behat/data_presets.feature +++ b/mod/data/tests/behat/data_presets.feature @@ -260,3 +260,30 @@ Feature: Users can view and manage data presets And I click on "Delete" "button" in the "Delete preset Saved preset by teacher1?" "dialogue" And I should see "Preset deleted" And I should not see "Saved preset by teacher1" + + @javascript + Scenario: Teachers can preview a saved preset from the notification + Given the following "mod_data > fields" exist: + | database | type | name | description | + | data1 | text | Test field name | Test field description | + And the following "mod_data > templates" exist: + | database | name | + | data1 | singletemplate | + | data1 | listtemplate | + | data1 | addtemplate | + | data1 | asearchtemplate | + | data1 | rsstemplate | + And I am on the "Mountain landscapes" "data activity" page logged in as teacher1 + And I follow "Templates" + And I click on "Save as preset" "button" + And I set the field "Name" to "New saved preset" + And I set the field "Description" to "My funny description goes here." + And I click on "Save" "button" in the "Save all fields and templates as preset" "dialogue" + And I should see "Preset saved" + When I click on "Preview preset" "link" + Then I should see "Preview" + And I should see "New saved preset" + And I should see "My funny description goes here" + And I should see "Test field name" + And I should see "This is a short text" + Then "Use preset" "button" should exist diff --git a/mod/data/tests/behat/preview_preset.feature b/mod/data/tests/behat/preview_preset.feature new file mode 100644 index 00000000000..3b7e576ecdf --- /dev/null +++ b/mod/data/tests/behat/preview_preset.feature @@ -0,0 +1,164 @@ +@mod @mod_data +Feature: Users can preview presets + In order to find the preset I am looking for + As a teacher + I need to preview the database activity presets + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "tags" exist: + | name | isstandard | + | Tag1 | 1 | + 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 I am on the "Test database name" "data activity" page logged in as teacher1 + + @javascript @_file_upload + Scenario: Preview a user preset as list view template in database + Given I follow "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" + And I set the field "Description" to "Behat test preset" + And I click on "Save" "button" in the "Save all fields and templates as preset" "dialogue" + When I follow "Presets" + And I click on "Saved preset by teacher1" "link" + # Check list template preview fields. + Then I should see "Behat test preset" in the "template-preview" "region" + And I should see "List header" in the "template-preview" "region" + And I should see "List footer" in the "template-preview" "region" + And I should see "List template content" in the "template-preview" "region" + And I should see "My text field" in the "template-preview" "region" + And I should see "This is a short text" in the "template-preview" "region" + And I should see "My multiple selection" in the "template-preview" "region" + And I should see "Multi 1" in the "template-preview" "region" + And I should see "My date field" in the "template-preview" "region" + And I should see "My checkbox field" in the "template-preview" "region" + And I should see "Check 2" in the "template-preview" "region" + And I should see "My geo field" in the "template-preview" "region" + And I should see "41.3912°N 2.1639°E" in the "template-preview" "region" + And I should see "My menu field" in the "template-preview" "region" + And I should see "Menu 2" in the "template-preview" "region" + And I should see "My number field" in the "template-preview" "region" + And I should see "1234" in the "template-preview" "region" + And I should see "My radio field" in the "template-preview" "region" + And I should see "Radio 2" in the "template-preview" "region" + And I should see "My text area field" in the "template-preview" "region" + And I should see "This is a text area" in the "template-preview" "region" + And I should see "My URL field" in the "template-preview" "region" + And I should see "https://example.com" in the "template-preview" "region" + And I should see "My file field" in the "template-preview" "region" + And "Comma-separated values" "icon" should exist in the "template-preview" "region" + And I should see "samplefile.csv" in the "template-preview" "region" + And I should see "My picture field" in the "template-preview" "region" + # Test CSS and JS templates. + And I should not see "This content should not be displayed" in the "template-preview" "region" + And I should not see "This text should change" in the "template-preview" "region" + And I should see "New value" in the "template-preview" "region" + + @javascript @_file_upload + Scenario: Preview a user preset as single view template in database + Given I follow "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" + And I set the field "Description" to "Behat test preset" + And I click on "Save" "button" in the "Save all fields and templates as preset" "dialogue" + When I follow "Presets" + And I click on "Saved preset by teacher1" "link" + And I set the field "Templates tertiary navigation" to "Single template" + # Check single template preview fields. + Then I should see "Behat test preset" in the "template-preview" "region" + And I should see "Single template content" in the "template-preview" "region" + And I should see "My text field" in the "template-preview" "region" + And I should see "This is a short text" in the "template-preview" "region" + And I should see "My multiple selection" in the "template-preview" "region" + And I should see "Multi 2" in the "template-preview" "region" + And I should see "My date field" in the "template-preview" "region" + And I should see "My checkbox field" in the "template-preview" "region" + And I should see "Check 2" in the "template-preview" "region" + And I should see "My geo field" in the "template-preview" "region" + And I should see "41.3912°N 2.1639°E" in the "template-preview" "region" + And I should see "My menu field" in the "template-preview" "region" + And I should see "Menu 2" in the "template-preview" "region" + And I should see "My number field" in the "template-preview" "region" + And I should see "1234" in the "template-preview" "region" + And I should see "My radio field" in the "template-preview" "region" + And I should see "Radio 2" in the "template-preview" "region" + And I should see "My text area field" in the "template-preview" "region" + And I should see "This is a text area" in the "template-preview" "region" + And I should see "My URL field" in the "template-preview" "region" + And I should see "https://example.com" in the "template-preview" "region" + And I should see "My file field" in the "template-preview" "region" + And "Comma-separated values" "icon" should exist in the "template-preview" "region" + And I should see "samplefile.csv" in the "template-preview" "region" + And I should see "My picture field" in the "template-preview" "region" + # Test CSS and JS templates. + And I should not see "This content should not be displayed" in the "template-preview" "region" + And I should not see "This text should change" in the "template-preview" "region" + And I should see "New value" in the "template-preview" "region" + + @javascript + Scenario: Preview a plugin preset in database + Given I follow "Presets" + When I click on "Journal" "link" + Then I should see "Use this preset for a journal, diary, reflections tool or research log." in the "template-preview" "region" + And I should see "This is a short text" + And I should see "This is a text area" + And I select "Single template" from the "Templates tertiary navigation" singleselect + And I should see "Use this preset for a journal, diary, reflections tool or research log." in the "template-preview" "region" + And I should see "This is a short text" + And I should see "This is a text area" + And I should see "This is a short text" in the "template-preview" "region" + + @javascript + Scenario: Use back button to return to the presets page in database + Given I follow "Presets" + And I click on "Image gallery" "link" + And I should see "Use this preset to collect images." in the "template-preview" "region" + When I click on "Back" "button" + Then I should see "Choose a preset to use as a starting point." + + @javascript + Scenario: Apply plugin preset from preview in database + 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" + And I should see "title" + + @javascript @_file_upload + Scenario: Apply user preset from preview in database + Given I follow "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" + And I set the field "Description" to "Behat test preset" + And I click on "Save" "button" in the "Save all fields and templates as preset" "dialogue" + 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" diff --git a/mod/data/tests/fixtures/behat_preset.zip b/mod/data/tests/fixtures/behat_preset.zip new file mode 100644 index 00000000000..b8296e11138 Binary files /dev/null and b/mod/data/tests/fixtures/behat_preset.zip differ diff --git a/mod/data/tests/manager_test.php b/mod/data/tests/manager_test.php index 4a977d42136..962c6185e12 100644 --- a/mod/data/tests/manager_test.php +++ b/mod/data/tests/manager_test.php @@ -399,4 +399,149 @@ class manager_test extends \advanced_testcase { $preset = reset($presets); $this->assertEquals($teacherpreset->name, $preset->name); } + + /** + * Test for can_view_preset(). + * + * @covers ::can_view_preset + * @dataProvider can_view_preset_provider + * @param string $rolename the user role name + * @param bool $ownpreset if the preset belongs to the user + * @param bool|null $useridparam if the method should be called with a user id param + * @param bool $plugin if the preset is a plugin or not + * @param bool $expected the expected result + */ + public function test_can_view_preset(string $rolename, bool $ownpreset, ?bool $useridparam, bool $plugin, bool $expected) { + + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_and_enrol($course, $rolename); + $activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]); + $cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST); + $manager = manager::create_from_coursemodule($cm); + + // Create preset. + if ($ownpreset) { + $this->setUser($user); + } else { + $this->setAdminUser(); + } + + if ($plugin) { + $preset = preset::create_from_plugin($manager, 'imagegallery'); + } else { + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $preset = $plugingenerator->create_preset($activity, (object)['name' => 'Preset name']); + } + + // Setup user param. + if ($useridparam) { + // Login as a different user to validate the userid param is working. + $otheruser = $this->getDataGenerator()->create_user(); + $this->setUser($otheruser); + $useridparam = $user->id; + } else { + $this->setUser($user); + } + + $result = $manager->can_view_preset($preset, $useridparam); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for test_can_view_preset. + * + * @return array + */ + public function can_view_preset_provider(): array { + return [ + // User presets. + 'Teacher owned preset without user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => true, + 'useridparam' => null, + 'plugin' => false, + 'expected' => true, + ], + 'Teacher owned preset with user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => true, + 'useridparam' => true, + 'plugin' => false, + 'expected' => true, + ], + 'Teacher not owned preset without user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => false, + 'useridparam' => null, + 'plugin' => false, + 'expected' => true, + ], + 'Teacher not owned preset with user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => false, + 'useridparam' => true, + 'plugin' => false, + 'expected' => true, + ], + 'Student owned preset without user id param' => [ + 'rolename' => 'student', + 'ownpreset' => true, + 'useridparam' => null, + 'plugin' => false, + 'expected' => true, + ], + 'Student owned preset with user id param' => [ + 'rolename' => 'student', + 'ownpreset' => true, + 'useridparam' => true, + 'plugin' => false, + 'expected' => true, + ], + 'Student not owned preset without user id param' => [ + 'rolename' => 'student', + 'ownpreset' => false, + 'useridparam' => null, + 'plugin' => false, + 'expected' => false, + ], + 'Student not owned preset with user id param' => [ + 'rolename' => 'student', + 'ownpreset' => false, + 'useridparam' => true, + 'plugin' => false, + 'expected' => false, + ], + // Plugin presets. + 'Teacher plugin preset without user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => false, + 'useridparam' => null, + 'plugin' => true, + 'expected' => true, + ], + 'Teacher plugin preset with user id param' => [ + 'rolename' => 'editingteacher', + 'ownpreset' => false, + 'useridparam' => true, + 'plugin' => true, + 'expected' => true, + ], + 'Student plugin preset without user id param' => [ + 'rolename' => 'student', + 'ownpreset' => false, + 'useridparam' => null, + 'plugin' => true, + 'expected' => true, + ], + 'Student plugin preset with user id param' => [ + 'rolename' => 'student', + 'ownpreset' => false, + 'useridparam' => true, + 'plugin' => true, + 'expected' => true, + ], + ]; + } } diff --git a/mod/data/tests/preset_test.php b/mod/data/tests/preset_test.php index 5d2e031caf4..72b8df62257 100644 --- a/mod/data/tests/preset_test.php +++ b/mod/data/tests/preset_test.php @@ -156,6 +156,53 @@ class preset_test extends \advanced_testcase { $this->assertEquals('/' . $presetname . '/', $result->get_path()); } + /** + * Test for static create_from_fullname method. + * + * @covers ::create_from_fullname + */ + public function test_create_from_fullname() { + $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 a saved preset. + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = (object) [ + 'name' => 'Testing preset name', + 'description' => 'Testing preset description', + ]; + $savedpreset = $plugingenerator->create_preset($activity, $record); + + // Check instantiate from plugin. + $pluginname = 'imagegallery'; + $fullname = '0/imagegallery'; + $result = preset::create_from_fullname($manager, $fullname); + $this->assertTrue($result->isplugin); + $this->assertEquals(get_string('modulename', "datapreset_$pluginname"), $result->name); + $this->assertEquals($pluginname, $result->shortname); + $this->assertEquals(get_string('modulename_help', "datapreset_$pluginname"), $result->description); + $this->assertEmpty($result->get_userid()); + $this->assertEmpty($result->storedfile); + $this->assertNull($result->get_path()); + + // Check instantiate from user preset + // Check create_from_instance is working as expected when a preset with this name exists. + $fullname = $savedpreset->get_userid() . '/' . $savedpreset->name; + $result = preset::create_from_fullname($manager, $fullname); + $this->assertFalse($result->isplugin); + $this->assertEquals($savedpreset->name, $result->name); + $this->assertEquals($savedpreset->shortname, $result->shortname); + $this->assertEquals($savedpreset->description, $savedpreset->description); + $this->assertEquals($savedpreset->storedfile->get_userid(), $result->get_userid()); + $this->assertNotEmpty($result->storedfile); + $this->assertEquals('/' . $savedpreset->name . '/', $result->get_path()); + } + /** * Test for the save a preset method when the preset hasn't been saved before. * @@ -726,4 +773,188 @@ class preset_test extends \advanced_testcase { $currentpresets = $manager->get_available_presets(); $this->assertEquals(count($initialpresets) - 1, count($currentpresets)); } + + /** + * Test for the get_fields method. + * + * @covers ::get_fields + */ + public function test_get_fields() { + $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); + + // Add a field to the activity. + $fieldrecord = new stdClass(); + $fieldrecord->name = 'field-1'; + $fieldrecord->type = 'text'; + $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $datagenerator->create_field($fieldrecord, $activity); + + // Create a saved preset. + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = (object) [ + 'name' => 'Testing preset name', + 'description' => 'Testing preset description', + ]; + $preset = $plugingenerator->create_preset($activity, $record); + + // Check regular fields. + $fields = $preset->get_fields(); + $this->assertCount(1, $fields); + $this->assertArrayHasKey('field-1', $fields); + $field = $fields['field-1']; + $this->assertEquals('text', $field->type); + $this->assertEquals('field-1', $field->get_name()); + $this->assertEquals(false, $field->get_preview()); + + // Check preview fields. + $savedpresets = $manager->get_available_saved_presets(); + $preset = reset($savedpresets); + $fields = $preset->get_fields(true); + $this->assertCount(1, $fields); + $this->assertArrayHasKey('field-1', $fields); + $field = $fields['field-1']; + $this->assertEquals('text', $field->type); + $this->assertEquals('field-1', $field->get_name()); + $this->assertEquals(true, $field->get_preview()); + } + + /** + * Test for the get_sample_entries method. + * + * @covers ::get_sample_entries + */ + public function test_get_sample_entries() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + // 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); + + // Add a field to the activity. + $fieldrecord = new stdClass(); + $fieldrecord->name = 'field-1'; + $fieldrecord->type = 'text'; + $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $datagenerator->create_field($fieldrecord, $activity); + + // Create a saved preset. + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = (object) [ + 'name' => 'Testing preset name', + 'description' => 'Testing preset description', + ]; + $preset = $plugingenerator->create_preset($activity, $record); + + $entries = $preset->get_sample_entries(3); + $this->assertCount(3, $entries); + foreach ($entries as $entry) { + $this->assertEquals($user->id, $entry->userid); + $this->assertEquals($user->email, $entry->email); + $this->assertEquals($user->firstname, $entry->firstname); + $this->assertEquals($user->lastname, $entry->lastname); + $this->assertEquals($activity->id, $entry->dataid); + $this->assertEquals(0, $entry->groupid); + $this->assertEquals(1, $entry->approved); + } + } + + /** + * Test for the get_template_content method. + * + * @covers ::get_template_content + */ + public function test_get_template_content() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $course = $this->getDataGenerator()->create_course(); + + // Module data with templates. + $templates = [ + 'singletemplate' => 'Single template content', + 'listtemplate' => 'List template content', + 'listtemplateheader' => 'List template content header', + 'listtemplatefooter' => 'List template content footer', + 'addtemplate' => 'Add template content', + 'rsstemplate' => 'RSS template content', + 'rsstitletemplate' => 'RSS title template content', + 'csstemplate' => 'CSS template content', + 'jstemplate' => 'JS template content', + 'asearchtemplate' => 'Advanced search template content', + ]; + $params = array_merge(['course' => $course], $templates); + + // Create a database activity. + $activity = $this->getDataGenerator()->create_module(manager::MODULE, $params); + $manager = manager::create_from_instance($activity); + + // Create a saved preset. + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = (object) [ + 'name' => 'Testing preset name', + 'description' => 'Testing preset description', + ]; + $preset = $plugingenerator->create_preset($activity, $record); + + // Test user preset templates. + foreach ($templates as $templatename => $templatecontent) { + $content = $preset->get_template_content($templatename); + $this->assertEquals($templatecontent, $content); + } + + // Test plugin preset content. + $pluginname = 'imagegallery'; + $preset = preset::create_from_plugin($manager, $pluginname); + foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) { + // Get real file contents. + $path = $manager->path . '/preset/' . $pluginname . '/' . $templatefile; + $templatecontent = file_get_contents($path); + $content = $preset->get_template_content($templatename); + $this->assertEquals($templatecontent, $content); + } + } + + /** + * Test for the get_fullname method. + * + * @covers ::get_fullname + */ + public function test_get_fullname() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $course = $this->getDataGenerator()->create_course(); + + // Create a database activity. + $activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]); + $manager = manager::create_from_instance($activity); + + // Create a saved preset. + $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = (object) [ + 'name' => 'Testing preset name', + 'description' => 'Testing preset description', + ]; + $preset = $plugingenerator->create_preset($activity, $record); + + // Test user preset templates. + $this->assertEquals("{$user->id}/Testing preset name", $preset->get_fullname()); + + // Test plugin preset content. + $pluginname = 'imagegallery'; + $preset = preset::create_from_plugin($manager, $pluginname); + $this->assertEquals("0/imagegallery", $preset->get_fullname()); + } }