Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [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  
  18  /**
  19   * Editor input element
  20   *
  21   * Contains class to create preffered editor form element
  22   *
  23   * @package   core_form
  24   * @copyright 2009 Petr Skoda {@link http://skodak.org}
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  global $CFG;
  29  
  30  require_once('HTML/QuickForm/element.php');
  31  require_once($CFG->dirroot.'/lib/filelib.php');
  32  require_once($CFG->dirroot.'/repository/lib.php');
  33  require_once ('templatable_form_element.php');
  34  
  35  /**
  36   * Editor element
  37   *
  38   * It creates preffered editor (textbox/TinyMce) form element for the format (Text/HTML) selected.
  39   *
  40   * @package   core_form
  41   * @category  form
  42   * @copyright 2009 Petr Skoda {@link http://skodak.org}
  43   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   * @todo      MDL-29421 element Freezing
  45   * @todo      MDL-29426 ajax format conversion
  46   */
  47  class MoodleQuickForm_editor extends HTML_QuickForm_element implements templatable {
  48      use templatable_form_element {
  49          export_for_template as export_for_template_base;
  50      }
  51  
  52      /** @var string html for help button, if empty then no help will icon will be dispalyed. */
  53      public $_helpbutton = '';
  54  
  55      /** @var string defines the type of editor */
  56      public $_type       = 'editor';
  57  
  58      /** @var array options provided to initalize filepicker */
  59      protected $_options = array('subdirs' => 0, 'maxbytes' => 0, 'maxfiles' => 0, 'changeformat' => 0,
  60              'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED, 'context' => null, 'noclean' => 0, 'trusttext' => 0,
  61              'return_types' => 15, 'enable_filemanagement' => true, 'removeorphaneddrafts' => false, 'autosave' => true);
  62      // 15 is $_options['return_types'] = FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK.
  63  
  64      /** @var array values for editor */
  65      protected $_values     = array('text'=>null, 'format'=>null, 'itemid'=>null);
  66  
  67      /** @var bool if true label will be hidden */
  68      protected $_hiddenLabel = false;
  69  
  70      /**
  71       * Constructor
  72       *
  73       * @param string $elementName (optional) name of the editor
  74       * @param string $elementLabel (optional) editor label
  75       * @param array $attributes (optional) Either a typical HTML attribute string
  76       *              or an associative array
  77       * @param array $options set of options to initalize filepicker
  78       */
  79      public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
  80          global $CFG, $PAGE;
  81  
  82          $options = (array)$options;
  83          foreach ($options as $name=>$value) {
  84              if (array_key_exists($name, $this->_options)) {
  85                  $this->_options[$name] = $value;
  86              }
  87          }
  88          if (!empty($options['maxbytes'])) {
  89              $this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $options['maxbytes']);
  90          }
  91          if (!$this->_options['context']) {
  92              // trying to set context to the current page context to make legacy files show in filepicker (e.g. forum post)
  93              if (!empty($PAGE->context->id)) {
  94                  $this->_options['context'] = $PAGE->context;
  95              } else {
  96                  $this->_options['context'] = context_system::instance();
  97              }
  98          }
  99          $this->_options['trusted'] = trusttext_trusted($this->_options['context']);
 100          parent::__construct($elementName, $elementLabel, $attributes);
 101  
 102          // Note: for some reason the code using this setting does not like bools.
 103          $this->_options['subdirs'] = (int)($this->_options['subdirs'] == 1);
 104  
 105          editors_head_setup();
 106      }
 107  
 108      /**
 109       * Old syntax of class constructor. Deprecated in PHP7.
 110       *
 111       * @deprecated since Moodle 3.1
 112       */
 113      public function MoodleQuickForm_editor($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
 114          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 115          self::__construct($elementName, $elementLabel, $attributes, $options);
 116      }
 117  
 118      /**
 119       * Called by HTML_QuickForm whenever form event is made on this element
 120       *
 121       * @param string $event Name of event
 122       * @param mixed $arg event arguments
 123       * @param object $caller calling object
 124       * @return bool
 125       */
 126      function onQuickFormEvent($event, $arg, &$caller)
 127      {
 128          switch ($event) {
 129              case 'createElement':
 130                  $caller->setType($arg[0] . '[format]', PARAM_ALPHANUM);
 131                  $caller->setType($arg[0] . '[itemid]', PARAM_INT);
 132                  break;
 133          }
 134          return parent::onQuickFormEvent($event, $arg, $caller);
 135      }
 136  
 137      /**
 138       * Sets name of editor
 139       *
 140       * @param string $name name of the editor
 141       */
 142      function setName($name) {
 143          $this->updateAttributes(array('name'=>$name));
 144      }
 145  
 146      /**
 147       * Returns name of element
 148       *
 149       * @return string
 150       */
 151      function getName() {
 152          return $this->getAttribute('name');
 153      }
 154  
 155      /**
 156       * Updates editor values, if part of $_values
 157       *
 158       * @param array $values associative array of values to set
 159       */
 160      function setValue($values) {
 161          $values = (array)$values;
 162          foreach ($values as $name=>$value) {
 163              if (array_key_exists($name, $this->_values)) {
 164                  $this->_values[$name] = $value;
 165              }
 166          }
 167      }
 168  
 169      /**
 170       * Returns editor values
 171       *
 172       * @return array
 173       */
 174      function getValue() {
 175          return $this->_values;
 176      }
 177  
 178      /**
 179       * Returns maximum file size which can be uploaded
 180       *
 181       * @return int
 182       */
 183      function getMaxbytes() {
 184          return $this->_options['maxbytes'];
 185      }
 186  
 187      /**
 188       * Sets maximum file size which can be uploaded
 189       *
 190       * @param int $maxbytes file size
 191       */
 192      function setMaxbytes($maxbytes) {
 193          global $CFG;
 194          $this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $maxbytes);
 195      }
 196  
 197       /**
 198       * Returns the maximum size of the area.
 199       *
 200       * @return int
 201       */
 202      function getAreamaxbytes() {
 203          return $this->_options['areamaxbytes'];
 204      }
 205  
 206      /**
 207       * Sets the maximum size of the area.
 208       *
 209       * @param int $areamaxbytes size limit
 210       */
 211      function setAreamaxbytes($areamaxbytes) {
 212          $this->_options['areamaxbytes'] = $areamaxbytes;
 213      }
 214  
 215      /**
 216       * Returns maximum number of files which can be uploaded
 217       *
 218       * @return int
 219       */
 220      function getMaxfiles() {
 221          return $this->_options['maxfiles'];
 222      }
 223  
 224      /**
 225       * Sets maximum number of files which can be uploaded.
 226       *
 227       * @param int $num number of files
 228       */
 229      function setMaxfiles($num) {
 230          $this->_options['maxfiles'] = $num;
 231      }
 232  
 233      /**
 234       * Returns true if subdirectoy can be created, else false
 235       *
 236       * @return bool
 237       */
 238      function getSubdirs() {
 239          return $this->_options['subdirs'];
 240      }
 241  
 242      /**
 243       * Set option to create sub directory, while uploading  file
 244       *
 245       * @param bool $allow true if sub directory can be created.
 246       */
 247      function setSubdirs($allow) {
 248          $this->_options['subdirs'] = (int)($allow == 1);
 249      }
 250  
 251      /**
 252       * Returns editor text content
 253       *
 254       * @return string Text content
 255       */
 256      public function get_text(): string {
 257          return $this->_values['text'];
 258      }
 259  
 260      /**
 261       * Returns editor format
 262       *
 263       * @return int.
 264       */
 265      function getFormat() {
 266          return $this->_values['format'];
 267      }
 268  
 269      /**
 270       * Checks if editor used is a required field
 271       *
 272       * @return bool true if required field.
 273       */
 274      function isRequired() {
 275          return (isset($this->_options['required']) && $this->_options['required']);
 276      }
 277  
 278      /**
 279       * @deprecated since Moodle 2.0
 280       */
 281      function setHelpButton($_helpbuttonargs, $function='_helpbutton') {
 282          throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
 283      }
 284  
 285      /**
 286       * Returns html for help button.
 287       *
 288       * @return string html for help button
 289       */
 290      function getHelpButton() {
 291          return $this->_helpbutton;
 292      }
 293  
 294      /**
 295       * Returns type of editor element
 296       *
 297       * @return string
 298       */
 299      function getElementTemplateType() {
 300          if ($this->_flagFrozen){
 301              return 'nodisplay';
 302          } else {
 303              return 'default';
 304          }
 305      }
 306  
 307      /**
 308       * Returns HTML for editor form element.
 309       *
 310       * @return string
 311       */
 312      function toHtml() {
 313          global $CFG, $PAGE, $OUTPUT;
 314          require_once($CFG->dirroot.'/repository/lib.php');
 315  
 316          if ($this->_flagFrozen) {
 317              return $this->getFrozenHtml();
 318          }
 319  
 320          $ctx = $this->_options['context'];
 321  
 322          $id           = $this->_attributes['id'];
 323          $elname       = $this->_attributes['name'];
 324  
 325          $subdirs      = $this->_options['subdirs'];
 326          $maxbytes     = $this->_options['maxbytes'];
 327          $areamaxbytes = $this->_options['areamaxbytes'];
 328          $maxfiles     = $this->_options['maxfiles'];
 329          $changeformat = $this->_options['changeformat']; // TO DO: implement as ajax calls
 330  
 331          $text         = $this->_values['text'];
 332          $format       = $this->_values['format'];
 333          $draftitemid  = $this->_values['itemid'];
 334  
 335          // security - never ever allow guest/not logged in user to upload anything
 336          if (isguestuser() or !isloggedin()) {
 337              $maxfiles = 0;
 338          }
 339  
 340          $str = $this->_getTabs();
 341          $str .= '<div>';
 342  
 343          $editor = editors_get_preferred_editor($format);
 344          $strformats = format_text_menu();
 345          $formats =  $editor->get_supported_formats();
 346          foreach ($formats as $fid) {
 347              $formats[$fid] = $strformats[$fid];
 348          }
 349  
 350          // get filepicker info
 351          //
 352          $fpoptions = array();
 353          if ($maxfiles != 0 ) {
 354              if (empty($draftitemid)) {
 355                  // no existing area info provided - let's use fresh new draft area
 356                  require_once("$CFG->libdir/filelib.php");
 357                  $this->setValue(array('itemid'=>file_get_unused_draft_itemid()));
 358                  $draftitemid = $this->_values['itemid'];
 359              }
 360  
 361              $args = new stdClass();
 362              // need these three to filter repositories list
 363              $args->accepted_types = array('web_image');
 364              $args->return_types = $this->_options['return_types'];
 365              $args->context = $ctx;
 366              $args->env = 'filepicker';
 367              // advimage plugin
 368              $image_options = initialise_filepicker($args);
 369              $image_options->context = $ctx;
 370              $image_options->client_id = uniqid();
 371              $image_options->maxbytes = $this->_options['maxbytes'];
 372              $image_options->areamaxbytes = $this->_options['areamaxbytes'];
 373              $image_options->env = 'editor';
 374              $image_options->itemid = $draftitemid;
 375  
 376              // moodlemedia plugin
 377              $args->accepted_types = array('video', 'audio');
 378              $media_options = initialise_filepicker($args);
 379              $media_options->context = $ctx;
 380              $media_options->client_id = uniqid();
 381              $media_options->maxbytes  = $this->_options['maxbytes'];
 382              $media_options->areamaxbytes  = $this->_options['areamaxbytes'];
 383              $media_options->env = 'editor';
 384              $media_options->itemid = $draftitemid;
 385  
 386              // advlink plugin
 387              $args->accepted_types = '*';
 388              $link_options = initialise_filepicker($args);
 389              $link_options->context = $ctx;
 390              $link_options->client_id = uniqid();
 391              $link_options->maxbytes  = $this->_options['maxbytes'];
 392              $link_options->areamaxbytes  = $this->_options['areamaxbytes'];
 393              $link_options->env = 'editor';
 394              $link_options->itemid = $draftitemid;
 395  
 396              $args->accepted_types = array('.vtt');
 397              $subtitle_options = initialise_filepicker($args);
 398              $subtitle_options->context = $ctx;
 399              $subtitle_options->client_id = uniqid();
 400              $subtitle_options->maxbytes  = $this->_options['maxbytes'];
 401              $subtitle_options->areamaxbytes  = $this->_options['areamaxbytes'];
 402              $subtitle_options->env = 'editor';
 403              $subtitle_options->itemid = $draftitemid;
 404  
 405              if (has_capability('moodle/h5p:deploy', $ctx)) {
 406                  // Only set H5P Plugin settings if the user can deploy new H5P content.
 407                  // H5P plugin.
 408                  $args->accepted_types = array('.h5p');
 409                  $h5poptions = initialise_filepicker($args);
 410                  $h5poptions->context = $ctx;
 411                  $h5poptions->client_id = uniqid();
 412                  $h5poptions->maxbytes  = $this->_options['maxbytes'];
 413                  $h5poptions->areamaxbytes  = $this->_options['areamaxbytes'];
 414                  $h5poptions->env = 'editor';
 415                  $h5poptions->itemid = $draftitemid;
 416                  $fpoptions['h5p'] = $h5poptions;
 417              }
 418  
 419              $fpoptions['image'] = $image_options;
 420              $fpoptions['media'] = $media_options;
 421              $fpoptions['link'] = $link_options;
 422              $fpoptions['subtitle'] = $subtitle_options;
 423          }
 424  
 425          //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
 426          if (($editor instanceof tinymce_texteditor)  && !is_null($this->getAttribute('onchange'))) {
 427              $this->_options['required'] = true;
 428          }
 429  
 430          // print text area - TODO: add on-the-fly switching, size configuration, etc.
 431          $editor->set_text($text);
 432          $editor->use_editor($id, $this->_options, $fpoptions);
 433  
 434          $rows = empty($this->_attributes['rows']) ? 15 : $this->_attributes['rows'];
 435          $cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
 436  
 437          //Apply editor validation if required field
 438          $context = [];
 439          $context['rows'] = $rows;
 440          $context['cols'] = $cols;
 441          $context['frozen'] = $this->_flagFrozen;
 442          foreach ($this->getAttributes() as $name => $value) {
 443              $context[$name] = $value;
 444          }
 445          $context['hasformats'] = count($formats) > 1;
 446          $context['formats'] = [];
 447          if (($format === '' || $format === null) && count($formats)) {
 448              $format = key($formats);
 449          }
 450          foreach ($formats as $formatvalue => $formattext) {
 451              $context['formats'][] = ['value' => $formatvalue, 'text' => $formattext, 'selected' => ($formatvalue == $format)];
 452          }
 453          $context['id'] = $id;
 454          $context['value'] = $text;
 455          $context['format'] = $format;
 456          $context['formatlabel'] = get_string('editorxformat', 'editor', $this->_label);
 457  
 458          if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
 459              $context['changelistener'] = true;
 460          }
 461  
 462          $str .= $OUTPUT->render_from_template('core_form/editor_textarea', $context);
 463  
 464          // during moodle installation, user area doesn't exist
 465          // so we need to disable filepicker here.
 466          if (!during_initial_install() && empty($CFG->adminsetuppending)) {
 467              // 0 means no files, -1 unlimited
 468              if ($maxfiles != 0 ) {
 469                  $str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
 470                          'value' => $draftitemid));
 471  
 472                  // used by non js editor only
 473                  $editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
 474                      'action'=>'browse',
 475                      'env'=>'editor',
 476                      'itemid'=>$draftitemid,
 477                      'subdirs'=>$subdirs,
 478                      'maxbytes'=>$maxbytes,
 479                      'areamaxbytes' => $areamaxbytes,
 480                      'maxfiles'=>$maxfiles,
 481                      'ctx_id'=>$ctx->id,
 482                      'course'=>$PAGE->course->id,
 483                      'sesskey'=>sesskey(),
 484                      ));
 485                  $str .= '<noscript>';
 486                  $str .= "<div><object type='text/html' data='$editorurl' height='160' width='600' style='border:1px solid #000'></object></div>";
 487                  $str .= '</noscript>';
 488              }
 489          }
 490  
 491  
 492          $str .= '</div>';
 493  
 494          return $str;
 495      }
 496  
 497      public function export_for_template(renderer_base $output) {
 498          $context = $this->export_for_template_base($output);
 499          $context['html'] = $this->toHtml();
 500          return $context;
 501      }
 502  
 503      /**
 504       * Returns the formatted value. The return from parent class is not acceptable.
 505       *
 506       * @return string
 507       */
 508      public function getFrozenHtml(): string {
 509          return format_text($this->get_text(), $this->getFormat()) . $this->_getPersistantData();
 510      }
 511  
 512      /**
 513       * Sets label to be hidden.
 514       *
 515       * @param bool $hiddenLabel Whether the label should be hidden or not.
 516       * @return void
 517       */
 518      function setHiddenLabel($hiddenLabel) {
 519          $this->_hiddenLabel = $hiddenLabel;
 520      }
 521  }