Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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