Differences Between: [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace mod_data; 18 19 use action_menu; 20 use action_menu_link_secondary; 21 use core\output\checkbox_toggleall; 22 use data_field_base; 23 use html_writer; 24 use mod_data\manager; 25 use moodle_url; 26 use pix_icon; 27 use stdClass; 28 use user_picture; 29 use core_user; 30 use portfolio_add_button; 31 use data_portfolio_caller; 32 use comment; 33 use core_tag_tag; 34 35 /** 36 * Class template for database activity 37 * 38 * @package mod_data 39 * @copyright 2022 Ferran Recio <ferran@moodle.com> 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class template { 43 44 /** @var manager the current instance manager. */ 45 private $manager; 46 47 /** @var stdClass the current instance record. */ 48 private $instance; 49 50 /** @var string the template. */ 51 private $templatecontent; 52 53 /** @var string the template name. */ 54 private $templatename; 55 56 /** @var moodle_url the base url. */ 57 private $baseurl; 58 59 /** @var string the current search if any. */ 60 private $search; 61 62 /** @var bool if ratings must be added. */ 63 private $ratings; 64 65 /** @var bool if comments must be added if not present in the template. */ 66 private $forcecomments; 67 68 /** @var bool if show more option must be added. */ 69 private $showmore; 70 71 /** @var bool if the current user can manage entries. */ 72 private $canmanageentries = null; 73 74 /** @var array if icons HTML. */ 75 private $icons = []; 76 77 /** @var array All template tags (calculated in load_template_tags). */ 78 protected $tags = []; 79 80 /** @var array The mod_data fields. */ 81 protected $fields = []; 82 83 /** @var array All fields that are not present in the template content. */ 84 protected $otherfields = []; 85 86 /** 87 * Class contructor. 88 * 89 * See the add_options method for the available display options. 90 * 91 * @param manager $manager the current instance manager 92 * @param string $templatecontent the template string to use 93 * @param array $options an array of extra diplay options 94 * @param array $fields alternative array of fields (for preview presets) 95 */ 96 public function __construct(manager $manager, string $templatecontent, array $options = [], array $fields = null) { 97 $this->manager = $manager; 98 $this->instance = $manager->get_instance(); 99 $this->templatecontent = $templatecontent; 100 101 $context = $manager->get_context(); 102 $this->canmanageentries = has_capability('mod/data:manageentries', $context); 103 $this->icons = $this->get_icons(); 104 $this->fields = $fields ?? $manager->get_fields(); 105 $this->add_options($options); 106 $this->load_template_tags($templatecontent); 107 } 108 109 /** 110 * Create a template class with the default template content. 111 * 112 * @param manager $manager the current instance manager. 113 * @param string $templatename the template name. 114 * @param bool $form whether the fields should be displayed as form instead of data. 115 * @return self The template with the default content (to be displayed when no template is defined). 116 */ 117 public static function create_default_template( 118 manager $manager, 119 string $templatename, 120 bool $form = false 121 ): self { 122 $renderer = $manager->get_renderer(); 123 $content = ''; 124 switch ($templatename) { 125 case 'addtemplate': 126 case 'asearchtemplate': 127 case 'listtemplate': 128 case 'rsstemplate': 129 case 'singletemplate': 130 $template = new \mod_data\output\defaulttemplate($manager->get_fields(), $templatename, $form); 131 $content = $renderer->render_defaulttemplate($template); 132 } 133 134 // Some templates have extra options. 135 $options = self::get_default_display_options($templatename); 136 137 return new self($manager, $content, $options); 138 } 139 140 /** 141 * Get default options for templates. 142 * 143 * For instance, the list template supports the show more button. 144 * 145 * @param string $templatename the template name. 146 * @return array an array of extra diplay options. 147 */ 148 public static function get_default_display_options(string $templatename): array { 149 $options = []; 150 151 if ($templatename === 'singletemplate') { 152 $options['comments'] = true; 153 $options['ratings'] = true; 154 } 155 if ($templatename === 'listtemplate') { 156 // The "Show more" button should be only displayed in the listtemplate. 157 $options['showmore'] = true; 158 } 159 160 return $options; 161 } 162 163 /** 164 * Return the raw template content. 165 * 166 * @return string the template content before parsing 167 */ 168 public function get_template_content(): string { 169 return $this->templatecontent; 170 } 171 172 /** 173 * Add extra display options. 174 * 175 * The extra options are: 176 * - page: the current pagination page 177 * - search: the current search text 178 * - baseurl: an alternative entry url (moodle_url) 179 * - comments: if comments must be added if not present 180 * - ratings: if ratings must be added 181 * 182 * @param array $options the array of options. 183 */ 184 public function add_options(array $options = []) { 185 $cm = $this->manager->get_coursemodule(); 186 $baseurl = $options['baseurl'] ?? new moodle_url('/mod/data/view.php', ['id' => $cm->id]); 187 if (isset($options['page'])) { 188 $baseurl->params([ 189 'page' => $options['page'], 190 ]); 191 } 192 $this->baseurl = $baseurl; 193 194 // Save options. 195 $this->search = $options['search'] ?? null; 196 $this->ratings = $options['ratings'] ?? false; 197 $this->forcecomments = $options['comments'] ?? false; 198 $this->showmore = $options['showmore'] ?? false; 199 $this->templatename = $options['templatename'] ?? 'singletemplate'; 200 } 201 202 /** 203 * Scan the template tags. 204 * 205 * This method detects which tags are used in this template and store them 206 * in the $this->tags attribute. This attribute will be used to determine 207 * which replacements needs to be calculated. 208 * 209 * @param string $templatecontent the current template 210 */ 211 protected function load_template_tags(string $templatecontent) { 212 // Detect action tags. 213 $pattern = '/##(?P<tags>\w+?)##/'; 214 $matches = []; 215 preg_match_all($pattern, $templatecontent, $matches); 216 if (!isset($matches['tags']) || empty($matches['tags'])) { 217 return; 218 } 219 $this->tags = $matches['tags']; 220 // Check if some tag require some extra template scan. 221 foreach ($this->tags as $tagname) { 222 $methodname = "preprocess_tag_{$tagname}"; 223 if (method_exists($this, $methodname)) { 224 $this->$methodname($templatecontent); 225 } 226 } 227 } 228 229 /** 230 * Check if a tag is present in the template. 231 * 232 * @param bool $tagname the tag to check (without ##) 233 * @return bool if the tag is present 234 */ 235 public function has_tag(string $tagname): bool { 236 return in_array($tagname, $this->tags); 237 } 238 239 /** 240 * Return the current template name. 241 * 242 * @return string the template name 243 */ 244 public function get_template_name(): string { 245 return $this->templatename; 246 } 247 248 /** 249 * Generate the list of action icons. 250 * 251 * @return pix_icon[] icon name => pix_icon 252 */ 253 protected function get_icons() { 254 $attrs = ['class' => 'iconsmall dataicon']; 255 return [ 256 'edit' => new pix_icon('t/editinline', get_string('edit'), '', $attrs), 257 'delete' => new pix_icon('t/delete', get_string('delete'), '', $attrs), 258 'more' => new pix_icon('t/preview', get_string('more', 'data'), '', $attrs), 259 'approve' => new pix_icon('t/approve', get_string('approve', 'data'), '', $attrs), 260 'disapprove' => new pix_icon('t/block', get_string('disapprove', 'data'), '', $attrs), 261 ]; 262 } 263 264 /** 265 * Return the parsed entry using a template. 266 * 267 * This method apply a template replacing all necessary tags. 268 * 269 * @param array $entries of entres to parse 270 * @return string the entries outputs using the template 271 */ 272 public function parse_entries(array $entries): string { 273 if (empty($entries)) { 274 return ''; 275 } 276 $result = ''; 277 foreach ($entries as $entry) { 278 $result .= $this->parse_entry($entry); 279 } 280 return $result; 281 } 282 283 /** 284 * Parse a single entry. 285 * 286 * @param stdClass $entry the entry to parse 287 * @return string the parsed entry 288 */ 289 private function parse_entry(stdClass $entry): string { 290 if (empty($this->templatecontent)) { 291 return ''; 292 } 293 $context = $this->manager->get_context(); 294 $canmanageentry = data_user_can_manage_entry($entry, $this->instance, $context); 295 296 // Load all replacements for the entry. 297 $fields = $this->get_fields_replacements($entry); 298 $tags = $this->get_tags_replacements($entry, $canmanageentry); 299 $replacements = array_merge($fields, $tags); 300 301 $patterns = array_keys($replacements); 302 $replacement = array_values($replacements); 303 $result = str_ireplace($patterns, $replacement, $this->templatecontent); 304 305 return $this->post_parse($result, $entry); 306 } 307 308 /** 309 * Get all field replacements. 310 * 311 * @param stdClass $entry the entry object 312 * @return array of pattern => replacement 313 */ 314 private function get_fields_replacements(stdClass $entry): array { 315 $result = []; 316 foreach ($this->fields as $field) { 317 // Field value. 318 $pattern = '[[' . $field->field->name . ']]'; 319 $result[$pattern] = highlight( 320 $this->search, 321 $field->display_browse_field($entry->id, $this->templatename) 322 ); 323 // Other dynamic field information. 324 $pattern = '[[' . $field->field->name . '#id]]'; 325 $result[$pattern] = $field->field->id; 326 $pattern = '[[' . $field->field->name . '#name]]'; 327 $result[$pattern] = $field->field->name; 328 $pattern = '[[' . $field->field->name . '#description]]'; 329 $result[$pattern] = $field->field->description; 330 } 331 return $result; 332 } 333 334 /** 335 * Get all standard tags replacements. 336 * 337 * @param stdClass $entry the entry object 338 * @param bool $canmanageentry if the current user can manage this entry 339 * @return array of pattern => replacement 340 */ 341 private function get_tags_replacements(stdClass $entry, bool $canmanageentry): array { 342 $result = []; 343 foreach ($this->tags as $tagname) { 344 $methodname = "get_tag_{$tagname}_replacement"; 345 if (method_exists($this, $methodname)) { 346 $pattern = "##$tagname##"; 347 $replacement = $this->$methodname($entry, $canmanageentry); 348 $result[$pattern] = $replacement; 349 } 350 } 351 return $result; 352 } 353 354 /** 355 * Add any extra information to the parsed entry. 356 * 357 * @param string $result the parsed template with the entry data 358 * @param stdClass $entry the entry object 359 * @return string the final parsed template 360 */ 361 private function post_parse(string $result, stdClass $entry): string { 362 if ($this->ratings) { 363 $result .= data_print_ratings($this->instance, $entry, false); 364 } 365 if ($this->forcecomments && strpos($this->templatecontent, '##comments##') === false) { 366 $result .= $this->get_tag_comments_replacement($entry, false); 367 } 368 return $result; 369 } 370 371 /** 372 * Returns the ##edit## tag replacement for an entry. 373 * 374 * @param stdClass $entry the entry object 375 * @param bool $canmanageentry if the current user can manage this entry 376 * @return string the tag replacement 377 */ 378 protected function get_tag_edit_replacement(stdClass $entry, bool $canmanageentry): string { 379 global $OUTPUT; 380 if (!$canmanageentry) { 381 return ''; 382 } 383 $backurl = new moodle_url($this->baseurl, [ 384 'rid' => $entry->id, 385 'mode' => 'single', 386 ]); 387 $url = new moodle_url('/mod/data/edit.php', $this->baseurl->params()); 388 $url->params([ 389 'rid' => $entry->id, 390 'sesskey' => sesskey(), 391 'backto' => urlencode($backurl->out(false)) 392 ]); 393 return html_writer::tag( 394 'span', 395 $OUTPUT->action_icon($url, $this->icons['edit']), 396 ['class' => 'edit'] 397 ); 398 } 399 400 /** 401 * Returns the ##delete## tag replacement for an entry. 402 * 403 * @param stdClass $entry the entry object 404 * @param bool $canmanageentry if the current user can manage this entry 405 * @return string the tag replacement 406 */ 407 protected function get_tag_delete_replacement(stdClass $entry, bool $canmanageentry): string { 408 global $OUTPUT; 409 if (!$canmanageentry) { 410 return ''; 411 } 412 $url = new moodle_url($this->baseurl, [ 413 'delete' => $entry->id, 414 'sesskey' => sesskey(), 415 'mode' => 'single', 416 ]); 417 418 return html_writer::tag( 419 'span', 420 $OUTPUT->action_icon($url, $this->icons['delete']), 421 ['class' => 'delete'] 422 ); 423 } 424 425 /** 426 * Returns the ##more## tag replacement for an entry. 427 * 428 * @param stdClass $entry the entry object 429 * @param bool $canmanageentry if the current user can manage this entry 430 * @return string the tag replacement 431 */ 432 protected function get_tag_more_replacement(stdClass $entry, bool $canmanageentry): string { 433 global $OUTPUT; 434 435 if (!$this->showmore) { 436 return ''; 437 } 438 439 $url = new moodle_url($this->baseurl, [ 440 'rid' => $entry->id, 441 'filter' => 1, 442 ]); 443 return html_writer::tag( 444 'span', 445 $OUTPUT->action_icon($url, $this->icons['more']), 446 ['class' => 'more'] 447 ); 448 } 449 450 /** 451 * Returns the ##moreurl## tag replacement for an entry. 452 * 453 * @param stdClass $entry the entry object 454 * @param bool $canmanageentry if the current user can manage this entry 455 * @return string the tag replacement 456 */ 457 protected function get_tag_moreurl_replacement(stdClass $entry, bool $canmanageentry): string { 458 $url = new moodle_url($this->baseurl, [ 459 'rid' => $entry->id, 460 'filter' => 1, 461 ]); 462 return $url->out(false); 463 } 464 465 /** 466 * Returns the ##delcheck## tag replacement for an entry. 467 * 468 * @param stdClass $entry the entry object 469 * @param bool $canmanageentry if the current user can manage this entry 470 * @return string the tag replacement 471 */ 472 protected function get_tag_delcheck_replacement(stdClass $entry, bool $canmanageentry): string { 473 global $OUTPUT; 474 if (!$this->canmanageentries) { 475 return ''; 476 } 477 $checkbox = new checkbox_toggleall('listview-entries', false, [ 478 'id' => "entry_{$entry->id}", 479 'name' => 'delcheck[]', 480 'classes' => 'recordcheckbox', 481 'value' => $entry->id, 482 ]); 483 return $OUTPUT->render($checkbox); 484 } 485 486 /** 487 * Returns the ##user## tag replacement for an entry. 488 * 489 * @param stdClass $entry the entry object 490 * @param bool $canmanageentry if the current user can manage this entry 491 * @return string the tag replacement 492 */ 493 protected function get_tag_user_replacement(stdClass $entry, bool $canmanageentry): string { 494 $cm = $this->manager->get_coursemodule(); 495 $url = new moodle_url('/user/view.php', [ 496 'id' => $entry->userid, 497 'course' => $cm->course, 498 ]); 499 return html_writer::tag( 500 'a', 501 fullname($entry), 502 ['href' => $url->out(false)] 503 ); 504 } 505 506 /** 507 * Returns the ##userpicture## tag replacement for an entry. 508 * 509 * @param stdClass $entry the entry object 510 * @param bool $canmanageentry if the current user can manage this entry 511 * @return string the tag replacement 512 */ 513 protected function get_tag_userpicture_replacement(stdClass $entry, bool $canmanageentry): string { 514 global $OUTPUT; 515 $cm = $this->manager->get_coursemodule(); 516 $user = user_picture::unalias($entry, null, 'userid'); 517 // If the record didn't come with user data, retrieve the user from database. 518 if (!isset($user->picture)) { 519 $user = core_user::get_user($entry->userid); 520 } 521 return $OUTPUT->user_picture($user, ['courseid' => $cm->course, 'size' => 64]); 522 } 523 524 /** 525 * Returns the ##export## tag replacement for an entry. 526 * 527 * @param stdClass $entry the entry object 528 * @param bool $canmanageentry if the current user can manage this entry 529 * @return string the tag replacement 530 */ 531 protected function get_tag_export_replacement(stdClass $entry, bool $canmanageentry): string { 532 global $CFG; 533 if (empty($CFG->enableportfolios)) { 534 return ''; 535 } 536 // Check the user can export the entry. 537 $cm = $this->manager->get_coursemodule(); 538 $context = $this->manager->get_context(); 539 $canexportall = has_capability('mod/data:exportentry', $context); 540 $canexportown = has_capability('mod/data:exportownentry', $context); 541 if (!$canexportall && !(data_isowner($entry->id) && $canexportown)) { 542 return ''; 543 } 544 // Add the portfolio export button. 545 require_once($CFG->libdir . '/portfoliolib.php'); 546 $button = new portfolio_add_button(); 547 $button->set_callback_options( 548 'data_portfolio_caller', 549 ['id' => $cm->id, 'recordid' => $entry->id], 550 'mod_data' 551 ); 552 list($formats, $files) = data_portfolio_caller::formats($this->fields, $entry); 553 $button->set_formats($formats); 554 $result = $button->to_html(PORTFOLIO_ADD_ICON_LINK); 555 if (is_null($result)) { 556 $result = ''; 557 } 558 return $result; 559 } 560 561 /** 562 * Returns the ##timeadded## tag replacement for an entry. 563 * 564 * @param stdClass $entry the entry object 565 * @param bool $canmanageentry if the current user can manage this entry 566 * @return string the tag replacement 567 */ 568 protected function get_tag_timeadded_replacement(stdClass $entry, bool $canmanageentry): string { 569 return html_writer::tag( 570 'span', 571 userdate($entry->timecreated, get_string('strftimedatemonthabbr', 'langconfig')), 572 ['title' => userdate($entry->timecreated)] 573 ); 574 } 575 576 /** 577 * Returns the ##timemodified## tag replacement for an entry. 578 * 579 * @param stdClass $entry the entry object 580 * @param bool $canmanageentry if the current user can manage this entry 581 * @return string the tag replacement 582 */ 583 protected function get_tag_timemodified_replacement(stdClass $entry, bool $canmanageentry): string { 584 return html_writer::tag( 585 'span', 586 userdate($entry->timemodified, get_string('strftimedatemonthabbr', 'langconfig')), 587 ['title' => userdate($entry->timemodified)] 588 ); 589 } 590 591 /** 592 * Returns the ##approve## tag replacement for an entry. 593 * 594 * @param stdClass $entry the entry object 595 * @param bool $canmanageentry if the current user can manage this entry 596 * @return string the tag replacement 597 */ 598 protected function get_tag_approve_replacement(stdClass $entry, bool $canmanageentry): string { 599 global $OUTPUT; 600 $context = $this->manager->get_context(); 601 if (!has_capability('mod/data:approve', $context) || !$this->instance->approval || $entry->approved) { 602 return ''; 603 } 604 $url = new moodle_url($this->baseurl, [ 605 'approve' => $entry->id, 606 'sesskey' => sesskey(), 607 ]); 608 return html_writer::tag( 609 'span', 610 $OUTPUT->action_icon($url, $this->icons['approve']), 611 ['class' => 'approve'] 612 ); 613 } 614 615 /** 616 * Returns the ##disapprove## tag replacement for an entry. 617 * 618 * @param stdClass $entry the entry object 619 * @param bool $canmanageentry if the current user can manage this entry 620 * @return string the tag replacement 621 */ 622 protected function get_tag_disapprove_replacement(stdClass $entry, bool $canmanageentry): string { 623 global $OUTPUT; 624 $context = $this->manager->get_context(); 625 if (!has_capability('mod/data:approve', $context) || !$this->instance->approval || !$entry->approved) { 626 return ''; 627 } 628 $url = new moodle_url($this->baseurl, [ 629 'disapprove' => $entry->id, 630 'sesskey' => sesskey(), 631 ]); 632 return html_writer::tag( 633 'span', 634 $OUTPUT->action_icon($url, $this->icons['disapprove']), 635 ['class' => 'disapprove'] 636 ); 637 } 638 639 /** 640 * Returns the ##approvalstatus## tag replacement for an entry. 641 * 642 * @param stdClass $entry the entry object 643 * @param bool $canmanageentry if the current user can manage this entry 644 * @return string the tag replacement 645 */ 646 protected function get_tag_approvalstatus_replacement(stdClass $entry, bool $canmanageentry): string { 647 if (!$this->instance->approval) { 648 return ''; 649 } 650 return ($entry->approved) ? '' : html_writer::div(get_string('notapproved', 'data'), 'mod-data-approval-status-badge'); 651 } 652 653 /** 654 * Returns the ##approvalstatusclass## tag replacement for an entry. 655 * 656 * @param stdClass $entry the entry object 657 * @param bool $canmanageentry if the current user can manage this entry 658 * @return string the tag replacement 659 */ 660 protected function get_tag_approvalstatusclass_replacement(stdClass $entry, bool $canmanageentry): string { 661 if (!$this->instance->approval) { 662 return ''; 663 } 664 return ($entry->approved) ? 'approved' : 'notapproved'; 665 } 666 667 /** 668 * Returns the ##comments## tag replacement for an entry. 669 * 670 * @param stdClass $entry the entry object 671 * @param bool $canmanageentry if the current user can manage this entry 672 * @return string the tag replacement 673 */ 674 protected function get_tag_comments_replacement(stdClass $entry, bool $canmanageentry): string { 675 global $CFG; 676 if (empty($CFG->usecomments) || empty($this->instance->comments)) { 677 return ''; 678 } 679 $context = $this->manager->get_context(); 680 require_once($CFG->dirroot . '/comment/lib.php'); 681 list($context, $course, $cm) = get_context_info_array($context->id); 682 $cmdata = (object)[ 683 'context' => $context, 684 'course' => $course, 685 'cm' => $cm, 686 'area' => 'database_entry', 687 'itemid' => $entry->id, 688 'showcount' => true, 689 'component' => 'mod_data', 690 ]; 691 $comment = new comment($cmdata); 692 return $comment->output(true); 693 } 694 695 /** 696 * Returns the ##tags## tag replacement for an entry. 697 * 698 * @param stdClass $entry the entry object 699 * @param bool $canmanageentry if the current user can manage this entry 700 * @return string the tag replacement 701 */ 702 protected function get_tag_tags_replacement(stdClass $entry, bool $canmanageentry): string { 703 global $OUTPUT; 704 if (!core_tag_tag::is_enabled('mod_data', 'data_records')) { 705 return ''; 706 } 707 return $OUTPUT->tag_list( 708 core_tag_tag::get_item_tags('mod_data', 'data_records', $entry->id), 709 '', 710 'data-tags' 711 ); 712 } 713 714 /** 715 * Returns the ##id## tag replacement for an entry. 716 * 717 * @param stdClass $entry the entry object 718 * @param bool $canmanageentry if the current user can manage this entry 719 * @return string the tag replacement 720 */ 721 protected function get_tag_id_replacement(stdClass $entry, bool $canmanageentry): string { 722 return (string) $entry->id; 723 } 724 725 /** 726 * Prepare otherfield tag scanning the present template fields. 727 * 728 * @param string $templatecontent the template content 729 */ 730 protected function preprocess_tag_otherfields(string $templatecontent) { 731 $otherfields = []; 732 $fields = $this->manager->get_fields(); 733 foreach ($fields as $field) { 734 if (strpos($templatecontent, "[[" . $field->field->name . "]]") === false) { 735 $otherfields[] = $field; 736 } 737 } 738 $this->otherfields = $otherfields; 739 } 740 741 /** 742 * Returns the ##otherfields## tag replacement for an entry. 743 * 744 * @param stdClass $entry the entry object 745 * @param bool $canmanageentry if the current user can manage this entry 746 * @return string the tag replacement 747 */ 748 protected function get_tag_otherfields_replacement(stdClass $entry, bool $canmanageentry): string { 749 global $OUTPUT; 750 $fields = []; 751 foreach ($this->otherfields as $field) { 752 $fieldvalue = highlight( 753 $this->search, 754 $field->display_browse_field($entry->id, $this->templatename) 755 ); 756 $fieldinfo = [ 757 'fieldname' => $field->field->name, 758 'fieldcontent' => $fieldvalue, 759 ]; 760 $fields[] = $fieldinfo; 761 } 762 return $OUTPUT->render_from_template('mod_data/fields_otherfields', ['fields' => $fields]); 763 } 764 765 /** 766 * Returns the ##actionsmenu## tag replacement for an entry. 767 * 768 * @param stdClass $entry the entry object 769 * @param bool $canmanageentry if the current user can manage this entry 770 * @return string the tag replacement 771 */ 772 protected function get_tag_actionsmenu_replacement(stdClass $entry, bool $canmanageentry): string { 773 global $OUTPUT, $CFG; 774 775 $actionmenu = new action_menu(); 776 $actionmenu->set_kebab_trigger(); 777 $actionmenu->set_action_label(get_string('actions')); 778 $actionmenu->set_additional_classes('entry-actionsmenu'); 779 780 // Show more. 781 if ($this->showmore) { 782 $showmoreurl = new moodle_url($this->baseurl, [ 783 'rid' => $entry->id, 784 'filter' => 1, 785 ]); 786 $actionmenu->add(new action_menu_link_secondary( 787 $showmoreurl, 788 null, 789 get_string('showmore', 'mod_data') 790 )); 791 } 792 793 if ($canmanageentry) { 794 // Edit entry. 795 $backurl = new moodle_url($this->baseurl, [ 796 'rid' => $entry->id, 797 'mode' => 'single', 798 ]); 799 $editurl = new moodle_url('/mod/data/edit.php', $this->baseurl->params()); 800 $editurl->params([ 801 'rid' => $entry->id, 802 'sesskey' => sesskey(), 803 'backto' => urlencode($backurl->out(false)) 804 ]); 805 806 $actionmenu->add(new action_menu_link_secondary( 807 $editurl, 808 null, 809 get_string('edit') 810 )); 811 812 // Delete entry. 813 $deleteurl = new moodle_url($this->baseurl, [ 814 'delete' => $entry->id, 815 'sesskey' => sesskey(), 816 'mode' => 'single', 817 ]); 818 819 $actionmenu->add(new action_menu_link_secondary( 820 $deleteurl, 821 null, 822 get_string('delete') 823 )); 824 } 825 826 // Approve/disapprove entry. 827 $context = $this->manager->get_context(); 828 if (has_capability('mod/data:approve', $context) && $this->instance->approval) { 829 if ($entry->approved) { 830 $disapproveurl = new moodle_url($this->baseurl, [ 831 'disapprove' => $entry->id, 832 'sesskey' => sesskey(), 833 ]); 834 $actionmenu->add(new action_menu_link_secondary( 835 $disapproveurl, 836 null, 837 get_string('disapprove', 'mod_data') 838 )); 839 } else { 840 $approveurl = new moodle_url($this->baseurl, [ 841 'approve' => $entry->id, 842 'sesskey' => sesskey(), 843 ]); 844 $actionmenu->add(new action_menu_link_secondary( 845 $approveurl, 846 null, 847 get_string('approve', 'mod_data') 848 )); 849 } 850 } 851 852 // Export entry to portfolio. 853 if (!empty($CFG->enableportfolios)) { 854 // Check the user can export the entry. 855 $cm = $this->manager->get_coursemodule(); 856 $canexportall = has_capability('mod/data:exportentry', $context); 857 $canexportown = has_capability('mod/data:exportownentry', $context); 858 if ($canexportall || (data_isowner($entry->id) && $canexportown)) { 859 // Add the portfolio export button. 860 require_once($CFG->libdir . '/portfoliolib.php'); 861 $button = new portfolio_add_button(); 862 $button->set_callback_options( 863 'data_portfolio_caller', 864 ['id' => $cm->id, 'recordid' => $entry->id], 865 'mod_data' 866 ); 867 $fields = $this->manager->get_fields(); 868 list($formats, $files) = data_portfolio_caller::formats($fields, $entry); 869 $button->set_formats($formats); 870 $exporturl = $button->to_html(PORTFOLIO_ADD_MOODLE_URL); 871 if (!is_null($exporturl)) { 872 $actionmenu->add(new action_menu_link_secondary( 873 $exporturl, 874 null, 875 get_string('addtoportfolio', 'portfolio') 876 )); 877 } 878 } 879 } 880 881 return $OUTPUT->render($actionmenu); 882 } 883 884 /** 885 * Parse the template as if it was for add entry. 886 * 887 * This method is similar to the parse_entry but it uses the display_add_field method 888 * instead of the display_browse_field. 889 * 890 * @param stdClass|null $processeddata the previous process data information. 891 * @param int|null $entryid the possible entry id 892 * @param stdClass|null $entrydata the entry data from a previous form or from a real entry 893 * @return string the add entry HTML content 894 */ 895 public function parse_add_entry( 896 ?stdClass $processeddata = null, 897 ?int $entryid = null, 898 ?stdClass $entrydata = null 899 ): string { 900 global $OUTPUT; 901 902 $manager = $this->manager; 903 $renderer = $manager->get_renderer(); 904 $templatecontent = $this->templatecontent; 905 906 if (!$processeddata) { 907 $processeddata = (object)[ 908 'generalnotifications' => [], 909 'fieldnotifications' => [], 910 ]; 911 } 912 913 $result = ''; 914 915 foreach ($processeddata->generalnotifications as $notification) { 916 $result .= $renderer->notification($notification); 917 } 918 919 $possiblefields = $manager->get_fields(); 920 $patterns = []; 921 $replacements = []; 922 923 // Then we generate strings to replace. 924 $otherfields = []; 925 foreach ($possiblefields as $field) { 926 $fieldinput = $this->get_field_input($processeddata, $field, $entryid, $entrydata); 927 if (strpos($templatecontent, "[[" . $field->field->name . "]]") !== false) { 928 // Replace the field tag. 929 $patterns[] = "[[" . $field->field->name . "]]"; 930 $replacements[] = $fieldinput; 931 } else { 932 // Is in another fields. 933 $otherfields[] = [ 934 'fieldname' => $field->field->name, 935 'fieldcontent' => $fieldinput, 936 ]; 937 } 938 939 // Replace the field id tag. 940 $patterns[] = "[[" . $field->field->name . "#id]]"; 941 $replacements[] = 'field_' . $field->field->id; 942 $patterns[] = '[[' . $field->field->name . '#name]]'; 943 $replacements[] = $field->field->name; 944 $patterns[] = '[[' . $field->field->name . '#description]]'; 945 $replacements[] = $field->field->description; 946 } 947 948 $patterns[] = "##otherfields##"; 949 if (!empty($otherfields)) { 950 $replacements[] = $OUTPUT->render_from_template( 951 'mod_data/fields_otherfields', 952 ['fields' => $otherfields] 953 ); 954 } else { 955 $replacements[] = ''; 956 } 957 958 if (core_tag_tag::is_enabled('mod_data', 'data_records')) { 959 $patterns[] = "##tags##"; 960 $replacements[] = data_generate_tag_form($entryid); 961 } 962 963 $result .= str_ireplace($patterns, $replacements, $templatecontent); 964 return $result; 965 } 966 967 /** 968 * Return the input form html from a specific field. 969 * 970 * @param stdClass $processeddata the previous process data information. 971 * @param data_field_base $field the field object 972 * @param int|null $entryid the possible entry id 973 * @param stdClass|null $entrydata the entry data from a previous form or from a real entry 974 * @return string the add entry HTML content 975 */ 976 private function get_field_input( 977 stdClass $processeddata, 978 data_field_base $field, 979 ?int $entryid = null, 980 ?stdClass $entrydata = null 981 ): string { 982 $renderer = $this->manager->get_renderer(); 983 $errors = ''; 984 $fieldnotifications = $processeddata->fieldnotifications[$field->field->name] ?? []; 985 if (!empty($fieldnotifications)) { 986 foreach ($fieldnotifications as $notification) { 987 $errors .= $renderer->notification($notification); 988 } 989 } 990 $fielddisplay = ''; 991 if ($field->type === 'unknown') { 992 if ($this->canmanageentries) { // Display notification for users that can manage entries. 993 $errors .= $renderer->notification(get_string( 994 'missingfieldtype', 995 'data', 996 (object)['name' => $field->field->name] 997 )); 998 } 999 } else { 1000 $fielddisplay = $field->display_add_field($entryid, $entrydata); 1001 } 1002 return $errors . $fielddisplay; 1003 } 1004 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body