5 * Integrate Wysiwyg editors into Drupal.
9 * Form builder for Wysiwyg profile form.
11 function wysiwyg_profile_form($form, &$form_state, $profile) {
13 $profile = (array) $profile;
18 if (empty($profile['settings'])) {
19 $profile['settings'] = array();
21 $profile['settings'] += array(
23 'user_choose' => FALSE
,
24 'show_toggle' => TRUE
,
25 'theme' => 'advanced',
28 'access_pages' => "node/*\nuser/*\ncomment/*",
30 'toolbar_loc' => 'top',
31 'toolbar_align' => 'left',
32 'path_loc' => 'bottom',
34 // Also available, but buggy in TinyMCE 2.x: blockquote,code,dt,dd,samp.
35 'block_formats' => 'p,address,pre,h2,h3,h4,h5,h6,div',
36 'verify_html' => TRUE
,
37 'preformatted' => FALSE
,
38 'convert_fonts_to_spans' => TRUE
,
39 'remove_linebreaks' => TRUE
,
40 'apply_source_formatting' => FALSE
,
41 'paste_auto_cleanup_on_paste' => FALSE
,
42 'css_setting' => 'theme',
44 'css_classes' => NULL
,
46 $profile = (object) $profile;
48 $formats = filter_formats();
49 $editor = wysiwyg_get_editor($profile->editor
);
50 drupal_set_title(t('%editor profile for %format', array('%editor' => $editor['title'], '%format' => $formats[$profile->format
]->name
)), PASS_THROUGH
);
52 $form['format'] = array('#type' => 'value', '#value' => $profile->format
);
53 $form['input_format'] = array('#type' => 'value', '#value' => $formats[$profile->format
]->name
);
54 $form['editor'] = array('#type' => 'value', '#value' => $profile->editor
);
56 $form['basic'] = array(
57 '#type' => 'fieldset',
58 '#title' => t('Basic setup'),
59 '#collapsible' => TRUE
,
63 $form['basic']['default'] = array(
64 '#type' => 'checkbox',
65 '#title' => t('Enabled by default'),
66 '#default_value' => $profile->settings
['default'],
68 '#description' => t('The default editor state for users having access to this profile. Users are able to override this state if the next option is enabled.'),
71 $form['basic']['user_choose'] = array(
72 '#type' => 'checkbox',
73 '#title' => t('Allow users to choose default'),
74 '#default_value' => $profile->settings
['user_choose'],
76 '#description' => t('If allowed, users will be able to choose their own editor default state in their user account settings.'),
79 $form['basic']['show_toggle'] = array(
80 '#type' => 'checkbox',
81 '#title' => t('Show <em>enable/disable rich text</em> toggle link'),
82 '#default_value' => $profile->settings
['show_toggle'],
84 '#description' => t('Whether or not to show the <em>enable/disable rich text</em> toggle link below a textarea. If disabled, the user setting or global default is used (see above).'),
87 $form['basic']['theme'] = array(
89 '#value' => $profile->settings
['theme'],
92 $form['basic']['language'] = array(
94 '#title' => t('Interface language'),
95 '#default_value' => $profile->settings
['language'],
97 // @see _locale_prepare_predefined_list()
98 require_once DRUPAL_ROOT .
'/includes/iso.inc';
99 $predefined = _locale_get_predefined_list();
100 foreach ($predefined as
$key => $value) {
101 // Include native name in output, if possible
102 if (count($value) > 1) {
103 $tname = t($value[0]);
104 $predefined[$key] = ($tname == $value[1]) ?
$tname : "$tname ($value[1])";
107 $predefined[$key] = t($value[0]);
111 $form['basic']['language']['#options'] = $predefined;
113 $form['buttons'] = array(
114 '#type' => 'fieldset',
115 '#title' => t('Buttons and plugins'),
116 '#collapsible' => TRUE
,
117 '#collapsed' => TRUE
,
119 '#theme' => 'wysiwyg_admin_button_table',
122 $plugins = wysiwyg_get_plugins($profile->editor
);
123 // Generate the button list.
124 foreach ($plugins as
$name => $meta) {
125 if (isset($meta['buttons']) && is_array($meta['buttons'])) {
126 foreach ($meta['buttons'] as
$button => $title) {
128 if (!empty($meta['path'])) {
129 // @todo Button icon locations are different in editors, editor versions,
130 // and contrib/custom plugins (like Image Assist, f.e.).
131 $img_src = $meta['path'] .
"/images/$name.gif";
132 // Handle plugins that have more than one button.
133 if (!file_exists($img_src)) {
134 $img_src = $meta['path'] .
"/images/$button.gif";
136 $icon = file_exists($img_src) ?
'<img src="' .
base_path() .
$img_src .
'" title="' .
$button .
'" style="border: 1px solid grey; vertical-align: middle;" />' : '';
138 $title = (isset($meta['url']) ?
l($title, $meta['url'], array('target' => '_blank')) : check_plain($title));
139 $title = (!empty($icon) ?
$icon .
' ' .
$title : $title);
140 $form['buttons'][$name][$button] = array(
141 '#type' => 'checkbox',
143 '#default_value' => !empty($profile->settings
['buttons'][$name][$button]) ?
$profile->settings
['buttons'][$name][$button] : FALSE
,
147 elseif (isset($meta['extensions']) && is_array($meta['extensions'])) {
148 foreach ($meta['extensions'] as
$extension => $title) {
149 $form['buttons'][$name][$extension] = array(
150 '#type' => 'checkbox',
151 '#title' => isset($meta['url']) ?
l($title, $meta['url'], array('target' => '_blank')) : check_plain($title),
152 '#default_value' => !empty($profile->settings
['buttons'][$name][$extension]) ?
$profile->settings
['buttons'][$name][$extension] : FALSE
,
158 $form['appearance'] = array(
159 '#type' => 'fieldset',
160 '#title' => t('Editor appearance'),
161 '#collapsible' => TRUE
,
162 '#collapsed' => TRUE
,
165 $form['appearance']['toolbar_loc'] = array(
167 '#title' => t('Toolbar location'),
168 '#default_value' => $profile->settings
['toolbar_loc'],
169 '#options' => array('bottom' => t('Bottom'), 'top' => t('Top')),
170 '#description' => t('This option controls whether the editor toolbar is displayed above or below the editing area.'),
173 $form['appearance']['toolbar_align'] = array(
175 '#title' => t('Button alignment'),
176 '#default_value' => $profile->settings
['toolbar_align'],
177 '#options' => array('center' => t('Center'), 'left' => t('Left'), 'right' => t('Right')),
178 '#description' => t('This option controls the alignment of icons in the editor toolbar.'),
181 $form['appearance']['path_loc'] = array(
183 '#title' => t('Path location'),
184 '#default_value' => $profile->settings
['path_loc'],
185 '#options' => array('none' => t('Hide'), 'top' => t('Top'), 'bottom' => t('Bottom')),
186 '#description' => t('Where to display the path to HTML elements (i.e. <code>body > table > tr > td</code>).'),
189 $form['appearance']['resizing'] = array(
190 '#type' => 'checkbox',
191 '#title' => t('Enable resizing button'),
192 '#default_value' => $profile->settings
['resizing'],
193 '#return_value' => 1,
194 '#description' => t('This option gives you the ability to enable/disable the resizing button. If enabled, the Path location toolbar must be set to "Top" or "Bottom" in order to display the resize icon.'),
197 $form['output'] = array(
198 '#type' => 'fieldset',
199 '#title' => t('Cleanup and output'),
200 '#collapsible' => TRUE
,
201 '#collapsed' => TRUE
,
204 $form['output']['verify_html'] = array(
205 '#type' => 'checkbox',
206 '#title' => t('Verify HTML'),
207 '#default_value' => $profile->settings
['verify_html'],
208 '#return_value' => 1,
209 '#description' => t('If enabled, potentially malicious code like <code><HEAD></code> tags will be removed from HTML contents.'),
212 $form['output']['preformatted'] = array(
213 '#type' => 'checkbox',
214 '#title' => t('Preformatted'),
215 '#default_value' => $profile->settings
['preformatted'],
216 '#return_value' => 1,
217 '#description' => t('If enabled, the editor will insert TAB characters on tab and preserve other whitespace characters just like a PRE element in HTML does.'),
220 $form['output']['convert_fonts_to_spans'] = array(
221 '#type' => 'checkbox',
222 '#title' => t('Convert <font> tags to styles'),
223 '#default_value' => $profile->settings
['convert_fonts_to_spans'],
224 '#return_value' => 1,
225 '#description' => t('If enabled, HTML tags declaring the font size, font family, font color and font background color will be replaced by inline CSS styles.'),
228 $form['output']['remove_linebreaks'] = array(
229 '#type' => 'checkbox',
230 '#title' => t('Remove linebreaks'),
231 '#default_value' => $profile->settings
['remove_linebreaks'],
232 '#return_value' => 1,
233 '#description' => t('If enabled, the editor will remove most linebreaks from contents. Disabling this option could avoid conflicts with other input filters.'),
236 $form['output']['apply_source_formatting'] = array(
237 '#type' => 'checkbox',
238 '#title' => t('Apply source formatting'),
239 '#default_value' => $profile->settings
['apply_source_formatting'],
240 '#return_value' => 1,
241 '#description' => t('If enabled, the editor will re-format the HTML source code. Disabling this option could avoid conflicts with other input filters.'),
244 $form['output']['paste_auto_cleanup_on_paste'] = array(
245 '#type' => 'checkbox',
246 '#title' => t('Force cleanup on standard paste'),
247 '#default_value' => $profile->settings
['paste_auto_cleanup_on_paste'],
248 '#return_value' => 1,
249 '#description' => t('If enabled, the default paste function (CTRL-V or SHIFT-INS) behaves like the "paste from word" plugin function.'),
252 $form['css'] = array(
253 '#type' => 'fieldset',
254 '#title' => t('CSS'),
255 '#collapsible' => TRUE
,
256 '#collapsed' => TRUE
,
259 $form['css']['block_formats'] = array(
260 '#type' => 'textfield',
261 '#title' => t('Block formats'),
262 '#default_value' => $profile->settings
['block_formats'],
265 '#description' => t('Comma separated list of HTML block formats. Possible values: <code>@format-list</code>.', array('@format-list' => 'p,h1,h2,h3,h4,h5,h6,div,blockquote,address,pre,code,dt,dd')),
268 $form['css']['css_setting'] = array(
270 '#title' => t('Editor CSS'),
271 '#default_value' => $profile->settings
['css_setting'],
272 '#options' => array('theme' => t('Use theme CSS'), 'self' => t('Define CSS'), 'none' => t('Editor default CSS')),
273 '#description' => t('Defines the CSS to be used in the editor area.<br />Use theme CSS - loads stylesheets from current site theme.<br/>Define CSS - enter path for stylesheet files below.<br />Editor default CSS - uses default stylesheets from editor.'),
276 $form['css']['css_path'] = array(
277 '#type' => 'textfield',
278 '#title' => t('CSS path'),
279 '#default_value' => $profile->settings
['css_path'],
282 '#description' => t('If "Define CSS" was selected above, enter path to a CSS file or a list of CSS files separated by a comma.') .
'<br />' .
t('Available tokens: <code>%b</code> (base path, eg: <code>/</code>), <code>%t</code> (path to theme, eg: <code>themes/garland</code>)') .
'<br />' .
t('Example:') .
' css/editor.css,/themes/garland/style.css,%b%t/style.css,http://example.com/external.css',
285 $form['css']['css_classes'] = array(
286 '#type' => 'textarea',
287 '#title' => t('CSS classes'),
288 '#default_value' => $profile->settings
['css_classes'],
289 '#description' => t('Optionally define CSS classes for the "Font style" dropdown list.<br />Enter one class on each line in the format: !format. Example: !example<br />If left blank, CSS classes are automatically imported from all loaded stylesheet(s).', array('!format' => '<code>[title]=[class]</code>', '!example' => 'My heading=header1')),
292 $form['submit'] = array(
294 '#value' => t('Save'),
297 $form['cancel'] = array(
298 '#value' => l(t('Cancel'), 'admin/config/content/wysiwyg'),
306 * Submit callback for Wysiwyg profile form.
308 * @see wysiwyg_profile_form()
310 function wysiwyg_profile_form_submit($form, &$form_state) {
311 $values = $form_state['values'];
312 if (isset($values['buttons'])) {
313 // Store only enabled buttons for each plugin.
314 foreach ($values['buttons'] as
$plugin => $buttons) {
315 $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]);
317 // Store only enabled plugins.
318 $values['buttons'] = array_filter($values['buttons']);
320 // Remove any white-space from 'block_formats' setting, since editor
321 // implementations rely on a comma-separated list to explode().
322 $values['block_formats'] = preg_replace('@\s+@', '', $values['block_formats']);
324 // Remove input format name.
325 $format = $values['format'];
326 $input_format = $values['input_format'];
327 $editor = $values['editor'];
328 unset($values['format'], $values['input_format'], $values['editor']);
330 // Remove FAPI values.
331 // @see system_settings_form_submit()
332 unset($values['submit'], $values['form_id'], $values['op'], $values['form_token'], $values['form_build_id']);
334 // Insert new profile data.
336 ->key(array('format' => $format))
339 'settings' => serialize($values),
342 wysiwyg_profile_cache_clear();
344 drupal_set_message(t('Wysiwyg profile for %format has been saved.', array('%format' => $input_format)));
346 $form_state['redirect'] = 'admin/config/content/wysiwyg';
350 * Layout for the buttons in the Wysiwyg Editor profile form.
352 function theme_wysiwyg_admin_button_table($variables) {
353 $form = $variables['form'];
356 // Flatten forms array.
357 foreach (element_children($form) as
$name) {
358 foreach (element_children($form[$name]) as
$button) {
359 $buttons[] = drupal_render($form[$name][$button]);
363 // Split checkboxes into rows with 3 columns.
364 $total = count($buttons);
366 for ($i = 0; $i < $total; $i += 3) {
368 $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array());
369 foreach ($row_buttons as
$row_button) {
370 $row[] = array('data' => $row_button);
375 $output = theme('table', array('rows' => $rows, 'attributes' => array('width' => '100%')));
381 * Display overview of setup Wysiwyg Editor profiles; menu callback.
383 function wysiwyg_profile_overview($form, &$form_state) {
384 include_once
'./includes/install.inc';
386 // Check which wysiwyg editors are installed.
387 $editors = wysiwyg_get_all_editors();
388 $count = count($editors);
390 $options = array('' => t('No editor'));
392 // D7's seven theme displays links in table headers as block elements.
393 drupal_add_css('table.system-status-report th a {display: inline;}', 'inline');
395 foreach ($editors as
$name => $editor) {
396 $status[$name] = array(
397 'severity' => (isset($editor['error']) ? REQUIREMENT_ERROR
: ($editor['installed'] ? REQUIREMENT_OK
: REQUIREMENT_INFO
)),
398 'title' => t('<a href="!vendor-url">@editor</a> (<a href="!download-url">Download</a>)', array('!vendor-url' => $editor['vendor url'], '@editor' => $editor['title'], '!download-url' => $editor['download url'])),
399 'value' => (isset($editor['installed version']) ?
$editor['installed version'] : t('Not installed.')),
400 'description' => (isset($editor['error']) ?
$editor['error'] : ''),
402 if ($editor['installed']) {
403 $options[$name] = $editor['title'] .
(isset($editor['installed version']) ?
' ' .
$editor['installed version'] : '');
406 // Build on-site installation instructions.
407 // @todo Setup $library in wysiwyg_load_editor() already.
408 $library = (isset($editor['library']) ?
$editor['library'] : key($editor['libraries']));
410 '@editor-path' => $editor['editor path'],
411 '@library-filepath' => $editor['library path'] .
'/' .
(isset($editor['libraries'][$library]['files'][0]) ?
$editor['libraries'][$library]['files'][0] : key($editor['libraries'][$library]['files'])),
413 $instructions = '<p>' .
t('Extract the archive and copy its contents into a new folder in the following location:<br /><code>@editor-path</code>', $targs) .
'</p>';
414 $instructions .
= '<p>' .
t('So the actual library can be found at:<br /><code>@library-filepath</code>', $targs) .
'</p>';
416 $status[$name]['description'] .
= $instructions;
419 // In case there is an error, always show installation instructions.
420 if (isset($editor['error'])) {
421 $show_instructions = TRUE
;
425 $show_instructions = TRUE
;
427 $form['status'] = array(
428 '#type' => 'fieldset',
429 '#title' => t('Installation instructions'),
430 '#collapsible' => TRUE
,
431 '#collapsed' => !isset($show_instructions),
432 '#description' => (!$count ?
t('There are no editor libraries installed currently. The following list contains a list of currently supported editors:') : ''),
435 $form['status']['report'] = array('#markup' => theme('status_report', array('requirements' => $status)));
441 $formats = filter_formats();
442 $profiles = wysiwyg_profile_load_all();
443 $form['formats'] = array(
445 '#description' => t('To assign a different editor to a text format, click "delete" to remove the existing first.'),
449 $enable_save = FALSE
;
450 foreach ($formats as
$id => $format) {
451 $form['formats'][$id]['name'] = array(
452 '#markup' => check_plain($format->name
),
454 // Only display editor selection for associated input formats to avoid
455 // confusion about disabled selection.
456 if (isset($profiles[$id]) && !empty($profiles[$id]->editor
)) {
457 $form['formats'][$id]['editor'] = array(
458 '#markup' => $options[$profiles[$id]->editor
],
462 $form['formats'][$id]['editor'] = array(
464 '#default_value' => '',
465 '#options' => $options,
469 if (isset($profiles[$id]) && !empty($profiles[$id]->editor
)) {
470 $form['formats'][$id]['edit'] = array(
471 '#markup' => l(t('Edit'), "admin/config/content/wysiwyg/profile/$id/edit"),
473 $form['formats'][$id]['delete'] = array(
474 '#markup' => l(t('Delete'), "admin/config/content/wysiwyg/profile/$id/delete"),
479 // Submitting the form when no editors can be selected causes errors.
481 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
487 * Return HTML for the Wysiwyg profile overview form.
489 function theme_wysiwyg_profile_overview($variables) {
490 $form = $variables['form'];
491 if (!isset($form['formats'])) {
495 $header = array(t('Text format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2));
497 foreach (element_children($form['formats']) as
$item) {
498 $format = &$form['formats'][$item];
500 drupal_render($format['name']),
501 drupal_render($format['editor']),
502 isset($format['edit']) ?
drupal_render($format['edit']) : '',
503 isset($format['delete']) ?
drupal_render($format['delete']) : '',
506 $form['formats']['table']['#markup'] = theme('table', array('header' => $header, 'rows' => $rows));
507 $output .
= drupal_render_children($form);
512 * Submit callback for Wysiwyg profile overview form.
514 function wysiwyg_profile_overview_submit($form, &$form_state) {
515 foreach ($form_state['values']['formats'] as
$format => $values) {
517 ->key(array('format' => $format))
519 'editor' => $values['editor'],
523 wysiwyg_profile_cache_clear();
527 * Delete editor profile confirmation form.
529 function wysiwyg_profile_delete_confirm($form, &$form_state, $profile) {
530 $formats = filter_formats();
531 $format = $formats[$profile->format
];
532 $form['format'] = array('#type' => 'value', '#value' => $format);
535 t('Are you sure you want to remove the profile for %name?', array('%name' => $format->name
)),
536 'admin/config/content/wysiwyg',
537 t('This action cannot be undone.'), t('Remove'), t('Cancel')
542 * Submit callback for Wysiwyg profile delete form.
544 * @see wysiwyg_profile_delete_confirm()
546 function wysiwyg_profile_delete_confirm_submit($form, &$form_state) {
547 $format = $form_state['values']['format'];
548 wysiwyg_profile_delete($format->format
);
549 wysiwyg_profile_cache_clear();
551 drupal_set_message(t('Wysiwyg profile for %name has been deleted.', array('%name' => $format->name
)));
552 $form_state['redirect'] = 'admin/config/content/wysiwyg';