| 14b92488 |
1 | <?php |
| 2 | // $Id$ |
| 3 | |
| 4 | /** |
| 5 | * @file |
| eb714832 |
6 | * Integrate client-side editors with Drupal. |
| 14b92488 |
7 | */ |
| 8 | |
| 9 | /** |
| eb714832 |
10 | * Implementation of hook_menu(). |
| 11 | */ |
| 12 | function wysiwyg_menu() { |
| 117ad234 |
13 | $items['admin/settings/wysiwyg'] = array( |
| eb714832 |
14 | 'title' => 'Wysiwyg', |
| 117ad234 |
15 | 'page callback' => 'drupal_get_form', |
| 16 | 'page arguments' => array('wysiwyg_profile_overview'), |
| 17 | 'description' => 'Configure client-side editors.', |
| 18 | 'access arguments' => array('administer filters'), |
| 19 | 'file' => 'wysiwyg.admin.inc', |
| 20 | ); |
| 21 | $items['admin/settings/wysiwyg/profile'] = array( |
| 22 | 'title' => 'Profiles', |
| 23 | 'type' => MENU_DEFAULT_LOCAL_TASK, |
| 24 | ); |
| 25 | $items['admin/settings/wysiwyg/profile/%wysiwyg_profile/edit'] = array( |
| 26 | 'title' => 'Edit', |
| 27 | 'page callback' => 'drupal_get_form', |
| 28 | 'page arguments' => array('wysiwyg_profile_form', 4), |
| af8246de |
29 | 'access arguments' => array('administer filters'), |
| eb714832 |
30 | 'file' => 'wysiwyg.admin.inc', |
| 117ad234 |
31 | 'tab_root' => 'admin/settings/wysiwyg/profile', |
| 32 | 'tab_parent' => 'admin/settings/wysiwyg/profile/%wysiwyg_profile', |
| 33 | 'type' => MENU_LOCAL_TASK, |
| 34 | ); |
| 35 | $items['admin/settings/wysiwyg/profile/%wysiwyg_profile/delete'] = array( |
| 36 | 'title' => 'Remove', |
| 37 | 'page callback' => 'drupal_get_form', |
| 38 | 'page arguments' => array('wysiwyg_profile_delete_confirm', 4), |
| 39 | 'access arguments' => array('administer filters'), |
| 40 | 'file' => 'wysiwyg.admin.inc', |
| 41 | 'tab_root' => 'admin/settings/wysiwyg/profile', |
| 42 | 'tab_parent' => 'admin/settings/wysiwyg/profile/%wysiwyg_profile', |
| 43 | 'type' => MENU_LOCAL_TASK, |
| 44 | 'weight' => 10, |
| eb714832 |
45 | ); |
| 6e91d64e |
46 | $items['wysiwyg/%'] = array( |
| 47 | 'page callback' => 'wysiwyg_dialog', |
| 48 | 'page arguments' => array(1), |
| 49 | 'access arguments' => array('access content'), |
| 50 | 'type' => MENU_CALLBACK, |
| 51 | 'file' => 'wysiwyg.dialog.inc', |
| 52 | ); |
| eb714832 |
53 | return $items; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Implementation of hook_theme(). |
| 6e91d64e |
58 | * |
| 59 | * @see drupal_common_theme(), common.inc |
| 60 | * @see template_preprocess_page(), theme.inc |
| eb714832 |
61 | */ |
| 62 | function wysiwyg_theme() { |
| 63 | return array( |
| 81a2754f |
64 | 'wysiwyg_profile_overview' => array('arguments' => array('form' => NULL)), |
| 65 | 'wysiwyg_admin_button_table' => array('arguments' => array('form' => NULL)), |
| 6e91d64e |
66 | 'wysiwyg_dialog_page' => array( |
| 67 | 'arguments' => array('content' => NULL, 'show_messages' => TRUE), |
| 68 | 'file' => 'wysiwyg.dialog.inc', |
| 69 | 'template' => 'wysiwyg-dialog-page', |
| 70 | ), |
| eb714832 |
71 | ); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Implementation of hook_help(). |
| 76 | */ |
| 77 | function wysiwyg_help($path, $arg) { |
| 78 | switch ($path) { |
| 117ad234 |
79 | case 'admin/settings/wysiwyg': |
| fddd288d |
80 | $output = '<p>' . t('A Wysiwyg profile is associated with an input format. A Wysiwyg profile defines which client-side editor is loaded with a particular input format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '</p>'; |
| 117ad234 |
81 | return $output; |
| eb714832 |
82 | } |
| 83 | } |
| 84 | |
| 85 | /** |
| eb714832 |
86 | * Implementation of hook_form_alter(). |
| 33fd8bc9 |
87 | * |
| 88 | * Before Drupal 7, there is no way to easily identify form fields that are |
| 89 | * input format enabled. As a workaround, we assign a form #after_build |
| 90 | * processing callback that is executed on all forms after they have been |
| 91 | * completely built, so form elements are in their effective order |
| 92 | * and position already. |
| 93 | * |
| 94 | * @see wysiwyg_process_form() |
| eb714832 |
95 | */ |
| 96 | function wysiwyg_form_alter(&$form, &$form_state) { |
| 33fd8bc9 |
97 | $form['#after_build'][] = 'wysiwyg_process_form'; |
| 715d4b42 |
98 | // Teaser splitter is unconditionally removed and NOT supported. |
| eb714832 |
99 | if (isset($form['body_field'])) { |
| 100 | unset($form['body_field']['teaser_js']); |
| 14b92488 |
101 | } |
| 14b92488 |
102 | } |
| 103 | |
| 104 | /** |
| eb714832 |
105 | * Process a textarea for Wysiwyg Editor. |
| 106 | * |
| 107 | * This way, we can recurse into the form and search for certain, hard-coded |
| 108 | * elements that have been added by filter_form(). If an input format selector |
| 109 | * or input format guidelines element is found, we assume that the preceding |
| 110 | * element is the corresponding textarea and use it's #id for attaching |
| 111 | * client-side editors. |
| 14b92488 |
112 | * |
| eb714832 |
113 | * @see wysiwyg_elements(), filter_form() |
| 114 | */ |
| 115 | function wysiwyg_process_form(&$form) { |
| 116 | // Iterate over element children; resetting array keys to access last index. |
| 117 | if ($children = array_values(element_children($form))) { |
| 118 | foreach ($children as $index => $item) { |
| 119 | $element = &$form[$item]; |
| 120 | |
| 121 | // filter_form() always uses the key 'format'. We need a type-agnostic |
| 122 | // match to prevent false positives. Also, there must have been at least |
| 123 | // one element on this level. |
| 124 | if ($item === 'format' && $index > 0) { |
| 125 | // Make sure we either match a input format selector or input format |
| 126 | // guidelines (displayed if user has access to one input format only). |
| 127 | if ((isset($element['#type']) && $element['#type'] == 'fieldset') || isset($element['format']['guidelines'])) { |
| 128 | // The element before this element is the target form field. |
| 129 | $field = &$form[$children[$index - 1]]; |
| 130 | |
| 131 | // Disable #resizable to avoid resizable behavior to hi-jack the UI, |
| 132 | // but load the behavior, so the 'none' editor can attach/detach it. |
| 133 | $extra_class = ''; |
| 134 | if (!empty($field['#resizable'])) { |
| 135 | // Due to our CSS class parsing, we can add arbitrary parameters |
| 136 | // for each input format. |
| 137 | $extra_class = ' wysiwyg-resizable-1'; |
| 138 | $field['#resizable'] = FALSE; |
| 139 | drupal_add_js('misc/textarea.js'); |
| 140 | } |
| 141 | |
| 142 | // Determine the available input formats. The last child element is a |
| 143 | // link to "More information about formatting options". When only one |
| 144 | // input format is displayed, we also have to remove formatting |
| 145 | // guidelines, stored in the child 'format'. |
| 146 | $formats = element_children($element); |
| 147 | array_pop($formats); |
| 1aabb694 |
148 | if (($key = array_search('format', $formats)) !== FALSE) { |
| 149 | unset($formats[$key]); |
| 150 | } |
| eb714832 |
151 | foreach ($formats as $format) { |
| 152 | // Default to 'none' editor (Drupal's default behaviors). |
| 153 | $editor = 'none'; |
| c3dc7f9a |
154 | $status = 1; |
| cb155c9f |
155 | $toggle = 1; |
| eb714832 |
156 | // Fetch the profile associated to this input format. |
| 157 | $profile = wysiwyg_get_profile($format); |
| 158 | if ($profile) { |
| 81a2754f |
159 | $editor = $profile->editor; |
| cb155c9f |
160 | $status = (int) wysiwyg_user_get_status($profile); |
| 161 | $toggle = (int) $profile->settings['show_toggle']; |
| eb714832 |
162 | // Check editor theme (and reset it if not/no longer available). |
| e16a9972 |
163 | $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : '')); |
| eb714832 |
164 | |
| 6e91d64e |
165 | // Add plugin settings (first) for this input format. |
| 166 | wysiwyg_add_plugin_settings($profile); |
| eb714832 |
167 | // Add profile settings for this input format. |
| 168 | wysiwyg_add_editor_settings($profile, $theme); |
| eb714832 |
169 | } |
| 170 | |
| 171 | // Use a prefix/suffix for a single input format, or attach to input |
| 172 | // format selector radio buttons. |
| 173 | if (isset($element['format']['guidelines'])) { |
| cb155c9f |
174 | $element['format']['guidelines']['#prefix'] = '<div class="wysiwyg wysiwyg-format-' . $format . ' wysiwyg-editor-' . $editor . ' wysiwyg-field-' . $field['#id'] . ' wysiwyg-status-' . $status . ' wysiwyg-toggle-' . $toggle . $extra_class . '">'; |
| 1aabb694 |
175 | $element['format']['guidelines']['#suffix'] = '</div>'; |
| 176 | // Edge-case: Default format contains no input filters. |
| 177 | if (empty($element['format']['guidelines']['#value'])) { |
| 178 | $element['format']['guidelines']['#value'] = ' '; |
| 179 | } |
| eb714832 |
180 | } |
| 181 | else { |
| cb155c9f |
182 | $element[$format]['#attributes']['class'] = (isset($element[$format]['#attributes']['class']) ? $element[$format]['#attributes']['class'] . ' ' : ''); |
| 183 | $element[$format]['#attributes']['class'] .= 'wysiwyg wysiwyg-format-' . $format . ' wysiwyg-editor-' . $editor . ' wysiwyg-field-' . $field['#id'] . ' wysiwyg-status-' . $status . ' wysiwyg-toggle-' . $toggle . $extra_class; |
| eb714832 |
184 | } |
| 185 | } |
| 186 | } |
| 187 | // If this element is 'format', do not recurse further. |
| 188 | continue; |
| 189 | } |
| 190 | // Recurse into children. |
| 191 | wysiwyg_process_form($element); |
| 192 | } |
| 193 | } |
| 194 | return $form; |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Determine the profile to use for a given input format id. |
| 14b92488 |
199 | * |
| eb714832 |
200 | * This function also performs sanity checks for the configured editor in a |
| 201 | * profile to ensure that we do not load a malformed editor. |
| 14b92488 |
202 | * |
| eb714832 |
203 | * @param $format |
| 204 | * The internal id of an input format. |
| 14b92488 |
205 | * |
| eb714832 |
206 | * @return |
| 207 | * A wysiwyg profile. |
| 208 | * |
| 209 | * @see wysiwyg_load_editor(), wysiwyg_get_editor() |
| 14b92488 |
210 | */ |
| eb714832 |
211 | function wysiwyg_get_profile($format) { |
| 117ad234 |
212 | if ($profile = wysiwyg_profile_load($format)) { |
| eb714832 |
213 | if (wysiwyg_load_editor($profile)) { |
| 214 | return $profile; |
| 215 | } |
| 216 | } |
| 217 | return FALSE; |
| 14b92488 |
218 | } |
| 219 | |
| 220 | /** |
| eb714832 |
221 | * Load an editor library and initialize basic Wysiwyg settings. |
| 222 | * |
| 223 | * @param $profile |
| 224 | * A wysiwyg editor profile. |
| 14b92488 |
225 | * |
| eb714832 |
226 | * @return |
| 227 | * TRUE if the editor has been loaded, FALSE if not. |
| 14b92488 |
228 | * |
| eb714832 |
229 | * @see wysiwyg_get_profile() |
| 230 | */ |
| 231 | function wysiwyg_load_editor($profile) { |
| 232 | static $settings_added; |
| 233 | static $loaded = array(); |
| 234 | |
| 81a2754f |
235 | $name = $profile->editor; |
| eb714832 |
236 | // Library files must be loaded only once. |
| 237 | if (!isset($loaded[$name])) { |
| 238 | // Load editor. |
| 239 | $editor = wysiwyg_get_editor($name); |
| 240 | if ($editor) { |
| 241 | // Determine library files to load. |
| 242 | // @todo Allow to configure the library/execMode to use. |
| 243 | if (isset($profile->settings['library']) && isset($editor['libraries'][$profile->settings['library']])) { |
| 244 | $library = $profile->settings['library']; |
| a3d9c868 |
245 | $files = $editor['libraries'][$library]['files']; |
| eb714832 |
246 | } |
| 247 | else { |
| a3d9c868 |
248 | // Fallback to the first defined library by default (external libraries can change). |
| eb714832 |
249 | $library = key($editor['libraries']); |
| 250 | $files = array_shift($editor['libraries']); |
| 251 | $files = $files['files']; |
| 252 | } |
| 46b43486 |
253 | foreach ($files as $file => $options) { |
| 254 | if (is_array($options)) { |
| 255 | $options += array('type' => 'module', 'scope' => 'header', 'defer' => FALSE, 'cache' => TRUE, 'preprocess' => TRUE); |
| 256 | drupal_add_js($editor['library path'] . '/' . $file, $options['type'], $options['scope'], $options['defer'], $options['cache'], $options['preprocess']); |
| 257 | } |
| 258 | else { |
| 259 | drupal_add_js($editor['library path'] . '/' . $options); |
| 260 | } |
| eb714832 |
261 | } |
| a3d9c868 |
262 | // If editor defines an additional load callback, invoke it. |
| 263 | // @todo Isn't the settings callback sufficient? |
| 264 | if (isset($editor['load callback']) && function_exists($editor['load callback'])) { |
| 265 | $editor['load callback']($editor, $library); |
| 266 | } |
| eb714832 |
267 | // Load JavaScript integration files for this editor. |
| a09d7c1c |
268 | $files = array(); |
| eb714832 |
269 | if (isset($editor['js files'])) { |
| 270 | $files = $editor['js files']; |
| 271 | } |
| 272 | foreach ($files as $file) { |
| 81a2754f |
273 | drupal_add_js($editor['js path'] . '/' . $file); |
| eb714832 |
274 | } |
| a09d7c1c |
275 | // Load CSS stylesheets for this editor. |
| 276 | $files = array(); |
| 277 | if (isset($editor['css files'])) { |
| 278 | $files = $editor['css files']; |
| 279 | } |
| 280 | foreach ($files as $file) { |
| 17473a9a |
281 | drupal_add_css($editor['css path'] . '/' . $file); |
| a09d7c1c |
282 | } |
| eb714832 |
283 | |
| eb714832 |
284 | drupal_add_js(array('wysiwyg' => array( |
| 285 | 'configs' => array($editor['name'] => array()), |
| 7623c9ce |
286 | // @todo Move into profile settings. |
| 9e8e526a |
287 | 'showToggle' => isset($profile->settings['show_toggle']) ? $profile->settings['show_toggle'] : TRUE, |
| 7623c9ce |
288 | // @todo Move into (global) editor settings. |
| eb714832 |
289 | // If JS compression is enabled, at least TinyMCE is unable to determine |
| 290 | // its own base path and exec mode since it can't find the script name. |
| 291 | 'editorBasePath' => base_path() . $editor['library path'], |
| 292 | 'execMode' => $library, |
| 293 | )), 'setting'); |
| 294 | |
| 295 | $loaded[$name] = TRUE; |
| 296 | } |
| 297 | else { |
| 298 | $loaded[$name] = FALSE; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | // Add basic Wysiwyg settings if any editor has been added. |
| 303 | if (!isset($settings_added) && $loaded[$name]) { |
| 304 | drupal_add_js(array('wysiwyg' => array( |
| 305 | 'configs' => array(), |
| 306 | 'disable' => t('Disable rich-text'), |
| 307 | 'enable' => t('Enable rich-text'), |
| 308 | )), 'setting'); |
| 309 | |
| c18af709 |
310 | $path = drupal_get_path('module', 'wysiwyg'); |
| eb714832 |
311 | // Initialize our namespaces in the *header* to do not force editor |
| 312 | // integration scripts to check and define Drupal.wysiwyg on its own. |
| c18af709 |
313 | drupal_add_js($path . '/wysiwyg.init.js', 'core'); |
| eb714832 |
314 | |
| 315 | // The 'none' editor is a special editor implementation, allowing us to |
| 316 | // attach and detach regular Drupal behaviors just like any other editor. |
| c18af709 |
317 | drupal_add_js($path . '/editors/js/none.js'); |
| eb714832 |
318 | |
| 319 | // Add wysiwyg.js to the footer to ensure it's executed after the |
| 320 | // Drupal.settings array has been rendered and populated. Also, since editor |
| 321 | // library initialization functions must be loaded first by the browser, |
| 7623c9ce |
322 | // and Drupal.wysiwygInit() must be executed AFTER editors registered |
| 323 | // their callbacks and BEFORE Drupal.behaviors are applied, this must come |
| eb714832 |
324 | // last. |
| c18af709 |
325 | drupal_add_js($path . '/wysiwyg.js', 'module', 'footer'); |
| eb714832 |
326 | |
| eb714832 |
327 | $settings_added = TRUE; |
| 328 | } |
| 329 | |
| 330 | return $loaded[$name]; |
| 331 | } |
| 332 | |
| 333 | /** |
| e16a9972 |
334 | * Add editor settings for a given input format. |
| 14b92488 |
335 | */ |
| eb714832 |
336 | function wysiwyg_add_editor_settings($profile, $theme) { |
| e16a9972 |
337 | static $formats = array(); |
| eb714832 |
338 | |
| e16a9972 |
339 | if (!isset($formats[$profile->format])) { |
| eb714832 |
340 | $config = wysiwyg_get_editor_config($profile, $theme); |
| e16a9972 |
341 | // drupal_to_js() does not properly convert numeric array keys, so we need |
| 342 | // to use a string instead of the format id. |
| 54a65ca8 |
343 | drupal_add_js(array('wysiwyg' => array('configs' => array($profile->editor => array('format' . $profile->format => $config)))), 'setting'); |
| e16a9972 |
344 | $formats[$profile->format] = TRUE; |
| eb714832 |
345 | } |
| 14b92488 |
346 | } |
| 347 | |
| 348 | /** |
| eb714832 |
349 | * Add settings for external plugins. |
| 55557fd1 |
350 | * |
| 351 | * Plugins can be used in multiple profiles, but not necessarily in all. Because |
| 352 | * of that, we need to process plugins for each profile, even if most of their |
| 353 | * settings are not stored per profile. |
| 354 | * |
| 355 | * Implementations of hook_wysiwyg_plugin() may execute different code for each |
| 356 | * editor. Therefore, we have to invoke those implementations for each editor, |
| 357 | * but process the resulting plugins separately for each profile. |
| 358 | * |
| 359 | * Drupal plugins differ to native plugins in that they have plugin-specific |
| 360 | * definitions and settings, which need to be processed only once. But they are |
| 361 | * also passed to the editor to prepare settings specific to the editor. |
| 362 | * Therefore, we load and process the Drupal plugins only once, and hand off the |
| 363 | * effective definitions for each profile to the editor. |
| 364 | * |
| eb714832 |
365 | * @param $profile |
| 366 | * A wysiwyg editor profile. |
| 55557fd1 |
367 | * |
| 368 | * @todo Rewrite wysiwyg_process_form() to build a registry of effective |
| 369 | * profiles in use, so we can process plugins in multiple profiles in one shot |
| 370 | * and simplify this entire function. |
| eb714832 |
371 | */ |
| 372 | function wysiwyg_add_plugin_settings($profile) { |
| c574d4c1 |
373 | static $plugins = array(); |
| 374 | static $processed_plugins = array(); |
| 86ef143a |
375 | static $processed_formats = array(); |
| 376 | |
| 377 | // Each input format must only processed once. |
| c574d4c1 |
378 | // @todo ...as long as we do not have multiple profiles per format. |
| 86ef143a |
379 | if (isset($processed_formats[$profile->format])) { |
| 380 | return; |
| 381 | } |
| c574d4c1 |
382 | $processed_formats[$profile->format] = TRUE; |
| 6e91d64e |
383 | |
| 384 | $editor = wysiwyg_get_editor($profile->editor); |
| eb714832 |
385 | |
| c574d4c1 |
386 | // Collect native plugins for this editor provided via hook_wysiwyg_plugin() |
| 387 | // and Drupal plugins provided via hook_wysiwyg_include_directory(). |
| 388 | if (!array_key_exists($editor['name'], $plugins)) { |
| 389 | $plugins[$editor['name']] = wysiwyg_get_plugins($editor['name']); |
| 6e91d64e |
390 | } |
| eb714832 |
391 | |
| c574d4c1 |
392 | // Nothing to do, if there are no plugins. |
| 393 | if (empty($plugins[$editor['name']])) { |
| 6e91d64e |
394 | return; |
| eb714832 |
395 | } |
| 6e91d64e |
396 | |
| c574d4c1 |
397 | // Determine name of proxy plugin for Drupal plugins. |
| 398 | $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : ''); |
| 399 | |
| 400 | // Process native editor plugins. |
| 401 | if (isset($editor['plugin settings callback'])) { |
| 402 | // @todo Require PHP 5.1 in 3.x and use array_intersect_key(). |
| 403 | $profile_plugins_native = array(); |
| 404 | foreach ($plugins[$editor['name']] as $plugin => $meta) { |
| 405 | // Skip Drupal plugins (handled below). |
| 406 | if ($plugin === $proxy) { |
| 407 | continue; |
| 55557fd1 |
408 | } |
| c574d4c1 |
409 | // Only keep native plugins that are enabled in this profile. |
| 410 | if (isset($profile->settings['buttons'][$plugin])) { |
| 411 | $profile_plugins_native[$plugin] = $meta; |
| 6e91d64e |
412 | } |
| 413 | } |
| c574d4c1 |
414 | // Invoke the editor's plugin settings callback, so it can populate the |
| 415 | // settings for native external plugins with required values. |
| 416 | $settings_native = call_user_func($editor['plugin settings callback'], $editor, $profile, $profile_plugins_native); |
| 54a65ca8 |
417 | |
| c574d4c1 |
418 | drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('native' => $settings_native)))), 'setting'); |
| 6e91d64e |
419 | } |
| 6e91d64e |
420 | |
| c574d4c1 |
421 | // Process Drupal plugins. |
| 422 | if ($proxy && isset($editor['proxy plugin settings callback'])) { |
| 423 | $profile_plugins_drupal = array(); |
| 424 | foreach (wysiwyg_get_all_plugins() as $plugin => $meta) { |
| 425 | if (isset($profile->settings['buttons'][$proxy][$plugin])) { |
| 426 | // JavaScript and plugin-specific settings for Drupal plugins must be |
| 427 | // loaded and processed only once. Plugin information is cached |
| 428 | // statically to pass it to the editor's proxy plugin settings callback. |
| 429 | if (!isset($processed_plugins[$proxy][$plugin])) { |
| 430 | $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin] = $meta; |
| 431 | // Load the Drupal plugin's JavaScript. |
| 54a65ca8 |
432 | drupal_add_js($meta['js path'] . '/' . $meta['js file']); |
| c574d4c1 |
433 | // Add plugin-specific settings. |
| 434 | if (isset($meta['settings'])) { |
| 435 | drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin => $meta['settings'])))), 'setting'); |
| 436 | } |
| 437 | } |
| 438 | else { |
| 439 | $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin]; |
| 440 | } |
| 441 | } |
| 442 | } |
| 443 | // Invoke the editor's proxy plugin settings callback, so it can populate |
| 444 | // the settings for Drupal plugins with custom, required values. |
| 445 | $settings_drupal = call_user_func($editor['proxy plugin settings callback'], $editor, $profile, $profile_plugins_drupal); |
| 54a65ca8 |
446 | |
| c574d4c1 |
447 | drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('drupal' => $settings_drupal)))), 'setting'); |
| 448 | } |
| eb714832 |
449 | } |
| 450 | |
| 451 | /** |
| e16a9972 |
452 | * Retrieve available themes for an editor. |
| 14b92488 |
453 | * |
| e16a9972 |
454 | * Editor themes control the visual presentation of an editor. |
| 14b92488 |
455 | * |
| eb714832 |
456 | * @param $profile |
| 457 | * A wysiwyg editor profile; passed/altered by reference. |
| 458 | * @param $selected_theme |
| 459 | * An optional theme name that ought to be used. |
| 14b92488 |
460 | * |
| eb714832 |
461 | * @return |
| 462 | * An array of theme names, or a single, checked theme name if $selected_theme |
| 463 | * was given. |
| 14b92488 |
464 | */ |
| eb714832 |
465 | function wysiwyg_get_editor_themes(&$profile, $selected_theme = NULL) { |
| 466 | static $themes = array(); |
| 467 | |
| 81a2754f |
468 | if (!isset($themes[$profile->editor])) { |
| 469 | $editor = wysiwyg_get_editor($profile->editor); |
| eb714832 |
470 | if (isset($editor['themes callback']) && function_exists($editor['themes callback'])) { |
| 471 | $themes[$editor['name']] = $editor['themes callback']($editor, $profile); |
| 472 | } |
| 473 | // Fallback to 'default' otherwise. |
| 474 | else { |
| 475 | $themes[$editor['name']] = array('default'); |
| 476 | } |
| 14b92488 |
477 | } |
| eb714832 |
478 | |
| 479 | // Check optional $selected_theme argument, if given. |
| 480 | if (isset($selected_theme)) { |
| 481 | // If the passed theme name does not exist, use the first available. |
| 81a2754f |
482 | if (!isset($themes[$profile->editor][$selected_theme])) { |
| 483 | $selected_theme = $profile->settings['theme'] = $themes[$profile->editor][0]; |
| eb714832 |
484 | } |
| 485 | } |
| 486 | |
| 81a2754f |
487 | return isset($selected_theme) ? $selected_theme : $themes[$profile->editor]; |
| 14b92488 |
488 | } |
| 489 | |
| 490 | /** |
| eb714832 |
491 | * Return plugin metadata from the plugin registry. |
| 14b92488 |
492 | * |
| eb714832 |
493 | * @param $editor_name |
| 494 | * The internal name of an editor to return plugins for. |
| 14b92488 |
495 | * |
| eb714832 |
496 | * @return |
| 497 | * An array for each plugin. |
| 498 | */ |
| 499 | function wysiwyg_get_plugins($editor_name) { |
| 500 | $plugins = array(); |
| 501 | if (!empty($editor_name)) { |
| 502 | $editor = wysiwyg_get_editor($editor_name); |
| 503 | // Add internal editor plugins. |
| 504 | if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) { |
| 505 | $plugins = $editor['plugin callback']($editor); |
| 506 | } |
| eb714832 |
507 | // Add editor plugins provided via hook_wysiwyg_plugin(). |
| 508 | $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version'])); |
| 6e91d64e |
509 | // Add API plugins provided by Drupal modules. |
| 510 | // @todo We need to pass the filepath to the plugin icon for Drupal plugins. |
| 511 | if (isset($editor['proxy plugin'])) { |
| 512 | $plugins += $editor['proxy plugin']; |
| 513 | $proxy = key($editor['proxy plugin']); |
| 514 | foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) { |
| 515 | $plugins[$proxy]['buttons'][$plugin_name] = $info['title']; |
| 516 | } |
| 517 | } |
| eb714832 |
518 | } |
| 519 | return $plugins; |
| 520 | } |
| 521 | |
| 522 | /** |
| e16a9972 |
523 | * Return an array of initial editor settings for a Wysiwyg profile. |
| eb714832 |
524 | */ |
| 525 | function wysiwyg_get_editor_config($profile, $theme) { |
| 81a2754f |
526 | $editor = wysiwyg_get_editor($profile->editor); |
| eb714832 |
527 | $settings = array(); |
| 528 | if (!empty($editor['settings callback']) && function_exists($editor['settings callback'])) { |
| 529 | $settings = $editor['settings callback']($editor, $profile->settings, $theme); |
| 530 | } |
| 531 | return $settings; |
| 532 | } |
| 533 | |
| 534 | /** |
| 5708da35 |
535 | * Retrieve stylesheets for HTML/IFRAME-based editors. |
| 536 | * |
| 537 | * This assumes that the content editing area only needs stylesheets defined |
| 538 | * for the scope 'theme'. |
| 539 | * |
| 540 | * @return |
| 541 | * An array containing CSS files, including proper base path. |
| 542 | */ |
| 543 | function wysiwyg_get_css() { |
| 544 | static $files; |
| 545 | |
| 546 | if (isset($files)) { |
| 547 | return $files; |
| 548 | } |
| a0313372 |
549 | // In node form previews, the theme has not been initialized yet. |
| 550 | init_theme(); |
| 551 | |
| 5708da35 |
552 | $files = array(); |
| 553 | foreach (drupal_add_css() as $media => $css) { |
| 554 | if ($media != 'print') { |
| 555 | foreach ($css['theme'] as $filepath => $preprocess) { |
| 556 | $files[] = base_path() . $filepath; |
| 557 | } |
| 558 | } |
| 559 | } |
| 560 | return $files; |
| 561 | } |
| 562 | |
| 563 | /** |
| 117ad234 |
564 | * Load profile for a given input format. |
| 565 | */ |
| 566 | function wysiwyg_profile_load($format) { |
| 567 | static $profiles; |
| 568 | |
| 569 | if (!isset($profiles) || !array_key_exists($format, $profiles)) { |
| 570 | $result = db_query('SELECT format, editor, settings FROM {wysiwyg} WHERE format = %d', $format); |
| 571 | while ($profile = db_fetch_object($result)) { |
| 572 | $profile->settings = unserialize($profile->settings); |
| 573 | $profiles[$profile->format] = $profile; |
| 574 | } |
| 575 | } |
| 576 | |
| 577 | return (isset($profiles[$format]) ? $profiles[$format] : FALSE); |
| 578 | } |
| 579 | |
| 580 | /** |
| 581 | * Load all profiles. |
| eb714832 |
582 | */ |
| 117ad234 |
583 | function wysiwyg_profile_load_all() { |
| eb714832 |
584 | static $profiles; |
| 585 | |
| eb714832 |
586 | if (!isset($profiles)) { |
| 587 | $profiles = array(); |
| 117ad234 |
588 | $result = db_query('SELECT format, editor, settings FROM {wysiwyg}'); |
| eb714832 |
589 | while ($profile = db_fetch_object($result)) { |
| 590 | $profile->settings = unserialize($profile->settings); |
| 81a2754f |
591 | $profiles[$profile->format] = $profile; |
| eb714832 |
592 | } |
| 593 | } |
| 594 | |
| 117ad234 |
595 | return $profiles; |
| eb714832 |
596 | } |
| 597 | |
| 598 | /** |
| 599 | * Implementation of hook_user(). |
| 600 | */ |
| 601 | function wysiwyg_user($type, &$edit, &$user, $category = NULL) { |
| 81a2754f |
602 | if ($type == 'form' && $category == 'account') { |
| 603 | // @todo http://drupal.org/node/322433 |
| 604 | $profile = new stdClass; |
| eb714832 |
605 | if (isset($profile->settings['user_choose']) && $profile->settings['user_choose']) { |
| 606 | $form['wysiwyg'] = array( |
| 607 | '#type' => 'fieldset', |
| 608 | '#title' => t('Wysiwyg Editor settings'), |
| 609 | '#weight' => 10, |
| 610 | '#collapsible' => TRUE, |
| 611 | '#collapsed' => TRUE, |
| 612 | ); |
| 613 | $form['wysiwyg']['wysiwyg_status'] = array( |
| 614 | '#type' => 'checkbox', |
| 615 | '#title' => t('Enable editor by default'), |
| 616 | '#default_value' => isset($user->wysiwyg_status) ? $user->wysiwyg_status : (isset($profile->settings['default']) ? $profile->settings['default'] : FALSE), |
| 617 | '#return_value' => 1, |
| 618 | '#description' => t('If enabled, rich-text editing is enabled by default in textarea fields.'), |
| 619 | ); |
| 620 | return array('wysiwyg' => $form); |
| 621 | } |
| 622 | } |
| 81a2754f |
623 | elseif ($type == 'validate' && isset($edit['wysiwyg_status'])) { |
| eb714832 |
624 | return array('wysiwyg_status' => $edit['wysiwyg_status']); |
| 625 | } |
| 626 | } |
| 627 | |
| eb714832 |
628 | function wysiwyg_user_get_status($profile) { |
| 629 | global $user; |
| eb714832 |
630 | |
| 143cb538 |
631 | if (!empty($profile->settings['user_choose']) && isset($user->wysiwyg_status)) { |
| eb714832 |
632 | $status = $user->wysiwyg_status; |
| 633 | } |
| 634 | else { |
| 9e8e526a |
635 | $status = isset($profile->settings['default']) ? $profile->settings['default'] : TRUE; |
| eb714832 |
636 | } |
| 637 | |
| 638 | return $status; |
| 639 | } |
| 640 | |
| 641 | /** |
| 642 | * @defgroup wysiwyg_api Wysiwyg API |
| 643 | * @{ |
| 14b92488 |
644 | * |
| eb714832 |
645 | * @todo Forked from Panels; abstract into a separate API module that allows |
| 646 | * contrib modules to define supported include/plugin types. |
| 647 | */ |
| 648 | |
| 649 | /** |
| 650 | * Return library information for a given editor. |
| 14b92488 |
651 | * |
| eb714832 |
652 | * @param $name |
| 653 | * The internal name of an editor. |
| 14b92488 |
654 | * |
| eb714832 |
655 | * @return |
| 656 | * The library information for the editor, or FALSE if $name is unknown or not |
| 657 | * installed properly. |
| 14b92488 |
658 | */ |
| eb714832 |
659 | function wysiwyg_get_editor($name) { |
| 660 | $editors = wysiwyg_get_all_editors(); |
| 661 | return isset($editors[$name]) && $editors[$name]['installed'] ? $editors[$name] : FALSE; |
| 662 | } |
| 663 | |
| 664 | /** |
| 665 | * Compile a list holding all supported editors including installed editor version information. |
| 666 | */ |
| 667 | function wysiwyg_get_all_editors() { |
| 668 | static $editors; |
| 669 | |
| 670 | if (isset($editors)) { |
| 671 | return $editors; |
| 14b92488 |
672 | } |
| eb714832 |
673 | |
| 674 | $editors = wysiwyg_load_includes('editors', 'editor'); |
| 675 | foreach ($editors as $editor => $properties) { |
| 676 | // Fill in required properties. |
| 677 | $editors[$editor] += array( |
| 678 | 'title' => '', |
| 679 | 'vendor url' => '', |
| 680 | 'download url' => '', |
| c18af709 |
681 | 'editor path' => wysiwyg_get_path($editors[$editor]['name']), |
| 682 | 'library path' => wysiwyg_get_path($editors[$editor]['name']), |
| eb714832 |
683 | 'libraries' => array(), |
| 684 | 'version callback' => NULL, |
| 685 | 'themes callback' => NULL, |
| 686 | 'settings callback' => NULL, |
| 687 | 'plugin callback' => NULL, |
| 688 | 'plugin settings callback' => NULL, |
| 689 | 'versions' => array(), |
| c18af709 |
690 | 'js path' => $editors[$editor]['path'] . '/js', |
| 691 | 'css path' => $editors[$editor]['path'] . '/css', |
| eb714832 |
692 | ); |
| 693 | // Check whether library is present. |
| c18af709 |
694 | if (!($editors[$editor]['installed'] = file_exists($editors[$editor]['library path']))) { |
| eb714832 |
695 | continue; |
| 696 | } |
| 697 | // Detect library version. |
| 698 | if (function_exists($editors[$editor]['version callback'])) { |
| c18af709 |
699 | $editors[$editor]['installed version'] = $editors[$editor]['version callback']($editors[$editor]); |
| eb714832 |
700 | } |
| 701 | if (empty($editors[$editor]['installed version'])) { |
| 702 | $editors[$editor]['error'] = t('The version of %editor could not be detected.', array('%editor' => $properties['title'])); |
| 703 | $editors[$editor]['installed'] = FALSE; |
| 704 | continue; |
| 705 | } |
| 706 | // Determine to which supported version the installed version maps. |
| 707 | ksort($editors[$editor]['versions']); |
| 708 | $version = 0; |
| 709 | foreach ($editors[$editor]['versions'] as $supported_version => $version_properties) { |
| 710 | if (version_compare($editors[$editor]['installed version'], $supported_version, '>=')) { |
| 711 | $version = $supported_version; |
| 14b92488 |
712 | } |
| eb714832 |
713 | } |
| 714 | if (!$version) { |
| c18af709 |
715 | $editors[$editor]['error'] = t('The installed version %version of %editor is not supported.', array('%version' => $editors[$editor]['installed version'], '%editor' => $editors[$editor]['title'])); |
| eb714832 |
716 | $editors[$editor]['installed'] = FALSE; |
| 717 | continue; |
| 718 | } |
| 719 | // Apply library version specific definitions and overrides. |
| 720 | $editors[$editor] = array_merge($editors[$editor], $editors[$editor]['versions'][$version]); |
| 721 | unset($editors[$editor]['versions']); |
| eb714832 |
722 | } |
| 723 | return $editors; |
| 724 | } |
| 725 | |
| 726 | /** |
| 6e91d64e |
727 | * Invoke hook_wysiwyg_plugin() in all modules. |
| 728 | */ |
| 729 | function wysiwyg_get_all_plugins() { |
| 730 | static $plugins; |
| 731 | |
| 732 | if (isset($plugins)) { |
| 733 | return $plugins; |
| 734 | } |
| 735 | |
| 736 | $plugins = wysiwyg_load_includes('plugins', 'plugin'); |
| 737 | foreach ($plugins as $name => $properties) { |
| 738 | $plugin = &$plugins[$name]; |
| 739 | // Fill in required/default properties. |
| 740 | $plugin += array( |
| 741 | 'title' => $plugin['name'], |
| 742 | 'vendor url' => '', |
| 743 | 'js path' => $plugin['path'] . '/' . $plugin['name'], |
| 744 | 'js file' => $plugin['name'] . '.js', |
| 745 | 'css path' => $plugin['path'] . '/' . $plugin['name'], |
| 746 | 'css file' => $plugin['name'] . '.css', |
| 747 | 'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images', |
| 748 | 'icon file' => $plugin['name'] . '.png', |
| 749 | 'dialog path' => $plugin['name'], |
| 750 | 'dialog settings' => array(), |
| 751 | 'settings callback' => NULL, |
| 752 | 'settings form callback' => NULL, |
| 753 | ); |
| 754 | // Fill in default settings. |
| 755 | $plugin['settings'] += array( |
| 756 | 'path' => base_path() . $plugin['path'] . '/' . $plugin['name'], |
| 757 | ); |
| 758 | // Check whether library is present. |
| 759 | if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) { |
| 760 | continue; |
| 761 | } |
| 762 | } |
| 763 | return $plugins; |
| 764 | } |
| 765 | |
| 766 | /** |
| eb714832 |
767 | * Load include files for wysiwyg implemented by all modules. |
| 768 | * |
| 769 | * @param $type |
| 770 | * The type of includes to search for, can be 'editors'. |
| 771 | * @param $hook |
| 772 | * The hook name to invoke. |
| 773 | * @param $file |
| 774 | * An optional include file name without .inc extension to limit the search to. |
| 775 | * |
| 776 | * @see wysiwyg_get_directories(), _wysiwyg_process_include() |
| 777 | */ |
| 778 | function wysiwyg_load_includes($type = 'editors', $hook = 'editor', $file = NULL) { |
| 779 | // Determine implementations. |
| 780 | $directories = wysiwyg_get_directories($type); |
| c18af709 |
781 | $directories['wysiwyg_'] = drupal_get_path('module', 'wysiwyg') . '/' . $type; |
| eb714832 |
782 | $file_list = array(); |
| 783 | foreach ($directories as $module => $path) { |
| 784 | $file_list[$module] = drupal_system_listing("$file" . '.inc$', $path, 'name', 0); |
| 785 | } |
| 786 | |
| 787 | // Load implementations. |
| 788 | $info = array(); |
| 789 | foreach (array_filter($file_list) as $module => $files) { |
| 790 | foreach ($files as $file) { |
| 791 | include_once './' . $file->filename; |
| 792 | $result = _wysiwyg_process_include('wysiwyg', $module . $file->name, dirname($file->filename), $hook); |
| 793 | if (is_array($result)) { |
| 794 | $info = array_merge($info, $result); |
| 14b92488 |
795 | } |
| eb714832 |
796 | } |
| 14b92488 |
797 | } |
| eb714832 |
798 | return $info; |
| 799 | } |
| 14b92488 |
800 | |
| eb714832 |
801 | /** |
| c18af709 |
802 | * Helper function to build paths to libraries. |
| eb714832 |
803 | * |
| c18af709 |
804 | * @param $library |
| 805 | * The external library name to return the path for. |
| eb714832 |
806 | * @param $base_path |
| 807 | * Whether to prefix the resulting path with base_path(). |
| eb714832 |
808 | * |
| 809 | * @return |
| c18af709 |
810 | * The path to the specified library. |
| 811 | * |
| 812 | * @ingroup libraries |
| 813 | */ |
| 814 | function wysiwyg_get_path($library, $base_path = FALSE) { |
| 815 | static $libraries; |
| 816 | |
| 817 | if (!isset($libraries)) { |
| 818 | $libraries = wysiwyg_get_libraries(); |
| 819 | } |
| 820 | if (!isset($libraries[$library])) { |
| 821 | // Most often, external libraries can be shared across multiple sites. |
| 822 | return 'sites/all/libraries/' . $library; |
| 823 | } |
| 824 | |
| 825 | $path = ($base_path ? base_path() : ''); |
| 826 | $path .= $libraries[$library]; |
| 827 | |
| 828 | return $path; |
| 829 | } |
| 830 | |
| 831 | /** |
| 832 | * Return an array of library directories. |
| 833 | * |
| 834 | * Returns an array of library directories from the all-sites directory |
| 835 | * (i.e. sites/all/libraries/), the profiles directory, and site-specific |
| 836 | * directory (i.e. sites/somesite/libraries/). The returned array will be keyed |
| 837 | * by the library name. Site-specific libraries are prioritized over libraries |
| 838 | * in the default directories. That is, if a library with the same name appears |
| 839 | * in both the site-wide directory and site-specific directory, only the |
| 840 | * site-specific version will be listed. |
| 841 | * |
| 842 | * @return |
| 843 | * A list of library directories. |
| 844 | * |
| 845 | * @ingroup libraries |
| eb714832 |
846 | */ |
| c18af709 |
847 | function wysiwyg_get_libraries() { |
| 848 | global $profile; |
| 849 | |
| 850 | // When this function is called during Drupal's initial installation process, |
| 851 | // the name of the profile that is about to be installed is stored in the |
| 852 | // global $profile variable. At all other times, the regular system variable |
| 853 | // contains the name of the current profile, and we can call variable_get() |
| 854 | // to determine the profile. |
| 855 | if (!isset($profile)) { |
| 856 | $profile = variable_get('install_profile', 'default'); |
| 857 | } |
| 858 | |
| 859 | $directory = 'libraries'; |
| 860 | $searchdir = array(); |
| 861 | $config = conf_path(); |
| 862 | |
| 863 | // The 'profiles' directory contains pristine collections of modules and |
| 864 | // themes as organized by a distribution. It is pristine in the same way |
| 865 | // that /modules is pristine for core; users should avoid changing anything |
| 866 | // there in favor of sites/all or sites/<domain> directories. |
| 867 | if (file_exists("profiles/$profile/$directory")) { |
| 868 | $searchdir[] = "profiles/$profile/$directory"; |
| 869 | } |
| 870 | |
| 871 | // Always search sites/all/*. |
| 54a65ca8 |
872 | $searchdir[] = 'sites/all/' . $directory; |
| c18af709 |
873 | |
| 874 | // Also search sites/<domain>/*. |
| 875 | if (file_exists("$config/$directory")) { |
| 876 | $searchdir[] = "$config/$directory"; |
| 877 | } |
| 878 | |
| 879 | // Retrieve list of directories. |
| 880 | // @todo Core: Allow to scan for directories. |
| 881 | $directories = array(); |
| 882 | $nomask = array('CVS'); |
| 883 | foreach ($searchdir as $dir) { |
| 884 | if (is_dir($dir) && $handle = opendir($dir)) { |
| 885 | while (FALSE !== ($file = readdir($handle))) { |
| 886 | if (!in_array($file, $nomask) && $file[0] != '.') { |
| 887 | if (is_dir("$dir/$file")) { |
| 888 | $directories[$file] = "$dir/$file"; |
| 889 | } |
| 890 | } |
| 891 | } |
| 892 | closedir($handle); |
| 893 | } |
| 894 | } |
| 895 | |
| 896 | return $directories; |
| eb714832 |
897 | } |
| 14b92488 |
898 | |
| 899 | /** |
| eb714832 |
900 | * Return a list of directories by modules implementing wysiwyg_include_directory(). |
| 14b92488 |
901 | * |
| eb714832 |
902 | * @param $plugintype |
| 903 | * The type of a plugin; can be 'editors'. |
| 904 | * |
| 905 | * @return |
| 906 | * An array containing module names suffixed with '_' and their defined |
| 907 | * directory. |
| 14b92488 |
908 | * |
| eb714832 |
909 | * @see wysiwyg_load_includes(), _wysiwyg_process_include() |
| 14b92488 |
910 | */ |
| eb714832 |
911 | function wysiwyg_get_directories($plugintype) { |
| 912 | $directories = array(); |
| 913 | foreach (module_implements('wysiwyg_include_directory') as $module) { |
| 914 | $result = module_invoke($module, 'wysiwyg_include_directory', $plugintype); |
| 915 | if (isset($result) && is_string($result)) { |
| 54a65ca8 |
916 | $directories[$module . '_'] = drupal_get_path('module', $module) . '/' . $result; |
| eb714832 |
917 | } |
| 14b92488 |
918 | } |
| eb714832 |
919 | return $directories; |
| 14b92488 |
920 | } |
| 921 | |
| eb714832 |
922 | /** |
| 923 | * Process a single hook implementation of a wysiwyg editor. |
| 924 | * |
| 925 | * @param $module |
| 926 | * The module that owns the hook. |
| 927 | * @param $identifier |
| 928 | * Either the module or 'wysiwyg_' . $file->name |
| 929 | * @param $hook |
| 930 | * The name of the hook being invoked. |
| 931 | */ |
| 932 | function _wysiwyg_process_include($module, $identifier, $path, $hook) { |
| 933 | $function = $identifier . '_' . $hook; |
| 934 | if (!function_exists($function)) { |
| 935 | return NULL; |
| 936 | } |
| 937 | $result = $function(); |
| 938 | if (!isset($result) || !is_array($result)) { |
| 939 | return NULL; |
| 940 | } |
| 941 | |
| 942 | // Fill in defaults. |
| 943 | foreach ($result as $editor => $properties) { |
| 944 | $result[$editor]['module'] = $module; |
| 945 | $result[$editor]['name'] = $editor; |
| 946 | $result[$editor]['path'] = $path; |
| 947 | } |
| 948 | return $result; |
| 949 | } |
| 950 | |
| 951 | /** |
| 952 | * @} End of "defgroup wysiwyg_api". |
| 953 | */ |
| 954 | |