' . t('Do NOT download the "CKEditor for Drupal" edition.') . '
'; +} + +/** + * 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) { + $library = $editor['library path'] . '/ckeditor.js'; + if (!file_exists($library)) { + return; + } + $library = fopen($library, 'r'); + $max_lines = 8; + while ($max_lines && $line = fgets($library, 500)) { + // version:'CKEditor 3.0 SVN',revision:'3665' + // version:'3.0 RC',revision:'3753' + // version:'3.0.1',revision:'4391' + if (preg_match('@version:\'(?:CKEditor )?([\d\.]+)(?:.+revision:\'([\d]+))?@', $line, $version)) { + fclose($library); + // Version numbers need to have three parts since 3.0.1. + $version[1] = preg_replace('/^(\d+)\.(\d+)$/', '${1}.${2}.0', $version[1]); + return $version[1] . '.' . $version[2]; + } + $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) { + // @todo Skins are not themes but this will do for now. + $path = $editor['library path'] . '/skins/'; + if (file_exists($path) && ($dir_handle = opendir($path))) { + $themes = array(); + while ($file = readdir($dir_handle)) { + if (is_dir($path . $file) && substr($file, 0, 1) != '.' && $file != 'CVS') { + $themes[] = $file; + } + } + closedir($dir_handle); + natcasesort($themes); + $themes = array_values($themes); + return !empty($themes) ? $themes : array('default'); + } + else { + return array('default'); + } +} + +/** + * Enhances the editor profile settings form for CKEditor. + * + * Adds support for CKEditor's advanced stylesSets, which are a more advanced + * implementation and combination of block formats and font styles that allow + * to adjust the HTML element, attributes, and CSS styles at once. + * + * @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles + * @see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html#.stylesSet + */ +function wysiwyg_ckeditor_settings_form(&$form, &$form_state) { + if (version_compare($form_state['wysiwyg']['editor']['installed version'], '3.2.1', '>=')) { + // Replace CSS classes element description to explain the advanced syntax. + $form['css']['css_classes']['#description'] = t('Optionally define CSS classes for the "Font style" dropdown list.[label]=[element].[class]
',
+ '!example' => 'Title=h1.title
',
+ ));
+ $form['css']['css_classes']['#element_validate'][] = 'wysiwyg_ckeditor_settings_form_validate_css_classes';
+ }
+ else {
+ // Versions below 3.2.1 do not support Font styles at all.
+ $form['css']['css_classes']['#access'] = FALSE;
+ }
+}
+
+/**
+ * #element_validate handler for CSS classes element altered by wysiwyg_ckeditor_settings_form().
+ */
+function wysiwyg_ckeditor_settings_form_validate_css_classes($element, &$form_state) {
+ if (wysiwyg_ckeditor_settings_parse_styles($element['#value']) === FALSE) {
+ form_error($element, t('The specified CSS classes are syntactically incorrect.'));
+ }
+}
+
+/**
+ * Returns an initialization JavaScript for this editor library.
+ *
+ * @param array $editor
+ * The editor library definition.
+ * @param string $library
+ * The library variant key from $editor['libraries'].
+ * @param object $profile
+ * The (first) wysiwyg editor profile.
+ *
+ * @return string
+ * A string containing inline JavaScript to execute before the editor library
+ * script is loaded.
+ */
+function wysiwyg_ckeditor_init($editor) {
+ // CKEditor unconditionally searches for its library filename in SCRIPT tags
+ // on the page upon loading the library in order to determine the base path to
+ // itself. When JavaScript aggregation is enabled, this search fails and all
+ // relative constructed paths within CKEditor are broken. The library has a
+ // CKEditor.basePath property, but it is not publicly documented and thus not
+ // reliable. The official documentation suggests to solve the issue through
+ // the global window variable.
+ // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Specifying_the_Editor_Path
+ $library_path = base_path() . $editor['library path'] . '/';
+ return <<. + // Linebreaks can be inserted before or after opening and closing tags. + if (settings.apply_source_formatting) { + // Mimic FCKeditor output, by breaking lines between tags. + for (var tag in tags) { + if (tag == 'pre') { + continue; + } + this.dataProcessor.writer.setRules(tag, { + indent: true, + breakBeforeOpen: true, + breakAfterOpen: false, + breakBeforeClose: false, + breakAfterClose: true + }); + } + } + else { + // CKEditor adds default formatting to
, so we want to remove that + // here too. + tags.br = 1; + // No indents or linebreaks; + for (var tag in tags) { + if (tag == 'pre') { + continue; + } + this.dataProcessor.writer.setRules(tag, { + indent: false, + breakBeforeOpen: false, + breakAfterOpen: false, + breakBeforeClose: false, + breakAfterClose: false + }); + } + } + }, + + pluginsLoaded: function(ev) { + // Override the conversion methods to let Drupal plugins modify the data. + var editor = ev.editor; + if (editor.dataProcessor && Drupal.settings.wysiwyg.plugins[params.format]) { + editor.dataProcessor.toHtml = CKEDITOR.tools.override(editor.dataProcessor.toHtml, function(originalToHtml) { + // Convert raw data for display in WYSIWYG mode. + return function(data, fixForBody) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name); + data = Drupal.wysiwyg.instances[params.field].prepareContent(data); + } + } + return originalToHtml.call(this, data, fixForBody); + }; + }); + editor.dataProcessor.toDataFormat = CKEDITOR.tools.override(editor.dataProcessor.toDataFormat, function(originalToDataFormat) { + // Convert WYSIWYG mode content to raw data. + return function(data, fixForBody) { + data = originalToDataFormat.call(this, data, fixForBody); + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name); + } + } + return data; + }; + }); + } + }, + + selectionChange: function (event) { + var pluginSettings = Drupal.settings.wysiwyg.plugins[params.format]; + if (pluginSettings && pluginSettings.drupal) { + $.each(pluginSettings.drupal, function (name) { + var plugin = Drupal.wysiwyg.plugins[name]; + if ($.isFunction(plugin.isNode)) { + var node = event.data.selection.getSelectedElement(); + var state = plugin.isNode(node ? node.$ : null) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF; + event.editor.getCommand(name).setState(state); + } + }); + } + }, + + focus: function(ev) { + Drupal.wysiwyg.activeId = ev.editor.name; + }, + + afterCommandExec: function(ev) { + // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. + if (ev.data.name != 'maximize') { + return; + } + if (ev.data.command.state == CKEDITOR.TRISTATE_ON) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } + } + }; + + // 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, trigger) { + var method = (trigger == 'serialize') ? 'updateElement' : 'destroy'; + if (typeof params != 'undefined') { + var instance = CKEDITOR.instances[params.field]; + if (instance) { + instance[method](); + } + } + else { + for (var instanceName in CKEDITOR.instances) { + if (CKEDITOR.instances.hasOwnProperty(instanceName)) { + CKEDITOR.instances[instanceName][method](); + } + } + } +}; + +Drupal.wysiwyg.editor.instance.ckeditor = { + addPlugin: function(pluginName, settings, pluginSettings) { + CKEDITOR.plugins.add(pluginName, { + // Wrap Drupal plugin in a proxy pluygin. + init: function(editor) { + if (settings.css) { + editor.on('mode', function(ev) { + if (ev.editor.mode == 'wysiwyg') { + // Inject CSS files directly into the editing area head tag. + $('head', $('#cke_contents_' + ev.editor.name + ' iframe').eq(0).contents()).append(''); + } + }); + } + if (typeof Drupal.wysiwyg.plugins[pluginName].invoke == 'function') { + var pluginCommand = { + exec: function (editor) { + var data = { format: 'html', node: null, content: '' }; + var selection = editor.getSelection(); + if (selection) { + data.node = selection.getSelectedElement(); + if (data.node) { + data.node = data.node.$; + } + if (selection.getType() == CKEDITOR.SELECTION_TEXT) { + if (CKEDITOR.env.ie) { + data.content = selection.getNative().createRange().text; + } + else { + data.content = selection.getNative().toString(); + } + } + else if (data.node) { + // content is supposed to contain the "outerHTML". + data.content = data.node.parentNode.innerHTML; + } + } + Drupal.wysiwyg.plugins[pluginName].invoke(data, pluginSettings, editor.name); + } + }; + editor.addCommand(pluginName, pluginCommand); + } + editor.ui.addButton(pluginName, { + label: settings.iconTitle, + command: pluginName, + icon: settings.icon + }); + + // @todo Add button state handling. + } + }); + }, + prepareContent: function(content) { + // @todo Don't know if we need this yet. + return content; + }, + + insert: function(content) { + content = this.prepareContent(content); + CKEDITOR.instances[this.field].insertHtml(content); + }, + + setContent: function (content) { + CKEDITOR.instances[this.field].setData(content); + }, + + getContent: function () { + return CKEDITOR.instances[this.field].getData(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/epiceditor.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/epiceditor.js new file mode 100644 index 00000000..03c48017 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/epiceditor.js @@ -0,0 +1,38 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.epiceditor = function (context, params, settings) { + var $target = $('#' + params.field); + var containerId = params.field + '-epiceditor'; + var defaultContent = $target.val(); + $target.hide().after(''); + + settings.container = containerId; + settings.file = { + defaultContent: defaultContent + }; + settings.theme = { + preview: '/themes/preview/preview-dark.css', + editor: '/themes/editor/' + settings.theme + '.css' + } + var editor = new EpicEditor(settings).load(); + $target.data('epiceditor', editor); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.epiceditor = function (context, params, trigger) { + var $target = $('#' + params.field); + var editor = $target.data('epiceditor'); + + $target.val(editor.exportFile()); + + editor.unload(function () { + $target.show(); + }); +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor-2.6.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor-2.6.js new file mode 100644 index 00000000..fd915e3f --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor-2.6.js @@ -0,0 +1,196 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.fckeditor = function(context, params, settings) { + var FCKinstance = new FCKeditor(params.field, settings.Width, settings.Height, settings.ToolbarSet); + // Apply editor instance settings. + FCKinstance.BasePath = settings.EditorPath; + FCKinstance.Config.wysiwygFormat = params.format; + FCKinstance.Config.CustomConfigurationsPath = settings.CustomConfigurationsPath; + + // Load Drupal plugins and apply format specific settings. + // @see fckeditor.config.js + // @see Drupal.wysiwyg.editor.instance.fckeditor.init() + + // Attach editor. + FCKinstance.ReplaceTextarea(); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.fckeditor = function (context, params, trigger) { + var instances = []; + if (typeof params != 'undefined' && typeof FCKeditorAPI != 'undefined') { + var instance = FCKeditorAPI.GetInstance(params.field); + if (instance) { + instances[params.field] = instance; + } + } + else { + instances = FCKeditorAPI.__Instances; + } + + for (var instanceName in instances) { + var instance = instances[instanceName]; + instance.UpdateLinkedField(); + if (trigger == 'serialize') { + // The editor is not being removed from the DOM, so updating the linked + // field is the only action necessary. + continue; + } + // Since we already detach the editor and update the textarea, the submit + // event handler needs to be removed to prevent data loss (in IE). + // FCKeditor uses 2 nested iFrames; instance.EditingArea.Window is the + // deepest. Its parent is the iFrame containing the editor. + var instanceScope = instance.EditingArea.Window.parent; + instanceScope.FCKTools.RemoveEventListener(instance.GetParentForm(), 'submit', instance.UpdateLinkedField); + // Run cleanups before forcing an unload of the iFrames or IE crashes. + // This also deletes the instance from the FCKeditorAPI.__Instances array. + instanceScope.FCKTools.RemoveEventListener(instanceScope, 'unload', instanceScope.FCKeditorAPI_Cleanup); + instanceScope.FCKTools.RemoveEventListener(instanceScope, 'beforeunload', instanceScope.FCKeditorAPI_ConfirmCleanup); + if (jQuery.isFunction(instanceScope.FCKIECleanup_Cleanup)) { + instanceScope.FCKIECleanup_Cleanup(); + } + instanceScope.FCKeditorAPI_ConfirmCleanup(); + instanceScope.FCKeditorAPI_Cleanup(); + // Remove the editor elements. + $('#' + instanceName + '___Config').remove(); + $('#' + instanceName + '___Frame').remove(); + $('#' + instanceName).show(); + } +}; + +Drupal.wysiwyg.editor.instance.fckeditor = { + 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); + if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) { + 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.fckeditor.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, ignoreIfEmptyParagraph, format) { + // 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, ignoreIfEmptyParagraph, format); + if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) { + 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; + } + + 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 + 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); + // FCKeditor sets the wrong state in WebKit browsers. + if (!$.support.queryCommandEnabled && state == FCK_TRISTATE_DISABLED) { + state = FCK_TRISTATE_OFF; + } + 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) { + // @todo Implement open dialog. + }, + + closeDialog: function(dialog) { + // @todo Implement close dialog. + }, + + prepareContent: function(content) { + // @todo Not needed for FCKeditor? + return content; + }, + + insert: function(content) { + var instance = FCKeditorAPI.GetInstance(this.field); + // @see FCK.InsertHtml(), FCK.InsertElement() + instance.InsertHtml(content); + }, + + getContent: function () { + var instance = FCKeditorAPI.GetInstance(this.field); + return instance.GetData(); + }, + + setContent: function (content) { + var instance = FCKeditorAPI.GetInstance(this.field); + instance.SetHTML(content); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor.config.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor.config.js new file mode 100644 index 00000000..42efc322 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/fckeditor.config.js @@ -0,0 +1,86 @@ + +Drupal = window.parent.Drupal; + +/** + * Fetch and provide original editor settings as local variable. + * + * FCKeditor does not support to pass complex variable types to the editor. + * Instance settings passed to FCKinstance.Config are temporarily stored in + * FCKConfig.PageConfig. + */ +var wysiwygFormat = FCKConfig.PageConfig.wysiwygFormat; +var wysiwygSettings = Drupal.settings.wysiwyg.configs.fckeditor[wysiwygFormat]; +var pluginSettings = (Drupal.settings.wysiwyg.plugins[wysiwygFormat] ? Drupal.settings.wysiwyg.plugins[wysiwygFormat] : { 'native': {}, 'drupal': {} }); + +/** + * Apply format-specific settings. + */ +for (var setting in wysiwygSettings) { + if (setting == 'buttons') { + // Apply custom Wysiwyg toolbar for this format. + // FCKConfig.ToolbarSets['Wysiwyg'] = wysiwygSettings.buttons; + + // Temporarily stack buttons into multiple button groups and remove + // separators until #277954 is solved. + FCKConfig.ToolbarSets['Wysiwyg'] = []; + for (var i = 0; i < wysiwygSettings.buttons[0].length; i++) { + FCKConfig.ToolbarSets['Wysiwyg'].push([wysiwygSettings.buttons[0][i]]); + } + FCKTools.AppendStyleSheet(document, '#xToolbar .TB_Start { display:none; }'); + // Set valid height of select element in silver and office2003 skins. + if (FCKConfig.SkinPath.match(/\/office2003\/$/)) { + FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 24px; } #xToolbar .TB_End { display: none; }'); + } + else if (FCKConfig.SkinPath.match(/\/silver\/$/)) { + FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 27px; }'); + } + } + else { + FCKConfig[setting] = wysiwygSettings[setting]; + } +} + +// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. +var oldFitWindowExecute = FCKFitWindow.prototype.Execute; +var $drupalToolbar = window.parent.jQuery('#toolbar', Drupal.overlayChild ? window.parent.window.parent.document : window.parent.document); +FCKFitWindow.prototype.Execute = function() { + oldFitWindowExecute.apply(this, arguments); + if (this.IsMaximized) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } +} + +/** + * Initialize this editor instance. + */ +Drupal.wysiwyg.editor.instance.fckeditor.init(window); + +/** + * Register native plugins for this input format. + * + * Parameters to Plugins.Add are: + * - Plugin name. + * - Languages the plugin is available in. + * - Location of the plugin folder;/fckplugin.js is appended. + */ +for (var plugin in pluginSettings['native']) { + // Languages and path may be undefined for internal plugins. + FCKConfig.Plugins.Add(plugin, pluginSettings['native'][plugin].languages, pluginSettings['native'][plugin].path); +} + +/** + * Register Drupal plugins for this input format. + * + * Parameters to addPlugin() are: + * - Plugin name. + * - Format specific plugin settings. + * - General plugin settings. + * - A reference to this window so the plugin setup can access FCKConfig. + */ +for (var plugin in pluginSettings.drupal) { + Drupal.wysiwyg.editor.instance.fckeditor.addPlugin(plugin, pluginSettings.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin], window); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/jwysiwyg.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/jwysiwyg.js new file mode 100644 index 00000000..d3e7490f --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/jwysiwyg.js @@ -0,0 +1,43 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.jwysiwyg = function(context, params, settings) { + // Attach editor. + $('#' + params.field).wysiwyg(); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.jwysiwyg = function (context, params, trigger) { + var $field = $('#' + params.field); + var editor = $field.data('wysiwyg'); + if (typeof editor != 'undefined') { + editor.saveContent(); + if (trigger != 'serialize') { + editor.element.remove(); + } + } + $field.removeData('wysiwyg'); + if (trigger != 'serialize') { + $field.show(); + } +}; + +Drupal.wysiwyg.editor.instance.jwysiwyg = { + insert: function (content) { + $('#' + this.field).wysiwyg('insertHtml', content); + }, + + setContent: function (content) { + $('#' + this.field).wysiwyg('setContent', content); + }, + + getContent: function () { + return $('#' + this.field).wysiwyg('getContent'); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/markitup.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/markitup.js new file mode 100644 index 00000000..00e10b97 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/markitup.js @@ -0,0 +1,46 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.markitup = function(context, params, settings) { + $('#' + params.field, context).markItUp(settings); + + // Adjust CSS for editor buttons. + $.each(settings.markupSet, function (button) { + $('.' + settings.nameSpace + ' .' + this.className + ' a') + .css({ backgroundImage: 'url(' + settings.root + 'sets/default/images/' + button + '.png' + ')' }) + .parents('li').css({ backgroundImage: 'none' }); + }); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.markitup = function (context, params, trigger) { + if (trigger == 'serialize') { + return; + } + if (typeof params != 'undefined') { + $('#' + params.field, context).markItUpRemove(); + } + else { + $('.markItUpEditor', context).markItUpRemove(); + } +}; + +Drupal.wysiwyg.editor.instance.markitup = { + insert: function (content) { + $.markItUp({ replaceWith: content }); + }, + + setContent: function (content) { + $('#' + this.field).val(content); + }, + + getContent: function () { + return $('#' + this.field).val(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/nicedit.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/nicedit.js new file mode 100644 index 00000000..10b7809c --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/nicedit.js @@ -0,0 +1,115 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.nicedit = function(context, params, settings) { + // Intercept and ignore submit handlers or they will revert changes made + // since the instance was removed. The handlers are anonymous and hidden out + // of scope in a closure so we can't unbind them. The same operations are + // performed when the instance is detached anyway. + var oldAddEvent = bkLib.addEvent; + bkLib.addEvent = function(obj, type, fn) { + if (type != 'submit') { + oldAddEvent(obj, type, fn); + } + } + // Attach editor. + var editor = new nicEditor(settings); + editor.panelInstance(params.field); + // The old addEvent() must be restored after creating a new instance, as + // plugins with dialogs use it to bind submit handlers to their forms. + bkLib.addEvent = oldAddEvent; + editor.addEvent('focus', function () { + Drupal.wysiwyg.activeId = params.field; + }); +}; + +/** + * Detach a single or all editors. + * + * See Drupal.wysiwyg.editor.detach.none() for a full description of this hook. + */ +Drupal.wysiwyg.editor.detach.nicedit = function (context, params, trigger) { + if (typeof params != 'undefined') { + var instance = nicEditors.findEditor(params.field); + if (instance) { + if (trigger == 'serialize') { + instance.saveContent(); + } + else { + instance.ne.removeInstance(params.field); + instance.ne.removePanel(); + } + } + } + else { + for (var e in nicEditors.editors) { + // Save contents of all editors back into textareas. + var instances = nicEditors.editors[e].nicInstances; + for (var i = 0; i < instances.length; i++) { + if (trigger == 'serialize') { + instances[i].saveContent(); + } + else { + instances[i].remove(); + } + } + // Remove all editor instances. + if (trigger != 'serialize') { + nicEditors.editors[e].nicInstances = []; + } + } + } +}; + +/** + * Instance methods for nicEdit. + */ +Drupal.wysiwyg.editor.instance.nicedit = { + insert: function (content) { + var instance = nicEditors.findEditor(this.field); + var editingArea = instance.getElm(); + var sel = instance.getSel(); + // IE. + if (document.selection) { + editingArea.focus(); + sel.createRange().pasteHTML(content); + } + else { + // Convert selection to a range. + var range; + // W3C compatible. + if (sel.getRangeAt) { + range = sel.getRangeAt(0); + } + // Safari. + else { + range = editingArea.ownerDocument.createRange(); + range.setStart(sel.anchorNode, sel.anchorOffset); + range.setEnd(sel.focusNode, userSeletion.focusOffset); + } + // The code below doesn't work in IE, but it never gets here. + var fragment = editingArea.ownerDocument.createDocumentFragment(); + // Fragments don't support innerHTML. + var wrapper = editingArea.ownerDocument.createElement('div'); + wrapper.innerHTML = content; + while (wrapper.firstChild) { + fragment.appendChild(wrapper.firstChild); + } + range.deleteContents(); + // Only fragment children are inserted. + range.insertNode(fragment); + } + }, + + setContent: function (content) { + nicEditors.findEditor(this.field).setContent(content); + }, + + getContent: function () { + return nicEditors.findEditor(this.field).getContent(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/none.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/none.js new file mode 100644 index 00000000..762f7fb2 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/none.js @@ -0,0 +1,91 @@ +(function($) { + +/** + * Attach this editor to a target element. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + * @param params + * An object containing input format parameters. Default parameters are: + * - editor: The internal editor name. + * - theme: The name/key of the editor theme/profile to use. + * - field: The CSS id of the target element. + * @param settings + * An object containing editor settings for all enabled editor themes. + */ +Drupal.wysiwyg.editor.attach.none = function(context, params, settings) { + if (params.resizable) { + var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first'); + $wrapper.addClass('resizable'); + if (Drupal.behaviors.textarea) { + Drupal.behaviors.textarea.attach(); + } + } +}; + +/** + * Detach a single or all editors. + * + * The editor syncs its contents back to the original field before its instance + * is removed. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + * @param params + * (optional) An object containing input format parameters. If defined, + * only the editor instance in params.field should be detached. Otherwise, + * all editors should be detached and saved, so they can be submitted in + * AJAX/AHAH applications. + * @param trigger + * A string describing why the editor is being detached. + * Possible triggers are: + * - unload: (default) Another or no editor is about to take its place. + * - move: Currently expected to produce the same result as unload. + * - serialize: The form is about to be serialized before an AJAX request or + * a normal form submission. If possible, perform a quick detach and leave + * the editor's GUI elements in place to avoid flashes or scrolling issues. + * @see Drupal.detachBehaviors + */ +Drupal.wysiwyg.editor.detach.none = function (context, params, trigger) { + if (typeof params != 'undefined' && (trigger != 'serialize')) { + var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first'); + $wrapper.removeOnce('textarea').removeClass('.resizable-textarea') + .find('.grippie').remove(); + } +}; + +/** + * Instance methods for plain text areas. + */ +Drupal.wysiwyg.editor.instance.none = { + insert: function(content) { + var editor = document.getElementById(this.field); + + // IE support. + if (document.selection) { + editor.focus(); + var sel = document.selection.createRange(); + sel.text = content; + } + // Mozilla/Firefox/Netscape 7+ support. + else if (editor.selectionStart || editor.selectionStart == '0') { + var startPos = editor.selectionStart; + var endPos = editor.selectionEnd; + editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length); + } + // Fallback, just add to the end of the content. + else { + editor.value += content; + } + }, + + setContent: function (content) { + $('#' + this.field).val(content); + }, + + getContent: function () { + return $('#' + this.field).val(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/openwysiwyg.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/openwysiwyg.js new file mode 100644 index 00000000..a01e8a0b --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/openwysiwyg.js @@ -0,0 +1,141 @@ + +// Backup $ and reset it to jQuery. +Drupal.wysiwyg._openwysiwyg = $; +$ = jQuery; + +// Wrap openWYSIWYG's methods to temporarily use its version of $. +jQuery.each(WYSIWYG, function (key, value) { + if (jQuery.isFunction(value)) { + WYSIWYG[key] = function () { + var old$ = $; + $ = Drupal.wysiwyg._openwysiwyg; + var result = value.apply(this, arguments); + $ = old$; + return result; + }; + } +}); + +// Override editor functions. +WYSIWYG.getEditor = function (n) { + return Drupal.wysiwyg._openwysiwyg("wysiwyg" + n); +}; + +(function($) { + +// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. +var oldMaximize = WYSIWYG.maximize; +WYSIWYG.maximize = function (n) { +var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document); + oldMaximize.apply(this, arguments); + if (this.maximized[n]) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } +} + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.openwysiwyg = function(context, params, settings) { + // Initialize settings. + settings.ImagesDir = settings.path + 'images/'; + settings.PopupsDir = settings.path + 'popups/'; + settings.CSSFile = settings.path + 'styles/wysiwyg.css'; + //settings.DropDowns = []; + var config = new WYSIWYG.Settings(); + for (var setting in settings) { + config[setting] = settings[setting]; + } + // Attach editor. + WYSIWYG.setSettings(params.field, config); + WYSIWYG_Core.includeCSS(WYSIWYG.config[params.field].CSSFile); + WYSIWYG._generate(params.field, config); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.openwysiwyg = function (context, params, trigger) { + if (typeof params != 'undefined') { + var instance = WYSIWYG.config[params.field]; + if (typeof instance != 'undefined') { + WYSIWYG.updateTextArea(params.field); + if (trigger != 'serialize') { + jQuery('#wysiwyg_div_' + params.field).remove(); + delete instance; + } + } + if (trigger != 'serialize') { + jQuery('#' + params.field).show(); + } + } + else { + jQuery.each(WYSIWYG.config, function(field) { + WYSIWYG.updateTextArea(field); + if (trigger != 'serialize') { + jQuery('#wysiwyg_div_' + field).remove(); + delete this; + jQuery('#' + field).show(); + } + }); + } +}; + +/** + * Instance methods for openWYSIWYG. + */ +Drupal.wysiwyg.editor.instance.openwysiwyg = { + insert: function (content) { + // If IE has dropped focus content will be inserted at the top of the page. + $('#wysiwyg' + this.field).contents().find('body').focus(); + WYSIWYG.insertHTML(content, this.field); + }, + + setContent: function (content) { + // Based on openWYSIWYG's _generate() method. + var doc = WYSIWYG.getEditorWindow(this.field).document; + if (WYSIWYG.config[this.field].ReplaceLineBreaks) { + content = content.replace(/\n\r|\n/ig, '
'); + } + if (WYSIWYG.viewTextMode[this.field]) { + var html = document.createTextNode(content); + doc.body.innerHTML = ''; + doc.body.appendChild(html); + } + else { + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Based on openWYSIWYG's updateTextarea() method. + var content = ''; + var doc = WYSIWYG.getEditorWindow(this.field).document; + if (WYSIWYG.viewTextMode[this.field]) { + if (WYSIWYG_Core.isMSIE) { + content = doc.body.innerText; + } + else { + var range = doc.body.ownerDocument.createRange(); + range.selectNodeContents(doc.body); + content = range.toString(); + } + } + else { + content = doc.body.innerHTML; + } + content = WYSIWYG.stripURLPath(this.field, content); + content = WYSIWYG_Core.replaceRGBWithHexColor(content); + if (WYSIWYG.config[this.field].ReplaceLineBreaks) { + content = content.replace(/(\r\n)|(\n)/ig, ''); + } + return content; + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-2.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-2.js new file mode 100644 index 00000000..61a60ade --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-2.js @@ -0,0 +1,203 @@ +(function($) { + +/** + * Initialize editor instances. + * + * This function needs to be called before the page is fully loaded, as + * calling tinyMCE.init() after the page is loaded breaks IE6. + * + * @param editorSettings + * An object containing editor settings for each input format. + */ +Drupal.wysiwyg.editor.init.tinymce = function(settings) { + // Initialize editor configurations. + for (var format in settings) { + tinyMCE.init(settings[format]); + if (Drupal.settings.wysiwyg.plugins[format]) { + // Load native external plugins. + // Array syntax required; 'native' is a predefined token in JavaScript. + for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) { + tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]); + } + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) { + Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); + } + } + } +}; + +/** + * Attach this editor to a target element. + * + * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook. + */ +Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { + // Configure editor settings for this input format. + for (var setting in settings) { + tinyMCE.settings[setting] = settings[setting]; + } + + // Remove TinyMCE's internal mceItem class, which was incorrectly added to + // submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class + // for placeholder elements. If preemptively set, the class prevents (native) + // editor plugins from gaining an active state, so we have to manually remove + // it prior to attaching the editor. This is done on the client-side instead + // of the server-side, as Wysiwyg has no way to figure out where content is + // stored, and the class only affects editing. + $field = $('#' + params.field); + $field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2')); + + // Attach editor. + tinyMCE.execCommand('mceAddControl', true, params.field); +}; + +/** + * Detach a single or all editors. + * + * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. + */ +Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) { + if (typeof params != 'undefined') { + tinyMCE.removeMCEControl(tinyMCE.getEditorId(params.field)); + $('#' + params.field).removeAttr('style'); + } +// else if (tinyMCE.activeEditor) { +// tinyMCE.triggerSave(); +// tinyMCE.activeEditor.remove(); +// } +}; + +Drupal.wysiwyg.editor.instance.tinymce = { + addPlugin: function(plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + tinyMCE.addPlugin(plugin, { + + // Register an editor command for this plugin, invoked by the plugin's button. + execCommand: function(editor_id, element, command, user_interface, value) { + switch (command) { + case plugin: + if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') { + var ed = tinyMCE.getInstanceById(editor_id); + var data = { format: 'html', node: ed.getFocusElement(), content: ed.getFocusElement() }; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.formTargetElementId); + return true; + } + } + // Pass to next handler in chain. + return false; + }, + + // Register the plugin button. + getControlHTML: function(control_name) { + switch (control_name) { + case plugin: + return tinyMCE.getButtonHTML(control_name, settings.iconTitle, settings.icon, plugin); + } + return ''; + }, + + // Load custom CSS for editor contents on startup. + initInstance: function(ed) { + if (settings.css) { + tinyMCE.importCSS(ed.getDoc(), settings.css); + } + }, + + cleanup: function(type, content) { + switch (type) { + case 'insert_to_editor': + // Attach: Replace plain text with HTML representations. + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, tinyMCE.selectedInstance.editorId); + content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(content); + } + break; + + case 'get_from_editor': + // Detach: Replace HTML representations with plain text. + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, tinyMCE.selectedInstance.editorId); + } + break; + } + // Pass through to next handler in chain + return content; + }, + + // isNode: Return whether the plugin button should be enabled for the + // current selection. + handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) { + if (node === null) { + return; + } + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + if (Drupal.wysiwyg.plugins[plugin].isNode(node)) { + tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonSelected'); + return true; + } + } + tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonNormal'); + return true; + }, + + /** + * Return information about the plugin as a name/value array. + */ + getInfo: function() { + return { + longname: settings.title + }; + } + }); + }, + + openDialog: function(dialog, params) { + var editor = tinyMCE.getInstanceById(this.field); + tinyMCE.openWindow({ + file: dialog.url + '/' + this.field, + width: dialog.width, + height: dialog.height, + inline: 1 + }, params); + }, + + closeDialog: function(dialog) { + var editor = tinyMCE.getInstanceById(this.field); + tinyMCEPopup.close(); + }, + + prepareContent: function(content) { + // Certain content elements need to have additional DOM properties applied + // to prevent this editor from highlighting an internal button in addition + // to the button of a Drupal plugin. + var specialProperties = { + img: { 'name': 'mce_drupal' } + }; + var $content = $('' + content + ''); // No .outerHTML() in jQuery :( + jQuery.each(specialProperties, function(element, properties) { + $content.find(element).each(function() { + for (var property in properties) { + if (property == 'class') { + $(this).addClass(properties[property]); + } + else { + $(this).attr(property, properties[property]); + } + } + }); + }); + return $content.html(); + }, + + insert: function(content) { + content = this.prepareContent(content); + var editor = tinyMCE.getInstanceById(this.field); + editor.execCommand('mceInsertContent', false, content); + editor.repaint(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-3.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-3.js new file mode 100644 index 00000000..83bae13a --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/tinymce-3.js @@ -0,0 +1,256 @@ +(function($) { + +/** + * Initialize editor instances. + * + * @todo Is the following note still valid for 3.x? + * This function needs to be called before the page is fully loaded, as + * calling tinyMCE.init() after the page is loaded breaks IE6. + * + * @param editorSettings + * An object containing editor settings for each input format. + */ +Drupal.wysiwyg.editor.init.tinymce = function(settings) { + // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. + var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document); + tinyMCE.onAddEditor.add(function (mgr, ed) { + if (ed.id == 'mce_fullscreen') { + $drupalToolbar.hide(); + } + }); + tinyMCE.onRemoveEditor.add(function (mgr, ed) { + if (ed.id == 'mce_fullscreen') { + $drupalToolbar.show(); + } + }); + + // Initialize editor configurations. + for (var format in settings) { + if (Drupal.settings.wysiwyg.plugins[format]) { + // Load native external plugins. + // Array syntax required; 'native' is a predefined token in JavaScript. + for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) { + tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]); + } + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) { + Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); + } + } + } +}; + +/** + * Attach this editor to a target element. + * + * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook. + */ +Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { + // Configure editor settings for this input format. + var ed = new tinymce.Editor(params.field, settings); + // Reset active instance id on any event. + ed.onEvent.add(function(ed, e) { + Drupal.wysiwyg.activeId = ed.id; + }); + // Indicate that the DOM has been loaded (in case of Ajax). + tinymce.dom.Event.domLoaded = true; + // Make toolbar buttons wrappable (required for IE). + ed.onPostRender.add(function (ed) { + var $toolbar = $(''); + $('#' + ed.editorContainer + ' table.mceToolbar > tbody > tr > td').each(function () { + $('').addClass(this.className).append($(this).children()).appendTo($toolbar); + }); + $('#' + ed.editorContainer + ' table.mceLayout td.mceToolbar').append($toolbar); + $('#' + ed.editorContainer + ' table.mceToolbar').remove(); + }); + + // Remove TinyMCE's internal mceItem class, which was incorrectly added to + // submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class + // for placeholder elements. If preemptively set, the class prevents (native) + // editor plugins from gaining an active state, so we have to manually remove + // it prior to attaching the editor. This is done on the client-side instead + // of the server-side, as Wysiwyg has no way to figure out where content is + // stored, and the class only affects editing. + $field = $('#' + params.field); + $field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2')); + + // Attach editor. + ed.render(); +}; + +/** + * Detach a single or all editors. + * + * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. + */ +Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) { + if (typeof params != 'undefined') { + var instance = tinyMCE.get(params.field); + if (instance) { + instance.save(); + if (trigger != 'serialize') { + instance.remove(); + } + } + } + else { + // Save contents of all editors back into textareas. + tinyMCE.triggerSave(); + if (trigger != 'serialize') { + // Remove all editor instances. + for (var instance in tinyMCE.editors) { + tinyMCE.editors[instance].remove(); + } + } + } +}; + +Drupal.wysiwyg.editor.instance.tinymce = { + addPlugin: function(plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + tinymce.create('tinymce.plugins.' + plugin, { + /** + * Initialize the plugin, executed after the plugin has been created. + * + * @param ed + * The tinymce.Editor instance the plugin is initialized in. + * @param url + * The absolute URL of the plugin location. + */ + init: function(ed, url) { + // Register an editor command for this plugin, invoked by the plugin's button. + ed.addCommand(plugin, function() { + if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') { + var data = { format: 'html', node: ed.selection.getNode(), content: ed.selection.getContent() }; + // TinyMCE creates a completely new instance for fullscreen mode. + var instanceId = ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId); + } + }); + + // Register the plugin button. + ed.addButton(plugin, { + title : settings.iconTitle, + cmd : plugin, + image : settings.icon + }); + + // Load custom CSS for editor contents on startup. + ed.onInit.add(function() { + if (settings.css) { + ed.dom.loadCSS(settings.css); + } + }); + + // Attach: Replace plain text with HTML representations. + ed.onBeforeSetContent.add(function(ed, data) { + var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id); + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, editorId); + data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content); + } + }); + + // Detach: Replace HTML representations with plain text. + ed.onGetContent.add(function(ed, data) { + var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id); + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, editorId); + } + }); + + // isNode: Return whether the plugin button should be enabled for the + // current selection. + ed.onNodeChange.add(function(ed, command, node) { + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node)); + } + }); + }, + + /** + * Return information about the plugin as a name/value array. + */ + getInfo: function() { + return { + longname: settings.title + }; + } + }); + + // Register plugin. + tinymce.PluginManager.add(plugin, tinymce.plugins[plugin]); + }, + + openDialog: function(dialog, params) { + var instanceId = this.getInstanceId(); + var editor = tinyMCE.get(instanceId); + editor.windowManager.open({ + file: dialog.url + '/' + instanceId, + width: dialog.width, + height: dialog.height, + inline: 1 + }, params); + }, + + closeDialog: function(dialog) { + var editor = tinyMCE.get(this.getInstanceId()); + editor.windowManager.close(dialog); + }, + + prepareContent: function(content) { + // Certain content elements need to have additional DOM properties applied + // to prevent this editor from highlighting an internal button in addition + // to the button of a Drupal plugin. + var specialProperties = { + img: { 'class': 'mceItem' } + }; + var $content = $('' + content + ''); // No .outerHTML() in jQuery :( + // Find all placeholder/replacement content of Drupal plugins. + $content.find('.drupal-content').each(function() { + // Recursively process DOM elements below this element to apply special + // properties. + var $drupalContent = $(this); + $.each(specialProperties, function(element, properties) { + $drupalContent.find(element).andSelf().each(function() { + for (var property in properties) { + if (property == 'class') { + $(this).addClass(properties[property]); + } + else { + $(this).attr(property, properties[property]); + } + } + }); + }); + }); + return $content.html(); + }, + + insert: function(content) { + content = this.prepareContent(content); + tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceInsertContent', false, content); + }, + + setContent: function (content) { + content = this.prepareContent(content); + tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceSetContent', false, content); + }, + + getContent: function () { + return tinyMCE.get(this.getInstanceId()).getContent(); + }, + + isFullscreen: function() { + // TinyMCE creates a completely new instance for fullscreen mode. + return tinyMCE.activeEditor.id == 'mce_fullscreen' && tinyMCE.activeEditor.getParam('fullscreen_editor_id') == this.field; + }, + + getInstanceId: function () { + return this.isFullscreen() ? 'mce_fullscreen' : this.field; + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-56.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-56.js new file mode 100644 index 00000000..3fc2fe57 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-56.js @@ -0,0 +1,155 @@ + +var wysiwygWhizzywig = { currentField: null, fields: {} }; +var buttonPath = null; + +/** + * Override Whizzywig's document.write() function. + * + * Whizzywig uses document.write() by default, which leads to a blank page when + * invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a + * shorthand w() substitute function that we can override to redirect the output + * into the global wysiwygWhizzywig variable. + * + * @see o() + */ +var w = function (string) { + if (string) { + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string; + } + return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]; +}; + +/** + * Override Whizzywig's document.getElementById() function. + * + * Since we redirect the output of w() into a temporary string upon attaching + * an editor, we also have to override the o() shorthand substitute function + * for document.getElementById() to search in the document or our container. + * This override function also inserts the editor instance when Whizzywig + * tries to access its IFRAME, so it has access to the full/regular window + * object. + * + * @see w() + */ +var o = function (id) { + // Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME, + // so we need to insert the editor into the DOM. + if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) { + jQuery('#' + wysiwygWhizzywig.currentField).after(''); + // Iframe's .contentWindow becomes null in Webkit if inserted via .after(). + jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w()); + // Prevent subsequent invocations from inserting the editor multiple times. + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = ''; + } + // If id exists in the regular window.document, return it. + if (jQuery('#' + id).size()) { + return jQuery('#' + id).get(0); + } + // Otherwise return id from our container. + return jQuery('#' + id, w()).get(0); +}; + +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { + // Previous versions used per-button images found in this location, + // now it is only used for custom buttons. + if (settings.buttonPath) { + window.buttonPath = settings.buttonPath; + } + // Assign the toolbar image path used for native buttons, if available. + if (settings.toolbarImagePath) { + btn._f = settings.toolbarImagePath; + } + // Fall back to text labels for all buttons. + else { + window.buttonPath = 'textbuttons'; + } + // Create Whizzywig container. + wysiwygWhizzywig.currentField = params.field; + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = ''; + // Whizzywig needs to have the width set 'inline'. + $field = $('#' + params.field); + var originalValues = Drupal.wysiwyg.instances[params.field]; + originalValues.originalStyle = $field.attr('style'); + $field.css('width', $field.width() + 'px'); + + // Attach editor. + makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); + // Whizzywig fails to detect and set initial textarea contents. + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { + var detach = function (index) { + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; + + // Save contents of editor back into textarea. + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } + // Remove editor instance. + $('#' + id + '-whizzywig').remove(); + whizzies.splice(index, 1); + + // Restore original textarea styling. + $field.removeAttr('style').attr('style', instance.originalStyle); + }; + + if (typeof params != 'undefined') { + for (var i = 0; i < whizzies.length; i++) { + if (whizzies[i] == params.field) { + detach(i); + break; + } + } + } + else { + while (whizzies.length > 0) { + detach(0); + } + } +}; + +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $('#' + this.field).val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Whizzywig's tidyH() expects a document node. Clone the editing iframe's + // document so tidyH() won't mess with it if this gets called while editing. + var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument; + // Whizzywig shows the original textarea in source mode so update the body. + if ($field.css('display') == 'block') { + clone.body.innerHTML = $('#' + this.field).val(); + } + return tidyH(clone); + } +}; +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-60.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-60.js new file mode 100644 index 00000000..bbc6e649 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig-60.js @@ -0,0 +1,107 @@ + +var buttonPath = null; + +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { + // Previous versions used per-button images found in this location, + // now it is only used for custom buttons. + if (settings.buttonPath) { + window.buttonPath = settings.buttonPath; + } + // Assign the toolbar image path used for native buttons, if available. + if (settings.toolbarImagePath) { + btn._f = settings.toolbarImagePath; + } + // Fall back to text labels for all buttons. + else { + window.buttonPath = 'textbuttons'; + } + // Whizzywig needs to have the width set 'inline'. + $field = $('#' + params.field); + var originalValues = Drupal.wysiwyg.instances[params.field]; + originalValues.originalStyle = $field.attr('style'); + $field.css('width', $field.width() + 'px'); + + // Attach editor. + makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); + // Whizzywig fails to detect and set initial textarea contents. + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { + var detach = function (index) { + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; + + // Save contents of editor back into textarea. + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } + // Move original textarea back to its previous location. + var $container = $('#CONTAINER' + id); + $field.insertBefore($container); + // Remove editor instance. + $container.remove(); + whizzies.splice(index, 1); + + // Restore original textarea styling. + $field.removeAttr('style').attr('style', instance.originalStyle); + } + + if (typeof params != 'undefined') { + for (var i = 0; i < whizzies.length; i++) { + if (whizzies[i] == params.field) { + detach(i); + break; + } + } + } + else { + while (whizzies.length > 0) { + detach(0); + } + } +}; + +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $('#' + this.field).val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Whizzywig's tidyH() expects a document node. Clone the editing iframe's + // document so tidyH() won't mess with it if this gets called while editing. + var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument; + // Whizzywig shows the original textarea in source mode so update the body. + if ($field.css('display') == 'block') { + clone.body.innerHTML = $('#' + this.field).val(); + } + return tidyH(clone); + } +}; +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig.js new file mode 100644 index 00000000..e89ac5f0 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/whizzywig.js @@ -0,0 +1,154 @@ + +var wysiwygWhizzywig = { currentField: null, fields: {} }; +var buttonPath = null; + +/** + * Override Whizzywig's document.write() function. + * + * Whizzywig uses document.write() by default, which leads to a blank page when + * invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a + * shorthand w() substitute function that we can override to redirect the output + * into the global wysiwygWhizzywig variable. + * + * @see o() + */ +var w = function (string) { + if (string) { + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string; + } + return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]; +}; + +/** + * Override Whizzywig's document.getElementById() function. + * + * Since we redirect the output of w() into a temporary string upon attaching + * an editor, we also have to override the o() shorthand substitute function + * for document.getElementById() to search in the document or our container. + * This override function also inserts the editor instance when Whizzywig + * tries to access its IFRAME, so it has access to the full/regular window + * object. + * + * @see w() + */ +var o = function (id) { + // Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME, + // so we need to insert the editor into the DOM. + if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) { + jQuery('#' + wysiwygWhizzywig.currentField).after(''); + // Iframe's .contentWindow becomes null in Webkit if inserted via .after(). + jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w()); + // Prevent subsequent invocations from inserting the editor multiple times. + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = ''; + } + // If id exists in the regular window.document, return it. + if (jQuery('#' + id).size()) { + return jQuery('#' + id).get(0); + } + // Otherwise return id from our container. + return jQuery('#' + id, w()).get(0); +}; + +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { + // Assign button images path, if available. + if (settings.buttonPath) { + window.buttonPath = settings.buttonPath; + } + // Create Whizzywig container. + wysiwygWhizzywig.currentField = params.field; + wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = ''; + // Whizzywig needs to have the width set 'inline'. + $field = $('#' + params.field); + var originalValues = Drupal.wysiwyg.instances[params.field]; + originalValues.originalStyle = $field.attr('style'); + $field.css('width', $field.width() + 'px'); + + // Attach editor. + makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); + // Whizzywig fails to detect and set initial textarea contents. + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { + var detach = function (index) { + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; + + // Save contents of editor back into textarea. + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } + // Remove editor instance. + $('#' + id + '-whizzywig').remove(); + whizzies.splice(index, 1); + + // Restore original textarea styling. + $field.removeAttr('style').attr('style', instance.originalStyle); + }; + + if (typeof params != 'undefined') { + for (var i = 0; i < whizzies.length; i++) { + if (whizzies[i] == params.field) { + detach(i); + break; + } + } + } + else { + while (whizzies.length > 0) { + detach(0); + } + } +}; + +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + var $field = $('#' + this.field); + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $field.val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + var $field = $('#' + this.field), + // Whizzywig shows the original textarea in source mode. + content = ($field.css('display') == 'block' ? + $field.val() : $('#whizzy' + this.field).contents().find('body').html() + ); + + content = tidyH(content); + // Whizzywig's get_xhtml() addon, if defined, expects a DOM node. + if ($.isFunction(window.get_xhtml)) { + var pre = document.createElement('pre'); + pre.innerHTML = content; + content = get_xhtml(pre); + } + return content.replace(location.href + '#', '#'); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/wymeditor.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/wymeditor.js new file mode 100644 index 00000000..4989dc60 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/wymeditor.js @@ -0,0 +1,74 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.wymeditor = function (context, params, settings) { + // Prepend basePath to wymPath. + settings.wymPath = settings.basePath + settings.wymPath; + // Update activeId on focus. + settings.postInit = function (instance) { + $(instance._doc).focus(function () { + Drupal.wysiwyg.activeId = params.field; + }); + }; + // Attach editor. + $('#' + params.field).wymeditor(settings); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.wymeditor = function (context, params, trigger) { + if (typeof params != 'undefined') { + var $field = $('#' + params.field); + var index = $field.data(WYMeditor.WYM_INDEX); + if (typeof index != 'undefined') { + var instance = WYMeditor.INSTANCES[index]; + instance.update(); + if (trigger != 'serialize') { + $(instance._box).remove(); + $(instance._element).show(); + delete instance; + } + } + if (trigger != 'serialize') { + $field.show(); + } + } + else { + jQuery.each(WYMeditor.INSTANCES, function () { + this.update(); + if (trigger != 'serialize') { + $(this._box).remove(); + $(this._element).show(); + delete this; + } + }); + } +}; + +Drupal.wysiwyg.editor.instance.wymeditor = { + insert: function (content) { + this.getInstance().insert(content); + }, + + setContent: function (content) { + this.getInstance().html(content); + }, + + getContent: function () { + return this.getInstance().xhtml(); + }, + + getInstance: function () { + var $field = $('#' + this.field); + var index = $field.data(WYMeditor.WYM_INDEX); + if (typeof index != 'undefined') { + return WYMeditor.INSTANCES[index]; + } + return null; + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/js/yui.js b/sites/all/modules/contrib/editor/wysiwyg/editors/js/yui.js new file mode 100644 index 00000000..3f4e7c63 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/js/yui.js @@ -0,0 +1,145 @@ +(function($) { + +/** + * Attach this editor to a target element. + * + * Since buttons must be added before the editor is rendered, we add plugins + * buttons on attach event rather than in init. + */ +Drupal.wysiwyg.editor.attach.yui = function(context, params, settings) { + // Apply theme. + $('#' + params.field).parent().addClass('yui-skin-' + settings.theme); + + // Load plugins stylesheet. + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + settings.extracss += settings.extracss+' @import "'+Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin].css+'"; '; + } + + // Attach editor. + var editor = new YAHOO.widget.Editor(params.field, settings); + + editor.on('toolbarLoaded', function() { + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + Drupal.wysiwyg.instances[params.field].addPlugin(plugin, Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); + } + }); + + // Allow plugins to act on setEditorHTML. + var oldSetEditorHTML = editor.setEditorHTML; + editor.setEditorHTML = function (content) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin]; + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, params.field); + content = Drupal.wysiwyg.instances[params.field].prepareContent(content); + } + } + oldSetEditorHTML.call(this, content); + }; + + // Allow plugins to act on getEditorHTML. + var oldGetEditorHTML = editor.getEditorHTML; + editor.getEditorHTML = function () { + var content = oldGetEditorHTML.call(this); + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin]; + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, params.field); + } + } + return content; + } + + // Reload the editor contents to give Drupal plugins a chance to act. + editor.on('editorContentLoaded', function (e) { + e.target.setEditorHTML(oldGetEditorHTML.call(e.target)); + }); + + editor.on('afterNodeChange', function (e) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + if (Drupal.wysiwyg.plugins[plugin].isNode(e.target._getSelectedElement())) { + this.toolbar.selectButton(plugin); + } + } + } + }); + + editor.render(); +}; + +/** + * Detach a single or all editors. + * + * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. + */ +Drupal.wysiwyg.editor.detach.yui = function (context, params, trigger) { + var method = (trigger && trigger == 'serialize') ? 'saveHTML' : 'destroy'; + if (typeof params != 'undefined') { + var instance = YAHOO.widget.EditorInfo._instances[params.field]; + if (instance) { + instance[method](); + if (method == 'destroy') { + delete YAHOO.widget.EditorInfo._instances[params.field]; + } + } + } + else { + for (var e in YAHOO.widget.EditorInfo._instances) { + // Save contents of all editors back into textareas. + var instance = YAHOO.widget.EditorInfo._instances[e]; + instance[method](); + if (method == 'destroy') { + delete YAHOO.widget.EditorInfo._instances[e]; + } + } + } +}; + +/** + * Instance methods for YUI Editor. + */ +Drupal.wysiwyg.editor.instance.yui = { + addPlugin: function (plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + var editor = YAHOO.widget.EditorInfo.getEditorById(this.field); + var button = editor.toolbar.getButtonByValue(plugin); + $(button._button).parent().css('background', 'transparent url(' + settings.icon + ') no-repeat center'); + // 'this' will reference the toolbar while inside the event handler. + var instanceId = this.field; + editor.toolbar.on(plugin + 'Click', function (e) { + var selectedElement = editor._getSelectedElement(); + // @todo Using .html() will cause XTHML vs HTML conflicts. + var data = { + format: 'html', + node: selectedElement, + content: $(selectedElement).html() + }; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId); + }); + }, + + prepareContent: function (content) { + var editor = YAHOO.widget.EditorInfo.getEditorById(this.field); + content = editor.cleanHTML(content); + return content; + }, + + insert: function (content) { + YAHOO.widget.EditorInfo.getEditorById(this.field).cmd_inserthtml(content); + }, + + setContent: function (content) { + YAHOO.widget.EditorInfo.getEditorById(this.field).setEditorHTML(content); + }, + + getContent: function () { + var instance = YAHOO.widget.EditorInfo.getEditorById(this.field); + return instance.cleanHTML(instance.getEditorHTML(content)); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/jwysiwyg.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/jwysiwyg.inc new file mode 100644 index 00000000..fa65b741 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/jwysiwyg.inc @@ -0,0 +1,62 @@ + 'jWYSIWYG', + 'vendor url' => 'http://code.google.com/p/jwysiwyg/', + 'download url' => 'http://code.google.com/p/jwysiwyg/downloads/list', + 'libraries' => array( + '' => array( + 'title' => 'Source', + 'files' => array('jquery.wysiwyg.js'), + ), + 'pack' => array( + 'title' => 'Packed', + 'files' => array('jquery.wysiwyg.pack.js'), + ), + ), + 'version callback' => 'wysiwyg_jwysiwyg_version', + // @todo Wrong property; add separate properties for editor requisites. + 'css path' => wysiwyg_get_path('jwysiwyg'), + 'versions' => array( + '0.5' => array( + 'js files' => array('jwysiwyg.js'), + 'css files' => array('jquery.wysiwyg.css'), + ), + ), + ); + return $editor; +} + +/** + * Detect editor version. + * + * @param $editor + * An array containing editor properties as returned from hook_editor(). + * + * @return + * The installed editor version. + */ +function wysiwyg_jwysiwyg_version($editor) { + $script = $editor['library path'] . '/jquery.wysiwyg.js'; + if (!file_exists($script)) { + return; + } + $script = fopen($script, 'r'); + fgets($script); + $line = fgets($script); + if (preg_match('@([0-9\.]+)$@', $line, $version)) { + fclose($script); + return $version[1]; + } + fclose($script); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/markitup.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/markitup.inc new file mode 100644 index 00000000..57a37e83 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/markitup.inc @@ -0,0 +1,189 @@ + 'markItUp', + 'vendor url' => 'http://markitup.jaysalvat.com', + 'download url' => 'http://markitup.jaysalvat.com/downloads', + 'library path' => wysiwyg_get_path('markitup'), + 'libraries' => array( + '' => array( + 'title' => 'Source', + 'files' => array('markitup/jquery.markitup.js'), + ), + 'pack' => array( + 'title' => 'Packed', + 'files' => array('markitup/jquery.markitup.pack.js'), + ), + ), + 'version callback' => 'wysiwyg_markitup_version', + 'themes callback' => 'wysiwyg_markitup_themes', + 'settings callback' => 'wysiwyg_markitup_settings', + 'plugin callback' => 'wysiwyg_markitup_plugins', + 'versions' => array( + '1.1.5' => array( + 'js files' => array('markitup.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_markitup_version($editor) { + // Changelog was in markitup/markitup/readme.txt <= 1.1.5. + $changelog = $editor['library path'] . '/markitup/readme.txt'; + if (!file_exists($changelog)) { + // Changelog was moved up to markitup/CHANGELOG.md after 1.1.5. + $changelog = $editor['library path'] . '/CHANGELOG.md'; + if (!file_exists($changelog)) { + return; + } + } + $changelog = fopen($changelog, 'r'); + $line = fgets($changelog); + if (preg_match('@([0-9\.]+)@', $line, $version)) { + fclose($changelog); + return $version[1]; + } + fclose($changelog); +} + +/** + * 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_markitup_themes($editor, $profile) { + return array('simple', 'markitup'); +} + +/** + * 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_markitup_settings($editor, $config, $theme) { + drupal_add_css($editor['library path'] . '/markitup/skins/' . $theme . '/style.css', array( + // Specify an alternate basename; otherwise, style.css would override a + // commonly used style.css file of the theme. + 'basename' => 'markitup.' . $theme . '.style.css', + 'group' => CSS_THEME, + )); + + $settings = array( + 'root' => base_path() . $editor['library path'] . '/markitup/', + 'nameSpace' => $theme, + 'markupSet' => array(), + ); + + // Add configured buttons or all available. + $default_buttons = array( + 'bold' => array( + 'name' => t('Bold'), + 'className' => 'markitup-bold', + 'key' => 'B', + 'openWith' => '(!(|!|)!)', + 'closeWith' => '(!(|!|)!)', + ), + 'italic' => array( + 'name' => t('Italic'), + 'className' => 'markitup-italic', + 'key' => 'I', + 'openWith' => '(!(|!|)!)', + 'closeWith' => '(!(|!|)!)', + ), + 'stroke' => array( + 'name' => t('Strike-through'), + 'className' => 'markitup-stroke', + 'key' => 'S', + 'openWith' => '', + 'closeWith' => '', + ), + 'image' => array( + 'name' => t('Image'), + 'className' => 'markitup-image', + 'key' => 'P', + 'replaceWith' => '', + ), + 'link' => array( + 'name' => t('Link'), + 'className' => 'markitup-link', + 'key' => 'K', + 'openWith' => '', + 'closeWith' => '', + 'placeHolder' => 'Your text to link...', + ), + // @todo + // 'cleanup' => array('name' => t('Clean-up'), 'className' => 'markitup-cleanup', 'replaceWith' => 'function(markitup) { return markitup.selection.replace(/<(.*?)>/g, "") }'), + 'preview' => array( + 'name' => t('Preview'), + 'className' => 'markitup-preview', + 'call' => 'preview', + ), + ); + $settings['markupSet'] = array(); + if (!empty($config['buttons'])) { + foreach ($config['buttons'] as $plugin) { + foreach ($plugin as $button => $enabled) { + if (isset($default_buttons[$button])) { + $settings['markupSet'][$button] = $default_buttons[$button]; + } + } + } + } + + return $settings; +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_markitup_plugins($editor) { + return array( + 'default' => array( + 'buttons' => array( + 'bold' => t('Bold'), 'italic' => t('Italic'), + 'stroke' => t('Strike-through'), + 'link' => t('Link'), + 'image' => t('Image'), + // 'cleanup' => t('Clean-up'), + 'preview' => t('Preview'), + ), + 'internal' => TRUE, + ), + ); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/nicedit.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/nicedit.inc new file mode 100644 index 00000000..6acc800d --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/nicedit.inc @@ -0,0 +1,119 @@ + 'NicEdit', + 'vendor url' => 'http://nicedit.com', + 'download url' => 'http://nicedit.com/download.php', + 'libraries' => array( + '' => array( + 'title' => 'Source', + 'files' => array('nicEdit.js'), + ), + ), + 'version callback' => 'wysiwyg_nicedit_version', + 'settings callback' => 'wysiwyg_nicedit_settings', + 'plugin callback' => 'wysiwyg_nicedit_plugins', + 'versions' => array( + '0.9' => array( + 'js files' => array('nicedit.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_nicedit_version($editor) { + // @see http://nicedit.com/forums/viewtopic.php?t=425 + return '0.9'; +} + +/** + * 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_nicedit_settings($editor, $config, $theme) { + $settings = array( + 'iconsPath' => base_path() . $editor['library path'] . '/nicEditorIcons.gif', + ); + + // Add configured buttons or all available. + $settings['buttonList'] = array(); + if (!empty($config['buttons'])) { + $buttons = array(); + foreach ($config['buttons'] as $plugin) { + $buttons = array_merge($buttons, $plugin); + } + $settings['buttonList'] = array_keys($buttons); + } + + // Add editor content stylesheet. + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css'; + if (file_exists($css)) { + $settings['externalCSS'] = base_path() . $css; + } + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + } + } + + return $settings; +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_nicedit_plugins($editor) { + return array( + 'default' => array( + 'buttons' => array( + 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'), + 'strikethrough' => t('Strike-through'), + 'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'), + 'ul' => t('Bullet list'), 'ol' => t('Numbered list'), + 'outdent' => t('Outdent'), 'indent' => t('Indent'), + 'image' => t('Image'), + 'forecolor' => t('Forecolor'), 'bgcolor' => t('Backcolor'), + 'superscript' => t('Superscript'), 'subscript' => t('Subscript'), + 'hr' => t('Horizontal rule'), + // @todo New challenge: Optional internal plugins packaged into editor + // library. + 'link' => t('Link'), 'unlink' => t('Unlink'), + 'fontFormat' => t('HTML block format'), 'fontFamily' => t('Font'), 'fontSize' => t('Font size'), + 'xhtml' => t('Source code'), + ), + 'internal' => TRUE, + ), + ); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/openwysiwyg.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/openwysiwyg.inc new file mode 100644 index 00000000..b3ad84dd --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/openwysiwyg.inc @@ -0,0 +1,173 @@ + 'openWYSIWYG', + 'vendor url' => 'http://www.openwebware.com', + 'download url' => 'http://www.openwebware.com/download.shtml', + 'library path' => wysiwyg_get_path('openwysiwyg') . '/scripts', + 'libraries' => array( + 'src' => array( + 'title' => 'Source', + 'files' => array('wysiwyg.js'), + ), + ), + 'version callback' => 'wysiwyg_openwysiwyg_version', + 'themes callback' => 'wysiwyg_openwysiwyg_themes', + 'settings callback' => 'wysiwyg_openwysiwyg_settings', + 'plugin callback' => 'wysiwyg_openwysiwyg_plugins', + 'versions' => array( + '1.4.7' => array( + 'js files' => array('openwysiwyg.js'), + 'css files' => array('openwysiwyg.css'), + ), + ), + ); + return $editor; +} + +/** + * Detect editor version. + * + * @param $editor + * An array containing editor properties as returned from hook_editor(). + * + * @return + * The installed editor version. + */ +function wysiwyg_openwysiwyg_version($editor) { + // 'library path' has '/scripts' appended already. + $changelog = $editor['editor path'] . '/changelog'; + if (!file_exists($changelog)) { + return; + } + $changelog = fopen($changelog, 'r'); + $line = fgets($changelog, 20); + if (preg_match('@v([\d\.]+)@', $line, $version)) { + fclose($changelog); + return $version[1]; + } + fclose($changelog); +} + +/** + * 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_openwysiwyg_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_openwysiwyg_settings($editor, $config, $theme) { + $settings = array( + 'path' => base_path() . $editor['editor path'] . '/', + 'Width' => '100%', + ); + + if (isset($config['path_loc']) && $config['path_loc'] == 'none') { + $settings['StatusBarEnabled'] = FALSE; + } + + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $settings['CSSFile'] = reset(wysiwyg_get_css()); + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['CSSFile'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + } + } + + $settings['Toolbar'] = array(); + if (!empty($config['buttons'])) { + $plugins = wysiwyg_get_plugins($editor['name']); + foreach ($config['buttons'] as $plugin => $buttons) { + foreach ($buttons as $button => $enabled) { + foreach (array('buttons', 'extensions') as $type) { + // Skip unavailable plugins. + if (!isset($plugins[$plugin][$type][$button])) { + continue; + } + // Add buttons. + if ($type == 'buttons') { + $settings['Toolbar'][0][] = $button; + } + } + } + } + } + + // @todo +// if (isset($config['block_formats'])) { +// $settings['DropDowns']['headings']['elements'] = explode(',', $config['block_formats']); +// } + + return $settings; +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_openwysiwyg_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'), + 'createlink' => t('Link'), + 'insertimage' => t('Image'), + 'cleanup' => t('Clean-up'), + 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'), + 'superscript' => t('Sup'), 'subscript' => t('Sub'), + 'blockquote' => t('Blockquote'), 'viewSource' => t('Source code'), + 'hr' => t('Horizontal rule'), + 'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'), + 'visualaid' => t('Visual aid'), + 'removeformat' => t('Remove format'), + 'charmap' => t('Character map'), + 'headings' => t('HTML block format'), 'font' => t('Font'), 'fontsize' => t('Font size'), + 'maximize' => t('Fullscreen'), + 'preview' => t('Preview'), + 'print' => t('Print'), + 'inserttable' => t('Table'), + 'help' => t('Help'), + ), + 'internal' => TRUE, + ), + ); + return $plugins; +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/tinymce.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/tinymce.inc new file mode 100644 index 00000000..1dbd594c --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/tinymce.inc @@ -0,0 +1,653 @@ +_alter() to add/inject optional libraries like gzip. + */ +function wysiwyg_tinymce_editor() { + $editor['tinymce'] = array( + 'title' => 'TinyMCE', + 'vendor url' => 'http://tinymce.moxiecode.com', + 'download url' => 'http://tinymce.moxiecode.com/download.php', + 'library path' => wysiwyg_get_path('tinymce') . '/jscripts/tiny_mce', + 'libraries' => array( + '' => array( + 'title' => 'Minified', + 'files' => array('tiny_mce.js'), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array('tiny_mce_src.js'), + ), + ), + 'version callback' => 'wysiwyg_tinymce_version', + 'themes callback' => 'wysiwyg_tinymce_themes', + 'init callback' => 'wysiwyg_tinymce_init', + 'settings callback' => 'wysiwyg_tinymce_settings', + 'plugin callback' => 'wysiwyg_tinymce_plugins', + 'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings', + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + 'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings', + 'versions' => array( + '2.1' => array( + 'js files' => array('tinymce-2.js'), + 'css files' => array('tinymce-2.css'), + 'download url' => 'http://sourceforge.net/project/showfiles.php?group_id=103281&package_id=111430&release_id=557383', + ), + // @todo Starting from 3.3, tiny_mce.js may support JS aggregation. + '3.1' => array( + 'js files' => array('tinymce-3.js'), + 'css files' => array('tinymce-3.css'), + 'libraries' => array( + '' => array( + 'title' => 'Minified', + 'files' => array( + 'tiny_mce.js' => array('preprocess' => FALSE), + ), + ), + 'jquery' => array( + 'title' => 'jQuery', + 'files' => array('tiny_mce_jquery.js'), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array('tiny_mce_src.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_tinymce_version($editor) { + $script = $editor['library path'] . '/tiny_mce.js'; + if (!file_exists($script)) { + return; + } + $script = fopen($script, 'r'); + // Version is contained in the first 200 chars. + $line = fgets($script, 200); + fclose($script); + // 2.x: this.majorVersion="2";this.minorVersion="1.3" + // 3.x: majorVersion:'3',minorVersion:'2.0.1' + if (preg_match('@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', $line, $version)) { + return $version[1] . '.' . $version[2]; + } +} + +/** + * 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_tinymce_themes($editor, $profile) { + /* + $themes = array(); + $dir = $editor['library path'] . '/themes/'; + if (is_dir($dir) && $dh = opendir($dir)) { + while (($file = readdir($dh)) !== FALSE) { + if (!in_array($file, array('.', '..', 'CVS', '.svn')) && is_dir($dir . $file)) { + $themes[$file] = $file; + } + } + closedir($dh); + asort($themes); + } + return $themes; + */ + return array('advanced', 'simple'); +} + +/** + * Returns an initialization JavaScript for this editor library. + * + * @param array $editor + * The editor library definition. + * @param string $library + * The library variant key from $editor['libraries']. + * @param object $profile + * The (first) wysiwyg editor profile. + * + * @return string + * A string containing inline JavaScript to execute before the editor library + * script is loaded. + */ +function wysiwyg_tinymce_init($editor, $library) { + // TinyMCE unconditionally searches for its library filename in SCRIPT tags on + // on the page upon loading the library in order to determine the base path to + // itself. When JavaScript aggregation is enabled, this search fails and all + // relative constructed paths within TinyMCE are broken. The library has a + // tinyMCE.baseURL property, but it is not publicly documented and thus not + // reliable. The official support forum suggests to solve the issue through + // the global window.tinyMCEPreInit variable also used by various serverside + // compressor scrips available from the official website. + // @see http://www.tinymce.com/forum/viewtopic.php?id=23286 + $settings = drupal_json_encode(array( + 'base' => base_path() . $editor['library path'], + 'suffix' => (strpos($library, 'src') !== FALSE || strpos($library, 'dev') !== FALSE ? '_src' : ''), + 'query' => '', + )); + return <<
TRUE, // @todo Add a setting for this. + 'document_base_url' => base_path(), + 'mode' => 'none', + 'plugins' => array(), + #'theme' => $theme, + 'theme' => 'advanced', + 'skin' => 'o2k7', + 'skin_variant' => "silver", + 'width' => '100%', + // Strict loading mode must be enabled; otherwise TinyMCE would use + // document.write() in IE and Chrome. + 'strict_loading_mode' => TRUE, + // TinyMCE's URL conversion magic breaks Drupal modules that use a special + // syntax for paths. This makes 'relative_urls' obsolete. + 'convert_urls' => FALSE, + // The default entity_encoding ('named') converts too many characters in + // languages (like Greek). Since Drupal supports Unicode, we only convert + // HTML control characters and invisible characters. TinyMCE always converts + // XML default characters '&', '<', '>'. + 'entities' => '160,nbsp,173,shy,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm', + ); + if (isset($config['apply_source_formatting'])) { + $settings['apply_source_formatting'] = $config['apply_source_formatting']; + } + if (isset($config['convert_fonts_to_spans'])) { + $settings['convert_fonts_to_spans'] = $config['convert_fonts_to_spans']; + } + if (isset($config['language'])) { + $settings['language'] = $config['language']; + } + if (isset($config['paste_auto_cleanup_on_paste'])) { + $settings['paste_auto_cleanup_on_paste'] = $config['paste_auto_cleanup_on_paste']; + } + if (isset($config['preformatted'])) { + $settings['preformatted'] = $config['preformatted']; + } + if (isset($config['remove_linebreaks'])) { + $settings['remove_linebreaks'] = $config['remove_linebreaks']; + } + if (isset($config['verify_html'])) { + // TinyMCE performs a type-agnostic comparison on this particular setting. + $settings['verify_html'] = (bool) $config['verify_html']; + } + + if (!empty($config['css_classes'])) { + $settings['theme_advanced_styles'] = implode(';', array_filter(explode("\n", str_replace("\r", '', $config['css_classes'])))); + } + + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $settings['content_css'] = implode(',', wysiwyg_get_css()); + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + } + } + + // Find the enabled buttons and the button row they belong on. + // Also map the plugin metadata for each button. + // @todo What follows is a pain; needs a rewrite. + // $settings['buttons'] are stacked into $settings['theme_advanced_buttons1'] + // later. + $settings['buttons'] = array(); + if (!empty($config['buttons']) && is_array($config['buttons'])) { + // Only array keys in $settings['extensions'] matter; added to + // $settings['plugins'] later. + $settings['extensions'] = array(); + // $settings['extended_valid_elements'] are just stacked, unique'd later, + // and transformed into a comma-separated string in + // wysiwyg_add_editor_settings(). + // @todo Needs a complete plugin API redesign using arrays for + // tag => attributes definitions and array_merge_recursive(). + $settings['extended_valid_elements'] = 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['buttons'][] = $button; + } + // Add external Drupal plugins to the list of extensions. + if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) { + $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1; + } + // Add external plugins to the list of extensions. + elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) { + $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1; + } + // Add internal buttons that also need to be loaded as extension. + elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) { + $settings['extensions'][$plugin] = 1; + } + // Add plain extensions. + elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) { + $settings['extensions'][$plugin] = 1; + } + // Allow plugins to add valid HTML elements. + if (!empty($plugins[$plugin]['extended_valid_elements'])) { + $settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']); + } + // Allow plugins to add or override global configuration settings. + if (!empty($plugins[$plugin]['options'])) { + $settings = array_merge($settings, $plugins[$plugin]['options']); + } + } + } + } + // Clean-up. + $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']); + if ($settings['extensions']) { + $settings['plugins'] = array_keys($settings['extensions']); + } + unset($settings['extensions']); + } + + // Add theme-specific settings. + switch ($theme) { + case 'advanced': + $settings += array( + 'theme_advanced_resize_horizontal' => FALSE, + 'theme_advanced_resizing_use_cookie' => FALSE, + 'theme_advanced_statusbar_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom', + 'theme_advanced_resizing' => isset($config['resizing']) ? $config['resizing'] : 1, + 'theme_advanced_toolbar_location' => isset($config['toolbar_loc']) ? $config['toolbar_loc'] : 'top', + 'theme_advanced_toolbar_align' => isset($config['toolbar_align']) ? $config['toolbar_align'] : 'left', + ); + if (isset($config['block_formats'])) { + $settings['theme_advanced_blockformats'] = $config['block_formats']; + } + if (isset($settings['buttons'])) { + // These rows explicitly need to be set to be empty, otherwise TinyMCE + // loads its default buttons of the advanced theme for each row. + $settings += array( + 'theme_advanced_buttons1' => array(), + 'theme_advanced_buttons2' => array(), + 'theme_advanced_buttons3' => array(), + ); + // @todo Allow to sort/arrange editor buttons. + for ($i = 0; $i < count($settings['buttons']); $i++) { + $settings['theme_advanced_buttons1'][] = $settings['buttons'][$i]; + } + } + break; + } + unset($settings['buttons']); + + // Convert the config values into the form expected by TinyMCE. + $csv_settings = array('plugins', 'extended_valid_elements', 'theme_advanced_buttons1', 'theme_advanced_buttons2', 'theme_advanced_buttons3'); + foreach ($csv_settings as $key) { + if (isset($settings[$key]) && is_array($settings[$key])) { + $settings[$key] = implode(',', $settings[$key]); + } + } + + return $settings; +} + +/** + * Build a JS settings array of native external plugins that need to be loaded separately. + * + * TinyMCE requires that external plugins (i.e. not residing in the editor's + * directory) are loaded (once) upon initializing the editor. + */ +function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + foreach ($plugins as $name => $plugin) { + if (!empty($plugin['load'])) { + // Add path for native external plugins; internal ones are loaded + // automatically. + if (empty($plugin['internal']) && isset($plugin['filename'])) { + $settings[$name] = base_path() . $plugin['path'] . '/' . $plugin['filename']; + } + } + } + return $settings; +} + +/** + * Build a JS settings array for Drupal plugins loaded via the proxy plugin. + */ +function wysiwyg_tinymce_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'], + ); + if (isset($plugin['css file'])) { + $settings[$name]['css'] = base_path() . $plugin['css path'] . '/' . $plugin['css file']; + } + } + return $settings; +} + +/** + * Add or remove leading hiven to/of external plugin names. + * + * TinyMCE requires that external plugins, which should not be loaded from + * its own plugin repository are prefixed with a hiven in the name. + * + * @param string $op + * Operation to perform, 'add' or 'remove' (hiven). + * @param string $name + * A plugin name. + */ +function _wysiwyg_tinymce_plugin_name($op, $name) { + if ($op == 'add') { + if (strpos($name, '-') !== 0) { + return '-' . $name; + } + return $name; + } + elseif ($op == 'remove') { + if (strpos($name, '-') === 0) { + return substr($name, 1); + } + return $name; + } +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_tinymce_plugins($editor) { + $plugins = array( + 'default' => array( + 'path' => $editor['library path'] . '/themes/advanced', + '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'), + 'bullist' => t('Bullet list'), 'numlist' => 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'), + 'cleanup' => t('Clean-up'), + 'formatselect' => t('Block format'), 'styleselect' => t('Styles'), + 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'), + 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'), + 'sup' => t('Superscript'), 'sub' => t('Subscript'), + 'blockquote' => t('Blockquote'), 'code' => t('Source code'), + 'hr' => t('Horizontal rule'), + 'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'), + 'visualaid' => t('Visual aid'), + 'removeformat' => t('Remove format'), + 'charmap' => t('Character map'), + 'help' => t('Help'), + ), + 'internal' => TRUE, + ), + 'advhr' => array( + 'path' => $editor['library path'] . '/plugins/advhr', + 'buttons' => array('advhr' => t('Advanced horizontal rule')), + 'extended_valid_elements' => array('hr[class|width|size|noshade]'), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advhr', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'advimage' => array( + 'path' => $editor['library path'] . '/plugins/advimage', + 'extensions' => array('advimage' => t('Advanced image')), + 'extended_valid_elements' => array('img[src|alt|title|align|width|height|usemap|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name|longdesc]'), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advimage', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'advlink' => array( + 'path' => $editor['library path'] . '/plugins/advlink', + 'extensions' => array('advlink' => t('Advanced link')), + 'extended_valid_elements' => array('a[name|href|target|title|class|onfocus|onblur|onclick|ondlbclick|onmousedown|onmouseup|onmouseover|onmouseout|onkeypress|onkeydown|onkeyup|id|style|rel]'), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlink', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'autosave' => array( + 'path' => $editor['library path'] . '/plugins/autosave', + 'extensions' => array('autosave' => t('Auto save')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autosave', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'contextmenu' => array( + 'path' => $editor['library path'] . '/plugins/contextmenu', + 'extensions' => array('contextmenu' => t('Context menu')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:contextmenu', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'directionality' => array( + 'path' => $editor['library path'] . '/plugins/directionality', + 'buttons' => array('ltr' => t('Left-to-right'), 'rtl' => t('Right-to-left')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:directionality', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'emotions' => array( + 'path' => $editor['library path'] . '/plugins/emotions', + 'buttons' => array('emotions' => t('Emotions')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:emotions', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'fullscreen' => array( + 'path' => $editor['library path'] . '/plugins/fullscreen', + 'buttons' => array('fullscreen' => t('Fullscreen')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:fullscreen', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'inlinepopups' => array( + 'path' => $editor['library path'] . '/plugins/inlinepopups', + 'extensions' => array('inlinepopups' => t('Inline popups')), + 'options' => array( + 'dialog_type' => array('modal'), + ), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:inlinepopups', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'insertdatetime' => array( + 'path' => $editor['library path'] . '/plugins/insertdatetime', + 'buttons' => array('insertdate' => t('Insert date'), 'inserttime' => t('Insert time')), + 'options' => array( + 'plugin_insertdate_dateFormat' => '%Y-%m-%d', + 'plugin_insertdate_timeFormat' => '%H:%M:%S', + ), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:insertdatetime', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'layer' => array( + 'path' => $editor['library path'] . '/plugins/layer', + 'buttons' => array('insertlayer' => t('Insert layer'), 'moveforward' => t('Move forward'), 'movebackward' => t('Move backward'), 'absolute' => t('Absolute')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:layer', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'paste' => array( + 'path' => $editor['library path'] . '/plugins/paste', + 'buttons' => array('pastetext' => t('Paste text'), 'pasteword' => t('Paste from Word'), 'selectall' => t('Select all')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:paste', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'preview' => array( + 'path' => $editor['library path'] . '/plugins/preview', + 'buttons' => array('preview' => t('Preview')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:preview', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'print' => array( + 'path' => $editor['library path'] . '/plugins/print', + 'buttons' => array('print' => t('Print')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:print', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'searchreplace' => array( + 'path' => $editor['library path'] . '/plugins/searchreplace', + 'buttons' => array('search' => t('Search'), 'replace' => t('Replace')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:searchreplace', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'style' => array( + 'path' => $editor['library path'] . '/plugins/style', + 'buttons' => array('styleprops' => t('Advanced CSS styles')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:style', + 'internal' => TRUE, + 'load' => TRUE, + ), + 'table' => array( + 'path' => $editor['library path'] . '/plugins/table', + 'buttons' => array('tablecontrols' => t('Table')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:table', + 'internal' => TRUE, + 'load' => TRUE, + ), + ); + if (version_compare($editor['installed version'], '3', '<')) { + $plugins['flash'] = array( + 'path' => $editor['library path'] . '/plugins/flash', + 'buttons' => array('flash' => t('Flash')), + 'extended_valid_elements' => array('img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|obj|param|embed]'), + 'internal' => TRUE, + 'load' => TRUE, + ); + } + if (version_compare($editor['installed version'], '2.0.6', '>')) { + $plugins['media'] = array( + 'path' => $editor['library path'] . '/plugins/media', + 'buttons' => array('media' => t('Media')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:media', + 'internal' => TRUE, + 'load' => TRUE, + ); + $plugins['xhtmlxtras'] = array( + 'path' => $editor['library path'] . '/plugins/xhtmlxtras', + 'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted'), 'attribs' => t('HTML attributes')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:xhtmlxtras', + 'internal' => TRUE, + 'load' => TRUE, + ); + } + if (version_compare($editor['installed version'], '3', '>')) { + $plugins['bbcode'] = array( + 'path' => $editor['library path'] . '/plugins/bbcode', + 'extensions' => array('bbcode' => t('BBCode')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:bbcode', + 'internal' => TRUE, + 'load' => TRUE, + ); + if (version_compare($editor['installed version'], '3.3', '<')) { + $plugins['safari'] = array( + 'path' => $editor['library path'] . '/plugins/safari', + 'extensions' => array('safari' => t('Safari compatibility')), + 'internal' => TRUE, + 'load' => TRUE, + ); + } + } + if (version_compare($editor['installed version'], '3.2.5', '>=')) { + $plugins['autoresize'] = array( + 'path' => $editor['library path'] . '/plugins/autoresize', + 'extensions' => array('autoresize' => t('Auto resize')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autoresize', + 'internal' => TRUE, + 'load' => TRUE, + ); + } + if (version_compare($editor['installed version'], '3.3', '>=')) { + $plugins['advlist'] = array( + 'path' => $editor['library path'] . '/plugins/advlist', + 'extensions' => array('advlist' => t('Advanced list')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlist', + 'internal' => TRUE, + 'load' => TRUE, + ); + } + if (version_compare($editor['installed version'], '3.2.6', '>=')) { + $plugins['wordcount'] = array( + 'path' => $editor['library path'] . '/plugins/wordcount', + 'extensions' => array('wordcount' => t('Word count')), + 'internal' => TRUE, + 'load' => TRUE, + ); + } + if (version_compare($editor['installed version'], '3.4.1', '>=')) { + $plugins['lists'] = array( + 'path' => $editor['library path'] . 'plugins/lists', + 'extensions' => array('lists' => t('List normalizer')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:lists', + 'internal' => TRUE, + 'load' => TRUE, + 'extended_valid_elements' => array( + 'li[class|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type|value]', + 'ol[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|start|style|title|type]', + 'ul[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type]', + ), + ); + } + return $plugins; +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/whizzywig.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/whizzywig.inc new file mode 100644 index 00000000..acfc7de3 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/whizzywig.inc @@ -0,0 +1,147 @@ + 'Whizzywig', + 'vendor url' => 'http://www.unverse.net', + 'download url' => 'http://www.unverse.net/whizzywig-download.html', + 'libraries' => array( + '' => array( + 'title' => 'Default', + 'files' => array('whizzywig.js', 'xhtml.js'), + ), + ), + 'version callback' => 'wysiwyg_whizzywig_version', + 'settings callback' => 'wysiwyg_whizzywig_settings', + 'plugin callback' => 'wysiwyg_whizzywig_plugins', + 'versions' => array( + '55' => array( + 'js files' => array('whizzywig.js'), + ), + '56' => array( + 'js files' => array('whizzywig-56.js'), + ), + '60' => array( + 'js files' => array('whizzywig-60.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_whizzywig_version($editor) { + $script = $editor['library path'] . '/whizzywig.js'; + if (!file_exists($script)) { + return; + } + $script = fopen($script, 'r'); + $line = fgets($script, 43); + // 55: Whizzywig v55i + // 60: Whizzywig 60 + if (preg_match('@Whizzywig v?([0-9]+)@', $line, $version)) { + fclose($script); + return $version[1]; + } + fclose($script); +} + +/** + * 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_whizzywig_settings($editor, $config, $theme) { + $settings = array(); + + // Add path to button images, if available. + if (is_dir($editor['library path'] . '/btn')) { + $settings['buttonPath'] = base_path() . $editor['library path'] . '/btn/'; + } + if (file_exists($editor['library path'] . '/WhizzywigToolbar.png')) { + $settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/WhizzywigToolbar.png'; + } + // Filename changed in version 60. + elseif (file_exists($editor['library path'] . '/icons.png')) { + $settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/icons.png'; + } + + // Add configured buttons or all available. + $settings['buttons'] = array(); + if (!empty($config['buttons'])) { + $buttons = array(); + foreach ($config['buttons'] as $plugin) { + $buttons = array_merge($buttons, $plugin); + } + $settings['buttons'] = implode(' ', array_keys($buttons)); + } + + // Add editor content stylesheet. + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css'; + if (file_exists($css)) { + $settings['externalCSS'] = base_path() . $css; + } + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + } + } + + return $settings; +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_whizzywig_plugins($editor) { + return array( + 'default' => array( + 'buttons' => array( + 'formatblock' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'), + 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'), + 'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'), + 'bullet' => t('Bullet list'), 'number' => t('Numbered list'), + 'outdent' => t('Outdent'), 'indent' => t('Indent'), + 'undo' => t('Undo'), 'redo' => t('Redo'), + 'image' => t('Image'), + 'color' => t('Forecolor'), 'hilite' => t('Backcolor'), + 'rule' => t('Horizontal rule'), + 'link' => t('Link'), + 'image' => t('Image'), + 'table' => t('Table'), + 'clean' => t('Clean-up'), + 'html' => t('Source code'), + 'spellcheck' => t('Spell check'), + ), + 'internal' => TRUE, + ), + ); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/wymeditor.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/wymeditor.inc new file mode 100644 index 00000000..5f44d641 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/wymeditor.inc @@ -0,0 +1,234 @@ + 'WYMeditor', + 'vendor url' => 'http://www.wymeditor.org/', + 'download url' => 'http://www.wymeditor.org/download/', + 'library path' => wysiwyg_get_path('wymeditor') . '/wymeditor', + 'libraries' => array( + 'min' => array( + 'title' => 'Minified', + 'files' => array('jquery.wymeditor.min.js'), + ), + 'pack' => array( + 'title' => 'Packed', + 'files' => array('jquery.wymeditor.pack.js'), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array('jquery.wymeditor.js'), + ), + ), + 'version callback' => 'wysiwyg_wymeditor_version', + 'themes callback' => 'wysiwyg_wymeditor_themes', + 'settings callback' => 'wysiwyg_wymeditor_settings', + 'plugin callback' => 'wysiwyg_wymeditor_plugins', + 'versions' => array( + '0.5-rc1' => array( + 'js files' => array('wymeditor.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_wymeditor_version($editor) { + $script = $editor['library path'] . '/jquery.wymeditor.js'; + if (!file_exists($script)) { + return; + } + $script = fopen($script, 'r'); + fgets($script); + $line = fgets($script); + if (preg_match('@version\s+([0-9a-z\.-]+)@', $line, $version)) { + fclose($script); + return $version[1]; + } + fclose($script); +} + +/** + * 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_wymeditor_themes($editor, $profile) { + return array('compact', 'default', 'minimal', 'silver', 'twopanels'); +} + +/** + * 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_wymeditor_settings($editor, $config, $theme) { + // @todo Setup $library in wysiwyg_load_editor() already. + $library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries'])); + $settings = array( + 'basePath' => base_path() . $editor['library path'] . '/', + 'wymPath' => $editor['libraries'][$library]['files'][0], + // @todo Does not work in Drupal; jQuery can live anywhere. + 'jQueryPath' => base_path() . 'misc/jquery.js', + 'updateSelector' => '.form-submit', + 'skin' => $theme, + ); + + if (isset($config['language'])) { + $settings['lang'] = $config['language']; + } + + // Add configured buttons. + $settings['toolsItems'] = array(); + if (!empty($config['buttons'])) { + $buttoninfo = _wysiwyg_wymeditor_button_info(); + $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') { + // Merge meta-data for internal default buttons. + if (isset($buttoninfo[$button])) { + $buttoninfo[$button] += array('name' => $button); + $settings['toolsItems'][] = $buttoninfo[$button]; + } + // For custom buttons, try to provide a valid button definition. + else { + $settings['toolsItems'][] = array( + 'name' => $button, + 'title' => $plugins[$plugin][$type][$button], + 'css' => 'wym_tools_' . $button, + ); + } + } + } + } + } + } + + if (!empty($config['block_formats'])) { + $containers = array( + 'p' => 'Paragraph', + 'h1' => 'Heading_1', + 'h2' => 'Heading_2', + 'h3' => 'Heading_3', + 'h4' => 'Heading_4', + 'h5' => 'Heading_5', + 'h6' => 'Heading_6', + 'pre' => 'Preformatted', + 'blockquote' => 'Blockquote', + 'th' => 'Table_Header', + ); + foreach (explode(',', $config['block_formats']) as $tag) { + if (isset($containers[$tag])) { + $settings['containersItems'][] = array( + 'name' => strtoupper($tag), + 'title' => $containers[$tag], + 'css' => 'wym_containers_' . $tag, + ); + } + } + } + + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + // WYMeditor only supports one CSS file currently. + $css = wysiwyg_get_css(); + $settings['stylesheet'] = reset($css); + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['stylesheet'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + } + } + + return $settings; +} + +/** + * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_wymeditor_plugins($editor) { + $plugins = array( + 'default' => array( + 'buttons' => array( + 'Bold' => t('Bold'), 'Italic' => t('Italic'), + 'InsertOrderedList' => t('Numbered list'), 'InsertUnorderedList' => t('Bullet list'), + 'Outdent' => t('Outdent'), 'Indent' => t('Indent'), + 'Undo' => t('Undo'), 'Redo' => t('Redo'), + 'CreateLink' => t('Link'), 'Unlink' => t('Unlink'), + 'InsertImage' => t('Image'), + 'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'), + 'ToggleHtml' => t('Source code'), + 'Paste' => t('Paste'), + 'InsertTable' => t('Table'), + 'Preview' => t('Preview'), + ), + 'internal' => TRUE, + ), + ); + return $plugins; +} + +/** + * Helper function to provide additional meta-data for internal default buttons. + */ +function _wysiwyg_wymeditor_button_info() { + return array( + 'Bold' => array('title' => 'Strong', 'css' => 'wym_tools_strong'), + 'Italic' => array('title' => 'Emphasis', 'css' => 'wym_tools_emphasis'), + 'Superscript' => array('title' => 'Superscript', 'css' => 'wym_tools_superscript'), + 'Subscript' => array('title' => 'Subscript', 'css' => 'wym_tools_subscript'), + 'InsertOrderedList' => array('title' => 'Ordered_List', 'css' => 'wym_tools_ordered_list'), + 'InsertUnorderedList' => array('title' => 'Unordered_List', 'css' => 'wym_tools_unordered_list'), + 'Indent' => array('title' => 'Indent', 'css' => 'wym_tools_indent'), + 'Outdent' => array('title' => 'Outdent', 'css' => 'wym_tools_outdent'), + 'Undo' => array('title' => 'Undo', 'css' => 'wym_tools_undo'), + 'Redo' => array('title' => 'Redo', 'css' => 'wym_tools_redo'), + 'CreateLink' => array('title' => 'Link', 'css' => 'wym_tools_link'), + 'Unlink' => array('title' => 'Unlink', 'css' => 'wym_tools_unlink'), + 'InsertImage' => array('title' => 'Image', 'css' => 'wym_tools_image'), + 'InsertTable' => array('title' => 'Table', 'css' => 'wym_tools_table'), + 'Paste' => array('title' => 'Paste_From_Word', 'css' => 'wym_tools_paste'), + 'ToggleHtml' => array('title' => 'HTML', 'css' => 'wym_tools_html'), + 'Preview' => array('title' => 'Preview', 'css' => 'wym_tools_preview'), + ); +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/editors/yui.inc b/sites/all/modules/contrib/editor/wysiwyg/editors/yui.inc new file mode 100644 index 00000000..36d0a596 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/editors/yui.inc @@ -0,0 +1,339 @@ + 'YUI editor', + 'vendor url' => 'http://developer.yahoo.com/yui/editor/', + 'download url' => 'http://developer.yahoo.com/yui/download/', + 'library path' => wysiwyg_get_path('yui') . '/build', + 'libraries' => array( + 'min' => array( + 'title' => 'Minified', + 'files' => array( + 'yahoo-dom-event/yahoo-dom-event.js', + 'animation/animation-min.js', + 'element/element-min.js', + 'container/container-min.js', + 'menu/menu-min.js', + 'button/button-min.js', + 'editor/editor-min.js', + ), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array( + 'yahoo-dom-event/yahoo-dom-event.js', + 'animation/animation.js', + 'element/element.js', + 'container/container.js', + 'menu/menu.js', + 'button/button.js', + 'editor/editor.js', + ), + ), + ), + 'version callback' => 'wysiwyg_yui_version', + 'themes callback' => 'wysiwyg_yui_themes', + 'load callback' => 'wysiwyg_yui_load', + 'settings callback' => 'wysiwyg_yui_settings', + 'plugin callback' => 'wysiwyg_yui_plugins', + 'plugin settings callback' => 'wysiwyg_yui_plugin_settings', + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + 'proxy plugin settings callback' => 'wysiwyg_yui_proxy_plugin_settings', + 'versions' => array( + '2.7.0' => array( + 'js files' => array('yui.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_yui_version($editor) { + $library = $editor['library path'] . '/editor/editor.js'; + if (!file_exists($library)) { + return; + } + $library = fopen($library, 'r'); + $max_lines = 10; + while ($max_lines && $line = fgets($library, 60)) { + if (preg_match('@version:\s([0-9\.]+)@', $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_yui_themes($editor, $profile) { + return array('sam'); +} + +/** + * Perform additional actions upon loading this editor. + * + * @param $editor + * A processed hook_editor() array of editor properties. + * @param $library + * The internal library name (array key) to use. + */ +function wysiwyg_yui_load($editor, $library) { + drupal_add_css($editor['library path'] . '/menu/assets/skins/sam/menu.css'); + drupal_add_css($editor['library path'] . '/button/assets/skins/sam/button.css'); + drupal_add_css($editor['library path'] . '/fonts/fonts-min.css'); + drupal_add_css($editor['library path'] . '/container/assets/skins/sam/container.css'); + drupal_add_css($editor['library path'] . '/editor/assets/skins/sam/editor.css'); +} + +/** + * 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_yui_settings($editor, $config, $theme) { + $settings = array( + 'theme' => $theme, + 'animate' => TRUE, + 'handleSubmit' => TRUE, + 'markup' => 'xhtml', + 'ptags' => TRUE, + ); + + if (isset($config['path_loc']) && $config['path_loc'] != 'none') { + $settings['dompath'] = $config['path_loc']; + } + // Enable auto-height feature when editor should be resizable. + if (!empty($config['resizing'])) { + $settings['autoHeight'] = TRUE; + } + + $settings += array( + 'toolbar' => array( + 'collapse' => FALSE, + 'draggable' => TRUE, + 'buttonType' => 'advanced', + 'buttons' => array(), + ), + ); + if (!empty($config['buttons'])) { + $buttons = array(); + foreach ($config['buttons'] as $plugin => $enabled_buttons) { + foreach ($enabled_buttons as $button => $enabled) { + $extra = array(); + if ($button == 'heading') { + $extra = array('menu' => array( + array('text' => 'Normal', 'value' => 'none', 'checked' => TRUE), + )); + if (!empty($config['block_formats'])) { + $headings = array( + 'p' => array('text' => 'Paragraph', 'value' => 'p'), + 'h1' => array('text' => 'Heading 1', 'value' => 'h1'), + 'h2' => array('text' => 'Heading 2', 'value' => 'h2'), + 'h3' => array('text' => 'Heading 3', 'value' => 'h3'), + 'h4' => array('text' => 'Heading 4', 'value' => 'h4'), + 'h5' => array('text' => 'Heading 5', 'value' => 'h5'), + 'h6' => array('text' => 'Heading 6', 'value' => 'h6'), + ); + foreach (explode(',', $config['block_formats']) as $tag) { + if (isset($headings[$tag])) { + $extra['menu'][] = $headings[$tag]; + } + } + } + } + elseif ($button == 'fontname') { + $extra = array('menu' => array( + array('text' => 'Arial', 'checked' => TRUE), + array('text' => 'Arial Black'), + array('text' => 'Comic Sans MS'), + array('text' => 'Courier New'), + array('text' => 'Lucida Console'), + array('text' => 'Tahoma'), + array('text' => 'Times New Roman'), + array('text' => 'Trebuchet MS'), + array('text' => 'Verdana'), + )); + } + $buttons[] = wysiwyg_yui_button_setting($editor, $plugin, $button, $extra); + } + } + // Group buttons in a dummy group. + $buttons = array('group' => 'default', 'label' => '', 'buttons' => $buttons); + $settings['toolbar']['buttons'] = array($buttons); + } + + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $settings['extracss'] = wysiwyg_get_css(); + } + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['extracss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); + $settings['extracss'] = explode(',', $settings['extracss']); + } + // YUI only supports inline CSS, so we need to use @import directives. + // Syntax: '@import "/base/path/to/theme/style.css"; ' + if (!empty($settings['extracss'])) { + $settings['extracss'] = '@import "' . implode('"; @import "', $settings['extracss']) . '";'; + } + } + + return $settings; +} + +/** + * Create the JavaScript structure for a YUI button. + * + * @param $editor + * A processed hook_editor() array of editor properties. + * @param $plugin + * The internal name of a plugin. + * @param $button + * The internal name of a button, defined by $plugin. + * @param $extra + * (optional) An array containing arbitrary other elements to add to the + * resulting button. + */ +function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) { + static $plugins; + + if (!isset($plugins)) { + $plugins = wysiwyg_get_plugins($editor['name']); + } + + // Return a simple separator. + if ($button === 'separator') { + return array('type' => 'separator'); + } + // Setup defaults. + $type = 'push'; + $label = $plugins[$plugin]['buttons'][$button]; + + // Special handling for certain buttons. + if (in_array($button, array('heading', 'fontname'))) { + $type = 'select'; + $label = $extra['menu'][0]['text']; + } + elseif (in_array($button, array('fontsize'))) { + $type = 'spin'; + } + elseif (in_array($button, array('forecolor', 'backcolor'))) { + $type = 'color'; + } + + $button = array( + 'type' => $type, + 'label' => $label, + 'value' => $button, + ); + // Add arbitrary other elements, if defined. + if (!empty($extra)) { + $button = array_merge($button, $extra); + } + return $button; +} + +/** + * Build a JS settings array of native external plugins that need to be loaded separately. + */ +function wysiwyg_yui_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + foreach ($plugins as $name => $plugin) { + if (!empty($plugin['load'])) { + // Add path for native external plugins; internal ones are loaded + // automatically. + if (empty($plugin['internal']) && isset($plugin['path'])) { + $settings[$name] = base_path() . $plugin['path']; + } + } + } + return $settings; +} + +/** + * Build a JS settings array for Drupal plugins loaded via the proxy plugin. + */ +function wysiwyg_yui_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_yui_plugins($editor) { + return 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'), + 'insertunorderedlist' => t('Bullet list'), 'insertorderedlist' => t('Numbered list'), + 'outdent' => t('Outdent'), 'indent' => t('Indent'), + 'undo' => t('Undo'), 'redo' => t('Redo'), + 'createlink' => t('Link'), + 'insertimage' => t('Image'), + 'forecolor' => t('Font Color'), 'backcolor' => t('Background Color'), + 'superscript' => t('Sup'), 'subscript' => t('Sub'), + 'hiddenelements' => t('Show/hide hidden elements'), + 'removeformat' => t('Remove format'), + 'heading' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'), + ), + 'internal' => TRUE, + ), + ); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break.inc b/sites/all/modules/contrib/editor/wysiwyg/plugins/break.inc new file mode 100644 index 00000000..887d5905 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break.inc @@ -0,0 +1,21 @@ + t('Teaser break'), + 'vendor url' => 'http://drupal.org/project/wysiwyg', + 'icon file' => 'break.gif', + 'icon title' => t('Separate the teaser and body of this content'), + 'settings' => array(), + ); + return $plugins; +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.css b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.css new file mode 100644 index 00000000..4aaab761 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.css @@ -0,0 +1,10 @@ + +.wysiwyg-break { + display: block; + border: 0; + border-top: 1px dotted #ccc; + margin-top: 1em; + width: 100%; + height: 12px; + background: transparent url(images/breaktext.gif) no-repeat center top; +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.js b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.js new file mode 100644 index 00000000..54aac4cd --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/break.js @@ -0,0 +1,68 @@ +(function ($) { + +// @todo Array syntax required; 'break' is a predefined token in JavaScript. +Drupal.wysiwyg.plugins['break'] = { + + /** + * Return whether the passed node belongs to this plugin. + */ + isNode: function(node) { + return ($(node).is('img.wysiwyg-break')); + }, + + /** + * Execute the button. + */ + invoke: function(data, settings, instanceId) { + if (data.format == 'html') { + // Prevent duplicating a teaser break. + if ($(data.node).is('img.wysiwyg-break')) { + return; + } + var content = this._getPlaceholder(settings); + } + else { + // Prevent duplicating a teaser break. + // @todo data.content is the selection only; needs access to complete content. + if (data.content.match(//)) { + return; + } + var content = ''; + } + if (typeof content != 'undefined') { + Drupal.wysiwyg.instances[instanceId].insert(content); + } + }, + + /** + * Replace all tags with images. + */ + attach: function(content, settings, instanceId) { + content = content.replace(//g, this._getPlaceholder(settings)); + return content; + }, + + /** + * Replace images with tags in content upon detaching editor. + */ + detach: function(content, settings, instanceId) { + var $content = $(' ' + content + ''); // No .outerHTML() in jQuery :( + // #404532: document.createComment() required or IE will strip the comment. + // #474908: IE 8 breaks when using jQuery methods to replace the elements. + // @todo Add a generic implementation for all Drupal plugins for this. + $.each($('img.wysiwyg-break', $content), function (i, elem) { + elem.parentNode.insertBefore(document.createComment('break'), elem); + elem.parentNode.removeChild(elem); + }); + return $content.html(); + }, + + /** + * Helper function to return a HTML placeholder. + */ + _getPlaceholder: function (settings) { + return ''; + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/break.gif b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/break.gif new file mode 100644 index 00000000..4ff564d5 Binary files /dev/null and b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/break.gif differ diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/breaktext.gif b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/breaktext.gif new file mode 100644 index 00000000..61978735 Binary files /dev/null and b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/breaktext.gif differ diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/spacer.gif b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/spacer.gif new file mode 100644 index 00000000..38848651 Binary files /dev/null and b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/images/spacer.gif differ diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/ca.js b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/ca.js new file mode 100644 index 00000000..5ead9380 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/ca.js @@ -0,0 +1,6 @@ + +tinyMCE.addToLang('break', { + title: 'Inserir marcador de document retallat', + desc: 'Generar el punt de separació entre la versió retallada del document i la resta del contingut' +}); + diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/de.js b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/de.js new file mode 100644 index 00000000..d869a697 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/de.js @@ -0,0 +1,6 @@ + +tinyMCE.addToLang('break', { + title: 'Anrisstext trennen', + desc: 'Separiert den Anrisstext und Textkörper des Inhalts an dieser Stelle' +}); + diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/en.js b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/en.js new file mode 100644 index 00000000..6d75ef73 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/en.js @@ -0,0 +1,6 @@ + +tinyMCE.addToLang('break', { + title: 'Insert teaser break', + desc: 'Separate teaser and body of this content' +}); + diff --git a/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/es.js b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/es.js new file mode 100644 index 00000000..206023da --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/plugins/break/langs/es.js @@ -0,0 +1,6 @@ + +tinyMCE.addToLang('break', { + title: 'Insertar marcador de documento recortado', + desc: 'Generar el punto de separación entre la versión recortada del documento y el resto del contenido' +}); + diff --git a/sites/all/modules/contrib/editor/wysiwyg/tests/wysiwyg.test b/sites/all/modules/contrib/editor/wysiwyg/tests/wysiwyg.test new file mode 100644 index 00000000..263563c2 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/tests/wysiwyg.test @@ -0,0 +1,7 @@ + 'Ajaxified form', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('wysiwyg_test_ajax_form'), + 'access callback' => TRUE, + ); + return $items; +} + +/** + * Form constructor for an ajaxified form lazy-loading a textarea. + */ +function wysiwyg_test_ajax_form($form, &$form_state) { + $form['enable'] = array( + '#type' => 'checkbox', + '#title' => 'Load textarea', + '#ajax' => array( + 'callback' => 'wysiwyg_test_ajax_form_callback', + 'wrapper' => 'ajax-wrapper', + ), + ); + $form['wrapper'] = array( + '#type' => 'container', + '#id' => 'ajax-wrapper', + ); + return $form; +} + +/** + * #ajax callback for wysiwyg_test_ajax_form(). + */ +function wysiwyg_test_ajax_form_callback($form, &$form_state) { + $form['body'] = array( + '#type' => 'text_format', + '#default_value' => '', + ); + form_builder($form['form_id']['#value'], $form, $form_state); + return $form['body']; +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg-dialog-page.tpl.php b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg-dialog-page.tpl.php new file mode 100644 index 00000000..3e77d79b --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg-dialog-page.tpl.php @@ -0,0 +1,28 @@ + + + diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.admin.inc b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.admin.inc new file mode 100644 index 00000000..497e5d4c --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.admin.inc @@ -0,0 +1,594 @@ + '', + 'editor' => '', + ); + if (empty($profile['settings'])) { + $profile['settings'] = array(); + } + $profile['settings'] += array( + 'default' => TRUE, + 'user_choose' => FALSE, + 'show_toggle' => TRUE, + 'theme' => 'advanced', + 'language' => 'en', + 'access' => 1, + 'access_pages' => "node/*\nuser/*\ncomment/*", + 'buttons' => array(), + 'toolbar_loc' => 'top', + 'toolbar_align' => 'left', + 'path_loc' => 'bottom', + 'resizing' => TRUE, + // Also available, but buggy in TinyMCE 2.x: blockquote,code,dt,dd,samp. + 'block_formats' => 'p,address,pre,h2,h3,h4,h5,h6,div', + 'verify_html' => TRUE, + 'preformatted' => FALSE, + 'convert_fonts_to_spans' => TRUE, + 'remove_linebreaks' => TRUE, + 'apply_source_formatting' => FALSE, + 'paste_auto_cleanup_on_paste' => FALSE, + 'css_setting' => 'theme', + 'css_path' => NULL, + 'css_classes' => NULL, + ); + $profile = (object) $profile; + + $formats = filter_formats(); + $editor = wysiwyg_get_editor($profile->editor); + drupal_set_title(t('%editor profile for %format', array('%editor' => $editor['title'], '%format' => $formats[$profile->format]->name)), PASS_THROUGH); + + $form['format'] = array('#type' => 'value', '#value' => $profile->format); + $form['input_format'] = array('#type' => 'value', '#value' => $formats[$profile->format]->name); + $form['editor'] = array('#type' => 'value', '#value' => $profile->editor); + + $form['basic'] = array( + '#type' => 'fieldset', + '#title' => t('Basic setup'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['basic']['default'] = array( + '#type' => 'checkbox', + '#title' => t('Enabled by default'), + '#default_value' => $profile->settings['default'], + '#return_value' => 1, + '#description' => t('The default editor state for users having access to this profile. Users are able to override this state if the next option is enabled.'), + ); + + $form['basic']['user_choose'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to choose default'), + '#default_value' => $profile->settings['user_choose'], + '#return_value' => 1, + '#description' => t('If allowed, users will be able to choose their own editor default state in their user account settings.'), + ); + + $form['basic']['show_toggle'] = array( + '#type' => 'checkbox', + '#title' => t('Show enable/disable rich text toggle link'), + '#default_value' => $profile->settings['show_toggle'], + '#return_value' => 1, + '#description' => t('Whether or not to show the enable/disable rich text toggle link below a textarea. If disabled, the user setting or global default is used (see above).'), + ); + + $form['basic']['theme'] = array( + '#type' => 'hidden', + '#value' => $profile->settings['theme'], + ); + + $form['basic']['language'] = array( + '#type' => 'select', + '#title' => t('Interface language'), + '#default_value' => $profile->settings['language'], + ); + // @see _locale_prepare_predefined_list() + require_once DRUPAL_ROOT . '/includes/iso.inc'; + $predefined = _locale_get_predefined_list(); + foreach ($predefined as $key => $value) { + // Include native name in output, if possible + if (count($value) > 1) { + $tname = t($value[0]); + $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])"; + } + else { + $predefined[$key] = t($value[0]); + } + } + asort($predefined); + $form['basic']['language']['#options'] = $predefined; + + $form['buttons'] = array( + '#type' => 'fieldset', + '#title' => t('Buttons and plugins'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + '#theme' => 'wysiwyg_admin_button_table', + ); + + $plugins = wysiwyg_get_plugins($profile->editor); + // Generate the button list. + foreach ($plugins as $name => $meta) { + if (isset($meta['buttons']) && is_array($meta['buttons'])) { + foreach ($meta['buttons'] as $button => $title) { + $icon = ''; + if (!empty($meta['path'])) { + // @todo Button icon locations are different in editors, editor versions, + // and contrib/custom plugins (like Image Assist, f.e.). + $img_src = $meta['path'] . "/images/$name.gif"; + // Handle plugins that have more than one button. + if (!file_exists($img_src)) { + $img_src = $meta['path'] . "/images/$button.gif"; + } + $icon = file_exists($img_src) ? '
' : ''; + } + $title = (!empty($icon) ? $icon . ' ' . check_plain($title) : check_plain($title)); + $form['buttons'][$name][$button] = array( + '#type' => 'checkbox', + '#title' => $title, + '#default_value' => !empty($profile->settings['buttons'][$name][$button]) ? $profile->settings['buttons'][$name][$button] : FALSE, + '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL, + ); + } + } + elseif (isset($meta['extensions']) && is_array($meta['extensions'])) { + foreach ($meta['extensions'] as $extension => $title) { + $form['buttons'][$name][$extension] = array( + '#type' => 'checkbox', + '#title' => check_plain($title), + '#default_value' => !empty($profile->settings['buttons'][$name][$extension]) ? $profile->settings['buttons'][$name][$extension] : FALSE, + '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL, + ); + } + } + } + + $form['appearance'] = array( + '#type' => 'fieldset', + '#title' => t('Editor appearance'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['appearance']['toolbar_loc'] = array( + '#type' => 'select', + '#title' => t('Toolbar location'), + '#default_value' => $profile->settings['toolbar_loc'], + '#options' => array('bottom' => t('Bottom'), 'top' => t('Top')), + '#description' => t('This option controls whether the editor toolbar is displayed above or below the editing area.'), + ); + + $form['appearance']['toolbar_align'] = array( + '#type' => 'select', + '#title' => t('Button alignment'), + '#default_value' => $profile->settings['toolbar_align'], + '#options' => array('center' => t('Center'), 'left' => t('Left'), 'right' => t('Right')), + '#description' => t('This option controls the alignment of icons in the editor toolbar.'), + ); + + $form['appearance']['path_loc'] = array( + '#type' => 'select', + '#title' => t('Path location'), + '#default_value' => $profile->settings['path_loc'], + '#options' => array('none' => t('Hide'), 'top' => t('Top'), 'bottom' => t('Bottom')), + '#description' => t('Where to display the path to HTML elements (i.e.
body > table > tr > td
).'), + ); + + $form['appearance']['resizing'] = array( + '#type' => 'checkbox', + '#title' => t('Enable resizing button'), + '#default_value' => $profile->settings['resizing'], + '#return_value' => 1, + '#description' => t('This option gives you the ability to enable/disable the resizing button. If enabled, the Path location toolbar must be set to "Top" or "Bottom" in order to display the resize icon.'), + ); + + $form['output'] = array( + '#type' => 'fieldset', + '#title' => t('Cleanup and output'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['output']['verify_html'] = array( + '#type' => 'checkbox', + '#title' => t('Verify HTML'), + '#default_value' => $profile->settings['verify_html'], + '#return_value' => 1, + '#description' => t('If enabled, potentially malicious code like<HEAD>
tags will be removed from HTML contents.'), + ); + + $form['output']['preformatted'] = array( + '#type' => 'checkbox', + '#title' => t('Preformatted'), + '#default_value' => $profile->settings['preformatted'], + '#return_value' => 1, + '#description' => t('If enabled, the editor will insert TAB characters on tab and preserve other whitespace characters just like a PRE element in HTML does.'), + ); + + $form['output']['convert_fonts_to_spans'] = array( + '#type' => 'checkbox', + '#title' => t('Convert <font> tags to styles'), + '#default_value' => $profile->settings['convert_fonts_to_spans'], + '#return_value' => 1, + '#description' => t('If enabled, HTML tags declaring the font size, font family, font color and font background color will be replaced by inline CSS styles.'), + ); + + $form['output']['remove_linebreaks'] = array( + '#type' => 'checkbox', + '#title' => t('Remove linebreaks'), + '#default_value' => $profile->settings['remove_linebreaks'], + '#return_value' => 1, + '#description' => t('If enabled, the editor will remove most linebreaks from contents. Disabling this option could avoid conflicts with other input filters.'), + ); + + $form['output']['apply_source_formatting'] = array( + '#type' => 'checkbox', + '#title' => t('Apply source formatting'), + '#default_value' => $profile->settings['apply_source_formatting'], + '#return_value' => 1, + '#description' => t('If enabled, the editor will re-format the HTML source code. Disabling this option could avoid conflicts with other input filters.'), + ); + + $form['output']['paste_auto_cleanup_on_paste'] = array( + '#type' => 'checkbox', + '#title' => t('Force cleanup on standard paste'), + '#default_value' => $profile->settings['paste_auto_cleanup_on_paste'], + '#return_value' => 1, + '#description' => t('If enabled, the default paste function (CTRL-V or SHIFT-INS) behaves like the "paste from word" plugin function.'), + ); + + $form['css'] = array( + '#type' => 'fieldset', + '#title' => t('CSS'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['css']['block_formats'] = array( + '#type' => 'textfield', + '#title' => t('Block formats'), + '#default_value' => $profile->settings['block_formats'], + '#size' => 40, + '#maxlength' => 250, + '#description' => t('Comma separated list of HTML block formats. Possible values:@format-list
.', array('@format-list' => 'p,h1,h2,h3,h4,h5,h6,div,blockquote,address,pre,code,dt,dd')), + ); + + $form['css']['css_setting'] = array( + '#type' => 'select', + '#title' => t('Editor CSS'), + '#default_value' => $profile->settings['css_setting'], + '#options' => array('theme' => t('Use theme CSS'), 'self' => t('Define CSS'), 'none' => t('Editor default CSS')), + '#description' => t('Defines the CSS to be used in the editor area.
Use theme CSS - loads stylesheets from current site theme.
Define CSS - enter path for stylesheet files below.
Editor default CSS - uses default stylesheets from editor.'), + ); + + $form['css']['css_path'] = array( + '#type' => 'textfield', + '#title' => t('CSS path'), + '#default_value' => $profile->settings['css_path'], + '#size' => 40, + '#maxlength' => 255, + '#description' => t('If "Define CSS" was selected above, enter path to a CSS file or a list of CSS files separated by a comma.') . '
' . t('Available tokens:%b
(base path, eg:/
),%t
(path to theme, eg:themes/garland
)') . '
' . t('Example:') . ' css/editor.css,/themes/garland/style.css,%b%t/style.css,http://example.com/external.css', + ); + + $form['css']['css_classes'] = array( + '#type' => 'textarea', + '#title' => t('CSS classes'), + '#default_value' => $profile->settings['css_classes'], + '#description' => t('Optionally define CSS classes for the "Font style" dropdown list.
Enter one class on each line in the format: !format. Example: !example
If left blank, CSS classes are automatically imported from all loaded stylesheet(s).', array('!format' => '[title]=[class]
', '!example' => 'My heading=header1')), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 100, + ); + $form['cancel'] = array( + '#value' => l(t('Cancel'), 'admin/config/content/wysiwyg'), + '#weight' => 110, + ); + + // Supply contextual information for other callbacks and handlers. + // @todo Modernize this form for D7+ and declare these earlier. + // $profile is the primary object of this form, and as an entity, usually + // expected to live in $form_state[$entity_type]. + $form_state['wysiwyg_profile'] = $profile; + $form_state['wysiwyg']['editor'] = $editor; + $form_state['wysiwyg']['plugins'] = $plugins; + + // Allow editor library specific changes to be made to the form. + if (isset($editor['settings form callback'])) { + $editor['settings form callback']($form, $form_state); + } + + return $form; +} + +/** + * Submit callback for Wysiwyg profile form. + * + * @see wysiwyg_profile_form() + */ +function wysiwyg_profile_form_submit($form, &$form_state) { + $values = $form_state['values']; + if (isset($values['buttons'])) { + // Store only enabled buttons for each plugin. + foreach ($values['buttons'] as $plugin => $buttons) { + $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]); + } + // Store only enabled plugins. + $values['buttons'] = array_filter($values['buttons']); + } + // Remove any white-space from 'block_formats' setting, since editor + // implementations rely on a comma-separated list to explode(). + $values['block_formats'] = preg_replace('@\s+@', '', $values['block_formats']); + + // Remove input format name. + $format = $values['format']; + $input_format = $values['input_format']; + $editor = $values['editor']; + unset($values['format'], $values['input_format'], $values['editor']); + + // Remove FAPI values. + // @see system_settings_form_submit() + unset($values['submit'], $values['form_id'], $values['op'], $values['form_token'], $values['form_build_id']); + + // Insert new profile data. + db_merge('wysiwyg') + ->key(array('format' => $format)) + ->fields(array( + 'editor' => $editor, + 'settings' => serialize($values), + )) + ->execute(); + wysiwyg_profile_cache_clear(); + + drupal_set_message(t('Wysiwyg profile for %format has been saved.', array('%format' => $input_format))); + + $form_state['redirect'] = 'admin/config/content/wysiwyg'; +} + +/** + * Layout for the buttons in the Wysiwyg Editor profile form. + */ +function theme_wysiwyg_admin_button_table($variables) { + $form = $variables['form']; + $buttons = array(); + + // Flatten forms array. + foreach (element_children($form) as $name) { + foreach (element_children($form[$name]) as $button) { + $buttons[] = drupal_render($form[$name][$button]); + } + } + + // Split checkboxes into rows with 3 columns. + $total = count($buttons); + $rows = array(); + for ($i = 0; $i < $total; $i += 3) { + $row = array(); + $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array()); + foreach ($row_buttons as $row_button) { + $row[] = array('data' => $row_button); + } + $rows[] = $row; + } + + $output = theme('table', array('rows' => $rows, 'attributes' => array('width' => '100%'))); + + return $output; +} + +/** + * Display overview of setup Wysiwyg Editor profiles; menu callback. + */ +function wysiwyg_profile_overview($form, &$form_state) { + include_once './includes/install.inc'; + + // Check which wysiwyg editors are installed. + $editors = wysiwyg_get_all_editors(); + $count = count($editors); + $status = array(); + $options = array('' => t('No editor')); + + // D7's seven theme displays links in table headers as block elements. + drupal_add_css('table.system-status-report th a {display: inline;}', 'inline'); + + foreach ($editors as $name => $editor) { + $status[$name] = array( + 'severity' => (isset($editor['error']) ? REQUIREMENT_ERROR : ($editor['installed'] ? REQUIREMENT_OK : REQUIREMENT_INFO)), + 'title' => t('@editor (Download)', array('!vendor-url' => $editor['vendor url'], '@editor' => $editor['title'], '!download-url' => $editor['download url'])), + 'value' => (isset($editor['installed version']) ? $editor['installed version'] : t('Not installed.')), + 'description' => (isset($editor['error']) ? $editor['error'] : ''), + ); + if ($editor['installed']) { + $options[$name] = $editor['title'] . (isset($editor['installed version']) ? ' ' . $editor['installed version'] : ''); + } + else { + // Build on-site installation instructions. + // @todo Setup $library in wysiwyg_load_editor() already. + $library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries'])); + $targs = array( + '@editor-path' => $editor['editor path'], + '@library-filepath' => $editor['library path'] . '/' . (isset($editor['libraries'][$library]['files'][0]) ? $editor['libraries'][$library]['files'][0] : key($editor['libraries'][$library]['files'])), + ); + $instructions = '' . t('Extract the archive and copy its contents into a new folder in the following location:
'; + $instructions .= '@editor-path
', $targs) . '' . t('So the actual library can be found at:
'; + + // Add any install notes. + if (!empty($editor['install note callback']) && function_exists($editor['install note callback'])) { + $instructions .= '@library-filepath
', $targs) . '' . $editor['install note callback']() . ''; + } + + $status[$name]['description'] .= $instructions; + $count--; + } + // In case there is an error, always show installation instructions. + if (isset($editor['error'])) { + $show_instructions = TRUE; + } + } + if (!$count) { + $show_instructions = TRUE; + } + $form['status'] = array( + '#type' => 'fieldset', + '#title' => t('Installation instructions'), + '#collapsible' => TRUE, + '#collapsed' => !isset($show_instructions), + '#description' => (!$count ? t('There are no editor libraries installed currently. The following list contains a list of currently supported editors:') : ''), + '#weight' => 10, + ); + $form['status']['report'] = array('#markup' => theme('status_report', array('requirements' => $status))); + + if (!$count) { + return $form; + } + + $formats = filter_formats(); + $profiles = wysiwyg_profile_load_all(); + $form['formats'] = array( + '#type' => 'item', + '#description' => t('To assign a different editor to a text format, click "delete" to remove the existing first.'), + '#tree' => TRUE, + ); + + $enable_save = FALSE; + foreach ($formats as $id => $format) { + $form['formats'][$id]['name'] = array( + '#markup' => check_plain($format->name), + ); + // Only display editor selection for associated input formats to avoid + // confusion about disabled selection. + if (isset($profiles[$id]) && !empty($profiles[$id]->editor)) { + $editor_name = $profiles[$id]->editor; + $installed = !empty($editors[$editor_name]['installed']); + $form['formats'][$id]['editor'] = array( + '#wysiwyg-editor-name' => $editor_name, + ); + if ($installed) { + $form['formats'][$id]['editor']['#markup'] = $options[$editor_name]; + } + else { + drupal_set_message(t('Missing %editor library for %format format. Re-install the %editor library or delete the editor profile.', array( + '%editor' => $editors[$editor_name]['title'], + '%format' => $format->name, + )), 'warning'); + } + } + else { + $form['formats'][$id]['editor'] = array( + '#type' => 'select', + '#default_value' => '', + '#options' => $options, + ); + $enable_save = TRUE; + } + if (isset($profiles[$id]) && !empty($profiles[$id]->editor)) { + $form['formats'][$id]['edit'] = array( + '#markup' => l(t('Edit'), "admin/config/content/wysiwyg/profile/$id/edit"), + ); + $form['formats'][$id]['delete'] = array( + '#markup' => l(t('Delete'), "admin/config/content/wysiwyg/profile/$id/delete"), + ); + } + } + + // Submitting the form when no editors can be selected causes errors. + if ($enable_save) { + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + } + return $form; +} + +/** + * Return HTML for the Wysiwyg profile overview form. + */ +function theme_wysiwyg_profile_overview($variables) { + $form = $variables['form']; + if (!isset($form['formats'])) { + return; + } + $editors = wysiwyg_get_all_editors(); + $output = ''; + $header = array(t('Text format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2)); + $rows = array(); + foreach (element_children($form['formats']) as $item) { + $format = &$form['formats'][$item]; + $row = array( + 'data' => array( + drupal_render($format['name']), + drupal_render($format['editor']), + isset($format['edit']) ? drupal_render($format['edit']) : '', + isset($format['delete']) ? drupal_render($format['delete']) : '', + ), + ); + if (empty($row['data'][1])) { + $row['data'][1] = array( + 'data' => t('Missing library: @library', array('@library' => $editors[$format['editor']['#wysiwyg-editor-name']]['title'])), + 'class' => 'error', + ); + $row['class'] = array('error'); + } + $rows[] = $row; + } + $form['formats']['table']['#markup'] = theme('table', array('header' => $header, 'rows' => $rows)); + $output .= drupal_render_children($form); + return $output; +} + +/** + * Submit callback for Wysiwyg profile overview form. + */ +function wysiwyg_profile_overview_submit($form, &$form_state) { + foreach ($form_state['values']['formats'] as $format => $values) { + db_merge('wysiwyg') + ->key(array('format' => $format)) + ->fields(array( + 'editor' => $values['editor'], + )) + ->execute(); + } + wysiwyg_profile_cache_clear(); +} + +/** + * Delete editor profile confirmation form. + */ +function wysiwyg_profile_delete_confirm($form, &$form_state, $profile) { + $formats = filter_formats(); + $format = $formats[$profile->format]; + $form['format'] = array('#type' => 'value', '#value' => $format); + return confirm_form( + $form, + t('Are you sure you want to remove the profile for %name?', array('%name' => $format->name)), + 'admin/config/content/wysiwyg', + t('This action cannot be undone.'), t('Remove'), t('Cancel') + ); +} + +/** + * Submit callback for Wysiwyg profile delete form. + * + * @see wysiwyg_profile_delete_confirm() + */ +function wysiwyg_profile_delete_confirm_submit($form, &$form_state) { + $format = $form_state['values']['format']; + wysiwyg_profile_delete($format->format); + wysiwyg_profile_cache_clear(); + + drupal_set_message(t('Wysiwyg profile for %name has been deleted.', array('%name' => $format->name))); + $form_state['redirect'] = 'admin/config/content/wysiwyg'; +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.js b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.js new file mode 100644 index 00000000..0318b0b4 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.js @@ -0,0 +1,97 @@ + +/** + * Wysiwyg plugin button implementation for Awesome plugin. + */ +Drupal.wysiwyg.plugins.awesome = { + /** + * Return whether the passed node belongs to this plugin. + * + * @param node + * The currently focused DOM element in the editor content. + */ + isNode: function(node) { + return ($(node).is('img.mymodule-awesome')); + }, + + /** + * Execute the button. + * + * @param data + * An object containing data about the current selection: + * - format: 'html' when the passed data is HTML content, 'text' when the + * passed data is plain-text content. + * - node: When 'format' is 'html', the focused DOM element in the editor. + * - content: The textual representation of the focused/selected editor + * content. + * @param settings + * The plugin settings, as provided in the plugin's PHP include file. + * @param instanceId + * The ID of the current editor instance. + */ + invoke: function(data, settings, instanceId) { + // Generate HTML markup. + if (data.format == 'html') { + // Prevent duplicating a teaser break. + if ($(data.node).is('img.mymodule-awesome')) { + return; + } + var content = this._getPlaceholder(settings); + } + // Generate plain text. + else { + var content = ''; + } + // Insert new content into the editor. + if (typeof content != 'undefined') { + Drupal.wysiwyg.instances[instanceId].insert(content); + } + }, + + /** + * Prepare all plain-text contents of this plugin with HTML representations. + * + * Optional; only required for "inline macro tag-processing" plugins. + * + * @param content + * The plain-text contents of a textarea. + * @param settings + * The plugin settings, as provided in the plugin's PHP include file. + * @param instanceId + * The ID of the current editor instance. + */ + attach: function(content, settings, instanceId) { + content = content.replace(//g, this._getPlaceholder(settings)); + return content; + }, + + /** + * Process all HTML placeholders of this plugin with plain-text contents. + * + * Optional; only required for "inline macro tag-processing" plugins. + * + * @param content + * The HTML content string of the editor. + * @param settings + * The plugin settings, as provided in the plugin's PHP include file. + * @param instanceId + * The ID of the current editor instance. + */ + detach: function(content, settings, instanceId) { + var $content = $('' + content + ''); + $.each($('img.mymodule-awesome', $content), function (i, elem) { + //... + }); + return $content.html(); + }, + + /** + * Helper function to return a HTML placeholder. + * + * The 'drupal-content' CSS class is required for HTML elements in the editor + * content that shall not trigger any editor's native buttons (such as the + * image button for this example placeholder markup). + */ + _getPlaceholder: function (settings) { + return ''; + } +}; diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.php b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.php new file mode 100644 index 00000000..c4d88579 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.api.php @@ -0,0 +1,283 @@ + 3) { + return array( + 'myplugin' => array( + // A URL to the plugin's homepage. + 'url' => 'http://drupal.org/project/img_assist', + // The full path to the native editor plugin, no trailing slash. + // Ignored when 'internal' is set to TRUE below. + 'path' => drupal_get_path('module', 'img_assist') . '/drupalimage', + // The name of the plugin's main JavaScript file. + // Ignored when 'internal' is set to TRUE below. + // Default value depends on which editor the plugin is for. + 'filename' => 'editor_plugin.js', + // A list of buttons provided by this native plugin. The key has to + // match the corresponding JavaScript implementation. The value is + // is displayed on the editor configuration form only. + 'buttons' => array( + 'img_assist' => t('Image Assist'), + ), + // A list of editor extensions provided by this native plugin. + // Extensions are not displayed as buttons and touch the editor's + // internals, so you should know what you are doing. + 'extensions' => array( + 'imce' => t('IMCE'), + ), + // A list of global, native editor configuration settings to + // override. To be used rarely and only when required. + 'options' => array( + 'file_browser_callback' => 'imceImageBrowser', + 'inline_styles' => TRUE, + ), + // Boolean whether the editor needs to load this plugin. When TRUE, + // the editor will automatically load the plugin based on the 'path' + // variable provided. If FALSE, the plugin either does not need to + // be loaded or is already loaded by something else on the page. + // Most plugins should define TRUE here. + 'load' => TRUE, + // Boolean whether this plugin is a native plugin, i.e. shipped with + // the editor. Definition must be ommitted for plugins provided by + // other modules. TRUE means 'path' and 'filename' above are ignored + // and the plugin is instead loaded from the editor's plugin folder. + 'internal' => TRUE, + // TinyMCE-specific: Additional HTML elements to allow in the markup. + 'extended_valid_elements' => array( + 'img[class|src|border=0|alt|title|width|height|align|name|style]', + ), + ), + ); + } + break; + } +} + +/** + * Register a directory containing Wysiwyg plugins. + * + * @param $type + * The type of objects being collected: either 'plugins' or 'editors'. + * @return + * A sub-directory of the implementing module that contains the corresponding + * plugin files. This directory must only contain integration files for + * Wysiwyg module. + */ +function hook_wysiwyg_include_directory($type) { + switch ($type) { + case 'plugins': + // You can just return $type, if you place your Wysiwyg plugins into a + // sub-directory named 'plugins'. + return $type; + } +} + +/** + * Define a Wysiwyg plugin. + * + * Supposed to be used for "Drupal plugins" (cross-editor plugins) only. + * + * @see hook_wysiwyg_plugin() + * + * Each plugin file in the specified plugin directory of a module needs to + * define meta information about the particular plugin provided. + * The plugin's hook implementation function name is built out of the following: + * - 'hook': The name of the module providing the plugin. + * - 'INCLUDE': The basename of the file containing the plugin definition. + * - 'plugin': Static. + * + * For example, if your module's name is 'mymodule' and + * mymodule_wysiwyg_include_directory() returned 'plugins' as plugin directory, + * and this directory contains an "awesome" plugin file named 'awesome.inc', i.e. + * sites/all/modules/mymodule/plugins/awesome.inc + * then the corresponding plugin hook function name is: + * mymodule_awesome_plugin() + * + * @see hook_wysiwyg_include_directory() + * + * @return + * Meta information about the buttons provided by this plugin. + */ +function hook_INCLUDE_plugin() { + $plugins['awesome'] = array( + // The plugin's title; defaulting to its internal name ('awesome'). + 'title' => t('Awesome plugin'), + // The (vendor) homepage of this plugin; defaults to ''. + 'vendor url' => 'http://drupal.org/project/wysiwyg', + // The path to the button's icon; defaults to + // '/[path-to-module]/[plugins-directory]/[plugin-name]/images'. + 'icon path' => 'path to icon', + // The button image filename; defaults to '[plugin-name].png'. + 'icon file' => 'name of the icon file with extension', + // The button title to display on hover. + 'icon title' => t('Do something'), + // An alternative path to the integration JavaScript; defaults to + // '[path-to-module]/[plugins-directory]/[plugin-name]'. + 'js path' => drupal_get_path('module', 'mymodule') . '/awesomeness', + // An alternative filename of the integration JavaScript; defaults to + // '[plugin-name].js'. + 'js file' => 'awesome.js', + // An alternative path to the integration stylesheet; defaults to + // '[path-to-module]/[plugins-directory]/[plugin-name]'. + 'css path' => drupal_get_path('module', 'mymodule') . '/awesomeness', + // An alternative filename of the integration stylesheet; defaults to + // '[plugin-name].css'. + 'css file' => 'awesome.css', + // An array of settings for this button. Required, but API is still in flux. + 'settings' => array( + ), + // TinyMCE-specific: Additional HTML elements to allow in the markup. + 'extended_valid_elements' => array( + 'tag1[attribute1|attribute2]', + 'tag2[attribute3|attribute4]', + ), + ); + return $plugins; +} + +/** + * Define a Wysiwyg editor library. + * + * @todo Complete this documentation. + */ +function hook_INCLUDE_editor() { + $editor['ckeditor'] = array( + // The official, human-readable label of the editor library. + 'title' => 'CKEditor', + // The URL to the library's homepage. + 'vendor url' => 'http://ckeditor.com', + // The URL to the library's download page. + 'download url' => 'http://ckeditor.com/download', + // A definition of available variants for the editor library. + // The first defined is used by default. + 'libraries' => array( + '' => array( + 'title' => 'Default', + 'files' => array( + 'ckeditor.js' => array('preprocess' => FALSE), + ), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array( + 'ckeditor_source.js' => array('preprocess' => FALSE), + ), + ), + ), + // (optional) A callback to invoke to return additional notes for installing + // the editor library in the administrative list/overview. + 'install note callback' => 'wysiwyg_ckeditor_install_note', + // A callback to determine the library's version. + 'version callback' => 'wysiwyg_ckeditor_version', + // A callback to return available themes/skins for the editor library. + 'themes callback' => 'wysiwyg_ckeditor_themes', + // (optional) A callback to perform editor-specific adjustments or + // enhancements for the administrative editor profile settings form. + 'settings form callback' => 'wysiwyg_ckeditor_settings_form', + // (optional) A callback to return an initialization JavaScript snippet for + // this editor library, loaded before the actual library files. The returned + // JavaScript is executed as inline script in a primitive environment, + // before the DOM is loaded; typically used to prime a base path and other + // global window variables for the editor library before it is loaded. + // All implementations should verbosely document what they are doing and + // why that is required. + 'init callback' => 'wysiwyg_ckeditor_init', + // A callback to convert administrative profile/editor settings into + // JavaScript settings. + 'settings callback' => 'wysiwyg_ckeditor_settings', + // A callback to supply definitions of available editor plugins. + 'plugin callback' => 'wysiwyg_ckeditor_plugins', + // A callback to convert administrative plugin settings for a editor profile + // into JavaScript settings. + 'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings', + // (optional) Defines the proxy plugin that handles plugins provided by + // Drupal modules, which work in all editors that support proxy plugins. + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + // (optional) A callback to convert proxy plugin settings into JavaScript + // settings. + 'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings', + // Defines the list of supported (minimum) versions of the editor library, + // and the respective Drupal integration files to load. + 'versions' => array( + '3.0.0.3665' => array( + 'js files' => array('ckeditor-3.0.js'), + ), + ), + ); + return $editor; +} + +/** + * Act on editor profile settings. + * + * This hook is invoked from wysiwyg_get_editor_config() after the JavaScript + * settings have been generated for an editor profile and before the settings + * are added to the page. The settings may be customized or enhanced; typically + * with options that cannot be controlled through Wysiwyg module's + * administrative UI currently. + * + * Modules implementing this hook to enforce settings that can also be + * controlled through the UI should also implement + * hook_form_wysiwyg_profile_form_alter() to adjust or at least indicate on the + * editor profile configuration form that certain/affected settings cannot be + * changed. + * + * @param $settings + * An associative array of JavaScript settings to pass to the editor. + * @param $context + * An associative array containing additional context information: + * - editor: The plugin definition array of the editor. + * - profile: The editor profile object, as loaded from the database. + * - theme: The name of the editor theme/skin. + */ +function hook_wysiwyg_editor_settings_alter(&$settings, $context) { + // Each editor has its own collection of native settings that may be extended + // or overridden. Please consult the respective official vendor documentation + // for details. + if ($context['profile']->editor == 'tinymce') { + // Supported values to JSON data types. + $settings['cleanup_on_startup'] = TRUE; + } +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.dialog.inc b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.dialog.inc new file mode 100644 index 00000000..c296d2e0 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.dialog.inc @@ -0,0 +1,183 @@ + $plugin, + 'instance' => $instance, + ); + drupal_add_js(array('wysiwyg' => $settings), 'setting'); + + $build = $callback($instance); + if (!is_array($build)) { + $build = array('#markup' => $build); + } + $build += array( + '#instance' => $instance, + '#plugin' => $plugin, + ); + return $build; +} + +/** + * @see drupal_deliver_html_page() + */ +function wysiwyg_deliver_dialog_page($page_callback_result) { + // Menu status constants are integers; page content is a string or array. + if (is_int($page_callback_result)) { + return drupal_deliver_html_page($page_callback_result); + } + + // Emit the correct charset HTTP header, but not if the page callback + // result is NULL, since that likely indicates that it printed something + // in which case, no further headers may be sent, and not if code running + // for this page request has already set the content type header. + if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) { + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); + } + + // Send appropriate HTTP-Header for browsers and search engines. + global $language; + drupal_add_http_header('Content-Language', $language->language); + + if (isset($page_callback_result)) { + // Print anything besides a menu constant, assuming it's not NULL or + // undefined. + print wysiwyg_render_dialog_page($page_callback_result); + } + + // Perform end-of-request tasks. + drupal_page_footer(); +} + +/** + * @see drupal_render_page() + */ +function wysiwyg_render_dialog_page($page) { + $main_content_display = &drupal_static('system_main_content_added', FALSE); + + // Allow menu callbacks to return strings or arbitrary arrays to render. + // If the array returned is not of #type page directly, we need to fill + // in the page with defaults. + if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) { + drupal_set_page_content($page); + $page = element_info('wysiwyg_dialog_page'); + } + + // Modules alter the $page as needed. Blocks are populated into regions like + // 'sidebar_first', 'footer', etc. + drupal_alter(array('wysiwyg_dialog_page', 'page'), $page); + + // If no module has taken care of the main content, add it to the page now. + // This allows the site to still be usable even if no modules that + // control page regions (for example, the Block module) are enabled. + if (!$main_content_display) { + $page['content']['system_main'] = drupal_set_page_content(); + } + + return drupal_render($page); +} + +/** + * Template preprocess function for theme_wysiwyg_dialog_page(). + * + * @see wysiwyg_dialog() + * @see wysiwyg-dialog-page.tpl.php + * @see template_preprocess_page() + */ +function template_preprocess_wysiwyg_dialog_page(&$variables) { + template_preprocess_page($variables); +} + + +/** + * Template process function for theme_wysiwyg_dialog_page(). + * + * @see wysiwyg_dialog() + * @see wysiwyg-dialog-page.tpl.php + * @see template_process_page() + */ +function template_process_wysiwyg_dialog_page(&$variables) { + template_process_page($variables); +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.features.inc b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.features.inc new file mode 100644 index 00000000..5edd6421 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.features.inc @@ -0,0 +1,92 @@ +name; + } + } + return $profiles; +} + +/** + * Implements hook_features_export(). + */ +function wysiwyg_features_export($data, &$export, $module_name = '') { + $pipe = array(); + + // The wysiwyg_default_formats() hook integration is provided by the + // features module so we need to add it as a dependency. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['wysiwyg'] = 'wysiwyg'; + + foreach ($data as $name) { + if ($profile = wysiwyg_get_profile($name)) { + // Add profile to exports. + $export['features']['wysiwyg'][$profile->format] = $profile->format; + + // Chain filter format for export. + $pipe['filter'][] = $profile->format; + } + } + + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function wysiwyg_features_export_render($module, $data, $export = NULL) { + $code = array(); + $code[] = ' $profiles = array();'; + $code[] = ''; + + foreach ($data as $name) { + if ($profile = wysiwyg_get_profile($name)) { + $profile_export = features_var_export($profile, ' '); + $profile_identifier = features_var_export($profile->format); + $code[] = " // Exported profile: {$profile->format}"; + $code[] = " \$profiles[{$profile_identifier}] = {$profile_export};"; + $code[] = ""; + } + } + + $code[] = ' return $profiles;'; + $code = implode("\n", $code); + return array('wysiwyg_default_profiles' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function wysiwyg_features_revert($module) { + return wysiwyg_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function wysiwyg_features_rebuild($module) { + if ($defaults = features_get_default('wysiwyg', $module)) { + foreach ($defaults as $profile) { + db_merge('wysiwyg') + ->key(array('format' => $profile['format'])) + ->fields(array( + 'editor' => $profile['editor'], + 'settings' => serialize($profile['settings']), + )) + ->execute(); + } + wysiwyg_profile_cache_clear(); + } +} + diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.info b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.info new file mode 100644 index 00000000..5f70c0cb --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.info @@ -0,0 +1,17 @@ +name = Wysiwyg +description = Allows to edit content with client-side editors. +package = User interface +;dependencies[] = libraries +;dependencies[] = ctools +;dependencies[] = debug +core = 7.x +configure = admin/config/content/wysiwyg +files[] = wysiwyg.module +files[] = tests/wysiwyg.test + +; Information added by drupal.org packaging script on 2012-10-02 +version = "7.x-2.2" +core = "7.x" +project = "wysiwyg" +datestamp = "1349213776" + diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.init.js b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.init.js new file mode 100644 index 00000000..6ccdb314 --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.init.js @@ -0,0 +1,19 @@ + +Drupal.wysiwyg = Drupal.wysiwyg || { 'instances': {} }; + +Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {}, 'instance': {} }; + +Drupal.wysiwyg.plugins = Drupal.wysiwyg.plugins || {}; + +(function ($) { + // Determine support for queryCommandEnabled(). + // An exception should be thrown for non-existing commands. + // Safari and Chrome (WebKit based) return -1 instead. + try { + document.queryCommandEnabled('__wysiwygTestCommand'); + $.support.queryCommandEnabled = false; + } + catch (error) { + $.support.queryCommandEnabled = true; + } +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.install b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.install new file mode 100644 index 00000000..e5dd046d --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.install @@ -0,0 +1,313 @@ + 'Stores Wysiwyg profiles.', + 'fields' => array( + 'format' => array( + 'description' => 'The {filter_format}.format of the text format.', + 'type' => 'varchar', + 'length' => 255, + // Primary keys are implicitly not null. + 'not null' => TRUE, + ), + 'editor' => array( + 'description' => 'Internal name of the editor attached to the text format.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'settings' => array( + 'description' => 'Configuration settings for the editor.', + 'type' => 'text', + 'size' => 'normal', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('format'), + 'foreign keys' => array( + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + ); + $schema['wysiwyg_user'] = array( + 'description' => 'Stores user preferences for wysiwyg profiles.', + 'fields' => array( + 'uid' => array( + 'description' => 'The {users}.uid of the user.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'format' => array( + 'description' => 'The {filter_format}.format of the text format.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the format is enabled by default.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'indexes' => array( + 'uid' => array('uid'), + 'format' => array('format'), + ), + 'foreign keys' => array( + 'uid' => array( + 'table' => 'users', + 'columns' => array('uid' => 'uid'), + ), + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + ); + return $schema; +} + +/** + * Implementation of hook_enable(). + */ +function wysiwyg_enable() { + // Disable conflicting, obsolete editor integration modules whenever this + // module is enabled. This is crude, but the only way to ensure no conflicts. + module_disable(array( + 'ckeditor', + 'editarea', + 'editonpro', + 'editor', + 'fckeditor', + 'freerte', + 'htmlarea', + 'htmlbox', + 'jwysiwyg', + 'markitup', + 'nicedit', + 'openwysiwyg', + 'pegoeditor', + 'quicktext', + 'tinymce', + 'tinymce_autoconf', + 'tinytinymce', + 'whizzywig', + 'widgeditor', + 'wymeditor', + 'xstandard', + 'yui_editor', + )); +} + +/** + * Implements hook_update_dependencies(). + */ +function wysiwyg_update_dependencies() { + // Ensure that format columns are only changed after Filter module has changed + // the primary records. + $dependencies['wysiwyg'][7000] = array( + 'filter' => 7010, + ); + + return $dependencies; +} + +/** + * Retrieve a list of input formats to associate profiles to. + */ +function _wysiwyg_install_get_formats() { + $formats = array(); + $result = db_query("SELECT format, name FROM {filter_formats}"); + while ($format = db_fetch_object($result)) { + // Build a list of all formats. + $formats[$format->format] = $format->name; + // Fetch filters. + $result2 = db_query("SELECT module, delta FROM {filters} WHERE format = %d", $format->format); + while ($filter = db_fetch_object($result2)) { + // If PHP filter is enabled, remove this format. + if ($filter->module == 'php') { + unset($formats[$format->format]); + break; + } + } + } + return $formats; +} + +/** + * Associate Wysiwyg profiles with input formats. + * + * Since there was no association yet, we can only assume that there is one + * profile only, and that profile must be duplicated and assigned to all input + * formats (except PHP code format). Also, input formats already have + * titles/names, so Wysiwyg profiles do not need an own. + * + * Because input formats are already granted to certain user roles only, we can + * remove our custom Wysiwyg profile permissions. A 1:1 relationship between + * input formats and permissions makes plugin_count obsolete, too. + * + * Since the resulting table is completely different, a new schema is installed. + */ +function wysiwyg_update_6001() { + $ret = array(); + if (db_table_exists('wysiwyg')) { + return $ret; + } + // Install new schema. + db_create_table($ret, 'wysiwyg', array( + 'fields' => array( + 'format' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'editor' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''), + 'settings' => array('type' => 'text', 'size' => 'normal'), + ), + 'primary key' => array('format'), + )); + + // Fetch all input formats. + $formats = _wysiwyg_install_get_formats(); + + // Fetch all profiles. + $result = db_query("SELECT name, settings FROM {wysiwyg_profile}"); + while ($profile = db_fetch_object($result)) { + $profile->settings = unserialize($profile->settings); + // Extract editor name from profile settings. + $profile->editor = $profile->settings['editor']; + // Clean-up. + unset($profile->settings['editor']); + unset($profile->settings['old_name']); + unset($profile->settings['name']); + unset($profile->settings['rids']); + // Sorry. There Can Be Only One. ;) + break; + } + + if ($profile) { + // Rebuild profiles and associate with input formats. + foreach ($formats as $format => $name) { + // Insert profiles. + // We can't use update_sql() here because of curly braces in serialized + // array. + db_query("INSERT INTO {wysiwyg} (format, editor, settings) VALUES (%d, '%s', '%s')", $format, $profile->editor, serialize($profile->settings)); + $ret[] = array( + 'success' => TRUE, + 'query' => strtr('Wysiwyg profile %profile converted and associated with input format %format.', array('%profile' => check_plain($profile->name), '%format' => check_plain($name))), + ); + } + } + + // Drop obsolete tables {wysiwyg_profile} and {wysiwyg_role}. + db_drop_table($ret, 'wysiwyg_profile'); + db_drop_table($ret, 'wysiwyg_role'); + + return $ret; +} + +/** + * Clear JS/CSS caches to ensure that clients load fresh copies. + */ +function wysiwyg_update_6200() { + $ret = array(); + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + + drupal_clear_css_cache(); + drupal_clear_js_cache(); + + // Rebuild the menu to remove old admin/settings/wysiwyg/profile item. + menu_rebuild(); + + // Flush content caches. + cache_clear_all(); + + $ret[] = array( + 'success' => TRUE, + 'query' => 'Caches have been flushed.', + ); + return $ret; +} + +/** + * Change {wysiwyg}.format into a string. + */ +function wysiwyg_update_7000() { + db_drop_primary_key('wysiwyg'); + db_change_field('wysiwyg', 'format', 'format', array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + )); + db_add_primary_key('wysiwyg', array('format')); +} + +/** + * Create the {wysiwyg_user} table. + */ +function wysiwyg_update_7200() { + if (!db_table_exists('wysiwyg_user')) { + db_create_table('wysiwyg_user', array( + 'description' => 'Stores user preferences for wysiwyg profiles.', + 'fields' => array( + 'uid' => array( + 'description' => 'The {users}.uid of the user.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'format' => array( + 'description' => 'The {filter_format}.format of the text format.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the format is enabled by default.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'indexes' => array( + 'uid' => array('uid'), + 'format' => array('format'), + ), + 'foreign keys' => array( + 'uid' => array( + 'table' => 'users', + 'columns' => array('uid' => 'uid'), + ), + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + )); + } + else { + db_change_field('wysiwyg_user', 'format', 'format', array( + 'description' => 'The {filter_format}.format of the text format.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + )); + } +} diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.js b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.js new file mode 100644 index 00000000..29e2c54b --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.js @@ -0,0 +1,269 @@ +(function($) { + +/** + * Initialize editor libraries. + * + * Some editors need to be initialized before the DOM is fully loaded. The + * init hook gives them a chance to do so. + */ +Drupal.wysiwygInit = function() { + // This breaks in Konqueror. Prevent it from running. + if (/KDE/.test(navigator.vendor)) { + return; + } + jQuery.each(Drupal.wysiwyg.editor.init, function(editor) { + // Clone, so original settings are not overwritten. + this(jQuery.extend(true, {}, Drupal.settings.wysiwyg.configs[editor])); + }); +}; + +/** + * Attach editors to input formats and target elements (f.e. textareas). + * + * This behavior searches for input format selectors and formatting guidelines + * that have been preprocessed by Wysiwyg API. All CSS classes of those elements + * with the prefix 'wysiwyg-' are parsed into input format parameters, defining + * the input format, configured editor, target element id, and variable other + * properties, which are passed to the attach/detach hooks of the corresponding + * editor. + * + * Furthermore, an "enable/disable rich-text" toggle link is added after the + * target element to allow users to alter its contents in plain text. + * + * This is executed once, while editor attach/detach hooks can be invoked + * multiple times. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + */ +Drupal.behaviors.attachWysiwyg = { + attach: function (context, settings) { + // This breaks in Konqueror. Prevent it from running. + if (/KDE/.test(navigator.vendor)) { + return; + } + + $('.wysiwyg', context).once('wysiwyg', function () { + if (!this.id || typeof Drupal.settings.wysiwyg.triggers[this.id] === 'undefined') { + return; + } + var $this = $(this); + var params = Drupal.settings.wysiwyg.triggers[this.id]; + for (var format in params) { + params[format].format = format; + params[format].trigger = this.id; + params[format].field = params.field; + } + var format = 'format' + this.value; + // Directly attach this editor, if the input format is enabled or there is + // only one input format at all. + if ($this.is(':input')) { + Drupal.wysiwygAttach(context, params[format]); + } + // Attach onChange handlers to input format selector elements. + if ($this.is('select')) { + $this.change(function() { + // If not disabled, detach the current and attach a new editor. + Drupal.wysiwygDetach(context, params[format]); + format = 'format' + this.value; + Drupal.wysiwygAttach(context, params[format]); + }); + } + // Detach any editor when the containing form is submitted. + $('#' + params.field).parents('form').submit(function (event) { + // Do not detach if the event was cancelled. + if (event.isDefaultPrevented()) { + return; + } + Drupal.wysiwygDetach(context, params[format], 'serialize'); + }); + }); + }, + + detach: function (context, settings, trigger) { + var wysiwygs; + // The 'serialize' trigger indicates that we should simply update the + // underlying element with the new text, without destroying the editor. + if (trigger == 'serialize') { + // Removing the wysiwyg-processed class guarantees that the editor will + // be reattached. Only do this if we're planning to destroy the editor. + wysiwygs = $('.wysiwyg-processed', context); + } + else { + wysiwygs = $('.wysiwyg', context).removeOnce('wysiwyg'); + } + wysiwygs.each(function () { + var params = Drupal.settings.wysiwyg.triggers[this.id]; + Drupal.wysiwygDetach(context, params, trigger); + }); + } +}; + +/** + * Attach an editor to a target element. + * + * This tests whether the passed in editor implements the attach hook and + * invokes it if available. Editor profile settings are cloned first, so they + * cannot be overridden. After attaching the editor, the toggle link is shown + * again, except in case we are attaching no editor. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + * @param params + * An object containing input format parameters. + */ +Drupal.wysiwygAttach = function(context, params) { + if (typeof Drupal.wysiwyg.editor.attach[params.editor] == 'function') { + // (Re-)initialize field instance. + Drupal.wysiwyg.instances[params.field] = {}; + // Provide all input format parameters to editor instance. + jQuery.extend(Drupal.wysiwyg.instances[params.field], params); + // Provide editor callbacks for plugins, if available. + if (typeof Drupal.wysiwyg.editor.instance[params.editor] == 'object') { + jQuery.extend(Drupal.wysiwyg.instances[params.field], Drupal.wysiwyg.editor.instance[params.editor]); + } + // Store this field id, so (external) plugins can use it. + // @todo Wrong point in time. Probably can only supported by editors which + // support an onFocus() or similar event. + Drupal.wysiwyg.activeId = params.field; + // Attach or update toggle link, if enabled. + if (params.toggle) { + Drupal.wysiwygAttachToggleLink(context, params); + } + // Otherwise, ensure that toggle link is hidden. + else { + $('#wysiwyg-toggle-' + params.field).hide(); + } + // Attach editor, if enabled by default or last state was enabled. + if (params.status) { + Drupal.wysiwyg.editor.attach[params.editor](context, params, (Drupal.settings.wysiwyg.configs[params.editor] ? jQuery.extend(true, {}, Drupal.settings.wysiwyg.configs[params.editor][params.format]) : {})); + } + // Otherwise, attach default behaviors. + else { + Drupal.wysiwyg.editor.attach.none(context, params); + Drupal.wysiwyg.instances[params.field].editor = 'none'; + } + } +}; + +/** + * Detach all editors from a target element. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + * @param params + * An object containing input format parameters. + * @param trigger + * A string describing what is causing the editor to be detached. + * + * @see Drupal.detachBehaviors + */ +Drupal.wysiwygDetach = function (context, params, trigger) { + // Do not attempt to detach an unknown editor instance (Ajax). + if (typeof Drupal.wysiwyg.instances[params.field] == 'undefined') { + return; + } + trigger = trigger || 'unload'; + var editor = Drupal.wysiwyg.instances[params.field].editor; + if (jQuery.isFunction(Drupal.wysiwyg.editor.detach[editor])) { + Drupal.wysiwyg.editor.detach[editor](context, params, trigger); + } +}; + +/** + * Append or update an editor toggle link to a target element. + * + * @param context + * A DOM element, supplied by Drupal.attachBehaviors(). + * @param params + * An object containing input format parameters. + */ +Drupal.wysiwygAttachToggleLink = function(context, params) { + if (!$('#wysiwyg-toggle-' + params.field).length) { + var text = document.createTextNode(params.status ? Drupal.settings.wysiwyg.disable : Drupal.settings.wysiwyg.enable); + var a = document.createElement('a'); + $(a).attr({ id: 'wysiwyg-toggle-' + params.field, href: 'javascript:void(0);' }).append(text); + var div = document.createElement('div'); + $(div).addClass('wysiwyg-toggle-wrapper').append(a); + $('#' + params.field).after(div); + } + $('#wysiwyg-toggle-' + params.field) + .html(params.status ? Drupal.settings.wysiwyg.disable : Drupal.settings.wysiwyg.enable).show() + .unbind('click.wysiwyg', Drupal.wysiwyg.toggleWysiwyg) + .bind('click.wysiwyg', { params: params, context: context }, Drupal.wysiwyg.toggleWysiwyg); + + // Hide toggle link in case no editor is attached. + if (params.editor == 'none') { + $('#wysiwyg-toggle-' + params.field).hide(); + } +}; + +/** + * Callback for the Enable/Disable rich editor link. + */ +Drupal.wysiwyg.toggleWysiwyg = function (event) { + var context = event.data.context; + var params = event.data.params; + if (params.status) { + // Detach current editor. + params.status = false; + Drupal.wysiwygDetach(context, params); + // After disabling the editor, re-attach default behaviors. + // @todo We HAVE TO invoke Drupal.wysiwygAttach() here. + Drupal.wysiwyg.editor.attach.none(context, params); + Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance.none; + Drupal.wysiwyg.instances[params.field].editor = 'none'; + Drupal.wysiwyg.instances[params.field].field = params.field; + $(this).html(Drupal.settings.wysiwyg.enable).blur(); + } + else { + // Before enabling the editor, detach default behaviors. + Drupal.wysiwyg.editor.detach.none(context, params); + // Attach new editor using parameters of the currently selected input format. + params = Drupal.settings.wysiwyg.triggers[params.trigger]['format' + $('#' + params.trigger).val()]; + params.status = true; + Drupal.wysiwygAttach(context, params); + $(this).html(Drupal.settings.wysiwyg.disable).blur(); + } +} + +/** + * Parse the CSS classes of an input format DOM element into parameters. + * + * Syntax for CSS classes is "wysiwyg-name-value". + * + * @param element + * An input format DOM element containing CSS classes to parse. + * @param params + * (optional) An object containing input format parameters to update. + */ +Drupal.wysiwyg.getParams = function(element, params) { + var classes = element.className.split(' '); + var params = params || {}; + for (var i = 0; i < classes.length; i++) { + if (classes[i].substr(0, 8) == 'wysiwyg-') { + var parts = classes[i].split('-'); + var value = parts.slice(2).join('-'); + params[parts[1]] = value; + } + } + // Convert format id into string. + params.format = 'format' + params.format; + // Convert numeric values. + params.status = parseInt(params.status, 10); + params.toggle = parseInt(params.toggle, 10); + params.resizable = parseInt(params.resizable, 10); + return params; +}; + +/** + * Allow certain editor libraries to initialize before the DOM is loaded. + */ +Drupal.wysiwygInit(); + +// Respond to CTools detach behaviors event. +$(document).bind('CToolsDetachBehaviors', function(event, context) { + Drupal.behaviors.attachWysiwyg.detach(context, {}, 'unload'); +}); + +})(jQuery); diff --git a/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.module b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.module new file mode 100644 index 00000000..22130eab --- /dev/null +++ b/sites/all/modules/contrib/editor/wysiwyg/wysiwyg.module @@ -0,0 +1,1135 @@ + 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() { + $items['admin/config/content/wysiwyg'] = array( + 'title' => 'Wysiwyg profiles', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('wysiwyg_profile_overview'), + 'description' => 'Configure client-side editors.', + 'access arguments' => array('administer filters'), + 'file' => 'wysiwyg.admin.inc', + ); + $items['admin/config/content/wysiwyg/profile'] = array( + 'title' => 'List', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/config/content/wysiwyg/profile/%wysiwyg_profile/edit'] = array( + 'title' => 'Edit', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('wysiwyg_profile_form', 5), + 'access arguments' => array('administer filters'), + 'file' => 'wysiwyg.admin.inc', + 'tab_root' => 'admin/config/content/wysiwyg/profile', + 'tab_parent' => 'admin/config/content/wysiwyg/profile/%wysiwyg_profile', + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/config/content/wysiwyg/profile/%wysiwyg_profile/delete'] = array( + 'title' => 'Remove', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('wysiwyg_profile_delete_confirm', 5), + 'access arguments' => array('administer filters'), + 'file' => 'wysiwyg.admin.inc', + 'tab_root' => 'admin/config/content/wysiwyg/profile', + 'tab_parent' => 'admin/config/content/wysiwyg/profile/%wysiwyg_profile', + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ); + // @see wysiwyg_dialog() + $items['wysiwyg/%'] = array( + 'page callback' => 'wysiwyg_dialog', + 'page arguments' => array(1), + 'delivery callback' => 'wysiwyg_deliver_dialog_page', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + 'file' => 'wysiwyg.dialog.inc', + ); + return $items; +} + +/** + * Implements hook_element_info(). + */ +function wysiwyg_element_info() { + // @see wysiwyg_dialog() + $types['wysiwyg_dialog_page'] = array( + '#theme' => 'wysiwyg_dialog_page', + '#theme_wrappers' => array('html'), + '#show_messages' => TRUE, + ); + return $types; +} + +/** + * Implementation of hook_theme(). + * + * @see drupal_common_theme(), common.inc + * @see template_preprocess_page(), theme.inc + */ +function wysiwyg_theme() { + return array( + 'wysiwyg_profile_overview' => array( + 'render element' => 'form', + ), + 'wysiwyg_admin_button_table' => array( + 'render element' => 'form', + ), + // @see wysiwyg_dialog() + 'wysiwyg_dialog_page' => array( + 'render element' => 'page', + 'file' => 'wysiwyg.dialog.inc', + 'template' => 'wysiwyg-dialog-page', + ), + ); +} + +/** + * Implementation of hook_help(). + */ +function wysiwyg_help($path, $arg) { + switch ($path) { + case 'admin/config/content/wysiwyg': + $output = '
' . t('A Wysiwyg profile is associated with a text format. A Wysiwyg profile defines which client-side editor is loaded with a particular text format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '
'; + return $output; + } +} + +/** + * Implementation of hook_form_alter(). + */ +function wysiwyg_form_alter(&$form, &$form_state) { + // Teaser splitter is unconditionally removed and NOT supported. + if (isset($form['body_field'])) { + unset($form['body_field']['teaser_js']); + } +} + +/** + * 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. + * + * The element's #id is used as reference to attach client-side editors. + */ +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; + } + + $format_field = &$element['format']; + $field = &$element['value']; + $settings = array( + 'field' => $field['#id'], + ); + + // 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'] : '')); + + // 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 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; +} + +/** + * Determine the profile to use for a given input format id. + * + * This function also performs sanity checks for the configured editor in a + * profile to ensure that we do not load a malformed editor. + * + * @param $format + * The internal id of an input format. + * + * @return + * A wysiwyg profile. + * + * @see wysiwyg_load_editor(), wysiwyg_get_editor() + */ +function wysiwyg_get_profile($format) { + if ($profile = wysiwyg_profile_load($format)) { + if (wysiwyg_load_editor($profile)) { + return $profile; + } + } + return FALSE; +} + +/** + * Load an editor library and initialize basic Wysiwyg settings. + * + * @param $profile + * A wysiwyg editor profile. + * + * @return + * TRUE if the editor has been loaded, FALSE if not. + * + * @see wysiwyg_get_profile() + */ +function wysiwyg_load_editor($profile) { + static $settings_added; + static $loaded = array(); + $path = drupal_get_path('module', 'wysiwyg'); + + $name = $profile->editor; + // Library files must be loaded only once. + if (!isset($loaded[$name])) { + // Load editor. + $editor = wysiwyg_get_editor($name); + if ($editor) { + $default_library_options = array( + 'type' => 'file', + 'scope' => 'header', + 'defer' => FALSE, + 'cache' => TRUE, + 'preprocess' => TRUE, + ); + // Determine library files to load. + // @todo Allow to configure the library/execMode to use. + if (isset($profile->settings['library']) && isset($editor['libraries'][$profile->settings['library']])) { + $library = $profile->settings['library']; + $files = $editor['libraries'][$library]['files']; + } + else { + // Fallback to the first defined library by default (external libraries can change). + $library = key($editor['libraries']); + $files = array_shift($editor['libraries']); + $files = $files['files']; + } + + // Check whether the editor requires an initialization script. + if (!empty($editor['init callback'])) { + $init = $editor['init callback']($editor, $library, $profile); + if (!empty($init)) { + // Build a file for each of the editors to hold the init scripts. + // @todo Aggregate all initialization scripts into one file. + $uri = 'public://js/wysiwyg/wysiwyg_' . $name . '_' . drupal_hash_base64($init) . '.js'; + $init_exists = file_exists($uri); + if (!$init_exists) { + $js_path = dirname($uri); + file_prepare_directory($js_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + } + // Attempt to create the file, or fall back to an inline script (which + // will not work in Ajax calls). + if (!$init_exists && !file_unmanaged_save_data($init, $uri, FILE_EXISTS_REPLACE)) { + drupal_add_js($init, array('type' => 'inline') + $default_library_options); + } + else { + drupal_add_js(file_create_url($uri), $default_library_options); + } + } + } + + foreach ($files as $file => $options) { + if (is_array($options)) { + $options += $default_library_options; + drupal_add_js($editor['library path'] . '/' . $file, $options); + } + else { + drupal_add_js($editor['library path'] . '/' . $options); + } + } + // If editor defines an additional load callback, invoke it. + // @todo Isn't the settings callback sufficient? + if (isset($editor['load callback']) && function_exists($editor['load callback'])) { + $editor['load callback']($editor, $library); + } + // Load JavaScript integration files for this editor. + $files = array(); + if (isset($editor['js files'])) { + $files = $editor['js files']; + } + foreach ($files as $file) { + drupal_add_js($editor['js path'] . '/' . $file); + } + // Load CSS stylesheets for this editor. + $files = array(); + if (isset($editor['css files'])) { + $files = $editor['css files']; + } + foreach ($files as $file) { + drupal_add_css($editor['css path'] . '/' . $file); + } + $loaded[$name] = TRUE; + } + else { + $loaded[$name] = FALSE; + } + } + + // Add basic Wysiwyg settings if any editor has been added. + if (!isset($settings_added) && $loaded[$name]) { + drupal_add_js(array('wysiwyg' => array( + 'configs' => array(), + 'plugins' => array(), + 'disable' => t('Disable rich-text'), + 'enable' => t('Enable rich-text'), + )), 'setting'); + + // 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('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. + drupal_add_js($path . '/editors/js/none.js'); + + // Add wysiwyg.js to the footer to ensure it's executed after the + // Drupal.settings array has been rendered and populated. Also, since editor + // library initialization functions must be loaded first by the browser, + // and Drupal.wysiwygInit() must be executed AFTER editors registered + // their callbacks and BEFORE Drupal.behaviors are applied, this must come + // last. + drupal_add_js($path . '/wysiwyg.js', array('scope' => 'footer')); + + $settings_added = TRUE; + } + + return $loaded[$name]; +} + +/** + * Add editor settings for a given input format. + */ +function wysiwyg_add_editor_settings($profile, $theme) { + static $formats = array(); + + if (!isset($formats[$profile->format])) { + $config = wysiwyg_get_editor_config($profile, $theme); + // drupal_to_js() does not properly convert numeric array keys, so we need + // to use a string instead of the format id. + if ($config) { + drupal_add_js(array('wysiwyg' => array('configs' => array($profile->editor => array('format' . $profile->format => $config)))), 'setting'); + } + $formats[$profile->format] = TRUE; + } +} + +/** + * Add settings for external plugins. + * + * Plugins can be used in multiple profiles, but not necessarily in all. Because + * of that, we need to process plugins for each profile, even if most of their + * settings are not stored per profile. + * + * Implementations of hook_wysiwyg_plugin() may execute different code for each + * editor. Therefore, we have to invoke those implementations for each editor, + * but process the resulting plugins separately for each profile. + * + * Drupal plugins differ to native plugins in that they have plugin-specific + * definitions and settings, which need to be processed only once. But they are + * also passed to the editor to prepare settings specific to the editor. + * Therefore, we load and process the Drupal plugins only once, and hand off the + * effective definitions for each profile to the editor. + * + * @param $profile + * A wysiwyg editor profile. + * + * @todo Rewrite wysiwyg_process_form() to build a registry of effective + * profiles in use, so we can process plugins in multiple profiles in one shot + * and simplify this entire function. + */ +function wysiwyg_add_plugin_settings($profile) { + static $plugins = array(); + static $processed_plugins = array(); + static $processed_formats = array(); + + // Each input format must only processed once. + // @todo ...as long as we do not have multiple profiles per format. + if (isset($processed_formats[$profile->format])) { + return; + } + $processed_formats[$profile->format] = TRUE; + + $editor = wysiwyg_get_editor($profile->editor); + + // Collect native plugins for this editor provided via hook_wysiwyg_plugin() + // and Drupal plugins provided via hook_wysiwyg_include_directory(). + if (!array_key_exists($editor['name'], $plugins)) { + $plugins[$editor['name']] = wysiwyg_get_plugins($editor['name']); + } + + // Nothing to do, if there are no plugins. + if (empty($plugins[$editor['name']])) { + return; + } + + // Determine name of proxy plugin for Drupal plugins. + $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : ''); + + // Process native editor plugins. + if (isset($editor['plugin settings callback'])) { + // @todo Require PHP 5.1 in 3.x and use array_intersect_key(). + $profile_plugins_native = array(); + foreach ($plugins[$editor['name']] as $plugin => $meta) { + // Skip Drupal plugins (handled below). + if ($plugin === $proxy) { + continue; + } + // Only keep native plugins that are enabled in this profile. + if (isset($profile->settings['buttons'][$plugin])) { + $profile_plugins_native[$plugin] = $meta; + } + } + // Invoke the editor's plugin settings callback, so it can populate the + // settings for native external plugins with required values. + $settings_native = call_user_func($editor['plugin settings callback'], $editor, $profile, $profile_plugins_native); + + if ($settings_native) { + drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('native' => $settings_native)))), 'setting'); + } + } + + // Process Drupal plugins. + if ($proxy && isset($editor['proxy plugin settings callback'])) { + $profile_plugins_drupal = array(); + foreach (wysiwyg_get_all_plugins() as $plugin => $meta) { + if (isset($profile->settings['buttons'][$proxy][$plugin])) { + // JavaScript and plugin-specific settings for Drupal plugins must be + // loaded and processed only once. Plugin information is cached + // statically to pass it to the editor's proxy plugin settings callback. + if (!isset($processed_plugins[$proxy][$plugin])) { + $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin] = $meta; + // Load the Drupal plugin's JavaScript. + drupal_add_js($meta['js path'] . '/' . $meta['js file']); + // Add plugin-specific settings. + if (isset($meta['settings'])) { + drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin => $meta['settings'])))), 'setting'); + } + } + else { + $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin]; + } + } + } + // Invoke the editor's proxy plugin settings callback, so it can populate + // the settings for Drupal plugins with custom, required values. + $settings_drupal = call_user_func($editor['proxy plugin settings callback'], $editor, $profile, $profile_plugins_drupal); + + if ($settings_drupal) { + drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('drupal' => $settings_drupal)))), 'setting'); + } + } +} + +/** + * Retrieve available themes for an editor. + * + * Editor themes control the visual presentation of an editor. + * + * @param $profile + * A wysiwyg editor profile; passed/altered by reference. + * @param $selected_theme + * An optional theme name that ought to be used. + * + * @return + * An array of theme names, or a single, checked theme name if $selected_theme + * was given. + */ +function wysiwyg_get_editor_themes(&$profile, $selected_theme = NULL) { + static $themes = array(); + + if (!isset($themes[$profile->editor])) { + $editor = wysiwyg_get_editor($profile->editor); + if (isset($editor['themes callback']) && function_exists($editor['themes callback'])) { + $themes[$editor['name']] = $editor['themes callback']($editor, $profile); + } + // Fallback to 'default' otherwise. + else { + $themes[$editor['name']] = array('default'); + } + } + + // Check optional $selected_theme argument, if given. + if (isset($selected_theme)) { + // If the passed theme name does not exist, use the first available. + if (!in_array($selected_theme, $themes[$profile->editor])) { + $selected_theme = $profile->settings['theme'] = $themes[$profile->editor][0]; + } + } + + return isset($selected_theme) ? $selected_theme : $themes[$profile->editor]; +} + +/** + * Return plugin metadata from the plugin registry. + * + * @param $editor_name + * The internal name of an editor to return plugins for. + * + * @return + * An array for each plugin. + */ +function wysiwyg_get_plugins($editor_name) { + $plugins = array(); + if (!empty($editor_name)) { + $editor = wysiwyg_get_editor($editor_name); + // Add internal editor plugins. + if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) { + $plugins = $editor['plugin callback']($editor); + } + // Add editor plugins provided via hook_wysiwyg_plugin(). + $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version'])); + // Add API plugins provided by Drupal modules. + // @todo We need to pass the filepath to the plugin icon for Drupal plugins. + if (isset($editor['proxy plugin'])) { + $plugins += $editor['proxy plugin']; + $proxy = key($editor['proxy plugin']); + foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) { + $plugins[$proxy]['buttons'][$plugin_name] = $info['title']; + } + } + } + return $plugins; +} + +/** + * Return an array of initial editor settings for a Wysiwyg profile. + */ +function wysiwyg_get_editor_config($profile, $theme) { + $editor = wysiwyg_get_editor($profile->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; +} + +/** + * Retrieve stylesheets for HTML/IFRAME-based editors. + * + * This assumes that the content editing area only needs stylesheets defined + * for the scope 'theme'. + * + * @return + * An array containing CSS files, including proper base path. + */ +function wysiwyg_get_css() { + static $files; + + if (isset($files)) { + return $files; + } + // In node form previews, the theme has not been initialized yet. + if (!empty($_POST)) { + drupal_theme_initialize(); + } + + $files = array(); + foreach (drupal_add_css() as $filepath => $info) { + if ($info['group'] >= CSS_THEME && $info['media'] != 'print') { + if ($info['type'] == 'external') { + $files[] = $filepath; + } + elseif (file_exists($filepath)) { + $files[] = base_path() . $filepath; + } + } + } + return $files; +} + +/** + * 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) { + $profiles = wysiwyg_profile_load_all(); + return (isset($profiles[$format]) ? $profiles[$format] : FALSE); +} + +/** + * Loads all profiles. + */ +function wysiwyg_profile_load_all() { + // 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)) { + // 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); + } + } + + return $profiles; +} + +/** + * 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; + } + } + } + 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, + ); + } +} + +/** + * 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 (!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 (bool) $status; +} + +/** + * @defgroup wysiwyg_api Wysiwyg API + * @{ + * + * @todo Forked from Panels; abstract into a separate API module that allows + * contrib modules to define supported include/plugin types. + */ + +/** + * Return library information for a given editor. + * + * @param $name + * The internal name of an editor. + * + * @return + * The library information for the editor, or FALSE if $name is unknown or not + * installed properly. + */ +function wysiwyg_get_editor($name) { + $editors = wysiwyg_get_all_editors(); + return isset($editors[$name]) && $editors[$name]['installed'] ? $editors[$name] : FALSE; +} + +/** + * Compile a list holding all supported editors including installed editor version information. + */ +function wysiwyg_get_all_editors() { + static $editors; + + if (isset($editors)) { + return $editors; + } + + $editors = wysiwyg_load_includes('editors', 'editor'); + foreach ($editors as $editor => $properties) { + // Fill in required properties. + $editors[$editor] += array( + 'title' => '', + 'vendor url' => '', + 'download url' => '', + 'editor path' => wysiwyg_get_path($editors[$editor]['name']), + 'library path' => wysiwyg_get_path($editors[$editor]['name']), + 'libraries' => array(), + 'version callback' => NULL, + 'themes callback' => NULL, + 'settings form callback' => NULL, + 'settings callback' => NULL, + 'plugin callback' => NULL, + 'plugin settings callback' => NULL, + 'versions' => array(), + 'js path' => $editors[$editor]['path'] . '/js', + 'css path' => $editors[$editor]['path'] . '/css', + ); + // Check whether library is present. + if (!($editors[$editor]['installed'] = file_exists($editors[$editor]['library path']))) { + continue; + } + // Detect library version. + if (function_exists($editors[$editor]['version callback'])) { + $editors[$editor]['installed version'] = $editors[$editor]['version callback']($editors[$editor]); + } + if (empty($editors[$editor]['installed version'])) { + $editors[$editor]['error'] = t('The version of %editor could not be detected.', array('%editor' => $properties['title'])); + $editors[$editor]['installed'] = FALSE; + continue; + } + // Determine to which supported version the installed version maps. + ksort($editors[$editor]['versions']); + $version = 0; + foreach ($editors[$editor]['versions'] as $supported_version => $version_properties) { + if (version_compare($editors[$editor]['installed version'], $supported_version, '>=')) { + $version = $supported_version; + } + } + if (!$version) { + $editors[$editor]['error'] = t('The installed version %version of %editor is not supported.', array('%version' => $editors[$editor]['installed version'], '%editor' => $editors[$editor]['title'])); + $editors[$editor]['installed'] = FALSE; + continue; + } + // Apply library version specific definitions and overrides. + $editors[$editor] = array_merge($editors[$editor], $editors[$editor]['versions'][$version]); + unset($editors[$editor]['versions']); + } + return $editors; +} + +/** + * Invoke hook_wysiwyg_plugin() in all modules. + */ +function wysiwyg_get_all_plugins() { + static $plugins; + + if (isset($plugins)) { + return $plugins; + } + + $plugins = wysiwyg_load_includes('plugins', 'plugin'); + foreach ($plugins as $name => $properties) { + $plugin = &$plugins[$name]; + // Fill in required/default properties. + $plugin += array( + 'title' => $plugin['name'], + 'vendor url' => '', + 'js path' => $plugin['path'] . '/' . $plugin['name'], + 'js file' => $plugin['name'] . '.js', + 'css path' => $plugin['path'] . '/' . $plugin['name'], + 'css file' => $plugin['name'] . '.css', + 'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images', + 'icon file' => $plugin['name'] . '.png', + 'dialog path' => $plugin['name'], + 'dialog settings' => array(), + 'settings callback' => NULL, + 'settings form callback' => NULL, + ); + // Fill in default settings. + $plugin['settings'] += array( + 'path' => base_path() . $plugin['path'] . '/' . $plugin['name'], + ); + // Check whether library is present. + if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) { + continue; + } + } + return $plugins; +} + +/** + * Load include files for wysiwyg implemented by all modules. + * + * @param $type + * The type of includes to search for, can be 'editors'. + * @param $hook + * The hook name to invoke. + * @param $file + * An optional include file name without .inc extension to limit the search to. + * + * @see wysiwyg_get_directories(), _wysiwyg_process_include() + */ +function wysiwyg_load_includes($type = 'editors', $hook = 'editor', $file = NULL) { + // Determine implementations. + $directories = wysiwyg_get_directories($type); + $directories['wysiwyg'] = drupal_get_path('module', 'wysiwyg') . '/' . $type; + $file_list = array(); + foreach ($directories as $module => $path) { + $file_list[$module] = drupal_system_listing("/{$file}.inc\$/", $path, 'name', 0); + } + + // Load implementations. + $info = array(); + foreach (array_filter($file_list) as $module => $files) { + foreach ($files as $file) { + include_once './' . $file->uri; + $result = _wysiwyg_process_include($module, $module . '_' . $file->name, dirname($file->uri), $hook); + if (is_array($result)) { + $info = array_merge($info, $result); + } + } + } + return $info; +} + +/** + * Helper function to build paths to libraries. + * + * @param $library + * The external library name to return the path for. + * @param $base_path + * Whether to prefix the resulting path with base_path(). + * + * @return + * The path to the specified library. + * + * @ingroup libraries + */ +function wysiwyg_get_path($library, $base_path = FALSE) { + static $libraries; + + if (!isset($libraries)) { + $libraries = wysiwyg_get_libraries(); + } + if (!isset($libraries[$library])) { + // Most often, external libraries can be shared across multiple sites. + return 'sites/all/libraries/' . $library; + } + + $path = ($base_path ? base_path() : ''); + $path .= $libraries[$library]; + + return $path; +} + +/** + * Return an array of library directories. + * + * Returns an array of library directories from the all-sites directory + * (i.e. sites/all/libraries/), the profiles directory, and site-specific + * directory (i.e. sites/somesite/libraries/). The returned array will be keyed + * by the library name. Site-specific libraries are prioritized over libraries + * in the default directories. That is, if a library with the same name appears + * in both the site-wide directory and site-specific directory, only the + * site-specific version will be listed. + * + * @return + * A list of library directories. + * + * @ingroup libraries + */ +function wysiwyg_get_libraries() { + global $profile; + + // When this function is called during Drupal's initial installation process, + // the name of the profile that is about to be installed is stored in the + // global $profile variable. At all other times, the regular system variable + // contains the name of the current profile, and we can call variable_get() + // to determine the profile. + if (!isset($profile)) { + $profile = variable_get('install_profile', 'default'); + } + + $directory = 'libraries'; + $searchdir = array(); + $config = conf_path(); + + // The 'profiles' directory contains pristine collections of modules and + // themes as organized by a distribution. It is pristine in the same way + // that /modules is pristine for core; users should avoid changing anything + // there in favor of sites/all or sites/directories. + if (file_exists("profiles/$profile/$directory")) { + $searchdir[] = "profiles/$profile/$directory"; + } + + // Always search sites/all/*. + $searchdir[] = 'sites/all/' . $directory; + + // Also search sites/ /*. + if (file_exists("$config/$directory")) { + $searchdir[] = "$config/$directory"; + } + + // Retrieve list of directories. + // @todo Core: Allow to scan for directories. + $directories = array(); + $nomask = array('CVS'); + foreach ($searchdir as $dir) { + if (is_dir($dir) && $handle = opendir($dir)) { + while (FALSE !== ($file = readdir($handle))) { + if (!in_array($file, $nomask) && $file[0] != '.') { + if (is_dir("$dir/$file")) { + $directories[$file] = "$dir/$file"; + } + } + } + closedir($handle); + } + } + + return $directories; +} + +/** + * Return a list of directories by modules implementing wysiwyg_include_directory(). + * + * @param $plugintype + * The type of a plugin; can be 'editors'. + * + * @return + * An array containing module names suffixed with '_' and their defined + * directory. + * + * @see wysiwyg_load_includes(), _wysiwyg_process_include() + */ +function wysiwyg_get_directories($plugintype) { + $directories = array(); + foreach (module_implements('wysiwyg_include_directory') as $module) { + $result = module_invoke($module, 'wysiwyg_include_directory', $plugintype); + if (isset($result) && is_string($result)) { + $directories[$module] = drupal_get_path('module', $module) . '/' . $result; + } + } + return $directories; +} + +/** + * Process a single hook implementation of a wysiwyg editor. + * + * @param $module + * The module that owns the hook. + * @param $identifier + * Either the module or 'wysiwyg_' . $file->name + * @param $hook + * The name of the hook being invoked. + */ +function _wysiwyg_process_include($module, $identifier, $path, $hook) { + $function = $identifier . '_' . $hook; + if (!function_exists($function)) { + return NULL; + } + $result = $function(); + if (!isset($result) || !is_array($result)) { + return NULL; + } + + // Fill in defaults. + foreach ($result as $editor => $properties) { + $result[$editor]['module'] = $module; + $result[$editor]['name'] = $editor; + $result[$editor]['path'] = $path; + } + return $result; +} + +/** + * @} End of "defgroup wysiwyg_api". + */ + +/** + * Implements hook_features_api(). + */ +function wysiwyg_features_api() { + return array( + 'wysiwyg' => array( + 'name' => t('Wysiwyg profiles'), + 'default_hook' => 'wysiwyg_default_profiles', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + 'file' => drupal_get_path('module', 'wysiwyg') . '/wysiwyg.features.inc', + ), + ); +} +