You are here

editor.module in Editor 6

Same filename and directory in other branches
  1. 5 editor.module
  2. 7 editor.module

Extendable WYSIWYG editor @author Tj Holowaychuk <http://www.350designs.com/> @package Editor

File

editor.module
View source
<?php

/**
 * @file 
 * Extendable WYSIWYG editor
 * @author Tj Holowaychuk <http://www.350designs.com/>
 * @package Editor
 */

// @todo: plugin event bindings
// @todo: plugin handlers not 'types', these may provide/handle additional attributes such as AJAX callbacks
// handler args? how to deal with this using editor_plugin_create().
// @todo: some plugins should be able to be enabled however not necessarily need to be a visible toolbar item
// @todo: status bar, method in JS to add/change status,
// @todo: display path in status bar
// @todo: support the textarea resizeing
// @todo: CSS sprites for core plugin images
// @todo: visibility API integration in D5
// @todo: repopulate option dialogs... automatically, no plugin code
// @todo: handling pasting of code and have paste listener plugins?
// @todo: object handles and transforming
// @todo: right click content menus?
// @todo: toggling html view (create revsion)
// @todo: settings page
// @todo: crossbrowser support
// @todo: setting to instantiate displaying textarea
// @todo: pass selection object to plugin handlers?
// @todo: disable anchor tags in preview
// @todo: show revision info in status bar
// @todo: other selection methods such as selectChildren blah blah
// @todo: listen for undo and redo keys
// @todo: dispatch more events
// @todo: _ExecPaste()
// @todo: change PIDHandler to PIDInit
// @todo: overridable or weighted paste handlers?
// @todo: select all
// @todo: only store revisions when the undo/redo plugins are enabled etc
// @todo: try catch blocks & Drupal.js implementations

/* -----------------------------------------------------------------

  Hook Implementations 

------------------------------------------------------------------ */

/**
 * Implementation of hook_perm();
 * 
 * @todo: implement. access should also check on callbacks as well
 */
function editor_perm() {
  return array(
    'administer editor',
    'access editor',
  );
}

/**
 * Implementation of hook_menu().
 */
function editor_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/editor',
      'title' => t('Text Editor'),
      'description' => t('Visual text editor options and configuration.'),
      'access' => user_access('administer editor'),
    );
    $items[] = array(
      'path' => 'admin/editor/editor',
      'title' => t('Editor'),
      'callback' => 'editor_settings',
      'access' => user_access('administer editor'),
    );
  }
  return $items;
}

/**
 * @todo: just testing take this out
 */
function editor_editor_plugin_api($op, &$plugin, $a1 = NULL, $a2 = NULL) {
  switch ($op) {
    case 'alter':
      break;
  }
}

/* -----------------------------------------------------------------

  General Functionality 

------------------------------------------------------------------ */
function editor_attach() {
  $plugins = editor_invoke_plugins();
  $profile = editor_profile_get('large');
  $module_path = drupal_get_path('module', 'editor');

  // Settings
  $data = array(
    'editor' => array(
      'toolbars' => editor_display_toolbars($profile),
    ),
  );

  // Plugin settings
  if (count($plugins)) {
    foreach ($plugins as $plugin) {
      $data['editor']['plugins'][$plugin->pid] = $plugin;
    }
  }

  // @todo: check permissions, styles etc
  drupal_add_css($module_path . '/editor.css');
  drupal_add_js($module_path . '/editor.js');
  drupal_add_js($module_path . '/editor.plugins.js');
  drupal_add_js($data, 'setting');
}

/**
 * @todo: handle different toolbars etc? or all themeing?
 */
function editor_display_toolbars($profile) {
  $items = array();
  $plugins = editor_profile_plugins($profile);
  $plugin_count = count($plugins);
  if (count($plugins)) {
    foreach ($plugins as $i => $plugin) {

      // Ignore processing hidden plugins
      if ($plugin->type == 'hidden') {
        continue;
      }

      // Check for spacers
      if ($plugin == '|') {

        // No need for spacers at the beginning or end or the toolbars
        if ($i != 0 && $i != $plugin_count - 1) {
          $items[] = theme('editor_spacer');
        }
        continue;
      }

      // Invoke alter op
      editor_invoke_plugin_api('alter', $plugin);
      $plugin_type = editor_plugin_type_get($plugin->type);

      // Type must be available
      if ($plugin && $plugin_type) {

        // Theme must be available
        $theme = 'theme_' . $plugin_type->theme;
        if (function_exists($theme)) {
          $args = array();
          $args[] = $plugin_type->theme;
          $args[] = $plugin;
          if (count($plugin->theme_args)) {

            // @todo: $args += array()??
            foreach ($plugin->theme_args as $arg) {
              $args[] = $arg;
            }
          }
          $items[] = @call_user_func_array('theme', $args);
        }
      }
    }
  }
  return theme('editor_toolbar', implode(' ', $items));
}

/**
 * Return a plugin type object or FALSE.
 */
function editor_profile_get($prid) {
  $profiles = editor_invoke_profiles();
  if (count($profiles)) {
    foreach ($profiles as $i => $profile) {
      if ($profile->prid == $prid) {
        return $profile;
      }
    }
  }
  return FALSE;
}

/**
 * Return plugins based on a profile's placeholders.
 */
function editor_profile_plugins($profile) {
  $plugins = array();
  if (count($profile->profile_array)) {
    foreach ($profile->profile_array as $pid) {
      if ($pid != '|') {
        $plugin = editor_plugin_get($pid);
        if ($plugin->pid) {
          $plugins[] = $plugin;
        }
      }
      else {
        $plugins[] = '|';
      }
    }
  }
  return $plugins;
}

/**
 * Parase a profile string into a usable array of pid's and placeholders.
 * 
 * @return array
 * 
 * @todo: rename profile_string and profile_array
 */
function editor_profile_parse_string($profile_string) {
  if (is_string($profile_string)) {
    if ($profile_array = preg_split('/[\\s,]+/', $profile_string)) {
      return $profile_array;
    }
    else {
      return FALSE;
    }
  }
}

/**
 * Create a profile object.
 *
 * @param string $prid  
 *   A unique identifier. This may be something similar to
 *   'basic', or 'full'.
 * 
 * @param string $profile_string 
 *   A string of pid's and placeholders used which is parsed 
 *   later parsed into an array. The following place holders
 *   are available. 
 * 
 *   - '|': spacer
 * 
 * @param string $name 
 *   A human readable string which should be wrapped in t().
 *    
 * @param string $description 
 *   (optional) Description wrapped in t(). 
 *   
 * @returns object
 */
function editor_profile_create($prid, $name, $profile_string, $description = '') {
  $profile = new stdClass();
  $profile->prid = $prid;
  $profile->name = $name;
  $profile->profile_string = $profile_string;
  $profile->description = $description;
  return $profile;
}

/**
 * Return a plugin type object or FALSE.
 */
function editor_plugin_type_get($ptid) {
  $plugin_types = editor_invoke_plugin_types();
  if (count($plugin_types)) {
    foreach ($plugin_types as $i => $plugin_type) {
      if ($plugin_type->ptid == $ptid) {
        return $plugin_type;
      }
    }
  }
  return FALSE;
}

/**
 * Return a plugin object or FALSE.
 */
function editor_plugin_get($pid) {
  $plugins = editor_invoke_plugins();
  if (count($plugins)) {
    foreach ($plugins as $i => $plugin) {
      if ($plugin->pid == $pid) {
        return $plugin;
      }
    }
  }
  return FALSE;
}

/**
 * Create a plugin object.
 *
 * @param string $pid  
 *   A unique identifier. This may be something similar to
 *   'align_left', 'image', or 'link'.
 *   
 * @param string $name 
 *   A human readable string which should be wrapped in t().
 *    
 * @param string $type 
 *   A plugin type ptid.
 * 
 * @param string $description 
 *   (optional) Description wrapped in t(). 
 * 
 * @param string $options 
 *   (optional) Markup which is displayed within the options panel. 
 * 
 * @param array $theme_args   
 *   (optional) An array of arguments which will be passed to the theme function.
 * 
 * @param array $dependencies   
 *   (optional) An array of plugins to which this plugin is dependant of. 
 *   
 * @returns object
 */
function editor_plugin_create($pid, $name, $type, $description = '', $options = array(), $theme_args = array(), $dependencies = array()) {
  $plugin = new stdClass();
  $plugin->pid = $pid;
  $plugin->name = $name;
  $plugin->type = $type;
  $plugin->description = $description;
  $plugin->options = $options;
  $plugin->theme_args = $theme_args;
  $plugin->dependencies = $dependencies;
  return $plugin;
}

/**
 * Create a plugin type object.
 *
 * @param string $ptid  
 *   A unique identifier. This may be something similar to
 *   'button', or 'select'.
 *   
 * @param string $name 
 *   A human readable string which should be wrapped in t().
 *    
 * @param string $theme 
 *   A theme function name without the 'theme_' prefix.
 *    
 * @param string $description 
 *   (optional) Description wrapped in t(). 
 *   
 * @returns object
 */
function editor_plugin_type_create($ptid, $name, $theme, $description = '') {
  $plugin_type = new stdClass();
  $plugin_type->ptid = $ptid;
  $plugin_type->name = $name;
  $plugin_type->theme = $theme;
  $plugin_type->description = $description;
  return $plugin_type;
}

/**
 * Invokes hook_editor_plugin_api().
 * 
 * This hook provides modules with access to different 
 * phases which fire during the plugin process allowing 
 * manipulation. These phases are detailed below see the
 * $op parameter.
 * 
 * @param string $op
 *   - 'alter': Allows altering of the plugin object before it is rendered.
 *   - 'response': The plugin has returned its own data which you may alter.
 * 
 * @param object $plugin
 * 
 * @param mixed $a1
 * 
 * @param mixed $a2
 */
function editor_invoke_plugin_api($op, &$plugin, $a1 = NULL, $a2 = NULL) {
  foreach (module_implements('editor_plugin_api') as $module) {
    $function = $module . '_editor_plugin_api';
    $function($op, $plugin, $a1, $a2);
  }
}

/**
 * Invokes hook_editor_profiles().
 * 
 * This hook provides modules with access to provide
 * their own editor profiles.
 * 
 * @see editor_profile_create()
 * 
 * @returns array
 *   Profiles
 */
function editor_invoke_profiles() {
  static $profiles;
  require_once 'editor.profiles.inc';
  if (!isset($profiles)) {
    $profiles = module_invoke_all('editor_profiles');
    if (count($profiles)) {
      foreach ($profiles as &$profile) {
        $profile->profile_array = editor_profile_parse_string($profile->profile_string);
      }
    }
  }
  return $profiles;
}

/**
 * Invokes hook_editor_plugins().
 * 
 * The editor plugins hook allows for 'plugin types'
 * which are displayed within the editor toolbar. This may 
 * range from buttons, select fields, etc.
 * 
 * @see editor_plugin_create()
 * 
 * @returns array
 *   Plugins
 */
function editor_invoke_plugins() {
  static $plugins;
  require_once 'editor.plugins.inc';
  if (!isset($plugins)) {
    $plugins = module_invoke_all('editor_plugins');
  }
  return $plugins;
}

/**
 * Invokes hook_editor_plugin_types().
 * 
 * The editor plugin types hook allows modules to provide
 * 'types' of plugins such as buttons or select fields.
 * 
 * @see editor_plugin_type_create()
 * 
 * @returns array
 *   Plugins
 */
function editor_invoke_plugin_types() {
  static $plugin_types;
  require_once 'editor.plugins.types.inc';
  if (!isset($plugin_types)) {
    $plugin_types = module_invoke_all('editor_plugin_types');
  }
  return $plugin_types;
}

/* -----------------------------------------------------------------

  Themes 

------------------------------------------------------------------ */

/**
 * Theme an editor toolbar.
 * 
 * @param string $content
 * 
 * @return string
 *   Markup.
 */
function theme_editor_toolbar($content) {
  return '<div class="editor-toolbar">' . $content . '<div class="clear-block"></div></div>';
}

/**
 * Theme a spacer.
 * 
 * @return string
 *   Markup.
 */
function theme_editor_spacer() {
  return '<div class="editor-spacer"></div>';
}

Functions

Namesort descending Description
editor_attach
editor_display_toolbars @todo: handle different toolbars etc? or all themeing?
editor_editor_plugin_api @todo: just testing take this out
editor_invoke_plugins Invokes hook_editor_plugins().
editor_invoke_plugin_api Invokes hook_editor_plugin_api().
editor_invoke_plugin_types Invokes hook_editor_plugin_types().
editor_invoke_profiles Invokes hook_editor_profiles().
editor_menu Implementation of hook_menu().
editor_perm Implementation of hook_perm();
editor_plugin_create Create a plugin object.
editor_plugin_get Return a plugin object or FALSE.
editor_plugin_type_create Create a plugin type object.
editor_plugin_type_get Return a plugin type object or FALSE.
editor_profile_create Create a profile object.
editor_profile_get Return a plugin type object or FALSE.
editor_profile_parse_string Parase a profile string into a usable array of pid's and placeholders.
editor_profile_plugins Return plugins based on a profile's placeholders.
theme_editor_spacer Theme a spacer.
theme_editor_toolbar Theme an editor toolbar.