See Release Notes
Long Term Support Release
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 defined('MOODLE_INTERNAL') || die(); 18 19 /** 20 * TinyMCE text editor plugin base class. 21 * 22 * This is a base class for TinyMCE plugins implemented within Moodle. These 23 * plugins can optionally provide new buttons/plugins within TinyMCE itself, 24 * or configure the TinyMCE options. 25 * 26 * As well as overridable functions, other utility functions in this class 27 * can be used when writing the plugins. 28 * 29 * Finally, a static function in this class is used to call into all the 30 * plugins when required. 31 * 32 * @package editor_tinymce 33 * @copyright 2012 The Open University 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 abstract class editor_tinymce_plugin { 37 /** @var string Plugin folder */ 38 protected $plugin; 39 40 /** @var array Plugin settings */ 41 protected $config = null; 42 43 /** @var array list of buttons defined by this plugin */ 44 protected $buttons = array(); 45 46 /** 47 * @param string $plugin Name of folder 48 */ 49 public function __construct($plugin) { 50 $this->plugin = $plugin; 51 } 52 53 /** 54 * Returns list of buttons defined by this plugin. 55 * useful mostly as information when setting custom toolbar. 56 * 57 * @return array 58 */ 59 public function get_buttons() { 60 return $this->buttons; 61 } 62 /** 63 * Makes sure config is loaded and cached. 64 * @return void 65 */ 66 protected function load_config() { 67 if (!isset($this->config)) { 68 $name = $this->get_name(); 69 $this->config = get_config("tinymce_$name"); 70 } 71 } 72 73 /** 74 * Returns plugin config value. 75 * @param string $name 76 * @param string $default value if config does not exist yet 77 * @return string value or default 78 */ 79 public function get_config($name, $default = null) { 80 $this->load_config(); 81 return isset($this->config->$name) ? $this->config->$name : $default; 82 } 83 84 /** 85 * Sets plugin config value. 86 * @param string $name name of config 87 * @param string $value string config value, null means delete 88 * @return string value 89 */ 90 public function set_config($name, $value) { 91 $pluginname = $this->get_name(); 92 $this->load_config(); 93 if ($value === null) { 94 unset($this->config->$name); 95 } else { 96 $this->config->$name = $value; 97 } 98 set_config($name, $value, "tinymce_$pluginname"); 99 } 100 101 /** 102 * Returns name of this tinymce plugin. 103 * @return string 104 */ 105 public function get_name() { 106 // All class names start with "tinymce_". 107 $words = explode('_', get_class($this), 2); 108 return $words[1]; 109 } 110 111 /** 112 * Adjusts TinyMCE init parameters for this plugin. 113 * 114 * Subclasses must implement this function in order to carry out changes 115 * to the TinyMCE settings. 116 * 117 * @param array $params TinyMCE init parameters array 118 * @param context $context Context where editor is being shown 119 * @param array $options Options for this editor 120 */ 121 protected abstract function update_init_params(array &$params, context $context, 122 array $options = null); 123 124 /** 125 * Gets the order in which to run this plugin. Order usually only matters if 126 * (a) the place you add your button might depend on another plugin, or 127 * (b) you want to make some changes to layout etc. that should happen last. 128 * The default order is 100; within that, plugins are sorted alphabetically. 129 * Return a lower number if you want this plugin to run earlier, or a higher 130 * number if you want it to run later. 131 */ 132 protected function get_sort_order() { 133 return 100; 134 } 135 136 /** 137 * Adds a button to the editor, after another button (or at the end). 138 * 139 * Specify the location of this button using the $after variable. If you 140 * leave this blank, the button will be added at the end. 141 * 142 * If you want to try different possible locations depending on existing 143 * plugins you can set $alwaysadd to false and check the return value 144 * to see if it succeeded. 145 * 146 * Note: button will not be added if it is already present in any row 147 * (separator is an exception). 148 * 149 * The following example will add the button 'newbutton' after the 150 * 'existingbutton' if it exists or in the end of the last row otherwise: 151 * <pre> 152 * if ($row = $this->find_button($params, 'existingbutton')) { 153 * $this->add_button_after($params, $row, 'newbutton', 'existingbutton'); 154 * } else { 155 * $this->add_button_after($params, $this->count_button_rows($params), 'newbutton'); 156 * } 157 * </pre> 158 * 159 * @param array $params TinyMCE init parameters array 160 * @param int $row Row to add button to (1 to 3) 161 * @param string $button Identifier of button/plugin 162 * @param string $after Adds button directly after the named plugin 163 * @param bool $alwaysadd If specified $after string not found, add at end 164 * @return bool True if added or button already exists (in any row) 165 */ 166 protected function add_button_after(array &$params, $row, $button, 167 $after = '', $alwaysadd = true) { 168 169 if ($button !== '|' && $this->find_button($params, $button)) { 170 return true; 171 } 172 173 $row = $this->fix_row($params, $row); 174 175 $field = 'theme_advanced_buttons' . $row; 176 $old = $params[$field]; 177 178 // Empty = add at end. 179 if ($after === '') { 180 $params[$field] = $old . ',' . $button; 181 return true; 182 } 183 184 // Try to add after given plugin. 185 $params[$field] = preg_replace('~(,|^)(' . preg_quote($after) . ')(,|$)~', 186 '$1$2,' . $button . '$3', $old); 187 if ($params[$field] !== $old) { 188 return true; 189 } 190 191 // If always adding, recurse to add it empty. 192 if ($alwaysadd) { 193 return $this->add_button_after($params, $row, $button); 194 } 195 196 // Otherwise return false (failed to add). 197 return false; 198 } 199 200 /** 201 * Adds a button to the editor. 202 * 203 * Specify the location of this button using the $before variable. If you 204 * leave this blank, the button will be added at the start. 205 * 206 * If you want to try different possible locations depending on existing 207 * plugins you can set $alwaysadd to false and check the return value 208 * to see if it succeeded. 209 * 210 * Note: button will not be added if it is already present in any row 211 * (separator is an exception). 212 * 213 * The following example will add the button 'newbutton' before the 214 * 'existingbutton' if it exists or in the end of the last row otherwise: 215 * <pre> 216 * if ($row = $this->find_button($params, 'existingbutton')) { 217 * $this->add_button_before($params, $row, 'newbutton', 'existingbutton'); 218 * } else { 219 * $this->add_button_after($params, $this->count_button_rows($params), 'newbutton'); 220 * } 221 * </pre> 222 * 223 * @param array $params TinyMCE init parameters array 224 * @param int $row Row to add button to (1 to 10) 225 * @param string $button Identifier of button/plugin 226 * @param string $before Adds button directly before the named plugin 227 * @param bool $alwaysadd If specified $before string not found, add at start 228 * @return bool True if added or button already exists (in any row) 229 */ 230 protected function add_button_before(array &$params, $row, $button, 231 $before = '', $alwaysadd = true) { 232 233 if ($button !== '|' && $this->find_button($params, $button)) { 234 return true; 235 } 236 $row = $this->fix_row($params, $row); 237 238 $field = 'theme_advanced_buttons' . $row; 239 $old = $params[$field]; 240 241 // Empty = add at start. 242 if ($before === '') { 243 $params[$field] = $button . ',' . $old; 244 return true; 245 } 246 247 // Try to add before given plugin. 248 $params[$field] = preg_replace('~(,|^)(' . preg_quote($before) . ')(,|$)~', 249 '$1' . $button . ',$2$3', $old); 250 if ($params[$field] !== $old) { 251 return true; 252 } 253 254 // If always adding, recurse to add it empty. 255 if ($alwaysadd) { 256 return $this->add_button_before($params, $row, $button); 257 } 258 259 // Otherwise return false (failed to add). 260 return false; 261 } 262 263 /** 264 * Tests if button is already present. 265 * 266 * @param array $params TinyMCE init parameters array 267 * @param string $button button name 268 * @return false|int false if button is not found, row number otherwise (row numbers start from 1) 269 */ 270 protected function find_button(array &$params, $button) { 271 foreach ($params as $key => $value) { 272 if (preg_match('/^theme_advanced_buttons(\d+)$/', $key, $matches) && 273 strpos(','. $value. ',', ','. $button. ',') !== false) { 274 return (int)$matches[1]; 275 } 276 } 277 return false; 278 } 279 280 /** 281 * Checks the row value is valid, fix if necessary. 282 * 283 * @param array $params TinyMCE init parameters array 284 * @param int $row Row to add button if exists 285 * @return int requested row if exists, lower number if does not exist. 286 */ 287 private function fix_row(array &$params, $row) { 288 if ($row <= 1) { 289 // Row 1 is always present. 290 return 1; 291 } else if (isset($params['theme_advanced_buttons' . $row])) { 292 return $row; 293 } else { 294 return $this->count_button_rows($params); 295 } 296 } 297 298 /** 299 * Counts the number of rows in TinyMCE editor (row numbering starts with 1) 300 * 301 * @param array $params TinyMCE init parameters array 302 * @return int the maximum existing row number 303 */ 304 protected function count_button_rows(array &$params) { 305 $maxrow = 1; 306 foreach ($params as $key => $value) { 307 if (preg_match('/^theme_advanced_buttons(\d+)$/', $key, $matches) && 308 (int)$matches[1] > $maxrow) { 309 $maxrow = (int)$matches[1]; 310 } 311 } 312 return $maxrow; 313 } 314 315 /** 316 * Adds a JavaScript plugin into TinyMCE. Note that adding a plugin does 317 * not by itself add a button; you must do both. 318 * 319 * If you leave $pluginname blank (default) it uses the folder name. 320 * 321 * @param array $params TinyMCE init parameters array 322 * @param string $pluginname Identifier for plugin within TinyMCE 323 * @param string $jsfile Name of JS file (within plugin 'tinymce' directory) 324 */ 325 protected function add_js_plugin(&$params, $pluginname='', $jsfile='editor_plugin.js') { 326 global $CFG; 327 328 // Set default plugin name. 329 if ($pluginname === '') { 330 $pluginname = $this->plugin; 331 } 332 333 // Add plugin to list in params, so it doesn't try to load it again. 334 $params['plugins'] .= ',-' . $pluginname; 335 336 // Add special param that causes Moodle TinyMCE init to load the plugin. 337 if (!isset($params['moodle_init_plugins'])) { 338 $params['moodle_init_plugins'] = ''; 339 } else { 340 $params['moodle_init_plugins'] .= ','; 341 } 342 343 // Get URL of main JS file and store in params. 344 $jsurl = $this->get_tinymce_file_url($jsfile, false); 345 $params['moodle_init_plugins'] .= $pluginname . ':' . $jsurl; 346 } 347 348 /** 349 * Returns URL to files in the TinyMCE folder within this plugin, suitable 350 * for client-side use such as loading JavaScript files. (This URL normally 351 * goes through loader.php and contains the plugin version to ensure 352 * correct and long-term cacheing.) 353 * 354 * @param string $file Filename or path within the folder 355 * @param bool $absolute Set false to get relative URL from plugins folder 356 */ 357 public function get_tinymce_file_url($file='', $absolute=true) { 358 global $CFG; 359 360 // Version number comes from plugin version.php, except in developer 361 // mode where the special string 'dev' is used (prevents cacheing and 362 // serves unminified JS). 363 if ($CFG->debugdeveloper) { 364 $version = '-1'; 365 } else { 366 $version = $this->get_version(); 367 } 368 369 // Calculate the JS url (relative to the TinyMCE plugins folder - using 370 // relative URL saves a few bytes in each HTML page). 371 if ($CFG->slasharguments) { 372 // URL is usually from loader.php... 373 $jsurl = 'loader.php/' . $this->plugin . '/' . $version . '/' . $file; 374 } else { 375 // ...except when slash arguments are turned off it serves direct. 376 // In this situation there is no version details and it is up to 377 // the browser and server to negotiate cacheing, which will mean 378 // requesting the JS files frequently (reduced performance). 379 $jsurl = $this->plugin . '/tinymce/' . $file; 380 } 381 382 if ($absolute) { 383 $jsurl = $CFG->wwwroot . '/lib/editor/tinymce/plugins/' . $jsurl; 384 } 385 386 return $jsurl; 387 } 388 389 /** 390 * Obtains version number from version.php for this plugin. 391 * 392 * @return string Version number 393 */ 394 protected function get_version() { 395 global $CFG; 396 397 $plugin = new stdClass; 398 require($CFG->dirroot . '/lib/editor/tinymce/plugins/' . $this->plugin . '/version.php'); 399 return $plugin->version; 400 } 401 402 /** 403 * Calls all available plugins to adjust the TinyMCE init parameters. 404 * 405 * @param array $params TinyMCE init parameters array 406 * @param context $context Context where editor is being shown 407 * @param array $options Options for this editor 408 */ 409 public static function all_update_init_params(array &$params, 410 context $context, array $options = null) { 411 global $CFG; 412 413 // Get list of plugin directories. 414 $plugins = core_component::get_plugin_list('tinymce'); 415 416 // Get list of disabled subplugins. 417 $disabled = array(); 418 if ($params['moodle_config']->disabledsubplugins) { 419 foreach (explode(',', $params['moodle_config']->disabledsubplugins) as $sp) { 420 $sp = trim($sp); 421 if ($sp !== '') { 422 $disabled[$sp] = $sp; 423 } 424 } 425 } 426 427 // Construct all the plugins. 428 $pluginobjects = array(); 429 foreach ($plugins as $plugin => $dir) { 430 if (isset($disabled[$plugin])) { 431 continue; 432 } 433 require_once($dir . '/lib.php'); 434 $classname = 'tinymce_' . $plugin; 435 $pluginobjects[] = new $classname($plugin); 436 } 437 438 // Sort plugins by sort order and name. 439 usort($pluginobjects, array('editor_tinymce_plugin', 'compare_plugins')); 440 441 // Run the function for each plugin. 442 foreach ($pluginobjects as $obj) { 443 $obj->update_init_params($params, $context, $options); 444 } 445 } 446 447 /** 448 * Gets a named plugin object. Will cause fatal error if plugin doesn't exist. 449 * 450 * @param string $plugin Name of plugin e.g. 'moodleemoticon' 451 * @return editor_tinymce_plugin Plugin object 452 */ 453 public static function get($plugin) { 454 $dir = core_component::get_component_directory('tinymce_' . $plugin); 455 require_once($dir . '/lib.php'); 456 $classname = 'tinymce_' . $plugin; 457 return new $classname($plugin); 458 } 459 460 /** 461 * Compares two plugins. 462 * @param editor_tinymce_plugin $a 463 * @param editor_tinymce_plugin $b 464 * @return Negative number if $a is before $b 465 */ 466 public static function compare_plugins(editor_tinymce_plugin $a, editor_tinymce_plugin $b) { 467 // Use sort order first. 468 $order = $a->get_sort_order() - $b->get_sort_order(); 469 if ($order != 0) { 470 return $order; 471 } 472 473 // Then sort alphabetically. 474 return strcmp($a->plugin, $b->plugin); 475 } 476 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body