From 776e48748f0ceccab1120dd2aac5fe827c3ba3d7 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 8 Jul 2022 18:07:38 +0200 Subject: [PATCH] MDL-75146 mod_data: refactor template parser --- mod/data/classes/external.php | 23 +- mod/data/classes/manager.php | 8 +- mod/data/classes/template.php | 598 ++++++++++++++++++++++++++++ mod/data/lib.php | 300 +++----------- mod/data/rsslib.php | 13 +- mod/data/tests/template_test.php | 656 +++++++++++++++++++++++++++++++ mod/data/upgrade.txt | 2 + mod/data/view.php | 23 +- 8 files changed, 1357 insertions(+), 266 deletions(-) create mode 100644 mod/data/classes/template.php create mode 100644 mod/data/tests/template_test.php diff --git a/mod/data/classes/external.php b/mod/data/classes/external.php index d1ff30159be..9e65b35192f 100644 --- a/mod/data/classes/external.php +++ b/mod/data/classes/external.php @@ -407,6 +407,8 @@ class mod_data_external extends external_api { } } + $manager = manager::create_from_instance($database); + list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) = data_search_entries($database, $cm, $context, 'list', $groupid, '', $params['sort'], $params['order'], $params['page'], $params['perpage']); @@ -449,11 +451,8 @@ class mod_data_external extends external_api { // Check if we should return the list rendered. if ($params['returncontents']) { - ob_start(); - // The return parameter stops the execution after the first record. - data_print_template('listtemplate', $records, $database, '', $page, false); - $result['listviewcontents'] = ob_get_contents(); - ob_end_clean(); + $parser = $manager->get_template('listtemplate', ['page' => $page]); + $result['listviewcontents'] = $parser->parse_entries($records); } return $result; @@ -518,6 +517,8 @@ class mod_data_external extends external_api { $canmanageentries = has_capability('mod/data:manageentries', $context); data_require_time_available($database, $canmanageentries); + $manager = manager::create_from_instance($database); + if ($record->groupid != 0) { if (!groups_group_visible($record->groupid, $course, $cm)) { throw new moodle_exception('notingroup'); @@ -546,7 +547,8 @@ class mod_data_external extends external_api { // Check if we should return the entry rendered. if ($params['returncontents']) { $records = [$record]; - $result['entryviewcontents'] = data_print_template('singletemplate', $records, $database, '', 0, true); + $parser = $manager->get_template('singletemplate'); + $result['entryviewcontents'] = $parser->parse_entries($records); } return $result; @@ -717,6 +719,8 @@ class mod_data_external extends external_api { // Check database is open in time. data_require_time_available($database, null, $context); + $manager = manager::create_from_instance($database); + if (!empty($params['groupid'])) { $groupid = $params['groupid']; // Determine is the group is visible to user. @@ -782,11 +786,8 @@ class mod_data_external extends external_api { // Check if we should return the list rendered. if ($params['returncontents']) { - ob_start(); - // The return parameter stops the execution after the first record. - data_print_template('listtemplate', $records, $database, '', $page, false); - $result['listviewcontents'] = ob_get_contents(); - ob_end_clean(); + $parser = $manager->get_template('listtemplate', ['page' => $page]); + $result['listviewcontents'] = $parser->parse_entries($records); } return $result; diff --git a/mod/data/classes/manager.php b/mod/data/classes/manager.php index 85a98f0afa2..1fead13611e 100644 --- a/mod/data/classes/manager.php +++ b/mod/data/classes/manager.php @@ -253,16 +253,16 @@ class manager { $templatename == 'singletemplate'; } $instance = $this->instance; - $templatestr = $instance->{$templatename} ?? ''; - if (empty($templatestr)) { - $templatestr = data_generate_default_template($instance, $templatename, 0, false, false); + $templatecontent = $instance->{$templatename} ?? ''; + if (empty($templatecontent)) { + $templatecontent = 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); + return new template($this, $templatecontent, $options); } /** diff --git a/mod/data/classes/template.php b/mod/data/classes/template.php new file mode 100644 index 00000000000..7516e3627ff --- /dev/null +++ b/mod/data/classes/template.php @@ -0,0 +1,598 @@ +. + +namespace mod_data; + +use core\output\checkbox_toggleall; +use html_writer; +use mod_data\manager; +use moodle_url; +use pix_icon; +use stdClass; +use user_picture; +use core_user; +use portfolio_add_button; +use data_portfolio_caller; +use comment; +use core_tag_tag; + +/** + * Class template for database activity + * + * @package mod_data + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class template { + + /** @var manager the current instance manager. */ + private $manager; + + /** @var stdClass the current instance record. */ + private $instance; + + /** @var string the template. */ + private $templatecontent; + + /** @var moodle_url the base url. */ + private $baseurl; + + /** @var string the current search if any. */ + private $search; + + /** @var bool if ratings must be added. */ + private $ratings; + + /** @var bool if comments must be added if not present in the template. */ + private $forcecomments; + + /** @var bool if the current user can manage entries. */ + private $canmanageentries = null; + + /** @var array if icons HTML. */ + private $icons = []; + + /** @var array All template tags (calculated in load_template_tags). */ + protected $tags = []; + + /** + * Class contructor. + * + * See the add_options method for the available display options. + * + * @param manager $manager the current instance manager + * @param string $templatecontent the template string to use + * @param array $options an array of extra diplay options + */ + public function __construct(manager $manager, string $templatecontent, array $options = []) { + $this->manager = $manager; + $this->instance = $manager->get_instance(); + $this->templatecontent = $templatecontent; + + $context = $manager->get_context(); + $this->canmanageentries = has_capability('mod/data:manageentries', $context); + $this->icons = $this->get_icons(); + $this->add_options($options); + $this->load_template_tags($templatecontent); + } + + /** + * Add extra display options. + * + * The extra options are: + * - page: the current pagination page + * - search: the current search text + * - baseurl: an alternative entry url (moodle_url) + * - comments: if comments must be added if not present + * - ratings: if ratings must be added + * + * @param array $options the array of options. + */ + public function add_options(array $options = []) { + $cm = $this->manager->get_coursemodule(); + $baseurl = $options['baseurl'] ?? new moodle_url('/mod/data/view.php', ['id' => $cm->id]); + if (isset($options['page'])) { + $baseurl->params([ + 'page' => $options['page'], + ]); + } + $this->baseurl = $baseurl; + + // Save options. + $this->search = $options['search'] ?? null; + $this->ratings = $options['ratings'] ?? false; + $this->forcecomments = $options['comments'] ?? false; + } + + /** + * Scan the template tags. + * + * This method detects which tags are used in this template and store them + * in the $this->tags attribute. This attribute will be used to determine + * which replacements needs to be calculated. + * + * @param string $templatecontent the current template + */ + protected function load_template_tags(string $templatecontent) { + // Detect action tags. + $pattern = '/##(?P\w+?)##/'; + $matches = []; + preg_match_all($pattern, $templatecontent, $matches); + if (!isset($matches['tags']) || empty($matches['tags'])) { + return; + } + $this->tags = $matches['tags']; + } + + /** + * Generate the list of action icons. + * + * @return pix_icon[] icon name => pix_icon + */ + protected function get_icons() { + $attrs = ['class' => 'iconsmall']; + return [ + 'edit' => new pix_icon('t/edit', get_string('edit'), '', $attrs), + 'delete' => new pix_icon('t/delete', get_string('delete'), '', $attrs), + 'more' => new pix_icon('t/preview', get_string('more', 'data'), '', $attrs), + 'approve' => new pix_icon('t/approve', get_string('approve', 'data'), '', $attrs), + 'disapprove' => new pix_icon('t/block', get_string('disapprove', 'data'), '', $attrs), + ]; + } + + /** + * Return the parsed entry using a template. + * + * This method apply a template replacing all necessary tags. + * + * @param array $entries of entres to parse + * @return string the entries outputs using the template + */ + public function parse_entries(array $entries): string { + if (empty($entries)) { + return ''; + } + $result = ''; + foreach ($entries as $entry) { + $result .= $this->parse_entry($entry); + } + return $result; + } + + /** + * Parse a single entry. + * + * @param stdClass $entry the entry to parse + * @return string the parsed entry + */ + private function parse_entry(stdClass $entry): string { + if (empty($this->templatecontent)) { + return ''; + } + $context = $this->manager->get_context(); + $canmanageentry = data_user_can_manage_entry($entry, $this->instance, $context); + + // Load all replacements for the entry. + $fields = $this->get_fields_replacements($entry); + $tags = $this->get_tags_replacements($entry, $canmanageentry); + $replacements = array_merge($fields, $tags); + + $patterns = array_keys($replacements); + $replacement = array_values($replacements); + $result = str_ireplace($patterns, $replacement, $this->templatecontent); + + return $this->post_parse($result, $entry); + } + + /** + * Get all field replacements. + * + * @param stdClass $entry the entry object + * @return array of pattern => replacement + */ + private function get_fields_replacements(stdClass $entry): array { + $result = []; + $fields = $this->manager->get_fields(); + foreach ($fields as $field) { + // Field value. + $pattern = '[[' . $field->field->name . ']]'; + $result[$pattern] = highlight( + $this->search, + $field->display_browse_field($entry->id, $this->templatecontent) + ); + // Field id. + $pattern = '[[' . $field->field->name . '#id]]'; + $result[$pattern] = $field->field->id; + } + return $result; + } + + /** + * Get all standard tags replacements. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return array of pattern => replacement + */ + private function get_tags_replacements(stdClass $entry, bool $canmanageentry): array { + $result = []; + foreach ($this->tags as $tagname) { + $methodname = "get_tag_{$tagname}_replacement"; + if (method_exists($this, $methodname)) { + $pattern = "##$tagname##"; + $replacement = $this->$methodname($entry, $canmanageentry); + $result[$pattern] = $replacement; + } + } + return $result; + } + + /** + * Add any extra information to the parsed entry. + * + * @param string $result the parsed template with the entry data + * @param stdClass $entry the entry object + * @return string the final parsed template + */ + private function post_parse(string $result, stdClass $entry): string { + if ($this->ratings) { + $result .= data_print_ratings($this->instance, $entry, false); + } + if ($this->forcecomments && strpos($this->templatecontent, '##comments##') === false) { + $result .= $this->get_tag_comments_replacement($entry, false); + } + return $result; + } + + /** + * Returns the ##edit## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_edit_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + if (!$canmanageentry) { + return ''; + } + $backurl = new moodle_url($this->baseurl, [ + 'rid' => $entry->id, + 'mode' => 'single', + ]); + $url = new moodle_url('/mod/data/edit.php', $this->baseurl->params()); + $url->params([ + 'rid' => $entry->id, + 'sesskey' => sesskey(), + 'backto' => urlencode($backurl->out(false)) + ]); + return html_writer::tag( + 'span', + $OUTPUT->action_icon($url, $this->icons['edit']), + ['class' => 'edit'] + ); + } + + /** + * Returns the ##delete## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_delete_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + if (!$canmanageentry) { + return ''; + } + $url = new moodle_url($this->baseurl, [ + 'delete' => $entry->id, + 'sesskey' => sesskey(), + 'mode' => 'single', + ]); + + return html_writer::tag( + 'span', + $OUTPUT->action_icon($url, $this->icons['delete']), + ['class' => 'delete'] + ); + } + + /** + * Returns the ##more## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_more_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + $url = new moodle_url($this->baseurl, [ + 'rid' => $entry->id, + 'filter' => 1, + ]); + return html_writer::tag( + 'span', + $OUTPUT->action_icon($url, $this->icons['more']), + ['class' => 'more'] + ); + } + + /** + * Returns the ##moreurl## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_moreurl_replacement(stdClass $entry, bool $canmanageentry): string { + $url = new moodle_url($this->baseurl, [ + 'rid' => $entry->id, + 'filter' => 1, + ]); + return $url->out(false); + } + + /** + * Returns the ##delcheck## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_delcheck_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + if (!$this->canmanageentries) { + return ''; + } + $checkbox = new checkbox_toggleall('listview-entries', false, [ + 'id' => "entry_{$entry->id}", + 'name' => 'delcheck[]', + 'classes' => 'recordcheckbox', + 'value' => $entry->id, + ]); + return $OUTPUT->render($checkbox); + } + + /** + * Returns the ##user## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_user_replacement(stdClass $entry, bool $canmanageentry): string { + $cm = $this->manager->get_coursemodule(); + $url = new moodle_url('/user/view.php', [ + 'id' => $entry->userid, + 'course' => $cm->course, + ]); + return html_writer::tag( + 'a', + fullname($entry), + ['href' => $url->out(false)] + ); + } + + /** + * Returns the ##userpicture## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_userpicture_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + $cm = $this->manager->get_coursemodule(); + $user = user_picture::unalias($entry, null, 'userid'); + // If the record didn't come with user data, retrieve the user from database. + if (!isset($user->picture)) { + $user = core_user::get_user($entry->userid); + } + return $OUTPUT->user_picture($user, ['courseid' => $cm->course]); + } + + /** + * Returns the ##export## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_export_replacement(stdClass $entry, bool $canmanageentry): string { + global $CFG; + if (empty($CFG->enableportfolios)) { + return ''; + } + // Check the user can export the entry. + $cm = $this->manager->get_coursemodule(); + $context = $this->manager->get_context(); + $canexportall = has_capability('mod/data:exportentry', $context); + $canexportown = has_capability('mod/data:exportownentry', $context); + if (!$canexportall && !(data_isowner($entry->id) && $canexportown)) { + return ''; + } + // Add the portfolio export button. + require_once($CFG->libdir . '/portfoliolib.php'); + $button = new portfolio_add_button(); + $button->set_callback_options( + 'data_portfolio_caller', + ['id' => $cm->id, 'recordid' => $entry->id], + 'mod_data' + ); + $fields = $this->manager->get_fields(); + list($formats, $files) = data_portfolio_caller::formats($fields, $entry); + $button->set_formats($formats); + return $button->to_html(PORTFOLIO_ADD_ICON_LINK); + } + + /** + * Returns the ##timeadded## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_timeadded_replacement(stdClass $entry, bool $canmanageentry): string { + return userdate($entry->timecreated); + } + + /** + * Returns the ##timemodified## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_timemodified_replacement(stdClass $entry, bool $canmanageentry): string { + return userdate($entry->timemodified); + } + + /** + * Returns the ##approve## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_approve_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + $context = $this->manager->get_context(); + if (!has_capability('mod/data:approve', $context) || !$this->instance->approval || $entry->approved) { + return ''; + } + $url = new moodle_url($this->baseurl, [ + 'approve' => $entry->id, + 'sesskey' => sesskey(), + ]); + return html_writer::tag( + 'span', + $OUTPUT->action_icon($url, $this->icons['approve']), + ['class' => 'approve'] + ); + } + + /** + * Returns the ##disapprove## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_disapprove_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + $context = $this->manager->get_context(); + if (!has_capability('mod/data:approve', $context) || !$this->instance->approval || !$entry->approved) { + return ''; + } + $url = new moodle_url($this->baseurl, [ + 'disapprove' => $entry->id, + 'sesskey' => sesskey(), + ]); + return html_writer::tag( + 'span', + $OUTPUT->action_icon($url, $this->icons['disapprove']), + ['class' => 'disapprove'] + ); + } + + /** + * Returns the ##approvalstatus## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_approvalstatus_replacement(stdClass $entry, bool $canmanageentry): string { + if (!$this->instance->approval) { + return ''; + } + return ($entry->approved) ? get_string('approved', 'data') : get_string('notapproved', 'data'); + } + + /** + * Returns the ##approvalstatusclass## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_approvalstatusclass_replacement(stdClass $entry, bool $canmanageentry): string { + if (!$this->instance->approval) { + return ''; + } + return ($entry->approved) ? 'approved' : 'notapproved'; + } + + /** + * Returns the ##comments## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_comments_replacement(stdClass $entry, bool $canmanageentry): string { + global $CFG; + if (empty($CFG->usecomments) || empty($this->instance->comments)) { + return ''; + } + $context = $this->manager->get_context(); + require_once($CFG->dirroot . '/comment/lib.php'); + list($context, $course, $cm) = get_context_info_array($context->id); + $cmdata = (object)[ + 'context' => $context, + 'course' => $course, + 'cm' => $cm, + 'area' => 'database_entry', + 'itemid' => $entry->id, + 'showcount' => true, + 'component' => 'mod_data', + ]; + $comment = new comment($cmdata); + return $comment->output(true); + } + + /** + * Returns the ##tags## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_tags_replacement(stdClass $entry, bool $canmanageentry): string { + global $OUTPUT; + if (!core_tag_tag::is_enabled('mod_data', 'data_records')) { + return ''; + } + return $OUTPUT->tag_list( + core_tag_tag::get_item_tags('mod_data', 'data_records', $entry->id), + '', + 'data-tags' + ); + } + + /** + * Returns the ##id## tag replacement for an entry. + * + * @param stdClass $entry the entry object + * @param bool $canmanageentry if the current user can manage this entry + * @return string the tag replacement + */ + protected function get_tag_id_replacement(stdClass $entry, bool $canmanageentry): string { + return (string) $entry->id; + } + +} diff --git a/mod/data/lib.php b/mod/data/lib.php index 2ce02926713..53686256e3e 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -600,21 +600,25 @@ class data_field_base { // Base class for Database Field Types (see field/*/ /** * Given a template and a dataid, generate a default case template * - * @global object - * @param object $data - * @param string template [addtemplate, singletemplate, listtempalte, rsstemplate] - * @param int $recordid - * @param bool $form - * @param bool $update - * @return bool|string + * @param stdClass $data the mod_data record. + * @param string $template the template name + * @param int $recordid the entry record + * @param bool $form print a form instead of data + * @param bool $update if the function update the $data object or not + * @return bool|string the template content. */ -function data_generate_default_template(&$data, $template, $recordid=0, $form=false, $update=true) { +function data_generate_default_template(&$data, $template, $recordid = 0, $form = false, $update = true) { global $DB; if (!$data && !$template) { return false; } - if ($template == 'csstemplate' or $template == 'jstemplate' ) { + if ($template == 'csstemplate' + || $template == 'jstemplate' + || $template == 'listtemplateheader' + || $template == 'listtemplatefooter' + || $template == 'rsstitletemplate' + ) { return ''; } @@ -1255,9 +1259,15 @@ function data_user_complete($course, $user, $mod, $data) { echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades')); } } - - if ($records = $DB->get_records('data_records', array('dataid'=>$data->id,'userid'=>$user->id), 'timemodified DESC')) { - data_print_template('singletemplate', $records, $data); + $records = $DB->get_records( + 'data_records', + ['dataid' => $data->id, 'userid' => $user->id], + 'timemodified DESC' + ); + if ($records) { + $manager = manager::create_from_instance($data); + $parser = $manager->get_template('singletemplate'); + echo $parser->parse_entries($records); } } @@ -1373,239 +1383,37 @@ function data_grade_item_delete($data) { * takes a list of records, the current data, a search string, * and mode to display prints the translated template * - * @global object - * @global object - * @param string $template - * @param array $records - * @param object $data - * @param string $search - * @param int $page - * @param bool $return - * @param object $jumpurl a moodle_url by which to jump back to the record list (can be null) - * @return mixed + * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more. + * @todo MDL-75189 Final deprecation in Moodle 4.5. + * @param string $templatename the template name + * @param array $records the entries records + * @param stdClass $data the database instance object + * @param string $search the current search term + * @param int $page page number for pagination + * @param bool $return if the result should be returned (true) or printed (false) + * @param moodle_url|null $jumpurl a moodle_url by which to jump back to the record list (can be null) + * @return mixed string with all parsed entries or nothing if $return is false */ -function data_print_template($template, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) { - global $CFG, $DB, $OUTPUT; +function data_print_template($templatename, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) { + debugging( + 'data_print_template is deprecated. Use mod_data\\manager::get_template and mod_data\\template::parse_entries instead', + DEBUG_DEVELOPER + ); - $cm = get_coursemodule_from_instance('data', $data->id); - $context = context_module::instance($cm->id); - - static $fields = array(); - static $dataid = null; - - if (empty($dataid)) { - $dataid = $data->id; - } else if ($dataid != $data->id) { - $fields = array(); + $options = [ + 'search' => $search, + 'page' => $page, + ]; + if ($jumpurl) { + $options['baseurl'] = $jumpurl; } - - if (empty($fields)) { - $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id)); - foreach ($fieldrecords as $fieldrecord) { - $fields[]= data_get_field($fieldrecord, $data); - } - } - - if (empty($records)) { - return; - } - - if (!$jumpurl) { - $jumpurl = new moodle_url('/mod/data/view.php', array('d' => $data->id)); - } - $jumpurl = new moodle_url($jumpurl, array('page' => $page, 'sesskey' => sesskey())); - - foreach ($records as $record) { // Might be just one for the single template - - // Replacing tags - $patterns = array(); - $replacement = array(); - - // Then we generate strings to replace for normal tags - foreach ($fields as $field) { - $patterns[]='[['.$field->field->name.']]'; - $replacement[] = highlight($search, $field->display_browse_field($record->id, $template)); - } - - $canmanageentries = has_capability('mod/data:manageentries', $context); - - // Replacing special tags (##Edit##, ##Delete##, ##More##) - $patterns[]='##edit##'; - $patterns[]='##delete##'; - if (data_user_can_manage_entry($record, $data, $context)) { - $backtourlparams = [ - 'd' => $data->id, - ]; - if ($template === 'singletemplate') { - $backtourlparams['mode'] = 'single'; - } - $backtourl = new \moodle_url('/mod/data/view.php', $backtourlparams); - $replacement[] = '' . - $OUTPUT->pix_icon('t/edit', get_string('edit')) . ''; - $replacement[] = '' . - $OUTPUT->pix_icon('t/delete', get_string('delete')) . ''; - } else { - $replacement[] = ''; - $replacement[] = ''; - } - - $moreurl = $CFG->wwwroot . '/mod/data/view.php?d=' . $data->id . '&rid=' . $record->id; - if ($search) { - $moreurl .= '&filter=1'; - } - $patterns[]='##more##'; - $replacement[] = '' . $OUTPUT->pix_icon('t/preview', get_string('more', 'data')) . ''; - - $patterns[]='##moreurl##'; - $replacement[] = $moreurl; - - $patterns[]='##delcheck##'; - if ($canmanageentries) { - $checkbox = new \core\output\checkbox_toggleall('listview-entries', false, [ - 'id' => "entry_{$record->id}", - 'name' => 'delcheck[]', - 'classes' => 'recordcheckbox', - 'value' => $record->id, - ]); - $replacement[] = $OUTPUT->render($checkbox); - } else { - $replacement[] = ''; - } - - $patterns[]='##user##'; - $replacement[] = ''.fullname($record).''; - - $patterns[] = '##userpicture##'; - $ruser = user_picture::unalias($record, null, 'userid'); - // If the record didn't come with user data, retrieve the user from database. - if (!isset($ruser->picture)) { - $ruser = core_user::get_user($record->userid); - } - $replacement[] = $OUTPUT->user_picture($ruser, array('courseid' => $data->course)); - - $patterns[]='##export##'; - - if (!empty($CFG->enableportfolios) && ($template == 'singletemplate' || $template == 'listtemplate') - && ((has_capability('mod/data:exportentry', $context) - || (data_isowner($record->id) && has_capability('mod/data:exportownentry', $context))))) { - require_once($CFG->libdir . '/portfoliolib.php'); - $button = new portfolio_add_button(); - $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id, 'recordid' => $record->id), 'mod_data'); - list($formats, $files) = data_portfolio_caller::formats($fields, $record); - $button->set_formats($formats); - $replacement[] = $button->to_html(PORTFOLIO_ADD_ICON_LINK); - } else { - $replacement[] = ''; - } - - $patterns[] = '##timeadded##'; - $replacement[] = userdate($record->timecreated); - - $patterns[] = '##timemodified##'; - $replacement [] = userdate($record->timemodified); - - $patterns[]='##approve##'; - if (has_capability('mod/data:approve', $context) && ($data->approval) && (!$record->approved)) { - $approveurl = new moodle_url($jumpurl, array('approve' => $record->id)); - $approveicon = new pix_icon('t/approve', get_string('approve', 'data'), '', array('class' => 'iconsmall')); - $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($approveurl, $approveicon), - array('class' => 'approve')); - } else { - $replacement[] = ''; - } - - $patterns[]='##disapprove##'; - if (has_capability('mod/data:approve', $context) && ($data->approval) && ($record->approved)) { - $disapproveurl = new moodle_url($jumpurl, array('disapprove' => $record->id)); - $disapproveicon = new pix_icon('t/block', get_string('disapprove', 'data'), '', array('class' => 'iconsmall')); - $replacement[] = html_writer::tag('span', $OUTPUT->action_icon($disapproveurl, $disapproveicon), - array('class' => 'disapprove')); - } else { - $replacement[] = ''; - } - - $patterns[] = '##approvalstatus##'; - $patterns[] = '##approvalstatusclass##'; - if (!$data->approval) { - $replacement[] = ''; - $replacement[] = ''; - } else if ($record->approved) { - $replacement[] = get_string('approved', 'data'); - $replacement[] = 'approved'; - } else { - $replacement[] = get_string('notapproved', 'data'); - $replacement[] = 'notapproved'; - } - - $patterns[]='##comments##'; - if (($template == 'listtemplate') && ($data->comments)) { - - if (!empty($CFG->usecomments)) { - require_once($CFG->dirroot . '/comment/lib.php'); - list($context, $course, $cm) = get_context_info_array($context->id); - $cmt = new stdClass(); - $cmt->context = $context; - $cmt->course = $course; - $cmt->cm = $cm; - $cmt->area = 'database_entry'; - $cmt->itemid = $record->id; - $cmt->showcount = true; - $cmt->component = 'mod_data'; - $comment = new comment($cmt); - $replacement[] = $comment->output(true); - } - } else { - $replacement[] = ''; - } - - if (core_tag_tag::is_enabled('mod_data', 'data_records')) { - $patterns[] = "##tags##"; - $replacement[] = $OUTPUT->tag_list( - core_tag_tag::get_item_tags('mod_data', 'data_records', $record->id), '', 'data-tags'); - } - - // actual replacement of the tags - $newtext = str_ireplace($patterns, $replacement, $data->{$template}); - - // no more html formatting and filtering - see MDL-6635 - if ($return) { - return $newtext; - } else { - echo $newtext; - - // hack alert - return is always false in singletemplate anyway ;-) - /********************************** - * Printing Ratings Form * - *********************************/ - if ($template == 'singletemplate') { //prints ratings options - data_print_ratings($data, $record); - } - - /********************************** - * Printing Comments Form * - *********************************/ - if (($template == 'singletemplate') && ($data->comments)) { - if (!empty($CFG->usecomments)) { - require_once($CFG->dirroot . '/comment/lib.php'); - list($context, $course, $cm) = get_context_info_array($context->id); - $cmt = new stdClass(); - $cmt->context = $context; - $cmt->course = $course; - $cmt->cm = $cm; - $cmt->area = 'database_entry'; - $cmt->itemid = $record->id; - $cmt->showcount = true; - $cmt->component = 'mod_data'; - $comment = new comment($cmt); - $comment->output(false); - } - } - } + $manager = manager::create_from_instance($data); + $parser = $manager->get_template($templatename, $options); + $content = $parser->parse_entries($records); + if ($return) { + return $content; } + echo $content; } /** @@ -1981,13 +1789,19 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order=' * @global object * @param object $data * @param object $record + * @param bool $print if the result must be printed or returner. * @return void Output echo'd */ -function data_print_ratings($data, $record) { +function data_print_ratings($data, $record, bool $print = true) { global $OUTPUT; + $result = ''; if (!empty($record->rating)){ - echo $OUTPUT->render($record->rating); + $result = $OUTPUT->render($record->rating); } + if (!$print) { + return $result; + } + echo $result; } /** diff --git a/mod/data/rsslib.php b/mod/data/rsslib.php index 1390fb21e3d..d985c903498 100644 --- a/mod/data/rsslib.php +++ b/mod/data/rsslib.php @@ -25,6 +25,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_data\manager; + defined('MOODLE_INTERNAL') || die(); /** @@ -59,6 +61,8 @@ defined('MOODLE_INTERNAL') || die(); return null; } + $manager = manager::create_from_instance($data); + $sql = data_rss_get_sql($data); //get the cache file info @@ -93,16 +97,18 @@ defined('MOODLE_INTERNAL') || die(); $recordarray = array(); array_push($recordarray, $record); - $item = null; + $item = new stdClass(); // guess title or not if (!empty($data->rsstitletemplate)) { - $item->title = data_print_template('rsstitletemplate', $recordarray, $data, '', 0, true); + $parser = $manager->get_template('rsstitletemplate'); + $item->title = $parser->parse_entries($recordarray); } else { // else we guess $item->title = strip_tags($DB->get_field('data_content', 'content', array('fieldid'=>$firstfield->id, 'recordid'=>$record->id))); } - $item->description = data_print_template('rsstemplate', $recordarray, $data, '', 0, true); + $parser = $manager->get_template('rsstemplate'); + $item->title = $parser->parse_entries($recordarray); $item->pubdate = $record->timecreated; $item->link = $CFG->wwwroot.'/mod/data/view.php?d='.$data->id.'&rid='.$record->id; @@ -190,4 +196,3 @@ defined('MOODLE_INTERNAL') || die(); rss_delete_file('mod_data', $data); } - diff --git a/mod/data/tests/template_test.php b/mod/data/tests/template_test.php new file mode 100644 index 00000000000..d7984dd2367 --- /dev/null +++ b/mod/data/tests/template_test.php @@ -0,0 +1,656 @@ +. + +namespace mod_data; + +use context_module; +use rating_manager; +use stdClass; + +/** + * Template tests class for mod_data. + * + * @package mod_data + * @category test + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \mod_data\template + */ +class template_test extends \advanced_testcase { + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setupBeforeClass(): void { + global $CFG; + require_once($CFG->dirroot . '/rating/lib.php'); + } + + /** + * Test for static create methods. + * + * @covers ::parse_entries + * @dataProvider parse_entries_provider + * @param string $templatecontent the template string + * @param string $expected expected output + * @param string $rolename the user rolename + * @param bool $enableexport is portfolio export is enabled + * @param bool $approved if the entry is approved + * @param bool $enablecomments is comments are enabled + * @param bool $enableratings if ratings are enabled + * @param array $options extra parser options + * @param bool $otherauthor if the entry is from another user + */ + public function test_parse_entries( + string $templatecontent, + string $expected, + string $rolename = 'editingteacher', + bool $enableexport = false, + bool $approved = true, + bool $enablecomments = false, + bool $enableratings = false, + array $options = [], + bool $otherauthor = false + ) { + global $DB, $PAGE; + // Comments, tags, approval, user role. + $this->resetAfterTest(); + + $params = ['approval' => true]; + + // Enable comments. + if ($enablecomments) { + set_config('usecomments', 1); + $params['comments'] = true; + $PAGE->reset_theme_and_output(); + $PAGE->set_url('/mod/data/view.php'); + } + + $course = $this->getDataGenerator()->create_course(); + $params['course'] = $course; + $activity = $this->getDataGenerator()->create_module('data', $params); + $cm = get_coursemodule_from_id('data', $activity->cmid, 0, false, MUST_EXIST); + $context = context_module::instance($cm->id); + + $user = $this->getDataGenerator()->create_user(); + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleids[$rolename]); + $author = $user; + + if ($otherauthor) { + $user2 = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleids[$rolename]); + $author = $user2; + } + + // Generate an entry. + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + + $this->setUser($user); + + $entryid = $generator->create_entry( + $activity, + [$field->field->id => 'Example entry'], + 0, + ['Cats', 'Dogs'], + ['approved' => $approved] + ); + + if ($enableexport) { + $this->enable_portfolio($user); + } + + $manager = manager::create_from_instance($activity); + + $entry = (object)[ + 'id' => $entryid, + 'approved' => $approved, + 'timecreated' => 1657618639, + 'timemodified' => 1657618650, + 'userid' => $author->id, + 'groupid' => 0, + 'dataid' => $activity->id, + 'picture' => 0, + 'firstname' => $author->firstname, + 'lastname' => $author->lastname, + 'firstnamephonetic' => $author->firstnamephonetic, + 'lastnamephonetic' => $author->lastnamephonetic, + 'middlename' => $author->middlename, + 'alternatename' => $author->alternatename, + 'imagealt' => 'PIXEXAMPLE', + 'email' => $author->email, + ]; + $entries = [$entry]; + + if ($enableratings) { + $entries = $this->enable_ratings($context, $activity, $entries, $user); + } + + // Some cooked variables for the regular expression. + $userfullname = fullname($user); + $timeadded = userdate($entry->timecreated); + $timemodified = userdate($entry->timemodified); + $fieldid = $field->field->id; + $replace = [ + '{authorfullname}' => fullname($author), + '{timeadded}' => userdate($entry->timecreated), + '{timemodified}' => userdate($entry->timemodified), + '{fieldid}' => $field->field->id, + '{entryid}' => $entry->id, + '{cmid}' => $cm->id, + '{courseid}' => $course->id, + '{authorid}' => $author->id + ]; + + $parser = new template($manager, $templatecontent, $options); + $result = $parser->parse_entries($entries); + + // We don't want line breaks for the validations. + $result = str_replace("\n", '', $result); + $regexp = str_replace(array_keys($replace), array_values($replace), $expected); + $this->assertMatchesRegularExpression($regexp, $result); + } + + /** + * Data provider for test_parse_entries(). + * + * @return array of scenarios + */ + public function parse_entries_provider(): array { + return [ + // Teacher scenarios. + 'Teacher id tag' => [ + 'templatecontent' => 'Some ##id## tag', + 'expected' => '|Some {entryid} tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher delete tag' => [ + 'templatecontent' => 'Some ##delete## tag', + 'expected' => '|Some .*delete.*{entryid}.*sesskey.*Delete.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher edit tag' => [ + 'templatecontent' => 'Some ##edit## tag', + 'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher more tag' => [ + 'templatecontent' => 'Some ##more## tag', + 'expected' => '|Some .*more.*{cmid}.*rid.*{entryid}.*More.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher moreurl tag' => [ + 'templatecontent' => 'Some ##moreurl## tag', + 'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher delcheck tag' => [ + 'templatecontent' => 'Some ##delcheck## tag', + 'expected' => '|Some .*input.*checkbox.*value.*{entryid}.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher user tag' => [ + 'templatecontent' => 'Some ##user## tag', + 'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.*{authorfullname}.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher userpicture tag' => [ + 'templatecontent' => 'Some ##userpicture## tag', + 'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher export tag' => [ + 'templatecontent' => 'Some ##export## tag', + 'expected' => '|Some .*portfolio/add.* tag|', + 'rolename' => 'editingteacher', + 'enableexport' => true, + ], + 'Teacher export tag not configured' => [ + 'templatecontent' => 'Some ##export## tag', + 'expected' => '|Some tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + ], + 'Teacher timeadded tag' => [ + 'templatecontent' => 'Some ##timeadded## tag', + 'expected' => '|Some {timeadded} tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher timemodified tag' => [ + 'templatecontent' => 'Some ##timemodified## tag', + 'expected' => '|Some {timemodified} tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher approve tag approved entry' => [ + 'templatecontent' => 'Some ##approve## tag', + 'expected' => '|Some tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + ], + 'Teacher approve tag disapproved entry' => [ + 'templatecontent' => 'Some ##approve## tag', + 'expected' => '|Some .*approve.*{entryid}.*sesskey.*Approve.* tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => false, + ], + 'Teacher disapprove tag approved entry' => [ + 'templatecontent' => 'Some ##disapprove## tag', + 'expected' => '|Some .*disapprove.*{entryid}.*sesskey.*Undo approval.* tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + ], + 'Teacher disapprove tag disapproved entry' => [ + 'templatecontent' => 'Some ##disapprove## tag', + 'expected' => '|Some tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => false, + ], + 'Teacher approvalstatus tag approved entry' => [ + 'templatecontent' => 'Some ##approvalstatus## tag', + 'expected' => '|Some Approved tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + ], + 'Teacher approvalstatus tag disapproved entry' => [ + 'templatecontent' => 'Some ##approvalstatus## tag', + 'expected' => '|Some .*not approved.* tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => false, + ], + 'Teacher approvalstatusclass tag approved entry' => [ + 'templatecontent' => 'Some ##approvalstatusclass## tag', + 'expected' => '|Some approved tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + ], + 'Teacher approvalstatusclass tag disapproved entry' => [ + 'templatecontent' => 'Some ##approvalstatusclass## tag', + 'expected' => '|Some notapproved tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => false, + ], + 'Teacher tags tag' => [ + 'templatecontent' => 'Some ##tags## tag', + 'expected' => '|Some .*Cats.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher field name tag' => [ + 'templatecontent' => 'Some [[myfield]] tag', + 'expected' => '|Some .*Example entry.* tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher field#id name tag' => [ + 'templatecontent' => 'Some [[myfield#id]] tag', + 'expected' => '|Some {fieldid} tag|', + 'rolename' => 'editingteacher', + ], + 'Teacher comments name tag with comments enabled' => [ + 'templatecontent' => 'Some ##comments## tag', + 'expected' => '|Some .*Comments.* tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => true, + ], + 'Teacher comments name tag with comments disabled' => [ + 'templatecontent' => 'Some ##comments## tag', + 'expected' => '|Some tag|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + ], + 'Teacher comment forced with comments enables' => [ + 'templatecontent' => 'No tags', + 'expected' => '|No tags.*Comments.*|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => true, + 'enableratings' => false, + 'options' => ['comments' => true], + ], + 'Teacher comment forced without comments enables' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags$|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => ['comments' => true], + ], + 'Teacher adding ratings without ratings configured' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags$|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => ['ratings' => true], + ], + 'Teacher adding ratings with ratings configured' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags.*Average of ratings|', + 'rolename' => 'editingteacher', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => true, + 'options' => ['ratings' => true], + ], + // Student scenarios. + 'Student id tag' => [ + 'templatecontent' => 'Some ##id## tag', + 'expected' => '|Some {entryid} tag|', + 'rolename' => 'student', + ], + 'Student delete tag' => [ + 'templatecontent' => 'Some ##delete## tag', + 'expected' => '|Some .*delete.*{entryid}.*sesskey.*Delete.* tag|', + 'rolename' => 'student', + ], + 'Student delete tag on other author entry' => [ + 'templatecontent' => 'Some ##delete## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => [], + 'otherauthor' => true, + ], + 'Student edit tag' => [ + 'templatecontent' => 'Some ##edit## tag', + 'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* tag|', + 'rolename' => 'student', + ], + 'Student edit tag on other author entry' => [ + 'templatecontent' => 'Some ##edit## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => [], + 'otherauthor' => true, + ], + 'Student more tag' => [ + 'templatecontent' => 'Some ##more## tag', + 'expected' => '|Some .*more.*{cmid}.*rid.*{entryid}.*More.* tag|', + 'rolename' => 'student', + ], + 'Student moreurl tag' => [ + 'templatecontent' => 'Some ##moreurl## tag', + 'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|', + 'rolename' => 'student', + ], + 'Student delcheck tag' => [ + 'templatecontent' => 'Some ##delcheck## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + ], + 'Student user tag' => [ + 'templatecontent' => 'Some ##user## tag', + 'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.*{authorfullname}.* tag|', + 'rolename' => 'student', + ], + 'Student userpicture tag' => [ + 'templatecontent' => 'Some ##userpicture## tag', + 'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.* tag|', + 'rolename' => 'student', + ], + 'Student export tag' => [ + 'templatecontent' => 'Some ##export## tag', + 'expected' => '|Some .*portfolio/add.* tag|', + 'rolename' => 'student', + 'enableexport' => true, + ], + 'Student export tag not configured' => [ + 'templatecontent' => 'Some ##export## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + ], + 'Student export tag on other user entry' => [ + 'templatecontent' => 'Some ##export## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => true, + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => [], + 'otherauthor' => true, + ], + 'Student timeadded tag' => [ + 'templatecontent' => 'Some ##timeadded## tag', + 'expected' => '|Some {timeadded} tag|', + 'rolename' => 'student', + ], + 'Student timemodified tag' => [ + 'templatecontent' => 'Some ##timemodified## tag', + 'expected' => '|Some {timemodified} tag|', + 'rolename' => 'student', + ], + 'Student approve tag approved entry' => [ + 'templatecontent' => 'Some ##approve## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + ], + 'Student approve tag disapproved entry' => [ + 'templatecontent' => 'Some ##approve## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => false, + ], + 'Student disapprove tag approved entry' => [ + 'templatecontent' => 'Some ##disapprove## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + ], + 'Student disapprove tag disapproved entry' => [ + 'templatecontent' => 'Some ##disapprove## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => false, + ], + 'Student approvalstatus tag approved entry' => [ + 'templatecontent' => 'Some ##approvalstatus## tag', + 'expected' => '|Some Approved tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + ], + 'Student approvalstatus tag disapproved entry' => [ + 'templatecontent' => 'Some ##approvalstatus## tag', + 'expected' => '|Some .*not approved.* tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => false, + ], + 'Student approvalstatusclass tag approved entry' => [ + 'templatecontent' => 'Some ##approvalstatusclass## tag', + 'expected' => '|Some approved tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + ], + 'Student approvalstatusclass tag disapproved entry' => [ + 'templatecontent' => 'Some ##approvalstatusclass## tag', + 'expected' => '|Some notapproved tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => false, + ], + 'Student tags tag' => [ + 'templatecontent' => 'Some ##tags## tag', + 'expected' => '|Some .*Cats.* tag|', + 'rolename' => 'student', + ], + 'Student field name tag' => [ + 'templatecontent' => 'Some [[myfield]] tag', + 'expected' => '|Some .*Example entry.* tag|', + 'rolename' => 'student', + ], + 'Student field#id name tag' => [ + 'templatecontent' => 'Some [[myfield#id]] tag', + 'expected' => '|Some {fieldid} tag|', + 'rolename' => 'student', + ], + 'Student comments name tag with comments enabled' => [ + 'templatecontent' => 'Some ##comments## tag', + 'expected' => '|Some .*Comments.* tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => true, + ], + 'Student comments name tag with comments disabled' => [ + 'templatecontent' => 'Some ##comments## tag', + 'expected' => '|Some tag|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + ], + 'Student comment forced with comments enables' => [ + 'templatecontent' => 'No tags', + 'expected' => '|No tags.*Comments.*|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => true, + 'enableratings' => false, + 'options' => ['comments' => true] + ], + 'Student comment forced without comments enables' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags$|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => ['comments' => true] + ], + 'Student adding ratings without ratings configured' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags$|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => false, + 'options' => ['ratings' => true] + ], + 'Student adding ratings with ratings configured' => [ + 'templatecontent' => 'No tags', + 'expected' => '|^No tags$|', + 'rolename' => 'student', + 'enableexport' => false, + 'approved' => true, + 'enablecomments' => false, + 'enableratings' => true, + 'options' => ['ratings' => true] + ], + ]; + } + + /** + * Create all the necessary data to enable portfolio export in mod_data + * + * @param stdClass $user the current user record. + */ + protected function enable_portfolio(stdClass $user) { + global $DB; + set_config('enableportfolios', 1); + + $plugin = 'download'; + $name = 'Download'; + + $portfolioinstance = (object) [ + 'plugin' => $plugin, + 'name' => $name, + 'visible' => 1 + ]; + $portfolioinstance->id = $DB->insert_record('portfolio_instance', $portfolioinstance); + $userinstance = (object) [ + 'instance' => $portfolioinstance->id, + 'userid' => $user->id, + 'name' => 'visible', + 'value' => 1 + ]; + $DB->insert_record('portfolio_instance_user', $userinstance); + + $DB->insert_record('portfolio_log', [ + 'portfolio' => $portfolioinstance->id, + 'userid' => $user->id, + 'caller_class' => 'data_portfolio_caller', + 'caller_component' => 'mod_data', + 'time' => time(), + ]); + } + + /** + * Enable the ratings on the database entries. + * + * @param context_module $context the activity context + * @param stdClass $activity the activity record + * @param array $entries database entries + * @param stdClass $user the current user record + * @return stdClass the entries with the rating attribute + */ + protected function enable_ratings(context_module $context, stdClass $activity, array $entries, stdClass $user) { + global $CFG; + $ratingoptions = (object)[ + 'context' => $context, + 'component' => 'mod_data', + 'ratingarea' => 'entry', + 'items' => $entries, + 'aggregate' => RATING_AGGREGATE_AVERAGE, + 'scaleid' => $activity->scale, + 'userid' => $user->id, + 'returnurl' => $CFG->wwwroot . '/mod/data/view.php', + 'assesstimestart' => $activity->assesstimestart, + 'assesstimefinish' => $activity->assesstimefinish, + ]; + $rm = new rating_manager(); + return $rm->get_ratings($ratingoptions); + } +} diff --git a/mod/data/upgrade.txt b/mod/data/upgrade.txt index 1cb9046e795..95ad4c8f212 100644 --- a/mod/data/upgrade.txt +++ b/mod/data/upgrade.txt @@ -3,6 +3,8 @@ information provided here is intended especially for developers. === 4.1 === * The method data_view is now deprecated. Use $maganer->set_module_viewed instead. +* The data_print_template function is now deprecated and replaced by mod_data\template class. +* The data_print_ratings function now has an extra $print to get the ratings output instead of printing it directly. === 3.7 === * External functions get_entries, get_entry and search_entries now return an additional field "tags" containing the entry tags. diff --git a/mod/data/view.php b/mod/data/view.php index c446bb549a4..7503a0378d3 100644 --- a/mod/data/view.php +++ b/mod/data/view.php @@ -286,7 +286,8 @@ if ($delete && confirm_sesskey() && (data_user_can_manage_entry($delete, $data, $deletebutton, 'view.php?d='.$data->id); $records[] = $deleterecord; - echo data_print_template('singletemplate', $records, $data, '', 0, true); + $parser = $manager->get_template('singletemplate'); + echo $parser->parse_entries($records); echo $OUTPUT->footer(); exit; @@ -329,7 +330,8 @@ if ($multidelete && confirm_sesskey() && $canmanageentries) { $cancelurl = new moodle_url('/mod/data/view.php', array('d' => $data->id)); $deletebutton = new single_button($action, get_string('delete')); echo $OUTPUT->confirm(get_string('confirmdeleterecords', 'data'), $deletebutton, $cancelurl); - echo data_print_template('listtemplate', $validrecords, $data, '', 0, false); + $parser = $manager->get_template('listtemplate'); + echo $parser->parse_entries($validrecords); echo $OUTPUT->footer(); exit; } @@ -459,7 +461,13 @@ if ($showactivity) { $records = $rm->get_ratings($ratingoptions); } - data_print_template('singletemplate', $records, $data, $search, $page, false, new moodle_url($baseurl)); + $options = [ + 'search' => $search, + 'page' => $page, + 'baseurl' => new moodle_url($baseurl), + ]; + $parser = $manager->get_template('singletemplate', $options); + echo $parser->parse_entries($records); echo $OUTPUT->paging_bar($totalcount, $page, $nowperpage, $baseurl); @@ -480,7 +488,14 @@ if ($showactivity) { data_generate_default_template($data, 'listtemplate', 0, false, false); } echo $data->listtemplateheader; - data_print_template('listtemplate', $records, $data, $search, $page, false, new moodle_url($baseurl)); + $options = [ + 'search' => $search, + 'page' => $page, + 'baseurl' => new moodle_url($baseurl), + ]; + $parser = $manager->get_template('listtemplate', $options); + echo $parser->parse_entries($records); + echo $data->listtemplatefooter; echo $OUTPUT->paging_bar($totalcount, $page, $nowperpage, $baseurl);