204931688ac816403f8023466b5077b54159a16b
[moodle.git] / lib / form / editor.php
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/>.
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  */
28 global $CFG;
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');
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     }
52     /** @var string html for help button, if empty then no help will icon will be dispalyed. */
53     public $_helpbutton = '';
55     /** @var string defines the type of editor */
56     public $_type       = 'editor';
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' => 7, 'enable_filemanagement' => true);
62     // $_options['return_types'] = FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE
64     /** @var array values for editor */
65     protected $_values     = array('text'=>null, 'format'=>null, 'itemid'=>null);
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;
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);
99         // Note: for some reason the code using this setting does not like bools.
100         $this->_options['subdirs'] = (int)($this->_options['subdirs'] == 1);
102         editors_head_setup();
103     }
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     }
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     }
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     }
143     /**
144      * Returns name of element
145      *
146      * @return string
147      */
148     function getName() {
149         return $this->getAttribute('name');
150     }
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     }
166     /**
167      * Returns editor values
168      *
169      * @return array
170      */
171     function getValue() {
172         return $this->_values;
173     }
175     /**
176      * Returns maximum file size which can be uploaded
177      *
178      * @return int
179      */
180     function getMaxbytes() {
181         return $this->_options['maxbytes'];
182     }
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     }
194      /**
195      * Returns the maximum size of the area.
196      *
197      * @return int
198      */
199     function getAreamaxbytes() {
200         return $this->_options['areamaxbytes'];
201     }
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     }
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     }
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     }
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     }
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     }
248     /**
249      * Returns editor format
250      *
251      * @return int.
252      */
253     function getFormat() {
254         return $this->_values['format'];
255     }
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     }
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     }
273     /**
274      * Returns html for help button.
275      *
276      * @return string html for help button
277      */
278     function getHelpButton() {
279         return $this->_helpbutton;
280     }
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     }
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');
304         if ($this->_flagFrozen) {
305             return $this->getFrozenHtml();
306         }
308         $ctx = $this->_options['context'];
310         $id           = $this->_attributes['id'];
311         $elname       = $this->_attributes['name'];
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
319         $text         = $this->_values['text'];
320         $format       = $this->_values['format'];
321         $draftitemid  = $this->_values['itemid'];
323         // security - never ever allow guest/not logged in user to upload anything
324         if (isguestuser() or !isloggedin()) {
325             $maxfiles = 0;
326         }
328         $str = $this->_getTabs();
329         $str .= '<div>';
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         }
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             }
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;
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;
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;
384             $fpoptions['image'] = $image_options;
385             $fpoptions['media'] = $media_options;
386             $fpoptions['link'] = $link_options;
387         }
389         //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
390         if (($editor instanceof tinymce_texteditor)  && !is_null($this->getAttribute('onchange'))) {
391             $this->_options['required'] = true;
392         }
394         // print text area - TODO: add on-the-fly switching, size configuration, etc.
395         $editor->set_text($text);
396         $editor->use_editor($id, $this->_options, $fpoptions);
398         $rows = empty($this->_attributes['rows']) ? 15 : $this->_attributes['rows'];
399         $cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
401         //Apply editor validation if required field
402         $context = [];
403         $context['rows'] = $rows;
404         $context['cols'] = $cols;
405         $context['frozen'] = $this->_flagFrozen;
406         foreach ($this->getAttributes() as $name => $value) {
407             $context[$name] = $value;
408         }
409         $context['hasformats'] = count($formats) > 1;
410         $context['formats'] = [];
411         foreach ($formats as $value => $text) {
412             $context['formats'][] = ['value' => $value, 'text' => $text, 'selected' => ($value == $format)];
413         }
414         $context['id'] = $id;
415         $context['value'] = $text;
416         $context['format'] = $format;
418         $str .= $OUTPUT->render_from_template('core_form/editor_textarea', $context);
420         if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
421             $context['changelistener'] = true;
422         }
424         // during moodle installation, user area doesn't exist
425         // so we need to disable filepicker here.
426         if (!during_initial_install() && empty($CFG->adminsetuppending)) {
427             // 0 means no files, -1 unlimited
428             if ($maxfiles != 0 ) {
429                 $str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
430                         'value' => $draftitemid));
432                 // used by non js editor only
433                 $editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
434                     'action'=>'browse',
435                     'env'=>'editor',
436                     'itemid'=>$draftitemid,
437                     'subdirs'=>$subdirs,
438                     'maxbytes'=>$maxbytes,
439                     'areamaxbytes' => $areamaxbytes,
440                     'maxfiles'=>$maxfiles,
441                     'ctx_id'=>$ctx->id,
442                     'course'=>$PAGE->course->id,
443                     'sesskey'=>sesskey(),
444                     ));
445                 $str .= '<noscript>';
446                 $str .= "<div><object type='text/html' data='$editorurl' height='160' width='600' style='border:1px solid #000'></object></div>";
447                 $str .= '</noscript>';
448             }
449         }
452         $str .= '</div>';
454         return $str;
455     }
457     public function export_for_template(renderer_base $output) {
458         $context = $this->export_for_template_base($output);
459         $context['html'] = $this->toHtml();
460         return $context;
461     }
463     /**
464      * What to display when element is frozen.
465      *
466      * @return empty string
467      */
468     function getFrozenHtml() {
470         return '';
471     }