* Minor bugfix.
[project/tinymce.git] / tinymce.module
CommitLineData
6f760c55
MW
1<?php
2// $Id$
3// A collaborative project by Matt Westgate <drupal at asitis dot org>
4// and Richard Bennett <richard.b@gritechnologies.com>
5
6/**
7 * @file
8 * Integrate the TinyMCE editor (http://tinymce.moxiecode.com/) into Drupal.
9 */
10
11/**
12 * Implementation of hook_menu().
6f760c55
MW
13 */
14function tinymce_menu($may_cache) {
01a85ffd 15 $items = array();
277d4328
MW
16 if ($may_cache) {
17 $items[] = array('path' => 'admin/settings/tinymce', 'title' => t('tinymce'),
18 'callback' => 'tinymce_admin',
19 'access' => user_access('administer tinymce'));
01a85ffd
MW
20 }
21 return $items;
6f760c55
MW
22}
23
24/**
25 * Implementation of hook_help().
26 */
27function tinymce_help($section) {
28 switch ($section) {
29 case 'admin/modules#description':
30 return t('The TinyMCE Javascript HTML WYSIWYG editor.');
31
32 case 'admin/settings/tinymce#pages':
33 return "node/*\nuser/*\ncomment/*";
34 }
35}
36
37/**
38 * Implementation of hook_perm().
39 */
40function tinymce_perm() {
01a85ffd 41 return array('administer tinymce', 'access tinymce');
6f760c55
MW
42}
43
44/**
64d74024
MW
45 * Implementation of hook_img_assist_head().
46 */
47function tinymce_img_assist_head() {
01a85ffd 48 global $base_url;
619695bd 49 // The tinymce docs say to include tiny_mce_popup.js, but this was killing IE!
093240f8
MW
50 $popup_path = $base_url .'/'. drupal_get_path('module', 'tinymce'). '/tinymce/jscripts/tiny_mce/tiny_mce_popup.js';
51 $img_assist_prop = $base_url .'/'. drupal_get_path('module', 'img_assist'). '/properties.js';
52 $clean_url = variable_get('clean_url', 0);
53 $img_template = variable_get('img_assist_img_html', img_assist_help('img_assist/template'));
54 $img_template = str_replace("\r\n", "\n", $img_template);
55 $img_template = str_replace("\n", '\n', addslashes($img_template));
64d74024
MW
56
57$output = <<<EOD
093240f8 58<script language="javascript" src="$img_assist_prop"></script>
64d74024 59<script language="javascript">
093240f8
MW
60 var clean_url = $clean_url;
61 var img_template = "$img_template";
62
64d74024
MW
63 function insertImage(form) {
64 if (window.opener) {
093240f8
MW
65 form['edit[thumbpath]'].value = window.opener.tinyMCE.convertURL(form['edit[thumbpath]'].value);
66 form['edit[filepath]'].value = window.opener.tinyMCE.convertURL(form['edit[filepath]'].value);
67 form['edit[nodePath]'].value = window.opener.tinyMCE.convertURL(form['edit[nodePath]'].value);
68
69 var img = generate_image_tag(form, 'html');
93c073b0
MW
70
71 // This next line allows IE to find the tinymce instance if the user
72 // didn't click inside the textarea before spawning img_assist. Otherwise
73 // the user will click 'insert image' and nothing will happen.
74 window.opener.tinyMCE.selectedInstance.contentWindow.focus();
093240f8 75 window.opener.tinyMCE.execCommand('mceInsertContent', false, img);
64d74024
MW
76 }
77 }
78</script>
79EOD;
80
81 return $output;
82}
83
84/**
85 * Implementation of hook_img_assist_on_submit().
86 */
87function tinymce_img_assist_on_submit() {
88 return 'parent.insertImage(this.form);';
89}
90
91/**
64d74024
MW
92 * Implementation of hook_textarea().
93 */
01a85ffd 94function tinymce_textarea($op, $textarea_name) {
dd7d7335
MW
95 static $is_running = FALSE;
96 if (!user_access('access tinymce')) return NULL;
6f760c55
MW
97
98 global $user;
277d4328 99 global $base_url;
6a17f63a 100 static $profile_name;
6f760c55 101
01a85ffd
MW
102 // Since tinymce_config() makes a db hit, only call it when we're pretty sure
103 // we're gonna render tinymce.
104 $valid_so_far = FALSE;
195b5d64 105 if ($op == 'post') {
6a17f63a
MW
106 if (!$profile_name) {
107 $profile_name = db_result(db_query('SELECT s.name FROM {tinymce_settings} s INNER JOIN {tinymce_role} r ON r.name = s.name WHERE r.rid IN (%s)', implode(',', array_keys($user->roles))));
108 }
109 $profile = tinymce_profile_load($profile_name);
01a85ffd
MW
110 $init = tinymce_config($profile);
111 $valid_so_far = TRUE;
112 }
6739c75d 113
01a85ffd 114 if ($valid_so_far && _tinymce_page_match($profile)) {
6739c75d 115 // Merge and overrivide user-defined TinyMCE settings.
01a85ffd 116 $init = array_merge($init, (array) theme('tinymce_theme', $init, $textarea_name, $init['theme'], $is_running));
6739c75d 117 foreach ($init as $k => $v) {
01a85ffd
MW
118 if (strtolower($v) != 'true' && strtolower($v) != 'false') {
119 $v = '"'. $v. '"';
120 }
121 $settings[] = $k. ' : '. $v;
6739c75d 122 }
2b031c84 123 $tinymce_settings = implode(",\n ", $settings);
6739c75d 124
93c073b0 125 if (function_exists('img_assist_help')) {
6a17f63a
MW
126 $img_assist_on = $base_url .'/'. url('img_assist/add&editor=tinymce') .'&textarea=edit['. $textarea_name .']';
127 $img_assist_off = $base_url .'/'. url('img_assist/add') .'&textarea=edit['. $textarea_name .']';
93c073b0
MW
128 }
129
277d4328
MW
130 $enable = t('enable rich-text');
131 $disable = t('disable rich-text');
132
01a85ffd 133$tinymce_invoke = <<<EOD
6f760c55
MW
134<script language="javascript" type="text/javascript">
135 tinyMCE.init({
6739c75d 136 $tinymce_settings
6f760c55 137 });
277d4328
MW
138EOD;
139
140$js_toggle = <<<EOD
141
142 function mceToggle(id, linkid) {
143 element = document.getElementById(id);
144 link = document.getElementById(linkid);
145 img_assist = document.getElementById('img_assist-link-'+ id);
146
147 if (tinyMCE.getEditorId(element.name) == null) {
148 tinyMCE.addMCEControl(element, element.name);
149 element.togg = 'on';
150 link.innerHTML = '$disable';
151 link.href = "javascript:mceToggle('" +id+ "', '" +linkid+ "');";
152 if (img_assist)
153 img_assist.href = "$img_assist_on";
154 link.blur();
155 }
156 else {
157 tinyMCE.removeMCEControl(tinyMCE.getEditorId(element.name));
158 element.togg = 'off';
159 link.innerHTML = '$enable';
160 link.href = "javascript:mceToggle('" +id+ "', '" +linkid+ "');";
161 if (img_assist)
162 img_assist.href = "$img_assist_off";
163 link.blur();
164 }
165 }
6f760c55
MW
166</script>
167EOD;
168
75d3842c 169$status = isset($user->tinymce_status) ? $user->tinymce_status : variable_get('tinymce_default_state', 0);
277d4328
MW
170$link_text = $status == 1 ? $disable : $enable;
171$no_wysiwyg = t('Your current web browser does not support WYSIWYG editing.');
01a85ffd
MW
172$wysiwyg_link = <<<EOD
173<script language="javascript" type="text/javascript">
6a17f63a
MW
174 img_assist = document.getElementById('img_assist-link-edit-$textarea_name');
175 if (img_assist) {
176 img_assist.href = tinyMCE.getEditorId('edit-$textarea_name') == null ? "$img_assist_on" : "$img_assist_off";
177 }
01a85ffd 178 if (typeof(document.execCommand) == 'undefined') {
277d4328 179 document.write('<div style="font-size:x-small">$no_wysiwyg</div>');
e473c3ca 180 img_assist.href = "$img_assist_off";
01a85ffd
MW
181 }
182 else {
183 document.write("<div><a href=\"javascript:mceToggle('edit-$textarea_name', 'wysiwyg4$textarea_name');\" id=\"wysiwyg4$textarea_name\">$link_text</div></a>");
184 }
185</script>
186EOD;
187
dd7d7335
MW
188 // We only invoke TinyMCE once per request, not once per textarea. We could
189 // do this check earlier in the conditional, but it's probably wise to let
190 // the themed functions know what's going on.
191 if (!$is_running) {
192 $is_running = TRUE;
277d4328
MW
193 // For some crazy reason IE will only load this JS file if the absolute reference is given to it.
194 drupal_set_html_head('<script language="javascript" type="text/javascript" src="'. $base_url .'/'. drupal_get_path('module', 'tinymce') .'/tinymce/jscripts/tiny_mce/tiny_mce.js"></script>');
01a85ffd 195 drupal_set_html_head($tinymce_invoke);
277d4328
MW
196 drupal_set_html_head($js_toggle);
197 // We have to do this becuase of some unfocused CSS in certain themes. See http://drupal.org/node/18879 for details
198 drupal_set_html_head('<style type="text/css" media="all">.mceEditor img { display: inline; }</style>');
dd7d7335 199 }
01a85ffd
MW
200
201 return $wysiwyg_link;
6f760c55
MW
202 }
203}
204
205/**
206 * Implementation of hook_user().
207 */
208function tinymce_user($type, &$edit, &$user, $category = NULL) {
dd7d7335 209 if ($type == 'form' && $category == 'account' && user_access('access tinymce')) {
9670911b
MW
210 $user_status = $edit['tinymce_status'] != NULL ? $edit['tinymce_status'] : ($user->tinymce_status != NULL ? $user->tinymce_status : variable_get('tinymce_default_state', 0));
211 $form = form_radios(t('Default status'), 'tinymce_status', $user_status, array(t('Off'), t('On')), t('Should rich-text editing be enabled or disabled by default in textarea fields?'));
6f760c55
MW
212 return array(array('title' => t('TinyMCE settings'), 'data' => $form));
213 }
214 if ($type == 'validate') {
01a85ffd 215 return array('tinymce_status' => $edit['tinymce_status']);
6f760c55
MW
216 }
217}
218
01a85ffd 219
6f760c55 220/**
6739c75d
MW
221 * @addtogroup themeable
222 * @{
223 */
224
225/**
ae7b55ca 226 * Customize a TinyMCE theme.
6739c75d
MW
227 *
228 * @param init
ae7b55ca
MW
229 * An array of settings TinyMCE should invoke a theme. You may override any
230 * of the TinyMCE settings. Details here:
6739c75d
MW
231 *
232 * http://tinymce.moxiecode.com/wrapper.php?url=tinymce/docs/using.htm
ae7b55ca
MW
233 *
234 * @param textarea_name
235 * The name of the textarea TinyMCE wants to enable.
236 *
237 * @param theme_name
238 * The default theme name to be enabled for this textarea. The sitewide
239 * default is 'simple', but the user may also override this.
dd7d7335
MW
240 *
241 * @param is_running
242 * A boolean flag that identifies id TinyMCE is currently running for this
243 * request life cycle. If it's already running then anything returned by this
244 * will be ignored. This is necessary since TinyMCE works by being invoked
245 * once per request and not once per textarea.
6739c75d 246 */
dd7d7335 247function theme_tinymce_theme($init, $textarea_name, $theme_name, $is_running) {
9670911b
MW
248 // Force the 'simple' theme for some of the smaller textareas.
249 switch ($textarea_name) {
250 case 'signature':
251 case 'site_mission':
252 case 'site_footer':
253 case 'settings][access_pages':
254 $init['theme'] = 'simple';
255 unset($init['theme_advanced_toolbar_location']);
256 unset($init['theme_advanced_toolbar_align']);
257 unset($init['theme_advanced_path_location']);
258 unset($init['theme_advanced_blockformats']);
259 unset($init['theme_advanced_styles']);
260 return $init;
261 }
262
ae7b55ca
MW
263 switch ($theme_name) {
264 case 'advanced':
01a85ffd 265 $init['extended_valid_elements'] = 'a[href|target|name|title|onclick]';
b436c922
JR
266 /*$init['theme_advanced_buttons3_add_before'] = 'tablecontrols,separator';*/
267 /*$init['plugins'] = file_exists(drupal_get_path('module', 'tinymce'). '/tinymce/jscripts/tiny_mce/plugins/drupalimage') ? 'drupalimage,table,emotions,print' : 'table,emotions,print';*/
268 /*$init['theme_advanced_buttons3_add'] = 'drupalimage,emotions,separator,print';*/
ae7b55ca
MW
269 return $init;
270 }
6739c75d
MW
271}
272
273/** @} End of addtogroup themeable */
274
275/**
6f760c55
MW
276 * Grab the themes available to TinyMCE.
277 *
278 * TinyMCE themes control the functionality and buttons that are available to a
279 * user. Themes are only looked for within the default TinyMCE theme directory.
280 *
281 * @return
282 * An array of theme names.
283 */
284function _tinymce_get_themes() {
285 static $themes = array();
286
287 if (!$themes) {
288 $theme_loc = drupal_get_path('module', 'tinymce') .'/tinymce/jscripts/tiny_mce/themes/';
289 if (is_dir($theme_loc) && $dh = opendir($theme_loc)) {
290 while (($file = readdir($dh)) !== false) {
ae24db96 291 if (!in_array($file, array('.', '..', 'CVS')) && is_dir($theme_loc . $file)) {
6f760c55
MW
292 $themes[$file] = $file;
293 }
294 }
295 closedir($dh);
296 asort($themes);
297 }
298 }
299
300 return $themes;
301}
302
b436c922
JR
303/**
304 * Return an array where key is plugins name and value is display text for listing.
305 * Sniffs out readme.txt files.
306 */
aa8221b4 307
b436c922
JR
308function _tinymce_get_plugins() {
309 static $plugins = array();
310
311 if (!$plugins) {
312 $plug_loc = drupal_get_path('module', 'tinymce') .'/tinymce/jscripts/tiny_mce/plugins/';
313 if (is_dir($plug_loc) && $dh = opendir($plug_loc)) {
314 while (($file = readdir($dh)) !== false) {
315 if (!in_array($file, array('.', '..', 'CVS')) && is_dir($plug_loc . $file)) {
316 $readme = '';
317 foreach (array('readme.txt','ReadMe.txt','README.txt', 'Readme.txt', 'README.TXT') as $readme_name) {
318 if (file_exists($plug_loc.$file.'/'.$readme_name)) {
319 $readme = $plug_loc.$file.'/'.$readme_name;
320 break;
321 }
322 }
323 //print $file.":";
324 //$buttons = _tinymce_get_buttons($plug_loc.$file.'/editor_plugin.js');
325 $plugins[$file] = $file." plugin";
326 $plugins[$file] .= $readme ? ' | <a href="'.$readme.'" onClick = "window.open(\''.$readme.'\',\'readme\',\'scrollbars,resizable,width=600,height=400\'); return false;">readme</a>' : '';
327 }
328 }
329 closedir($dh);
330 asort($plugins);
331 }
332 }
333 return $plugins;
334}
335
336
337/**
338 * Parse a JavaScript file and attempt to find definitions of the _getControlHTML function.
339 * return them as an array
340 * This is far from adequate, but until TinyMCE has a standardized way of getting buttons, it'll have to do.
341 */
342
343function _tinymce_get_buttons($js) {
344 $buttons = array();
345 // generally, the buttons are defined by switch() cases as part of the _getControlHTML function
346 if ($script = file_get_contents($js)) {
aa8221b4 347 // search for the function
b436c922
JR
348 $regex = "/.unction.*?_getControlHTML[\w\W]*?(?=function)/";
349 preg_match($regex, $script, $matches);
350 unset($function);
351 $function = $matches[0];
352 // find 'case "foo"' within it, and remember "foo"
353 $regex = '/case.*?"(.*?)"/';
354 preg_match_all($regex, $function, $matches);
355 unset($buttons);
356 $buttons = $matches[1];
357 if ($function && !$buttons) {
358 // unfortunately some modules don't use cases...
359 // they use an array system...
360 $regex = '/^.*?ontrol.*?rray.*?\(([\w\W]*?)(?=\)[\s]*?;)/';
361 preg_match($regex, $function, $matches);
362 $array = $matches[1];
363 //var_dump($array);
364 $regex = '/\[[\'"](.*?)(?=[\'"].*?][,\)])/';
365 preg_match_all($regex, $array, $matches);
366 $buttons = $matches[1];
367 }
368 if ($function && !$buttons) {
369 //one more method... just for extra insanity
370 $regex = '/if.*?\(control.*?==\s*?[\'"](.*?)[\'"]/';
aa8221b4 371 preg_match_all($regex, $function, $matches);
b436c922
JR
372 $buttons = $matches[1];
373 }
374 }
375 return $buttons;
376}
377
01a85ffd
MW
378/********************************************************************
379 * Module Functions :: Public
380 ********************************************************************/
381
382/**
383 * Controller for tinymce administrative settings.
384 */
385function tinymce_admin($arg = NULL) {
386 $edit = $_POST['edit'];
387 $op = $_POST['op'];
388
389 $op = $arg && !$op ? $arg : $op;
390
391 switch ($op) {
392 case 'add':
393 $breadcrumb[] = array('path' => 'admin', 'title' => t('administer'));
277d4328
MW
394 $breadcrumb[] = array('path' => 'admin/settings/tinymce', 'title' => t('tinymce'));
395 $breadcrumb[] = array('path' => 'admin/settings/tinymce/add', 'title' => t('Add new tinymce profile'));
01a85ffd
MW
396 menu_set_location($breadcrumb);
397 $output = tinymce_profile_form($edit);
398 break;
399
400 case 'edit':
401 drupal_set_title(t('Edit tinymce profile'));
277d4328 402 $output = tinymce_profile_form(tinymce_profile_load(urldecode(arg(4))));
01a85ffd
MW
403 break;
404
405 case 'delete':
277d4328 406 tinymce_profile_delete(urldecode(arg(4)));
01a85ffd 407 drupal_set_message(t('Deleted profile'));
277d4328 408 drupal_goto('admin/settings/tinymce');
01a85ffd
MW
409 break;
410
411 case t('Create profile');
412 case t('Update profile');
413 if (tinymce_profile_validate($edit)) {
414 tinymce_profile_save($edit);
415 $edit['old_name'] ? drupal_set_message(t('Your tinymce profile has been updated.')) : drupal_set_message(t('Your tinymce profile has been created.'));
277d4328 416 drupal_goto('admin/settings/tinymce');
01a85ffd
MW
417 }
418 else {
419 $output = tinymce_profile_form($edit);
420 }
421 break;
422
423 case t('Save settings'):
424 variable_set('tinymce_default_state', $edit['tinymce_default_state']);
425 drupal_set_message(t('Settings updated'));
277d4328 426 drupal_goto('admin/settings/tinymce');
01a85ffd
MW
427 break;
428
429 default:
430 drupal_set_title(t('TinyMCE settings (%revision)', array('%revision' => '$Revision$')));
431 //Check if TinyMCE is installed.
432 $tinymce_loc = drupal_get_path('module', 'tinymce') .'/tinymce/';
433 if (!is_dir($tinymce_loc)) {
434 drupal_set_message(t('Could not find the TinyMCE engine installed at <strong>%tinymce-directory</strong>. Please <a href="http://tinymce.moxiecode.com/">download TinyMCE</a>, uncompress it and copy the folder into %tinymce-path.', array('%tinymce-path' => drupal_get_path('module', 'tinymce'), '%tinymce-directory' => $tinymce_loc)), 'error');
435 }
436 $output = tinymce_profile_overview();
437 }
438
439 print theme('page', $output);
440}
441
442/**
443 * Return an array of initial tinymce config options from the current role.
444 */
445function tinymce_config($profile) {
446 global $base_url;
9670911b 447 global $user;
01a85ffd 448
195b5d64 449 $settings = $profile->settings;
01a85ffd
MW
450
451 // Build a default list of TinyMCE settings.
9670911b
MW
452
453 // Is tinymce on by default?
454 $init['mode'] = variable_get('tinymce_default_state', 0) == 1 ? 'textareas' : 'none';
75d3842c
MW
455 $status = isset($user->tinymce_status) ? $user->tinymce_status : variable_get('tinymce_default_state', 0);
456 $init['mode'] = $status == 1 ? 'textareas' : 'none';
01a85ffd
MW
457 $init['theme'] = $settings['theme'] ? $settings['theme'] : 'simple';
458 $init['document_base_url'] = "$base_url/";
459
460 $init['verify_html'] = $settings['verify_html'] ? $settings['verify_html'] : 'false';
461 $init['auto_cleanup_word'] = $settings['msword'] ? $settings['msword'] : 'false';
462 $init['preformatted'] = $settings['preformatted'] ? $settings['preformatted'] : 'false';
463 $init['force_br_newlines'] = $settings['force_br'] ? $settings['force_br'] : 'false';
464 $init['force_p_newlines'] = $settings['force_p'] ? $settings['force_p'] : 'false';
93c073b0
MW
465 if ($init['theme'] == 'advanced') {
466 $init['theme_advanced_toolbar_location'] = $settings['toolbar_loc'] ? $settings['toolbar_loc'] : 'bottom';
467 $init['theme_advanced_toolbar_align'] = $settings['toolbar_align'] ? $settings['toolbar_align'] : 'left';
468 $init['theme_advanced_path_location'] = $settings['path_loc'] ? $settings['path_loc'] : 'none';
469 $init['theme_advanced_blockformats'] = $settings['block_formats'] ? $settings['block_formats'] : 'p,address,pre,h1,h2,h3,h4,h5,h6';
b436c922
JR
470 $init['plugins'] = $settings['plugins'] ? implode(',', $settings['plugins']) : '';
471 $init['theme_advanced_buttons3_add'] = $settings['buttons'] ? implode(',', $settings['buttons']) : '';
93c073b0 472 }
01a85ffd
MW
473
474 if ($edit['css_classes']) $init['theme_advanced_styles'] = $settings['css_classes'];
475 if ($settings['width']) $init['width'] = $settings['width'];
476 if ($settings['height']) $init['height'] = $settings['height'];
477
478 if ($settings['css_setting'] == 'theme') {
aa8221b4 479 $css = $base_url .'/'. drupal_get_path('theme', init_theme()) . '/style.css';
01a85ffd 480 if (file_exists($css)) {
6a17f63a 481 $init['content_css'] = $css;
01a85ffd
MW
482 }
483 else if ($settings['css_setting'] == 'self') {
484 $init['content_css'] = $edit['css_path'];
485 }
486 }
487
488 return $init;
489}
490
6f760c55 491/**
01a85ffd
MW
492 * Remove a profile from the database.
493 */
494function tinymce_profile_delete($name) {
195b5d64
MW
495 db_query("DELETE FROM {tinymce_settings} WHERE name = '%s'", $name);
496 db_query("DELETE FROM {tinymce_role} WHERE name = '%s'", $name);
01a85ffd
MW
497}
498
499/**
500 * Return an HTML form for profile configuration.
501 */
502function tinymce_profile_form($edit) {
195b5d64 503 $edit = array2object($edit);
01a85ffd
MW
504
505 // Only display the roles that currently don't have a tinymce profile. One
506 // profile per role.
507 $orig_roles = user_roles(1);
508 $roles = $orig_roles;
277d4328 509 if (arg(3) == 'add') {
195b5d64 510 $result = db_query('SELECT DISTINCT(rid) FROM {tinymce_role}');
01a85ffd 511 while ($data = db_fetch_object($result)) {
195b5d64 512 unset($roles[$data->rid]);
01a85ffd
MW
513 }
514 if (!$roles) {
515 drupal_set_message(t('You will not be allowed to create a new profile since all user roles have already been assigned profiles. First remove an existing tinymce profile from at least one role in order to create a new profile.'), 'error');
516 }
517 else if (count($orig_roles) != count($roles)) {
518 drupal_set_message(t('Not all user roles are shown since they already have tinymce profiles. You must first unassign profiles in order to add them to a new one.'));
519 }
520 $btn = t('Create profile');
521 }
522 else {
195b5d64 523 $output = form_hidden('old_name', $edit->name);
01a85ffd
MW
524 $btn = t('Update profile');
525 }
526
195b5d64
MW
527 $group = form_textfield(t('Profile name'), 'name', $edit->name, 40, 128, t('Enter an unique name for this profile. This name is only visible in the tinymce administration page.'), NULL, TRUE);
528 $group .= form_checkboxes(t('Roles allowed to use this profile'), 'rids', array_keys((array) $edit->rids), $roles, t('Select at least one role.'), NULL, TRUE);
529 $group .= form_radios(t('Theme'), 'settings][theme', $edit->settings['theme'] ? $edit->settings['theme'] : 'simple', _tinymce_get_themes(), t('Select the tinymce theme.'));
530 $group .= form_radios(t('Make tinymce visible on'), 'settings][access', $edit->settings['access'], array(t('specific pages'), t('all textareas')));
531 $group .= form_textarea(t('Specific pages'), 'settings][access_pages', $edit->settings['access_pages'] ? $edit->settings['access_pages'] : tinymce_help('admin/settings/tinymce#pages'), 40, 5, t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are '<em>blog</em>' for the blog page and '<em>blog/*</em>' for every personal blog. '<em>&lt;front&gt;</em>' is the front page."));
01a85ffd
MW
532 $output .= form_group(t('Basic setup'), $group);
533
534 $output .= t('<h3>Advanced options</h3>');
535
195b5d64
MW
536 $group = form_textfield(t('Editor width'), 'settings][width', $edit->settings['width'], 3, 5, t('Set width of editor. Leave blank to use size of textarea being replaced.'));
537 $group .= form_textfield(t('Editor height'), 'settings][height', $edit->settings['height'], 3, 5, t('Set height of editor. Leave blank to use size of textarea being replaced.'));
538 $group .= form_select(t('Toolbar location'), 'settings][toolbar_loc', $edit->settings['toolbar_loc'], array('bottom' => 'bottom', 'top' => 'top'), t('Show toolbar at the top or bottom of the editor area?'));
539 $group .= form_select(t('Toolbar alignment'), 'settings][toolbar_align', $edit->settings['toolbar_align'], array('center' => 'center', 'left' => 'left', 'right' => 'right'), t('Align tool icons left, center, or right within the toolbar.'));
540 $group .= form_select(t('Path location'), 'settings][path_loc', $edit->settings['path_loc'], array('none' => 'none', 'top' => 'top', 'bottom' => 'bottom'), t('Path to html elements (i.e. "body>table>tr>td"). Show at top, bottom, or not at all.'));
b436c922
JR
541 $subgroup = '<div style="height:200px; overflow:auto;">';
542 //$subgroup .= form_checkboxes('', 'settings][plugins', $edit->settings['plugins'] ? $edit->settings['plugins'] : array(''), _tinymce_get_plugins());
543 $subgroup .= form_hidden('settings][plugins', 0);
544 $subgroup .= form_hidden('settings][buttons', 0);
545 $plug_loc = drupal_get_path('module', 'tinymce') .'/tinymce/jscripts/tiny_mce/plugins/';
546 foreach(_tinymce_get_plugins() as $plugin => $text) {
547 //print $plugin.":".$text."<br />";
548 $subgroup .= _tinymce_checkbox($text, 'settings][plugins][', $plugin, in_array($plugin, $edit->settings['plugins'] ? $edit->settings['plugins'] : array()) ? true : false);
549 if ($buttons = _tinymce_get_buttons($plug_loc.$plugin.'/editor_plugin.js')){
550 $subgroup .= '<div style="margin-left:5em">';
551 foreach ($buttons as $button) {
552 $subgroup .= _tinymce_checkbox($button . ' button', 'settings][buttons][', $button, in_array($button, $edit->settings['buttons'] ? $edit->settings['buttons'] : array()) ? true : false);
553 }
554 $subgroup .= '</div>';
aa8221b4 555 }
b436c922
JR
556 }
557 $subgroup .= '</div><br />';
558 $group .= form_group(t('TinyMCE Plugins'), $subgroup, t('Activate TinyMCE plugins and buttons. Each plugin can define buttons. In order to display these buttons in the editor, you need to activate both the plugin and its associated buttons. Silly, I know, but that\'s how they do it.'));
01a85ffd
MW
559 $output .= form_group(t('Editor display'), $group);
560
195b5d64
MW
561 $group = form_select(t('Auto cleanup Word'), 'settings][msword', $edit->settings['msword'], array('true' => 'true', 'false' => 'false'), t('Automatically cleanup MS Office/Word HTML will be executed automatically on paste operations. (Only works in Internet Explorer)'));
562 $group .= form_select(t('Verify HTML'), 'settings][verify_html', $edit->settings['verify_html'], array('true' => 'true', 'false' => 'false'), t('Should the HTML contents be verified or not? Verifying will strip &lt;head&gt tags, so choose false if you will be editing full page HTML.'));
563 $group .= form_select(t('Preformatted'), 'settings][preformatted', $edit->settings['preformatted'], array('false' => 'false', 'true' => 'true'), t('If this option is set to true, the editor will insert TAB characters on tab and preserve other whitespace characters just like a PRE HTML element does.'));
01a85ffd
MW
564 $output .= form_group(t('On save'), $group);
565
195b5d64
MW
566 $group = form_select(t('Editor CSS'), 'settings][css_setting', $edit->settings['css_setting'] ? $edit->settings['css_setting'] : 'theme', array('theme' => 'use theme css', 'self' => 'define css', 'none' => 'tinyMCE default'), t('Defines the CSS to be used in the editor area.<br />use theme css - get css from current Drupal theme.<br/>define css - enter path for css file below.<br />tinyMCE default - uses default CSS from editor.'));
567 $group .= form_textfield(t('CSS path'), 'settings][css_path', $edit->settings['css_path'], 40, 255, t('Enter path to CSS file (example: "/css/editor.css"). Select "define css" above.'));
568 $group .= form_textfield(t('CSS classes'), 'settings][css_classes', $edit->settings['css_classes'], 40, 255, t('Adds CSS classes to the "styles" droplist. Format is "&lt;title&gt;=&lt;class&gt;;"<br/> Example: "Header 1=header1;Header 2=header2;Header 3=header3;"<br />Leave blank to automatically import list of CSS classes from style sheet.'));
01a85ffd
MW
569 $output .= form_group(t('CSS'), $group);
570
6a17f63a 571 $group = form_textfield(t('Block formats'), 'settings][block_formats', $edit->settings['block_formats'] ? $edit->settings['block_formats'] : 'p,address,pre,h1,h2,h3,h4,h5,h6', 40, 250, t('Comma separated list of HTML block formats. You can only remove elements, not add.'));
195b5d64
MW
572 $group .= form_select(t('Force BR new lines'), 'settings][force_br', $edit->settings['force_br'] ? $edit->settings['force_br'] : 'false', array('true' => 'true', 'false' => 'false'), t('Use BR tags for new lines rather than P.'));
573 $group .= form_select(t('Force P new lines'), 'settings][force_p', $edit->settings['force_p'] ? $edit->settings['force_p'] : 'true', array('true' => 'true', 'false' => 'false'), t('When enabled, Mozilla/Firefox will generate P elements on Enter/Return key and BR elements on Shift+Enter/Return..'));
01a85ffd
MW
574 $output .= form_group(t('Formatting'), $group);
575
576 $output .= form_submit($btn);
577
578 return form($output);
579}
580
581/**
582 * Load all profiles.
583 */
584function tinymce_profile_load($name = '') {
585 static $profiles = array();
586
587 if (!$profiles) {
195b5d64 588 $roles = user_roles(1);
01a85ffd
MW
589 $result = db_query('SELECT * FROM {tinymce_settings}');
590 while ($data = db_fetch_object($result)) {
591 $data->settings = unserialize($data->settings);
195b5d64 592 $result2 = db_query("SELECT rid FROM {tinymce_role} WHERE name = '%s'", $data->name);
01a85ffd 593 $role = array();
195b5d64
MW
594 while ($r = db_fetch_object($result2)) {
595 $role[$r->rid] = $roles[$r->rid];
01a85ffd
MW
596 }
597 $data->rids = $role;
93c073b0 598
01a85ffd
MW
599 $profiles[$data->name] = $data;
600 }
601 }
93c073b0
MW
602
603 return ($name ? $profiles[$name] : $profiles);
01a85ffd
MW
604}
605
606/**
607 * Controller for tinymce profiles.
608 */
609function tinymce_profile_overview() {
610 $output = '';
611
277d4328 612 $output .= t('<p><a href="%create-profile-url">Create new profile</a></p>', array('%create-profile-url' => url('admin/settings/tinymce/add')));
01a85ffd
MW
613
614 $profiles = tinymce_profile_load();
615 if ($profiles) {
616 $roles = user_roles(1);
617 $header = array(t('Profile'), t('Roles'), t('Operations'));
618 foreach ($profiles as $p) {
277d4328 619 $rows[] = array(array('data' => $p->name, 'valign' => 'top'), array('data' => implode("<br />\n", $p->rids)), array('data' => l(t('edit'), 'admin/settings/tinymce/edit/'. urlencode($p->name)) . ' '. l(t('delete'), 'admin/settings/tinymce/delete/'. urlencode($p->name)), 'valign' => 'top'));
01a85ffd
MW
620 }
621 $output .= theme('table', $header, $rows). '<p>&nbsp;</p>';
622 }
623 else {
624 $output .= t('<p>No profiles found.</p>');
625 }
626
d6db52f5 627 $group = form_radios(t('Default tinymce state'), 'tinymce_default_state', variable_get('tinymce_default_state', 0), array(t('Off'), t('On')), t('Should tinymce be enabled or disabled by default when it\'s first loaded from a textarea? Note: The user may override this setting in their profile.'));
01a85ffd
MW
628 $output .= form_group(t('Default settings'), $group);
629 $output .= form_submit(t('Save settings'));
01a85ffd
MW
630
631 return form($output);
632}
633
634/**
635 * Save a profile to the database.
636 */
637function tinymce_profile_save($edit) {
638 db_query("DELETE FROM {tinymce_settings} WHERE name = '%s' or name = '%s'", $edit['name'], $edit['old_name']);
195b5d64
MW
639 db_query("DELETE FROM {tinymce_role} WHERE name = '%s' or name = '%s'", $edit['name'], $edit['old_name']);
640 db_query("INSERT INTO {tinymce_settings} (name, settings) VALUES ('%s', '%s')", $edit['name'], serialize($edit['settings']));
641 foreach ($edit['rids'] as $rid) {
642 db_query("INSERT INTO {tinymce_role} (name, rid) VALUES ('%s', %d)", $edit['name'], $rid);
643 }
01a85ffd
MW
644}
645
646/**
647 * Profile validation.
648 */
649function tinymce_profile_validate($edit) {
650 $errors = array();
651
652 if (!$edit['name']) {
195b5d64 653 $errors['name'] = t('You must give a profile name.');
01a85ffd
MW
654 }
655
656 if (!$edit['rids']) {
657 $errors['rids'] = t('You must select at least one role.');
658 }
659
660 foreach ($errors as $name => $message) {
661 form_set_error($name, $message);
662 }
663
664 return count($errors) == 0;
665}
666
667/********************************************************************
668 * Module Functions :: Private
669 ********************************************************************/
670
671/**
672 * Determine if TinyMCE has permission to be used on the current page.
6f760c55
MW
673 *
674 * @return
675 * TRUE if can render, FALSE if not allowed.
676 */
01a85ffd 677function _tinymce_page_match($edit) {
6f760c55 678 //Kill TinyMCE if we're editing a textarea with PHP in it!
9670911b
MW
679 if ($_POST['edit']['format'] == 2) {
680 return FALSE;
6f760c55
MW
681 }
682 else {
683 // PHP input formats are #2 in the filters table.
684 preg_match("|^node/(\d+)(/edit)$|", $_GET['q'], $match);
685 if (intval($match[1]) > 0) {
686 if (db_result(db_query('SELECT format FROM {node} WHERE nid = %d AND format = 2', $match[1]))) {
687 return FALSE;
688 }
689 }
690 }
691
7438a632 692 if ($edit->settings['access'] == 1) {
6f760c55
MW
693 return TRUE;
694 }
695 else {
696 $page_match = FALSE;
277d4328 697 $pages = $edit->settings['access_pages'] ? $edit->settings['access_pages'] : tinymce_help('admin/settings/tinymce#pages');
6f760c55
MW
698 if ($pages) {
699 $path = drupal_get_path_alias($_GET['q']);
700 $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. variable_get('site_frontpage', 'node') .'\2'), preg_quote($pages, '/')) .')$/';
701 $page_match = preg_match($regexp, $path);
702 }
703 return $page_match;
704 }
705}
b436c922
JR
706
707/**
708 * A kludge to render checkboxes for our interlaced buttons and plugins.
709 * A slight modification to the drupal form_checkbox function
710 */
711
712function _tinymce_checkbox($title, $name, $value = 1, $checked = FALSE, $description = NULL, $attributes = NULL, $required = FALSE) {
713 $element = '<input type="checkbox" class="'. _form_get_class('form-checkbox', $required, _form_get_error($name)) .'" name="edit['. $name .']" id="edit-'. $name .'" value="'. $value .'"'. ($checked ? ' checked="checked"' : '') . drupal_attributes($attributes) .' />';
714 if (!is_null($title)) {
715 $element = '<label class="option">'. $element .' '. $title .'</label>';
716 }
717 // Note: because unchecked boxes are not included in the POST data, we include
718 // a form_hidden() which will be overwritten for a checked box.
719 // we're handling the form_hidden ourselves... thank you very much
720 return theme('form_element', NULL, $element, $description, $name, $required, _form_get_error($name));
aa8221b4 721}
b436c922 722
ae24db96 723?>