Index: editors/ckeditor.inc
===================================================================
RCS file: editors/ckeditor.inc
diff -N editors/ckeditor.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ editors/ckeditor.inc	14 Jun 2009 02:28:01 -0000
@@ -0,0 +1,294 @@
+<?php
+// $Id: ckeditor.inc,v 1.17 2009/06/09 00:18:11 sun Exp $
+
+/**
+ * @file
+ * Editor integration functions for CKeditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_ckeditor_editor() {
+  $editor['ckeditor'] = array(
+    'title' => 'CKeditor',
+    'vendor url' => 'http://ckeditor.com',
+    'download url' => 'http://ckeditor.com',
+    'libraries' => array(
+      '' => array(
+        'title' => 'Default',
+        'files' => array('ckeditor.js'),
+      ),
+      'src' => array(
+        'title' => 'Source',
+        'files' => array('ckeditor_source.js'),
+      ),
+    ),
+    'version callback' => 'wysiwyg_ckeditor_version',
+    'themes callback' => 'wysiwyg_ckeditor_themes',
+    'settings callback' => 'wysiwyg_ckeditor_settings',
+    'plugin callback' => 'wysiwyg_ckeditor_plugins',
+    'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings',
+    'proxy plugin' => array(
+      'drupal' => array(
+        'load' => TRUE,
+        'proxy' => TRUE,
+      ),
+    ),
+    'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings',
+    'versions' => array(
+      '3.0-beta2' => array(
+        'js files' => array('ckeditor-3.0.js'),
+      ),
+    ),
+  );
+  return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ *   An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ *   The installed editor version.
+ */
+function wysiwyg_ckeditor_version($editor) {
+  // @todo http://dev.fckeditor.net/ticket/3721
+  return '3.0-beta2';
+  $library = $editor['library path'] . '/ckeditor.js';
+  $library = fopen($library, 'r');
+  $max_lines = 100;
+  while ($max_lines && $line = fgets($library, 60)) {
+    if (preg_match('@^CKEDITOR.prototype.Version\s*= \'([\d\.]+)@', $line, $version)) {
+      fclose($library);
+      return $version[1];
+    }
+    $max_lines--;
+  }
+  fclose($library);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ *   A processed hook_editor() array of editor properties.
+ * @param $profile
+ *   A wysiwyg editor profile.
+ *
+ * @return
+ *   An array of theme names. The first returned name should be the default
+ *   theme name.
+ */
+function wysiwyg_ckeditor_themes($editor, $profile) {
+  return array('default');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ *   A processed hook_editor() array of editor properties.
+ * @param $config
+ *   An array containing wysiwyg editor profile settings.
+ * @param $theme
+ *   The name of a theme/GUI/skin to use.
+ *
+ * @return
+ *   A settings array to be populated in
+ *   Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_ckeditor_settings($editor, $config, $theme) {
+  $settings = array(
+    'basePath' => base_path() . $editor['library path'] . '/',
+    'SkinPath' => base_path() . $editor['library path'] . '/editor/skins/' . $theme . '/',
+    'Width' => '100%',
+    'Height' => 420,
+    'LinkBrowser' => FALSE,
+    'LinkUpload' => FALSE,
+    'ImageBrowser' => FALSE,
+    'ImageUpload' => FALSE,
+    'FlashBrowser' => FALSE,
+    'FlashUpload' => FALSE,
+    // By default, CKeditor converts most characters into HTML entities. Since
+    // it does not support a custom definition, but Drupal supports Unicode, we
+    // disable at least the additional character sets. CKeditor always converts
+    // XML default characters '&', '<', '>'.
+    // @todo Check whether completely disabling ProcessHTMLEntities is an option.
+    'IncludeLatinEntities' => FALSE,
+    'IncludeGreekEntities' => FALSE,
+  );
+  if (isset($config['block_formats'])) {
+    $settings['FontFormats'] = strtr($config['block_formats'], array(',' => ';'));
+  }
+  if (isset($config['apply_source_formatting'])) {
+    $settings['FormatSource'] = $config['apply_source_formatting'];
+  }
+  if (isset($config['preformatted'])) {
+    $settings['FormatOutput'] = $config['preformatted'];
+  }
+
+  if (isset($config['css_setting'])) {
+    if ($config['css_setting'] == 'theme') {
+      // CKeditor only supports one CSS file currently.
+      $settings['contentsCss'] = reset(wysiwyg_get_css());
+    }
+    else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+      $settings['contentsCss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
+    }
+  }
+
+  if (!empty($config['buttons'])) {
+    $settings['toolbar'] = array();
+    $plugins = wysiwyg_get_plugins($editor['name']);
+    foreach ($config['buttons'] as $plugin => $buttons) {
+      foreach ($buttons as $button => $enabled) {
+        // Iterate separately over buttons and extensions properties.
+        foreach (array('buttons', 'extensions') as $type) {
+          // Skip unavailable plugins.
+          if (!isset($plugins[$plugin][$type][$button])) {
+            continue;
+          }
+          // Add buttons.
+          if ($type == 'buttons') {
+            $settings['toolbar'][] = $button;
+          }
+          // Allow plugins to add or override global configuration settings.
+          if (!empty($plugins[$plugin]['options'])) {
+            $settings = array_merge($settings, $plugins[$plugin]['options']);
+          }
+        }
+      }
+    }
+    // For now, all buttons are placed into one row.
+    if (!empty($settings['toolbar'])) {
+      $settings['toolbar'] = array($settings['toolbar']);
+    }
+  }
+
+  return $settings;
+}
+
+/**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ */
+function wysiwyg_ckeditor_plugin_settings($editor, $profile, $plugins) {
+  $settings = array();
+  foreach ($plugins as $name => $plugin) {
+    // Register all plugins that need to be loaded.
+    if (!empty($plugin['load'])) {
+      $settings[$name] = array();
+      // Add path for native external plugins; internal ones do not need a path.
+      if (empty($plugin['internal']) && isset($plugin['path'])) {
+        $settings[$name]['path'] = base_path() . $plugin['path'];
+      }
+      if (!empty($plugin['languages'])) {
+        $settings[$name]['languages'] = $plugin['languages'];
+      }
+    }
+  }
+  return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_ckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
+  $settings = array();
+  foreach ($plugins as $name => $plugin) {
+    // Populate required plugin settings.
+    $settings[$name] = $plugin['dialog settings'] + array(
+      'title' => $plugin['title'],
+      'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
+      'iconTitle' => $plugin['icon title'],
+      // @todo These should only be set if the plugin defined them.
+      'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
+    );
+  }
+  return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_ckeditor_plugins($editor) {
+  $plugins = array(
+    'default' => array(
+      'buttons' => array(
+        'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
+        'StrikeThrough' => t('Strike-through'),
+        'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyFull' => t('Justify'),
+        'UnorderedList' => t('Bullet list'), 'OrderedList' => t('Numbered list'),
+        'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
+        'Undo' => t('Undo'), 'Redo' => t('Redo'),
+        'Link' => t('Link'), 'Unlink' => t('Unlink'), 'Anchor' => t('Anchor'),
+        'Image' => t('Image'),
+        'TextColor' => t('Forecolor'), 'BGColor' => t('Backcolor'),
+        'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
+        'Blockquote' => t('Blockquote'), 'Source' => t('Source code'),
+        'Rule' => t('Horizontal rule'),
+        'Cut' => t('Cut'), 'Copy' => t('Copy'), 'Paste' => t('Paste'),
+        'PasteText' => t('Paste Text'), 'PasteWord' => t('Paste from Word'),
+        'ShowBlocks' => t('Show blocks'),
+        'RemoveFormat' => t('Remove format'),
+        'SpecialChar' => t('Character map'),
+        'About' => t('About'),
+        'FontFormat' => t('HTML block format'), 'FontName' => t('Font'), 'FontSize' => t('Font size'), 'Style' => t('Font style'),
+        'Table' => t('Table'),
+        'Find' => t('Search'), 'Replace' => t('Replace'), 'SelectAll' => t('Select all'),
+        'CreateDiv' => t('Create DIV container'),
+        'Flash' => t('Flash'), 'Smiley' => t('Smiley'),
+        'FitWindow' => t('FitWindow'),
+        'SpellCheck' => t('Check spelling'),
+      ),
+      'internal' => TRUE,
+    ),
+    'autogrow' => array(
+      'path' => $editor['library path'] . '/editor/plugins',
+      'extensions' => array(
+        'autogrow' => t('Autogrow'),
+      ),
+      'options' => array(
+        'AutoGrowMax' => 800,
+      ),
+      'internal' => TRUE,
+      'load' => TRUE,
+    ),
+    'bbcode' => array(
+      'path' => $editor['library path'] . '/editor/plugins',
+      'extensions' => array(
+        'bbcode' => t('BBCode'),
+      ),
+      'internal' => TRUE,
+      'load' => TRUE,
+    ),
+    'dragresizetable' => array(
+      'path' => $editor['library path'] . '/editor/plugins',
+      'extensions' => array(
+        'dragresizetable' => t('Table drag/resize'),
+      ),
+      'internal' => TRUE,
+      'load' => TRUE,
+    ),
+    'tablecommands' => array(
+      'path' => $editor['library path'] . '/editor/plugins',
+      'buttons' => array(
+        'TableCellProp' => t('Table: Cell properties'),
+        'TableInsertRowAfter' => t('Table: Insert row after'),
+        'TableInsertColumnAfter' => t('Table: Insert column after'),
+        'TableInsertCellAfter' => t('Table: Insert cell after'),
+        'TableDeleteRows' => t('Table: Delete rows'),
+        'TableDeleteColumns' => t('Table: Delete columns'),
+        'TableDeleteCells' => t('Table: Delete cells'),
+        'TableMergeCells' => t('Table: Merge cells'),
+        'TableHorizontalSplitCell' => t('Table: Horizontal split cell'),
+      ),
+      'internal' => TRUE,
+      'load' => TRUE,
+    ),
+  );
+  return $plugins;
+}
+
Index: editors/js/ckeditor-3.0.js
===================================================================
RCS file: editors/js/ckeditor-3.0.js
diff -N editors/js/ckeditor-3.0.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ editors/js/ckeditor-3.0.js	14 Jun 2009 02:29:20 -0000
@@ -0,0 +1,162 @@
+// $Id: ckeditor-2.6.js,v 1.16 2009/06/04 00:53:10 sun Exp $
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) {
+  // Apply editor instance settings.
+  CKEDITOR.config.customConfig = '';
+
+  // Attach editor.
+  CKEDITOR.replace(params.field, settings);
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * @todo 3.x: editor.prototype.getInstances() should always return an array
+ *   containing all instances or the passed in params.field instance, but
+ *   always return an array to simplify all detach functions.
+ */
+Drupal.wysiwyg.editor.detach.ckeditor = function(context, params) {
+  if (typeof params != 'undefined') {
+    var instance = CKEDITOR.instances[params.field];
+    if (instance) {
+      instance.destroy();
+    }
+  }
+  else {
+    for (var instanceName in CKEDITOR.instances) {
+      CKEDITOR.instances[instanceName].destroy();
+    }
+  }
+};
+
+// @todo
+Drupal.wysiwyg.editor.instance.ckeditor = {
+  init: function(instance) {
+    // Track which editor instance is active.
+    instance.FCK.Events.AttachEvent('OnFocus', function(editorInstance) {
+      Drupal.wysiwyg.activeId = editorInstance.Name;
+    });
+
+    // Create a custom data processor to wrap the default one and allow Drupal
+    // plugins modify the editor contents.
+    var wysiwygDataProcessor = function() {};
+    wysiwygDataProcessor.prototype = new instance.FCKDataProcessor();
+    // Attach: Convert text into HTML.
+    wysiwygDataProcessor.prototype.ConvertToHtml = function(data) {
+      // Called from SetData() with stripped comments/scripts, revert those
+      // manipulations and attach Drupal plugins.
+      var data = instance.FCKConfig.ProtectedSource.Revert(data);
+      for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+        if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+          data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
+          data = Drupal.wysiwyg.editor.instance.ckeditor.prepareContent(data);
+        }
+      }
+      // Re-protect the source and use the original data processor to convert it
+      // into XHTML.
+      data = instance.FCKConfig.ProtectedSource.Protect(data);
+      return instance.FCKDataProcessor.prototype.ConvertToHtml.call(this, data);
+    };
+    // Detach: Convert HTML into text.
+    wysiwygDataProcessor.prototype.ConvertToDataFormat = function(rootNode, excludeRoot) {
+      // Called from GetData(), convert the content's DOM into a XHTML string
+      // using the original data processor and detach Drupal plugins.
+      var data = instance.FCKDataProcessor.prototype.ConvertToDataFormat.call(this, rootNode, excludeRoot);
+      for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+        if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+          data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
+        }
+      }
+      return data;
+    };
+    instance.FCK.DataProcessor = new wysiwygDataProcessor();
+  },
+
+  addPlugin: function(plugin, settings, pluginSettings, instance) {
+    if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+      return;
+    }
+
+    // Add stylesheet for editor content area.
+    if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css) {
+      instance.FCKConfig.EditorAreaCSS += ',' + Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css;
+    }
+
+    // @see fckcommands.js, fck_othercommands.js, fckpastewordcommand.js
+    if (pluginSettings.dialog) {
+      instance.FCKCommands.RegisterCommand(plugin, new instance.FCKDialogCommand(
+        plugin,
+        this.field,
+        pluginSettings.dialog.url + '/' + this.field,
+        pluginSettings.dialog.width,
+        pluginSettings.dialog.height,
+        null,
+        null,
+        true
+      ));
+    }
+    else {
+    instance.FCKCommands.RegisterCommand(plugin, {
+      // Invoke the plugin's button.
+      Execute: function () {
+        if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+          var data = { format: 'html', node: instance.FCKSelection.GetParentElement() };
+          // @todo This is NOT the same as data.node.
+          data.content = data.node.innerHTML;
+          Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instance.FCK.Name);
+        }
+      },
+
+      // isNode: Return whether the plugin button should be enabled for the
+      // current selection.
+      // @see FCKUnlinkCommand.prototype.GetState()
+      GetState: function () {
+        // Always disabled if not in WYSIWYG mode.
+        if (instance.FCK.EditMode != FCK_EDITMODE_WYSIWYG) {
+          return FCK_TRISTATE_DISABLED;
+        }
+        var state = instance.FCK.GetNamedCommandState(this.Name);
+        if (state == FCK_TRISTATE_OFF && instance.FCK.EditMode == FCK_EDITMODE_WYSIWYG) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+            var node = instance.FCKSelection.GetSelectedElement();
+            state = Drupal.wysiwyg.plugins[plugin].isNode(node) ? FCK_TRISTATE_ON : FCK_TRISTATE_OFF;
+          }
+        }
+        return state;
+      },
+
+      /**
+       * Return information about the plugin as a name/value array.
+       */
+      Name: plugin
+    });
+    }
+
+    // Register the plugin button.
+    // Arguments: commandName, label, tooltip, style, sourceView, contextSensitive, icon.
+    instance.FCKToolbarItems.RegisterItem(plugin, new instance.FCKToolbarButton(plugin, settings.iconTitle, settings.iconTitle, null, false, true, settings.icon));
+  },
+
+  openDialog: function(dialog, params) {
+    // Who needs dialogs after all? :P
+  },
+
+  closeDialog: function(dialog) {
+    // @todo Implement close dialog.
+  },
+
+  prepareContent: function(content) {
+    // @todo Not needed for CKeditor?
+    return content;
+  },
+
+  insert: function(content) {
+    var instance = FCKeditorAPI.GetInstance(this.field);
+    // @see FCK.InsertHtml(), FCK.InsertElement()
+    instance.InsertHtml(content);
+  }
+};
+
