Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 format
 253       *
 254       * @return int.
 255       */
 256      function getFormat() {
 257          return $this->_values['format'];
 258      }
 259  
 260      /**
 261       * Checks if editor used is a required field
 262       *
 263       * @return bool true if required field.
 264       */
 265      function isRequired() {
 266          return (isset($this->_options['required']) && $this->_options['required']);
 267      }
 268  
 269      /**
 270       * @deprecated since Moodle 2.0
 271       */
 272      function setHelpButton($_helpbuttonargs, $function='_helpbutton') {
 273          throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
 274      }
 275  
 276      /**
 277       * Returns html for help button.
 278       *
 279       * @return string html for help button
 280       */
 281      function getHelpButton() {
 282          return $this->_helpbutton;
 283      }
 284  
 285      /**
 286       * Returns type of editor element
 287       *
 288       * @return string
 289       */
 290      function getElementTemplateType() {
 291          if ($this->_flagFrozen){
 292              return 'nodisplay';
 293          } else {
 294              return 'default';
 295          }
 296      }
 297  
 298      /**
 299       * Returns HTML for editor form element.
 300       *
 301       * @return string
 302       */
 303      function toHtml() {
 304          global $CFG, $PAGE, $OUTPUT;
 305          require_once($CFG->dirroot.'/repository/lib.php');
 306  
 307          if ($this->_flagFrozen) {
 308              return $this->getFrozenHtml();
 309          }
 310  
 311          $ctx = $this->_options['context'];
 312  
 313          $id           = $this->_attributes['id'];
 314          $elname       = $this->_attributes['name'];
 315  
 316          $subdirs      = $this->_options['subdirs'];
 317          $maxbytes     = $this->_options['maxbytes'];
 318          $areamaxbytes = $this->_options['areamaxbytes'];
 319          $maxfiles     = $this->_options['maxfiles'];
 320          $changeformat = $this->_options['changeformat']; // TO DO: implement as ajax calls
 321  
 322          $text         = $this->_values['text'];
 323          $format       = $this->_values['format'];
 324          $draftitemid  = $this->_values['itemid'];
 325  
 326          // security - never ever allow guest/not logged in user to upload anything
 327          if (isguestuser() or !isloggedin()) {
 328              $maxfiles = 0;
 329          }
 330  
 331          $str = $this->_getTabs();
 332          $str .= '<div>';
 333  
 334          $editor = editors_get_preferred_editor($format);
 335          $strformats = format_text_menu();
 336          $formats =  $editor->get_supported_formats();
 337          foreach ($formats as $fid) {
 338              $formats[$fid] = $strformats[$fid];
 339          }
 340  
 341          // get filepicker info
 342          //
 343          $fpoptions = array();
 344          if ($maxfiles != 0 ) {
 345              if (empty($draftitemid)) {
 346                  // no existing area info provided - let's use fresh new draft area
 347                  require_once("$CFG->libdir/filelib.php");
 348                  $this->setValue(array('itemid'=>file_get_unused_draft_itemid()));
 349                  $draftitemid = $this->_values['itemid'];
 350              }
 351  
 352              $args = new stdClass();
 353              // need these three to filter repositories list
 354              $args->accepted_types = array('web_image');
 355              $args->return_types = $this->_options['return_types'];
 356              $args->context = $ctx;
 357              $args->env = 'filepicker';
 358              // advimage plugin
 359              $image_options = initialise_filepicker($args);
 360              $image_options->context = $ctx;
 361              $image_options->client_id = uniqid();
 362              $image_options->maxbytes = $this->_options['maxbytes'];
 363              $image_options->areamaxbytes = $this->_options['areamaxbytes'];
 364              $image_options->env = 'editor';
 365              $image_options->itemid = $draftitemid;
 366  
 367              // moodlemedia plugin
 368              $args->accepted_types = array('video', 'audio');
 369              $media_options = initialise_filepicker($args);
 370              $media_options->context = $ctx;
 371              $media_options->client_id = uniqid();
 372              $media_options->maxbytes  = $this->_options['maxbytes'];
 373              $media_options->areamaxbytes  = $this->_options['areamaxbytes'];
 374              $media_options->env = 'editor';
 375              $media_options->itemid = $draftitemid;
 376  
 377              // advlink plugin
 378              $args->accepted_types = '*';
 379              $link_options = initialise_filepicker($args);
 380              $link_options->context = $ctx;
 381              $link_options->client_id = uniqid();
 382              $link_options->maxbytes  = $this->_options['maxbytes'];
 383              $link_options->areamaxbytes  = $this->_options['areamaxbytes'];
 384              $link_options->env = 'editor';
 385              $link_options->itemid = $draftitemid;
 386  
 387              $args->accepted_types = array('.vtt');
 388              $subtitle_options = initialise_filepicker($args);
 389              $subtitle_options->context = $ctx;
 390              $subtitle_options->client_id = uniqid();
 391              $subtitle_options->maxbytes  = $this->_options['maxbytes'];
 392              $subtitle_options->areamaxbytes  = $this->_options['areamaxbytes'];
 393              $subtitle_options->env = 'editor';
 394              $subtitle_options->itemid = $draftitemid;
 395  
 396              if (has_capability('moodle/h5p:deploy', $ctx)) {
 397                  // Only set H5P Plugin settings if the user can deploy new H5P content.
 398                  // H5P plugin.
 399                  $args->accepted_types = array('.h5p');
 400                  $h5poptions = initialise_filepicker($args);
 401                  $h5poptions->context = $ctx;
 402                  $h5poptions->client_id = uniqid();
 403                  $h5poptions->maxbytes  = $this->_options['maxbytes'];
 404                  $h5poptions->areamaxbytes  = $this->_options['areamaxbytes'];
 405                  $h5poptions->env = 'editor';
 406                  $h5poptions->itemid = $draftitemid;
 407                  $fpoptions['h5p'] = $h5poptions;
 408              }
 409  
 410              $fpoptions['image'] = $image_options;
 411              $fpoptions['media'] = $media_options;
 412              $fpoptions['link'] = $link_options;
 413              $fpoptions['subtitle'] = $subtitle_options;
 414          }
 415  
 416          //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
 417          if (($editor instanceof tinymce_texteditor)  && !is_null($this->getAttribute('onchange'))) {
 418              $this->_options['required'] = true;
 419          }
 420  
 421          // print text area - TODO: add on-the-fly switching, size configuration, etc.
 422          $editor->set_text($text);
 423          $editor->use_editor($id, $this->_options, $fpoptions);
 424  
 425          $rows = empty($this->_attributes['rows']) ? 15 : $this->_attributes['rows'];
 426          $cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
 427  
 428          //Apply editor validation if required field
 429          $context = [];
 430          $context['rows'] = $rows;
 431          $context['cols'] = $cols;
 432          $context['frozen'] = $this->_flagFrozen;
 433          foreach ($this->getAttributes() as $name => $value) {
 434              $context[$name] = $value;
 435          }
 436          $context['hasformats'] = count($formats) > 1;
 437          $context['formats'] = [];
 438          if (($format === '' || $format === null) && count($formats)) {
 439              $format = key($formats);
 440          }
 441          foreach ($formats as $formatvalue => $formattext) {
 442              $context['formats'][] = ['value' => $formatvalue, 'text' => $formattext, 'selected' => ($formatvalue == $format)];
 443          }
 444          $context['id'] = $id;
 445          $context['value'] = $text;
 446          $context['format'] = $format;
 447          $context['formatlabel'] = get_string('editorxformat', 'editor', $this->_label);
 448  
 449          if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
 450              $context['changelistener'] = true;
 451          }
 452  
 453          $str .= $OUTPUT->render_from_template('core_form/editor_textarea', $context);
 454  
 455          // during moodle installation, user area doesn't exist
 456          // so we need to disable filepicker here.
 457          if (!during_initial_install() && empty($CFG->adminsetuppending)) {
 458              // 0 means no files, -1 unlimited
 459              if ($maxfiles != 0 ) {
 460                  $str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
 461                          'value' => $draftitemid));
 462  
 463                  // used by non js editor only
 464                  $editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
 465                      'action'=>'browse',
 466                      'env'=>'editor',
 467                      'itemid'=>$draftitemid,
 468                      'subdirs'=>$subdirs,
 469                      'maxbytes'=>$maxbytes,
 470                      'areamaxbytes' => $areamaxbytes,
 471                      'maxfiles'=>$maxfiles,
 472                      'ctx_id'=>$ctx->id,
 473                      'course'=>$PAGE->course->id,
 474                      'sesskey'=>sesskey(),
 475                      ));
 476                  $str .= '<noscript>';
 477                  $str .= "<div><object type='text/html' data='$editorurl' height='160' width='600' style='border:1px solid #000'></object></div>";
 478                  $str .= '</noscript>';
 479              }
 480          }
 481  
 482  
 483          $str .= '</div>';
 484  
 485          return $str;
 486      }
 487  
 488      public function export_for_template(renderer_base $output) {
 489          $context = $this->export_for_template_base($output);
 490          $context['html'] = $this->toHtml();
 491          return $context;
 492      }
 493  
 494      /**
 495       * What to display when element is frozen.
 496       *
 497       * @return empty string
 498       */
 499      function getFrozenHtml() {
 500  
 501          return '';
 502      }
 503  
 504      /**
 505       * Sets label to be hidden.
 506       *
 507       * @param bool $hiddenLabel Whether the label should be hidden or not.
 508       * @return void
 509       */
 510      function setHiddenLabel($hiddenLabel) {
 511          $this->_hiddenLabel = $hiddenLabel;
 512      }
 513  }