Merge branch 'MDL-39851-master-squashed' of git://github.com/damyon/moodle
[moodle.git] / lib / editor / tinymce / lib.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/>.
17 /**
18  * TinyMCE text editor integration.
19  *
20  * @package    editor
21  * @subpackage tinymce
22  * @copyright  2009 Petr Skoda (http://skodak.org)
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 class tinymce_texteditor extends texteditor {
29     /** @var string active version - this is the directory name where to find tinymce code */
30     public $version = '3.5.8';
32     /**
33      * Is the current browser supported by this editor?
34      * @return bool
35      */
36     public function supported_by_browser() {
37         if (check_browser_version('MSIE', 6)) {
38             return true;
39         }
40         if (check_browser_version('Gecko', 20030516)) {
41             return true;
42         }
43         if (check_browser_version('Safari', 412)) {
44             return true;
45         }
46         if (check_browser_version('Chrome', 6)) {
47             return true;
48         }
49         if (check_browser_version('Opera', 9)) {
50             return true;
51         }
52         if (check_browser_version('Safari iOS', 534)) {
53             return true;
54         }
55         if (check_browser_version('WebKit', 534)) {
56             return true;
57         }
59         return false;
60     }
62     /**
63      * Returns array of supported text formats.
64      * @return array
65      */
66     public function get_supported_formats() {
67         // FORMAT_MOODLE is not supported here, sorry.
68         return array(FORMAT_HTML => FORMAT_HTML);
69     }
71     /**
72      * Returns text format preferred by this editor.
73      * @return int
74      */
75     public function get_preferred_format() {
76         return FORMAT_HTML;
77     }
79     /**
80      * Does this editor support picking from repositories?
81      * @return bool
82      */
83     public function supports_repositories() {
84         return true;
85     }
87     /**
88      * Sets up head code if necessary.
89      */
90     public function head_setup() {
91     }
93     /**
94      * Use this editor for give element.
95      *
96      * @param string $elementid
97      * @param array $options
98      * @param null $fpoptions
99      */
100     public function use_editor($elementid, array $options=null, $fpoptions=null) {
101         global $PAGE, $CFG;
102         // Note: use full moodle_url instance to prevent standard JS loader, make sure we are using https on profile page if required.
103         if (debugging('', DEBUG_DEVELOPER)) {
104             $PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce_src.js'));
105         } else {
106             $PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce.js'));
107         }
108         $PAGE->requires->js_init_call('M.editor_tinymce.init_editor', array($elementid, $this->get_init_params($elementid, $options)), true);
109         if ($fpoptions) {
110             $PAGE->requires->js_init_call('M.editor_tinymce.init_filepicker', array($elementid, $fpoptions), true);
111         }
112         $this->initialise_collapse_js();
113     }
115     protected function get_init_params($elementid, array $options=null) {
116         global $CFG, $PAGE, $OUTPUT;
118         //TODO: we need to implement user preferences that affect the editor setup too
120         $directionality = get_string('thisdirection', 'langconfig');
121         $strtime        = get_string('strftimetime');
122         $strdate        = get_string('strftimedaydate');
123         $lang           = current_language();
124         $contentcss     = $PAGE->theme->editor_css_url()->out(false);
126         $context = empty($options['context']) ? context_system::instance() : $options['context'];
128         $config = get_config('editor_tinymce');
129         if (!isset($config->disabledsubplugins)) {
130             $config->disabledsubplugins = '';
131         }
133         $fontselectlist = empty($config->fontselectlist) ? '' : $config->fontselectlist;
135         $langrev = -1;
136         if (!empty($CFG->cachejs)) {
137             $langrev = get_string_manager()->get_revision();
138         }
140         $params = array(
141             'moodle_config' => $config,
142             'mode' => "exact",
143             'elements' => $elementid,
144             'relative_urls' => false,
145             'document_base_url' => $CFG->httpswwwroot,
146             'moodle_plugin_base' => "$CFG->httpswwwroot/lib/editor/tinymce/plugins/",
147             'content_css' => $contentcss,
148             'language' => $lang,
149             'directionality' => $directionality,
150             'plugin_insertdate_dateFormat ' => $strdate,
151             'plugin_insertdate_timeFormat ' => $strtime,
152             'theme' => "advanced",
153             'skin' => "o2k7",
154             'skin_variant' => "silver",
155             'apply_source_formatting' => true,
156             'remove_script_host' => false,
157             'entity_encoding' => "raw",
158             'plugins' => 'lists,table,style,layer,advhr,advlink,emotions,inlinepopups,' .
159                 'searchreplace,paste,directionality,fullscreen,nonbreaking,contextmenu,' .
160                 'insertdatetime,save,iespell,preview,print,noneditable,visualchars,' .
161                 'xhtmlxtras,template,pagebreak',
162             'gecko_spellcheck' => true,
163             'theme_advanced_font_sizes' => "1,2,3,4,5,6,7",
164             'theme_advanced_layout_manager' => "SimpleLayout",
165             'theme_advanced_toolbar_align' => "left",
166             'theme_advanced_fonts' => $fontselectlist,
167             'theme_advanced_resize_horizontal' => true,
168             'theme_advanced_resizing' => true,
169             'theme_advanced_resizing_min_height' => 30,
170             'min_height' => 30,
171             'theme_advanced_toolbar_location' => "top",
172             'theme_advanced_statusbar_location' => "bottom",
173             'language_load' => false, // We load all lang strings directly from Moodle.
174             'langrev' => $langrev,
175         );
177         // Should we override the default toolbar layout unconditionally?
178         if (!empty($config->customtoolbar) and $customtoolbar = self::parse_toolbar_setting($config->customtoolbar)) {
179             $i = 1;
180             foreach ($customtoolbar as $line) {
181                 $params['theme_advanced_buttons'.$i] = $line;
182                 $i++;
183             }
184         } else {
185             // At least one line is required.
186             $params['theme_advanced_buttons1'] = '';
187         }
189         if (!empty($config->customconfig)) {
190             $config->customconfig = trim($config->customconfig);
191             $decoded = json_decode($config->customconfig, true);
192             if (is_array($decoded)) {
193                 foreach ($decoded as $k=>$v) {
194                     $params[$k] = $v;
195                 }
196             }
197         }
199         if (!empty($options['legacy']) or !empty($options['noclean']) or !empty($options['trusted'])) {
200             // now deal somehow with non-standard tags, people scream when we do not make moodle code xtml strict,
201             // but they scream even more when we strip all tags that are not strict :-(
202             $params['valid_elements'] = 'script[src|type],*[*]'; // for some reason the *[*] does not inlcude javascript src attribute MDL-25836
203             $params['invalid_elements'] = '';
204         }
205         // Add unique moodle elements - unfortunately we have to decide if these are SPANs or DIVs.
206         $params['extended_valid_elements'] = 'nolink,tex,algebra,lang[lang]';
207         $params['custom_elements'] = 'nolink,~tex,~algebra,lang';
209         //Add onblur event for client side text validation
210         if (!empty($options['required'])) {
211             $params['init_instance_callback'] = 'M.editor_tinymce.onblur_event';
212         }
214         // Allow plugins to adjust parameters.
215         editor_tinymce_plugin::all_update_init_params($params, $context, $options);
217         // Remove temporary parameters.
218         unset($params['moodle_config']);
220         return $params;
221     }
223     /**
224      * Parse the custom toolbar setting.
225      * @param string $customtoolbar
226      * @return array csv toolbar lines
227      */
228     public static function parse_toolbar_setting($customtoolbar) {
229         $result = array();
230         $customtoolbar = trim($customtoolbar);
231         if ($customtoolbar === '') {
232             return $result;
233         }
234         $customtoolbar = str_replace("\r", "\n", $customtoolbar);
235         $customtoolbar = strtolower($customtoolbar);
236         $i = 0;
237         foreach (explode("\n", $customtoolbar) as $line) {
238             $line = preg_replace('/[^a-z0-9_,\|\-]/', ',', $line);
239             $line = str_replace('|', ',|,', $line);
240             $line = preg_replace('/,,+/', ',', $line);
241             $line = trim($line, ',|');
242             if ($line === '') {
243                 continue;
244             }
245             if ($i == 10) {
246                 // Maximum is ten lines, merge the rest to the last line.
247                 $result[9] = $result[9].','.$line;
248             } else {
249                 $result[] = $line;
250                 $i++;
251             }
252         }
253         return $result;
254     }
256     /**
257      * Gets a named plugin object. Will cause fatal error if plugin doesn't
258      * exist. This is intended for use by plugin files themselves.
259      *
260      * @param string $plugin Name of plugin e.g. 'moodleemoticon'
261      * @return editor_tinymce_plugin Plugin object
262      */
263     public function get_plugin($plugin) {
264         global $CFG;
265         return editor_tinymce_plugin::get($plugin);
266     }
268     /**
269      * Equivalent to tinyMCE.baseURL value available from JavaScript,
270      * always use instead of /../ when referencing tinymce core code from moodle plugins!
271      *
272      * @return moodle_url url pointing to the root of TinyMCE javascript code.
273      */
274     public function get_tinymce_base_url() {
275         global $CFG;
276         return new moodle_url("$CFG->httpswwwroot/lib/editor/tinymce/tiny_mce/$this->version/");
277     }
279     /**
280      * Initialise javascript form elements
281      * @return void
282      */
283     public function initialise_collapse_js() {
284         global $PAGE;
285         // This method is called for every editor instance. Ensure it's only run once.
286         // Static is a clunky solution but the best we could find to keep everything simple and encapsulated.
287         static $isinitialised;
288         if ($isinitialised) {
289             return;
290         }
292         // Initialise language strings.
293         $PAGE->requires->strings_for_js(array('hideeditortoolbar', 'showeditortoolbar'), 'form');
294         $PAGE->requires->yui_module('moodle-editor_tinymce-collapse', 'M.editor_collapse.init');
295         $isinitialised = true;
296     }