wysiwyg.module

Tracking 6.x-3.x branch
  1. drupal
    1. 6 contributions/wysiwyg/wysiwyg.module
    2. 7 contributions/wysiwyg/wysiwyg.module

Integrate client-side editors with Drupal.

Functions & methods

NameDescription
wysiwyg_add_editor_settingsAdd editor settings for a given input format.
wysiwyg_add_plugin_settingsAdd settings for external plugins.
wysiwyg_form_alterImplementation of hook_form_alter().
wysiwyg_get_all_editorsCompile a list holding all supported editors including installed editor version information.
wysiwyg_get_all_pluginsInvoke hook_wysiwyg_plugin() in all modules.
wysiwyg_get_cssRetrieve stylesheets for HTML/IFRAME-based editors.
wysiwyg_get_directoriesReturn a list of directories by modules implementing wysiwyg_include_directory().
wysiwyg_get_editorReturn library information for a given editor.
wysiwyg_get_editor_configReturn an array of initial editor settings for a Wysiwyg profile.
wysiwyg_get_editor_themesRetrieve available themes for an editor.
wysiwyg_get_librariesReturn an array of library directories.
wysiwyg_get_pathHelper function to build paths to libraries.
wysiwyg_get_pluginsReturn plugin metadata from the plugin registry.
wysiwyg_get_profileDetermine the profile to use for a given input format id.
wysiwyg_helpImplementation of hook_help().
wysiwyg_load_editorLoad an editor library and initialize basic Wysiwyg settings.
wysiwyg_load_includesLoad include files for wysiwyg implemented by all modules.
wysiwyg_menuImplementation of hook_menu().
wysiwyg_process_formProcess a textarea for Wysiwyg Editor.
wysiwyg_profile_loadLoad profile for a given input format.
wysiwyg_profile_load_allLoad all profiles.
wysiwyg_themeImplementation of hook_theme().
wysiwyg_userImplementation of hook_user().
wysiwyg_user_get_status
_wysiwyg_process_includeProcess a single hook implementation of a wysiwyg editor.

File

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