Skip to content
wysiwyg.module 9.8 KiB
Newer Older
<?php
// $Id$

/**
 * @file
 * Implements a WYSIWYG API/framework/controller for Drupal.
 * Implements a generic WYSIWYG editor (wrapper) module for Drupal.
 */

/**
 * Implementation of hook_perm().
 *
 * Considerations:
 * - Allow to access an editor.
 * - Allow to access a particular editor profile.
 */
function wysiwyg_perm() {
  // Implement common permissions.
  $permissions = array('use wysiwyg editor', 'administer wysiwyg settings');
  // Fetch editors or editor profiles.
  
  return $permissions;
}

/**
 * Implementation of hook_menu().
 */
function wysiwyg_menu($may_cache) {
  $items = array();
  
  if ($may_cache) {
    // General wysiwyg settings, including editor selection, editor/user roles
    // mapping.
    
    // Editor profile setup and custom wysiwyg controller overrides.
    // Note: Depends on resulting implementation of wysiwyg_controller()

    // Enable/disable default editor plugins and Drupal plugins.
    
    // Configure editor-specific layout, including layout of plugins.
    
    // Generic Drupal plugin wrapper callback (AJAX).
  }
  
  return $items;
}

/**
 * Handle an editor profile.
 *
 * This function should adopt the implementation of node objects in Drupal. It
 * should at least allow to load, save and duplicate an editor profile. Since
 * not all editors support the same settings, the array of settings probably
 * needs to be stored serialized in the database.
 *
 * Considerations:
 * - Will Drupal editor plugins need to hook into profile operations?
 * - Discuss moving profile rules into render.module.
 *
 * @param string $op
 *   An operation to perform, one of 'load', 'save', 'duplicate'.
 * @param string $profile
 *   An editor profile object containing at least an id, title, array of plugins
 *   and an array of settings.
 *
 * @todo '_profile' is an ambigious hook in Drupal. Needs a unique name.
 */
function wysiwyg_profile() {
}

/**
 * Wysiwyg editor execution controller.
 * 
 * Disables an editor for certain fields that were never intended to be edited
 * with an editor.
 * @see http://drupal.org/node/81297
 *
 * Considerations:
 * - Take D6 FAPI3 / widgets into account (see #138706 and #86535).
 * - Run registration either in hook_elements() or hook_form_alter().
 *   form_alter() might also allow to load needed JS/CSS.
 * - Use an attribute or class name or both (editor code analysis needed). Or
 *   alter field #type 'textarea' to 'html' (IIRC, suggested by moshe).
 * - Disable an editor for fields using the PHP input format.
 * - D6: Perhaps depend on the input format to *enable* a field.
 * - Allow to take the current path into account.
 * - Allow to force an editor for certain CCK fields (f.e. user input).
 * - Disable core's #resizable if an editor does not support it.
 * - Allow contrib modules to extend this definition.
 * - Allow to manually extend this list via wysiwyg settings page.
 * - Include an editor profile or user role to limit/enhance available plugins.
 * - Take other input formats (e.g. bbcode) into account.
 *
 * @todo Rename function to appropriate/chosen Drupal hook.
 */
function wysiwyg_controller() {
}

/**
 * hook_wysiwyg_plugin(). Return an array of editor plugins.
 *
 * Each wysiwyg editor as well as each contrib module implementing an editor
 * plugin has to return an associative array of available plugins. Each module
 * can add one or more plugins and editor buttons.
 *
 * Notes for TinyMCE:
 * A module is able to override almost all TinyMCE initialization settings.
 * However, modules should only make use of that if a plugin really needs to,
 * because customized configuration settings may clash with overrides by another
 * module. TinyMCE automatically assigns the baseURL of your plugin to the plugin
 * object. If you need to load or access additional files from your plugin
 * directory, retrieve the path via this.baseURL. tinyMCE.baseURL returns the
 * path of TinyMCE and not your module. For example:
 * @code
 * initInstance: function(inst) {
 *   tinyMCE.importCSS(inst.getDoc(), this.baseURL + '/myplugin.css');
 * },
 * @endcode
 * 
 * @param string $editor
 *   An (lowercase) editor name to return plugins for.
 * @return array
 *   An associative array having internal plugin names as keys, an array of
 *   plugin meta-information as values:
 *   - type: 'external' (optional); if omitted, wysiwyg editors will likely
 *     search for the plugin in their own plugins folder.
 *   - title: A human readable title of the plugin.
 *   - description: A (one-line) description of the plugin.
 *   - path: The patch to the javascript plugin.
 *   - callback: A Drupal menu callback returning the plugin UI. A plugin
 *     should return a callback *or* a path.
 *   - icon: An icon (usually 16x16 pixels) for the plugin button (optional).
 *   - ... Any other custom editor settings (optional).
 *
 * @todo Move this template into hooks.php.
 */
function hook_wysiwyg_plugin($editor) {
  switch ($editor) {
    case 'tinymce':
      return array(
        'myplugin' => array(
          'type'        => 'external',
          'title'       => t('My plugin title'),
          'description' => t('My plugin title'),
          // Regular callback URL for external TinyMCE plugins.
          'path'        => drupal_get_path('module', 'mymodule') .'/myplugin',
          // Wysiwyg wrapper plugin AJAX callback.
          'callback'    => url('myplugin/browse'),
          'icon'        => drupal_get_path('module', 'mymodule') .'/myplugin/myplugin.png',
          'extended_valid_elements' => array('tag[attribute1|attribute2=default_value]'),
          // Might need to be set later on; after retrieving customized editor
          // layout.
          'theme_advanced_buttons1' => array(t('Button title (optional)') => 'myplugin'),
        ),
      );
  }
}

/**
 * Implementation of hook_wysiwyg_plugin().
 *
 * Implements a generic wrapper plugin for Drupal (module-based) editor plugins.
 *
 * Considerations:
 * - By comparing the javascript of available editor plugins, most of them are
 *   based on img_assist.
 * - An editor plugin basically consists of a title, icon (button), description
 *   (localized) and menu path returning the actual Drupal module. This
 *   meta-information is available via hook_wysiwyg_plugin().
 * - Each editor implements its own plugin API, but the JS-PHP-JS communication
 *   is always the same.
 * - Since Drupal 5, jQuery is always available and allows to perform AJAX/HTTP
 *   requests at any time.
 * - Each editor plugin returns HTML via Javascript to the editor.
 * - Do we really require each Drupal editor plugin to write their own
 *   javascript or are we able to supply Drupal editor plugins to all editors
 *   through a wrapper module?
 * - We can transform a regular editor plugin template into a wrapper plugin or
 *   we can use a Drupal menu path to serve a editor plugin template which
 *   (optionally) already has additional plugin code injected.
 */
function wysiwyg_wysiwyg_plugin($editor) {
  switch ($editor) {
    case 'tinymce':
      // Define generic plugin properties.
      $path = drupal_get_path('module', 'wysiwyg') .'/wrapper/tinymce'
      
      // Load all Drupal editor plugins into an array.
      $plugins = module_invoke_all('wysiwyg_plugin', $editor);
      
      // Define all Drupal editor plugins by looping through the array,
      // omitting all non-'external' plugins; assigning type, title, icon,
      // custom editor settings and most important:
      // wrapper plugin path followed by callback path (if JS: use query string).
      
      
      return $plugins;
  }
}

/**
 * Return an array of editor plugins.
 *
 * @param string $op
 *   A performed action:
 *   - 'list': Plugins are about to be listed or registered.
 *   - 'load': Plugins are about to be loaded.
 * @param string $editor
 *   An (lowercase) editor name to retrieve plugins for.
 *
 * @see hook_wysiwyg_plugin()
 *
 * @todo Implement TinyMCE-specific code as hook into wysiwyg_tinymce.inc.
 */
function wysiwyg_get_plugins($op, $editor) {
  static $plugins;
  
  if (!isset($plugins)) {
    $plugins = module_invoke_all('wysiwyg_plugin', $editor);
  }
  
  switch ($editor) {
    case 'tinymce':
      if ($op == 'list') {
        // Do not alter cached array.
        $plugin_list = $plugins;
        foreach ($plugin_list as $name => $plugin) {
          if ($plugin['type'] == 'external') {
            $plugin_list[$name] = _wysiwyg_plugin_name('add', $name);
          }
        }
        return $plugin_list;
      }
      else if ($op == 'load') {
        static $plugins_added;
        
        if (!isset($plugins_added)) {
          $plugins_added = TRUE;

          $init_plugins = '';
          foreach ($plugins as $name => $plugin) {
            // Ensure there is no leading hiven in external plugin names.
            $name = _wysiwyg_plugin_name('remove', $name);
            
            $init_plugins .= "tinyMCE.loadPlugin('$name', '". base_path() . $plugin['path'] ."');\n";
          }
          if (!empty($init_plugins)) {
            drupal_add_js($init_plugins, 'inline');
          }
        }
      }
      break;
  }
} 


/**
 * Add or remove leading hiven to/of (external) plugin names.
 *
 * Externally loaded TinyMCE plugins need to be prefixed with a hiven. TinyMCE
 * will not try to add and load external plugins from the default plugins
 * folder.
 * 
 * @param string $op
 *   Operation to perform, 'add' or 'remove'.
 * @param string $editor
 *   An editor name.
 * @param string $name
 *   A plugin name.
 *
 * @todo Implement TinyMCE-specific code as hook into wysiwyg_tinymce.inc.
 */
function _wysiwyg_plugin_name($op, $editor, $name) {
  switch ($editor) {
    case 'tinymce':
      if ($op == 'add') {
        if (strpos($name, '-') !== 0) {
          return '-'. $name;
        }
        return $name;
      }
      else {
        if (strpos($name, '-') === 0) {
          return substr($name, 1);
        }
        return $name;
      }
      break;
  }
}