#373542 by sun: Fixed TinyMCE: entity_encoding 'raw' removes HTML entities.
[project/wysiwyg.git] / editors / tinymce.inc
CommitLineData
14759316 1<?php
2// $Id$
3
4
5/**
6 * Plugin implementation of hook_editor().
7 *
8 * - Function signature and returned editor name nust match include filename.
9 * - Returns an array of supported editor versions along with support files.
10 *
11 * @todo wysiwyg_<editor>_alter() to add/inject optional libraries like gzip.
12 */
13function wysiwyg_tinymce_editor() {
14 $editor = array();
15 $editor['tinymce'] = array(
16 // Required properties
17 'title' => 'TinyMCE',
18 'vendor url' => 'http://tinymce.moxiecode.com',
19 'download url' => 'http://tinymce.moxiecode.com/download.php',
14759316 20 'library path' => wysiwyg_get_path('tinymce') . '/jscripts/tiny_mce',
21 'libraries' => array( // We cannot assume that all editors need just one js library.
22 '' => array( // Key may be used in wysiwyg_tinymce_settings() for exec mode.
23 'title' => 'Minified',
24 'files' => array('tiny_mce.js'),
25 ),
26 'src' => array(
27 'title' => 'Source',
28 'files' => array('tiny_mce_src.js'),
29 ),
30 ),
14759316 31 'version callback' => 'wysiwyg_tinymce_version',
32 'themes callback' => 'wysiwyg_tinymce_themes',
33 'settings callback' => 'wysiwyg_tinymce_settings',
34 'plugin callback' => 'wysiwyg_tinymce_plugins',
35 'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings',
6e91d64e 36 'proxy plugin' => array(
37 'drupal' => array(
38 'load' => TRUE,
39 'proxy' => TRUE,
40 ),
41 ),
42 'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings',
14759316 43 'versions' => array( // Each version can override global editor properties.
44 '2.1' => array(
45 // 'include files' => array('tinymce-2.inc'),
46 'js files' => array('tinymce-2.js'),
a09d7c1c 47 'css files' => array('tinymce-2.css'),
fb9108a1 48 'download url' => 'http://sourceforge.net/project/showfiles.php?group_id=103281&package_id=111430&release_id=557383',
14759316 49 ),
50 '3.2' => array(
51 // 'include files' => array('tinymce-3.inc'),
52 'js files' => array('tinymce-3.js'),
a09d7c1c 53 'css files' => array('tinymce-3.css'),
14759316 54 // 'plugin callback' => 'wysiwyg_tinymce_3_plugins',
55 'libraries' => array(
56 '' => array(
57 'title' => 'Minified',
46b43486 58 'files' => array(
59 'tiny_mce.js' => array('preprocess' => FALSE),
60 ),
14759316 61 ),
62 'jquery' => array(
63 'title' => 'jQuery',
64 'files' => array('tiny_mce_jquery.js'),
65 ),
66 'src' => array(
67 'title' => 'Source',
68 'files' => array('tiny_mce_src.js'),
69 ),
70 ),
71 ),
72 ),
73 // Optional properties
166a12f1 74 // 'editor path' => wysiwyg_get_path('tinymce'), // Assumed by default.
75 // 'js path' => wysiwyg_get_path('editors/js'), // Assumed by default.
76 // 'css path' => wysiwyg_get_path('editors/css'), // Assumed by default.
14759316 77 );
78 return $editor;
79}
80
81/**
82 * Detect editor version.
83 *
84 * @param $editor
85 * An array containing editor properties as returned from hook_editor().
86 *
87 * @return
88 * The installed editor version.
89 */
90function wysiwyg_tinymce_version($editor) {
c0a227fc 91 $script = $editor['library path'] .'/tiny_mce.js';
92 $script = fopen($script, 'r');
93 // Version is contained in the first 80 chars.
94 $line = fgets($script, 80);
95 // 2.x: this.majorVersion="2";this.minorVersion="1.3"
96 // 3.x: majorVersion:'3',minorVersion:'2.0.1'
97 if (preg_match('@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', $line, $version)) {
98 fclose($script);
99 return $version[1] .'.'. $version[2];
14759316 100 }
101}
102
103/**
104 * Return runtime editor settings for a given wysiwyg profile.
105 *
106 * @param $editor
107 * A processed hook_editor() array of editor properties.
108 * @param $config
109 * An array containing wysiwyg editor profile settings.
110 * @param $theme
111 * The name of a theme/GUI/skin to use.
112 *
113 * @return
114 * A settings array to be populated in
f178028c 115 * Drupal.settings.wysiwyg.configs.{editor}
14759316 116 */
117function wysiwyg_tinymce_settings($editor, $config, $theme) {
118 $init = array(
14759316 119 'button_tile_map' => TRUE, // @todo Add a setting for this.
14759316 120 'document_base_url' => base_path(),
14759316 121 'mode' => 'none',
14759316 122 'plugins' => array(),
14759316 123 'relative_urls' => FALSE,
14759316 124 'theme' => $theme,
7b70802f 125 'width' => '100%',
cf180dce 126 'strict_loading_mode' => TRUE,
14759316 127 );
9e8e526a 128 if (isset($config['apply_source_formatting'])) {
129 $init['apply_source_formatting'] = $config['apply_source_formatting'];
130 }
131 if (isset($config['convert_fonts_to_spans'])) {
132 $init['convert_fonts_to_spans'] = $config['convert_fonts_to_spans'];
133 }
134 if (isset($config['language'])) {
135 $init['language'] = $config['language'];
136 }
137 if (isset($config['paste_auto_cleanup_on_paste'])) {
138 $init['paste_auto_cleanup_on_paste'] = $config['paste_auto_cleanup_on_paste'];
139 }
140 if (isset($config['preformatted'])) {
141 $init['preformatted'] = $config['preformatted'];
142 }
143 if (isset($config['remove_linebreaks'])) {
144 $init['remove_linebreaks'] = $config['remove_linebreaks'];
145 }
146 if (isset($config['verify_html'])) {
147 $init['verify_html'] = $config['verify_html'];
148 }
14759316 149
9e8e526a 150 if (!empty($config['css_classes'])) {
14759316 151 $init['theme_advanced_styles'] = implode(';', array_filter(explode("\n", str_replace("\r", '', $config['css_classes']))));
152 }
153
9e8e526a 154 if (isset($config['css_setting'])) {
155 if ($config['css_setting'] == 'theme') {
5708da35 156 $init['content_css'] = implode(',', wysiwyg_get_css());
9e8e526a 157 }
158 else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
159 $init['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
14759316 160 }
14759316 161 }
162
163 // Find the enabled buttons and the button row they belong on.
164 // Also map the plugin metadata for each button.
165 // @todo What follows is a pain; needs a rewrite.
166 if (!empty($config['buttons']) && is_array($config['buttons'])) {
167 // $init['buttons'] are stacked into $init['theme_advanced_buttons1'] later.
168 // @todo Add a toolbar designer based on jQuery UI.
169 $init['buttons'] = array();
170 // Only array keys in $init['extensions'] matter; added to $init['plugins']
171 // later.
172 $init['extensions'] = array();
173 // $init['extended_valid_elements'] are just stacked, unique'd later, and
eb714832 174 // transformed into a comma-separated string in wysiwyg_add_editor_settings().
14759316 175 // @todo Needs a complete plugin API redesign using arrays for
176 // tag => attributes definitions and array_merge_recursive().
177 $init['extended_valid_elements'] = array();
178
eb714832 179 $plugins = wysiwyg_get_plugins($editor['name']);
14759316 180 foreach ($config['buttons'] as $plugin => $buttons) {
181 foreach ($buttons as $button => $enabled) {
182 // Iterate separately over buttons and extensions properties.
183 foreach (array('buttons', 'extensions') as $type) {
184 // Skip unavailable plugins.
185 if (!isset($plugins[$plugin][$type][$button])) {
186 continue;
187 }
188 // Add buttons.
189 if ($type == 'buttons') {
190 $init['buttons'][] = $button;
191 }
6e91d64e 192 // Add external Drupal plugins to the list of extensions.
193 if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
194 $init['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1;
195 }
14759316 196 // Add external plugins to the list of extensions.
6e91d64e 197 else if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
14759316 198 $init['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1;
199 }
200 // Add internal buttons that also need to be loaded as extension.
c2eb2080 201 else if ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
14759316 202 $init['extensions'][$plugin] = 1;
203 }
204 // Add plain extensions.
c2eb2080 205 else if ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
14759316 206 $init['extensions'][$plugin] = 1;
207 }
208 // Allow plugins to add valid HTML elements.
209 if (!empty($plugins[$plugin]['extended_valid_elements'])) {
210 $init['extended_valid_elements'] = array_merge($init['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
211 }
212 // Allow plugins to add or override global configuration settings.
213 if (!empty($plugins[$plugin]['options'])) {
214 $init = array_merge($init, $plugins[$plugin]['options']);
215 }
216 }
217 }
218 }
219 // Clean-up.
220 $init['extended_valid_elements'] = array_unique($init['extended_valid_elements']);
221 if ($init['extensions']) {
222 $init['plugins'] = array_keys($init['extensions']);
223 unset($init['extensions']);
224 } else {
225 unset($init['extensions']);
226 }
227 }
228
229 // Add theme-specific settings.
230 switch ($theme) {
231 case 'advanced':
232 $init += array(
14759316 233 'theme_advanced_resize_horizontal' => FALSE,
234 'theme_advanced_resizing_use_cookie' => FALSE,
713eaa03 235 'theme_advanced_path_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom',
236 'theme_advanced_resizing' => isset($config['resizing']) ? $config['resizing'] : 1,
237 'theme_advanced_toolbar_location' => isset($config['toolbar_loc']) ? $config['toolbar_loc'] : 'top',
238 'theme_advanced_toolbar_align' => isset($config['toolbar_align']) ? $config['toolbar_align'] : 'left',
14759316 239 );
9e8e526a 240 if (isset($config['block_formats'])) {
241 $init['theme_advanced_blockformats'] = $config['block_formats'];
242 }
9e8e526a 243 if (isset($init['buttons'])) {
713eaa03 244 // These rows explicitly need to be set to be empty, otherwise TinyMCE
245 // loads its default buttons of the advanced theme for each row.
9e8e526a 246 $init += array(
247 'theme_advanced_buttons1' => array(),
248 'theme_advanced_buttons2' => array(),
249 'theme_advanced_buttons3' => array(),
250 );
713eaa03 251 // @todo Allow to sort/arrange editor buttons.
9e8e526a 252 for ($i = 0; $i < count($init['buttons']); $i++) {
253 $init['theme_advanced_buttons1'][] = $init['buttons'][$i];
254 }
14759316 255 }
256 break;
257 }
258 unset($init['buttons']);
259
4c552b90 260 // Convert the config values into the form expected by TinyMCE.
261 foreach ($init as $key => $value) {
262 if (is_bool($value)) {
263 continue;
264 }
265 if (is_array($value)) {
266 $init[$key] = implode(',', $init[$key]);
267 }
268 }
269
14759316 270 return $init;
271}
272
273/**
274 * Determine available editor themes or check/reset a given one.
275 *
276 * @param $editor
277 * A processed hook_editor() array of editor properties.
278 * @param $profile
279 * A wysiwyg editor profile.
280 *
281 * @return
282 * An array of theme names. The first returned name should be the default
283 * theme name.
284 */
285function wysiwyg_tinymce_themes($editor, $profile) {
286 /*
287 $themes = array();
288 $dir = $editor['library path'] . '/themes/';
289 if (is_dir($dir) && $dh = opendir($dir)) {
290 while (($file = readdir($dh)) !== FALSE) {
291 if (!in_array($file, array('.', '..', 'CVS', '.svn')) && is_dir($dir . $file)) {
292 $themes[$file] = $file;
293 }
294 }
295 closedir($dh);
296 asort($themes);
297 }
298 return $themes;
299 */
300 return array('advanced', 'simple');
301}
302
303/**
6e91d64e 304 * Build a JS settings array of native external plugins that need to be loaded separately.
14759316 305 *
306 * TinyMCE requires that external plugins (i.e. not residing in the editor's
6e91d64e 307 * directory) are loaded (once) upon initializing the editor.
14759316 308 */
325352d7 309function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) {
310 $settings = array();
311 foreach ($plugins as $name => $plugin) {
f012ff6d 312 if (empty($plugin['internal'])) {
325352d7 313 $settings[$name] = base_path() . $plugin['path'];
14759316 314 }
315 }
325352d7 316 return $settings;
14759316 317}
318
319/**
6e91d64e 320 * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
321 */
322function wysiwyg_tinymce_proxy_plugin_settings($editor, $profile, $plugins) {
323 $settings = array();
324 foreach ($plugins as $name => $plugin) {
325 // Populate required plugin settings.
326 $settings[$name] = $plugin['dialog settings'] + array(
327 'title' => $plugin['title'],
328 'icon' => base_path() . $plugin['icon path'] .'/'. $plugin['icon file'],
329 'iconTitle' => $plugin['icon title'],
330 // @todo These should only be set if the plugin defined them.
331 'css' => base_path() . $plugin['css path'] .'/'. $plugin['css file'],
332 );
333 }
334 return $settings;
335}
336
337/**
14759316 338 * Add or remove leading hiven to/of external plugin names.
339 *
340 * TinyMCE requires that external plugins, which should not be loaded from
341 * its own plugin repository are prefixed with a hiven in the name.
342 *
343 * @param string $op
344 * Operation to perform, 'add' or 'remove' (hiven).
345 * @param string $name
346 * A plugin name.
347 */
348function _wysiwyg_tinymce_plugin_name($op, $name) {
349 if ($op == 'add') {
350 if (strpos($name, '-') !== 0) {
351 return '-'. $name;
352 }
353 return $name;
354 }
355 else if ($op == 'remove') {
356 if (strpos($name, '-') === 0) {
357 return substr($name, 1);
358 }
359 return $name;
360 }
361}
362
363/**
364 * Return internal plugins for TinyMCE; semi-implementation of hook_wysiwyg_plugin().
365 */
366function wysiwyg_tinymce_plugins($editor) {
329c0500 367 $plugins = array(
14759316 368 'default' => array(
369 'path' => $editor['library path'] .'/themes/advanced',
370 'buttons' => array(
371 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
372 'strikethrough' => t('Strike-through'),
373 'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
374 'bullist' => t('Bullet list'), 'numlist' => t('Numbered list'),
375 'outdent' => t('Outdent'), 'indent' => t('Indent'),
376 'undo' => t('Undo'), 'redo' => t('Redo'),
377 'link' => t('Link'), 'unlink' => t('Unlink'), 'anchor' => t('Anchor'),
378 'image' => t('Image'),
379 'cleanup' => t('Clean-up'),
380 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'),
381 'sup' => t('Sup'), 'sub' => t('Sub'),
da006eca 382 'blockquote' => t('Blockquote'), 'code' => t('Source code'),
14759316 383 'hr' => t('Horizontal rule'),
384 'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
385 'visualaid' => t('Visual aid'),
386 'removeformat' => t('Remove format'),
387 'charmap' => t('Character map'),
388 'help' => t('Help')),
389 'internal' => TRUE,
390 ),
391 'advhr' => array(
392 'path' => $editor['library path'] .'/plugins/advhr',
393 'buttons' => array('advhr' => t('Advanced horizontal rule')),
394 'extended_valid_elements' => array('hr[class|width|size|noshade]'),
395 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr',
396 'internal' => TRUE,
397 'load' => TRUE,
398 ),
399 'advimage' => array(
400 'path' => $editor['library path'] .'/plugins/advimage',
401 'extensions' => array('advimage' => t('Advanced image')),
c3bbf69b 402 'extended_valid_elements' => array('img[src|alt|title|align|width|height|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name]'),
14759316 403 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage',
404 'internal' => TRUE,
405 'load' => TRUE,
406 ),
407 'advlink' => array(
408 'path' => $editor['library path'] .'/plugins/advlink',
409 'extensions' => array('advlink' => t('Advanced link')),
8942000a 410 'extended_valid_elements' => array('a[name|href|target|title|class|onfocus|onblur|onclick|ondlbclick|onmousedown|onmouseup|onmouseover|onmouseout|onkeypress|onkeydown|onkeyup|id|style|rel]'),
14759316 411 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink',
412 'internal' => TRUE,
413 'load' => TRUE,
414 ),
415 'autosave' => array(
416 'path' => $editor['library path'] .'/plugins/autosave',
417 'extensions' => array('autosave' => t('Auto save')),
418 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
419 'internal' => TRUE,
420 'load' => TRUE,
421 ),
422 'contextmenu' => array(
423 'path' => $editor['library path'] .'/plugins/contextmenu',
424 'extensions' => array('contextmenu' => t('Context menu')),
425 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu',
426 'internal' => TRUE,
c2eb2080 427 'load' => TRUE,
14759316 428 ),
429 'directionality' => array(
430 'path' => $editor['library path'] .'/plugins/directionality',
431 'buttons' => array('ltr' => t('Left-to-right'), 'rtl' => t('Right-to-left')),
432 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality',
433 'internal' => TRUE,
434 'load' => TRUE,
435 ),
436 'emotions' => array(
437 'path' => $editor['library path'] .'/plugins/emotions',
438 'buttons' => array('emotions' => t('Emotions')),
439 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions',
440 'internal' => TRUE,
441 'load' => TRUE,
442 ),
443 'flash' => array(
444 'path' => $editor['library path'] .'/plugins/flash',
445 'buttons' => array('flash' => t('Flash')),
446 'extended_valid_elements' => array('img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|obj|param|embed]'),
447 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/flash',
448 'internal' => TRUE,
449 'load' => TRUE,
450 ),
451 'font' => array(
452 'path' => $editor['library path'] .'/plugins/font',
453 'buttons' => array('formatselect' => t('HTML block format'), 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'), 'styleselect' => t('Font style')),
454 'extended_valid_elements' => array('font[face|size|color|style],span[class|align|style]'),
455 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/font',
456 'internal' => TRUE,
457 ),
458 'fullscreen' => array(
459 'path' => $editor['library path'] .'/plugins/fullscreen',
460 'buttons' => array('fullscreen' => t('Fullscreen')),
461 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen',
462 'internal' => TRUE,
463 'load' => TRUE,
464 ),
465 'inlinepopups' => array(
466 'path' => $editor['library path'] .'/plugins/inlinepopups',
467 'extensions' => array('inlinepopups' => t('Inline popups')),
468 'options' => array(
469 'dialog_type' => array('modal'),
470 ),
471 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups',
472 'internal' => TRUE,
473 'load' => TRUE,
474 ),
475 'insertdatetime' => array(
476 'path' => $editor['library path'] .'/plugins/insertdatetime',
477 'buttons' => array('insertdate' => t('Insert date'), 'inserttime' => t('Insert time')),
478 'options' => array(
479 'plugin_insertdate_dateFormat' => array('%Y-%m-%d'),
480 'plugin_insertdate_timeFormat' => array('%H:%M:%S'),
481 ),
482 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/insertdatetime',
483 'internal' => TRUE,
484 'load' => TRUE,
485 ),
486 'layer' => array(
487 'path' => $editor['library path'] .'/plugins/layer',
488 'buttons' => array('insertlayer' => t('Insert layer'), 'moveforward' => t('Move forward'), 'movebackward' => t('Move backward'), 'absolute' => t('Absolute')),
489 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer',
490 'internal' => TRUE,
491 'load' => TRUE,
492 ),
493 'paste' => array(
494 'path' => $editor['library path'] .'/plugins/paste',
495 'buttons' => array('pastetext' => t('Paste text'), 'pasteword' => t('Paste from Word'), 'selectall' => t('Select all')),
496 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',
497 'internal' => TRUE,
498 'load' => TRUE,
499 ),
500 'preview' => array(
501 'path' => $editor['library path'] .'/plugins/preview',
502 'buttons' => array('preview' => t('Preview')),
503 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview',
504 'internal' => TRUE,
505 'load' => TRUE,
506 ),
507 'print' => array(
508 'path' => $editor['library path'] .'/plugins/print',
509 'buttons' => array('print' => t('Print')),
510 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print',
511 'internal' => TRUE,
512 'load' => TRUE,
513 ),
514 'searchreplace' => array(
515 'path' => $editor['library path'] .'/plugins/searchreplace',
516 'buttons' => array('search' => t('Search'), 'replace' => t('Replace')),
517 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace',
518 'internal' => TRUE,
519 'load' => TRUE,
520 ),
521 'style' => array(
522 'path' => $editor['library path'] .'/plugins/style',
523 'buttons' => array('styleprops' => t('Style properties')),
524 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style',
525 'internal' => TRUE,
526 'load' => TRUE,
527 ),
528 'table' => array(
529 'path' => $editor['library path'] .'/plugins/table',
530 'buttons' => array('tablecontrols' => t('Table')),
531 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table',
532 'internal' => TRUE,
533 'load' => TRUE,
534 ),
535 );
329c0500 536 if ($editor['installed version'] > 3) {
537 $plugins['safari'] = array(
538 'path' => $editor['library path'] .'/plugins/safari',
539 'extensions' => array('safari' => t('Safari compatibility')),
540 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
541 'internal' => TRUE,
542 'load' => TRUE,
543 );
544 }
545 return $plugins;
14759316 546}
547