<?php
-// $Id$
/**
* @file
- * Integrate client-side editors with Drupal.
+ * Integrates client-side editors with Drupal.
*/
/**
+ * Implements hook_entity_info().
+ */
+function wysiwyg_entity_info() {
+ $types['wysiwyg_profile'] = array(
+ 'label' => t('Wysiwyg profile'),
+ 'base table' => 'wysiwyg',
+ 'controller class' => 'WysiwygProfileController',
+ 'fieldable' => FALSE,
+ // When loading all entities, DrupalDefaultEntityController::load() ignores
+ // its static cache. Therefore, wysiwyg_profile_load_all() implements a
+ // custom static cache.
+ 'static cache' => FALSE,
+ 'entity keys' => array(
+ 'id' => 'format',
+ ),
+ );
+ return $types;
+}
+
+/**
+ * Controller class for Wysiwyg profiles.
+ */
+class WysiwygProfileController extends DrupalDefaultEntityController {
+ /**
+ * Overrides DrupalDefaultEntityController::attachLoad().
+ */
+ function attachLoad(&$queried_entities, $revision_id = FALSE) {
+ // Unserialize the profile settings.
+ foreach ($queried_entities as $key => $record) {
+ $queried_entities[$key]->settings = unserialize($record->settings);
+ }
+ // Call the default attachLoad() method.
+ parent::attachLoad($queried_entities, $revision_id);
+ }
+}
+
+/**
* Implementation of hook_menu().
*/
function wysiwyg_menu() {
/**
* Implementation of hook_form_alter().
- *
- * Before Drupal 7, there is no way to easily identify form fields that are
- * input format enabled. As a workaround, we assign a form #after_build
- * processing callback that is executed on all forms after they have been
- * completely built, so form elements are in their effective order
- * and position already.
- *
- * @see wysiwyg_process_form()
*/
function wysiwyg_form_alter(&$form, &$form_state) {
- $form['#after_build'][] = 'wysiwyg_process_form';
// Teaser splitter is unconditionally removed and NOT supported.
if (isset($form['body_field'])) {
unset($form['body_field']['teaser_js']);
}
/**
- * Process a form to attach wysiwyg editors.
- *
- * Recurse into the form and if an text format-enabled element is found, use its
- * #id for attaching client-side editors.
+ * Implements hook_element_info_alter().
+ */
+function wysiwyg_element_info_alter(&$types) {
+ $types['text_format']['#pre_render'][] = 'wysiwyg_pre_render_text_format';
+}
+
+/**
+ * Process a text format widget to load and attach editors.
*
- * @see form_process_text_format()
+ * The element's #id is used as reference to attach client-side editors.
*/
-function wysiwyg_process_form(&$form) {
- foreach (element_children($form) as $item) {
- // filter_form() always uses the key 'format'.
- if (isset($form[$item]['#text_format'])) {
- $element = &$form[$item]['format'];
- $field = &$form[$item]['value'];
- $settings = array(
- 'field' => $field['#id'],
- );
+function wysiwyg_pre_render_text_format($element) {
+ // filter_process_format() copies properties to the expanded 'value' child
+ // element. Skip this text format widget, if it contains no 'format' or when
+ // the current user does not have access to edit the value.
+ if (!isset($element['format']) || !empty($element['value']['#disabled'])) {
+ return $element;
+ }
+ // Allow modules to programmatically enforce no client-side editor by setting
+ // the #wysiwyg property to FALSE.
+ if (isset($element['#wysiwyg']) && !$element['#wysiwyg']) {
+ return $element;
+ }
- // If this textarea is #resizable and we will load at least one
- // editor, then only load the behavior and let the 'none' editor
- // attach/detach it to avoid hi-jacking the UI. Due to our CSS class
- // parsing, we can add arbitrary parameters for each input format.
- // The #resizable property will be removed below, if at least one
- // profile has been loaded.
- $resizable = 0;
- if (!empty($field['#resizable'])) {
- $resizable = 1;
- drupal_add_js('misc/textarea.js');
- }
- // Determine the available input formats.
- foreach ($element['format']['#options'] as $format_id => $format_name) {
- $format = 'format' . $format_id;
- // Initialize default settings, defaulting to 'none' editor.
- $settings[$format]['editor'] = 'none';
- $settings[$format]['status'] = 1;
- $settings[$format]['toggle'] = 1;
- $settings[$format]['resizable'] = $resizable;
- // Fetch the profile associated to this text format.
- $profile = wysiwyg_get_profile($format_id);
- if ($profile) {
- $loaded = TRUE;
- $settings[$format]['editor'] = $profile->editor;
- $settings[$format]['status'] = (int) wysiwyg_user_get_status($profile);
- if (isset($profile->settings['show_toggle'])) {
- $settings[$format]['toggle'] = (int) $profile->settings['show_toggle'];
- }
- // Check editor theme (and reset it if not/no longer available).
- $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : ''));
-
- // Add plugin settings (first) for this text format.
- wysiwyg_add_plugin_settings($profile);
- // Add profile settings for this text format.
- wysiwyg_add_editor_settings($profile, $theme);
-
- }
- }
- // Use a prefix/suffix for a single text format, or attach to text
- // format selector radio buttons.
- if (!$element['format']['#access']) {
- $element['format_guidelines']['format'] = array(
- '#type' => 'hidden',
- '#name' => $element['format']['#name'],
- '#id' => $element['format']['#id'],
- '#value' => $format_id,
- '#attributes' => array('class' => array('wysiwyg')),
- );
- $element['format_guidelines']['#attached']['js'][] = array(
- 'data' => array('wysiwyg' => array('triggers' => array($element['format']['#id'] => $settings))),
- 'type' => 'setting',
- );
- }
- else {
- $element['format']['#attributes']['class'][] = 'wysiwyg';
- $element['format']['#attached']['js'][] = array(
- 'data' => array('wysiwyg' => array('triggers' => array($element['format']['#id'] => $settings))),
- 'type' => 'setting',
- );
- }
+ $format_field = &$element['format'];
+ $field = &$element['value'];
+ $settings = array(
+ 'field' => $field['#id'],
+ );
- // If we loaded at least one editor, then the 'none' editor will
- // handle resizable textareas instead of core.
- if (isset($loaded) && $resizable) {
- $field['#resizable'] = FALSE;
+ // If this textarea is #resizable and we will load at least one
+ // editor, then only load the behavior and let the 'none' editor
+ // attach/detach it to avoid hi-jacking the UI. Due to our CSS class
+ // parsing, we can add arbitrary parameters for each input format.
+ // The #resizable property will be removed below, if at least one
+ // profile has been loaded.
+ $resizable = 0;
+ if (!empty($field['#resizable'])) {
+ $resizable = 1;
+ drupal_add_js('misc/textarea.js');
+ }
+ // Determine the available text formats.
+ foreach ($format_field['format']['#options'] as $format_id => $format_name) {
+ $format = 'format' . $format_id;
+ // Initialize default settings, defaulting to 'none' editor.
+ $settings[$format] = array(
+ 'editor' => 'none',
+ 'status' => 1,
+ 'toggle' => 1,
+ 'resizable' => $resizable,
+ );
+
+ // Fetch the profile associated to this text format.
+ $profile = wysiwyg_get_profile($format_id);
+ if ($profile) {
+ $loaded = TRUE;
+ $settings[$format]['editor'] = $profile->editor;
+ $settings[$format]['status'] = (int) wysiwyg_user_get_status($profile);
+ if (isset($profile->settings['show_toggle'])) {
+ $settings[$format]['toggle'] = (int) $profile->settings['show_toggle'];
}
+ // Check editor theme (and reset it if not/no longer available).
+ $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : ''));
- // If this element has '#text_format', do not recurse further.
- continue;
+ // Add plugin settings (first) for this text format.
+ wysiwyg_add_plugin_settings($profile);
+ // Add profile settings for this text format.
+ wysiwyg_add_editor_settings($profile, $theme);
}
- // Recurse into children.
- wysiwyg_process_form($form[$item]);
}
- return $form;
+ // Use a hidden element for a single text format.
+ if (!$format_field['format']['#access']) {
+ $format_field['wysiwyg'] = array(
+ '#type' => 'hidden',
+ '#name' => $format_field['format']['#name'],
+ '#value' => $format_id,
+ '#attributes' => array(
+ 'id' => $format_field['format']['#id'],
+ 'class' => array('wysiwyg'),
+ ),
+ );
+ $format_field['wysiwyg']['#attached']['js'][] = array(
+ 'data' => array(
+ 'wysiwyg' => array(
+ 'triggers' => array(
+ $format_field['format']['#id'] => $settings,
+ ),
+ ),
+ ),
+ 'type' => 'setting',
+ );
+ }
+ // Otherwise, attach to text format selector.
+ else {
+ $format_field['format']['#attributes']['class'][] = 'wysiwyg';
+ $format_field['format']['#attached']['js'][] = array(
+ 'data' => array(
+ 'wysiwyg' => array(
+ 'triggers' => array(
+ $format_field['format']['#id'] => $settings,
+ ),
+ ),
+ ),
+ 'type' => 'setting',
+ );
+ }
+
+ // If we loaded at least one editor, then the 'none' editor will
+ // handle resizable textareas instead of core.
+ if (isset($loaded) && $resizable) {
+ $field['#resizable'] = FALSE;
+ }
+
+ return $element;
}
/**
$path = drupal_get_path('module', 'wysiwyg');
// Initialize our namespaces in the *header* to do not force editor
// integration scripts to check and define Drupal.wysiwyg on its own.
- drupal_add_js($path . '/wysiwyg.init.js', array('weight' => JS_LIBRARY));
+ drupal_add_js($path . '/wysiwyg.init.js', array('group' => JS_LIBRARY));
// The 'none' editor is a special editor implementation, allowing us to
// attach and detach regular Drupal behaviors just like any other editor.
$settings = array();
if (!empty($editor['settings callback']) && function_exists($editor['settings callback'])) {
$settings = $editor['settings callback']($editor, $profile->settings, $theme);
+
+ // Allow other modules to alter the editor settings for this format.
+ $context = array('editor' => $editor, 'profile' => $profile, 'theme' => $theme);
+ drupal_alter('wysiwyg_editor_settings', $settings, $context);
}
return $settings;
}
$files = array();
foreach (drupal_add_css() as $filepath => $info) {
- if ($info['weight'] >= CSS_THEME && $info['media'] != 'print') {
+ if ($info['group'] >= CSS_THEME && $info['media'] != 'print') {
if (file_exists($filepath)) {
$files[] = base_path() . $filepath;
}
}
/**
- * Load profile for a given input format.
+ * Loads a profile for a given text format.
+ *
+ * Since there are commonly not many text formats, and each text format-enabled
+ * form element will possibly have to load every single profile, all existing
+ * profiles are loaded and cached once to reduce the amount of database queries.
*/
function wysiwyg_profile_load($format) {
- static $profiles;
-
- if (!isset($profiles) || !array_key_exists($format, $profiles)) {
- $result = db_query('SELECT format, editor, settings FROM {wysiwyg} WHERE format = :format', array(':format' => $format));
- foreach ($result as $profile) {
- $profile->settings = unserialize($profile->settings);
- $profiles[$profile->format] = $profile;
- }
- }
-
+ $profiles = wysiwyg_profile_load_all();
return (isset($profiles[$format]) ? $profiles[$format] : FALSE);
}
/**
- * Load all profiles.
+ * Loads all profiles.
*/
function wysiwyg_profile_load_all() {
- static $profiles;
+ // entity_load(..., FALSE) does not re-use its own static cache upon
+ // repetitive calls, so a custom static cache is required.
+ // @see wysiwyg_entity_info()
+ $profiles = &drupal_static(__FUNCTION__);
if (!isset($profiles)) {
- $profiles = array();
- $result = db_query('SELECT format, editor, settings FROM {wysiwyg}');
- foreach ($result as $profile) {
- $profile->settings = unserialize($profile->settings);
- $profiles[$profile->format] = $profile;
+ // Additional database cache to support alternative caches like memcache.
+ if ($cached = cache_get('wysiwyg_profiles')) {
+ $profiles = $cached->data;
+ }
+ else {
+ $profiles = entity_load('wysiwyg_profile', FALSE);
+ cache_set('wysiwyg_profiles', $profiles);
}
}
}
/**
- * Implementation of hook_user().
- */
-function wysiwyg_user($type, &$edit, &$user, $category = NULL) {
- if ($type == 'form' && $category == 'account') {
- // @todo http://drupal.org/node/322433
- $profile = new stdClass;
- if (isset($profile->settings['user_choose']) && $profile->settings['user_choose']) {
- $form['wysiwyg'] = array(
- '#type' => 'fieldset',
- '#title' => t('Wysiwyg Editor settings'),
- '#weight' => 10,
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- $form['wysiwyg']['wysiwyg_status'] = array(
- '#type' => 'checkbox',
- '#title' => t('Enable editor by default'),
- '#default_value' => isset($user->wysiwyg_status) ? $user->wysiwyg_status : (isset($profile->settings['default']) ? $profile->settings['default'] : FALSE),
- '#return_value' => 1,
- '#description' => t('If enabled, rich-text editing is enabled by default in textarea fields.'),
- );
- return array('wysiwyg' => $form);
+ * Deletes a profile from the database.
+ */
+function wysiwyg_profile_delete($format) {
+ db_delete('wysiwyg')
+ ->condition('format', $format)
+ ->execute();
+}
+
+/**
+ * Clear all Wysiwyg profile caches.
+ */
+function wysiwyg_profile_cache_clear() {
+ entity_get_controller('wysiwyg_profile')->resetCache();
+ drupal_static_reset('wysiwyg_profile_load_all');
+ cache_clear_all('wysiwyg_profiles', 'cache');
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function wysiwyg_form_user_profile_form_alter(&$form, &$form_state, $form_id) {
+ $account = $form['#user'];
+ $user_formats = filter_formats($account);
+ $options = array();
+ $options_default = array();
+ foreach (wysiwyg_profile_load_all() as $format => $profile) {
+ // Only show profiles that have user_choose enabled.
+ if (!empty($profile->settings['user_choose']) && isset($user_formats[$format])) {
+ $options[$format] = check_plain($user_formats[$format]->name);
+ if (wysiwyg_user_get_status($profile, $account)) {
+ $options_default[] = $format;
+ }
}
}
- elseif ($type == 'validate' && isset($edit['wysiwyg_status'])) {
- return array('wysiwyg_status' => $edit['wysiwyg_status']);
+ if (!empty($options)) {
+ $form['wysiwyg']['wysiwyg_status'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Text formats enabled for rich-text editing'),
+ '#options' => $options,
+ '#default_value' => $options_default,
+ );
}
}
-function wysiwyg_user_get_status($profile) {
+/**
+ * Implements hook_user_insert().
+ *
+ * Wysiwyg's user preferences are normally not exposed on the user registration
+ * form, but in case they are manually altered in, we invoke
+ * wysiwyg_user_update() accordingly.
+ */
+function wysiwyg_user_insert(&$edit, $account, $category) {
+ wysiwyg_user_update($edit, $account, $category);
+}
+
+/**
+ * Implements hook_user_update().
+ */
+function wysiwyg_user_update(&$edit, $account, $category) {
+ if (isset($edit['wysiwyg_status'])) {
+ db_delete('wysiwyg_user')
+ ->condition('uid', $account->uid)
+ ->execute();
+ $query = db_insert('wysiwyg_user')
+ ->fields(array('uid', 'format', 'status'));
+ foreach ($edit['wysiwyg_status'] as $format => $status) {
+ $query->values(array(
+ 'uid' => $account->uid,
+ 'format' => $format,
+ 'status' => (int) (bool) $status,
+ ));
+ }
+ $query->execute();
+ }
+}
+
+function wysiwyg_user_get_status($profile, $account = NULL) {
global $user;
- if (!empty($profile->settings['user_choose']) && isset($user->wysiwyg_status)) {
- $status = $user->wysiwyg_status;
+ if (!isset($account)) {
+ $account = $user;
+ }
+
+ // Default wysiwyg editor status information is only required on forms, so we
+ // do not pre-emptively load and attach this information on every user_load().
+ if (!isset($account->wysiwyg_status)) {
+ $account->wysiwyg_status = db_query("SELECT format, status FROM {wysiwyg_user} WHERE uid = :uid", array(
+ ':uid' => $account->uid,
+ ))->fetchAllKeyed();
+ }
+
+ if (!empty($profile->settings['user_choose']) && isset($account->wysiwyg_status[$profile->format])) {
+ $status = $account->wysiwyg_status[$profile->format];
}
else {
$status = isset($profile->settings['default']) ? $profile->settings['default'] : TRUE;
}
- return $status;
+ return (bool) $status;
}
/**
/**
* @} End of "defgroup wysiwyg_api".
*/
-