From 7266e09b8e7bd8956161fab694827ed622b1caba Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 10 Jul 2015 10:33:03 -0400 Subject: [PATCH] CRM-16444 - Add CKEditor Toolbar Configurator --- CRM/Admin/Form/Preferences/Display.php | 10 + CRM/Admin/Page/CKEditorConfig.php | 235 ++++++++++++++++++ CRM/Core/Resources.php | 17 +- CRM/Core/xml/Menu/Admin.xml | 6 + CRM/Logging/ReportDetail.php | 2 +- js/wysiwyg/admin.ckeditor-configurator.js | 65 +++++ js/wysiwyg/crm.ckeditor.js | 16 +- .../CRM/Admin/Form/Preferences/Display.tpl | 15 +- templates/CRM/Admin/Page/CKEditorConfig.tpl | 77 ++++++ 9 files changed, 429 insertions(+), 14 deletions(-) create mode 100644 CRM/Admin/Page/CKEditorConfig.php create mode 100644 js/wysiwyg/admin.ckeditor-configurator.js create mode 100644 templates/CRM/Admin/Page/CKEditorConfig.tpl diff --git a/CRM/Admin/Form/Preferences/Display.php b/CRM/Admin/Form/Preferences/Display.php index 599272b50b..0c79ba3e63 100644 --- a/CRM/Admin/Form/Preferences/Display.php +++ b/CRM/Admin/Form/Preferences/Display.php @@ -143,6 +143,7 @@ class CRM_Admin_Form_Preferences_Display extends CRM_Admin_Form_Preferences { $extra = array(); $this->addElement('select', 'editor_id', ts('WYSIWYG Editor'), $wysiwyg_options, $extra); + $this->addElement('submit', 'ckeditor_config', ts('Configure CKEditor')); $editOptions = CRM_Core_OptionGroup::values('contact_edit_options', FALSE, FALSE, FALSE, 'AND v.filter = 0'); $this->assign('editOptions', $editOptions); @@ -188,6 +189,15 @@ class CRM_Admin_Form_Preferences_Display extends CRM_Admin_Form_Preferences { $this->_config->editor_id = $this->_params['editor_id']; $this->postProcessCommon(); + + // If "Configure CKEditor" button was clicked + if (!empty($this->_params['ckeditor_config'])) { + // Suppress the "Saved" status message and redirect to the CKEditor Config page + $session = CRM_Core_Session::singleton(); + $session->getStatus(TRUE); + $url = CRM_Utils_System::url('civicrm/admin/ckeditor', 'reset=1'); + $session->pushUserContext($url); + } } } diff --git a/CRM/Admin/Page/CKEditorConfig.php b/CRM/Admin/Page/CKEditorConfig.php new file mode 100644 index 0000000000..ef3e1679d5 --- /dev/null +++ b/CRM/Admin/Page/CKEditorConfig.php @@ -0,0 +1,235 @@ + 'moono', + 'extraPlugins' => '', + ); + + /** + * @return string + */ + public function run() { + // If the form was submitted, take appropriate action. + if (!empty($_POST['revert'])) { + self::deleteConfigFile(); + } + elseif (!empty($_POST['config'])) { + $this->save($_POST); + } + + CRM_Core_Resources::singleton() + ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js', 1) + ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/abstracttoolbarmodifier.js', 2) + ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js', 3) + ->addScriptFile('civicrm', 'js/wysiwyg/admin.ckeditor-configurator.js', 10) + ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/css/fontello.css') + ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/css/samples.css') + ->addVars('ckConfig', array( + 'plugins' => array_values($this->getCKPlugins()), + )); + + $this->assign('skins', $this->getCKSkins()); + $this->assign('skin', $this->getConfigSetting('skin')); + $this->assign('extraPlugins', $this->getConfigSetting('extraPlugins')); + $this->assign('configUrl', self::getConfigUrl()); + $this->assign('revertConfirm', htmlspecialchars(ts('Are you sure you want to revert all changes?', array('escape' => 'js')))); + + CRM_Utils_System::appendBreadCrumb(array(array( + 'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'), + 'title' => ts('Display Preferences'), + ))); + + return parent::run(); + } + + /** + * Generate the config js file based on posted data. + * + * @param array $params + */ + public function save($params) { + $config = "/**\n" + . " * CKEditor config file auto-generated by CiviCRM.\n" + . " *\n" + . " * Note: This file will be overwritten if settings are modified at:\n" + . " * @link " . CRM_Utils_System::url(CRM_Utils_System::currentPath(), NULL, TRUE, NULL, FALSE) . "\n" + . " */\n\n" + // Standardize line-endings + . preg_replace('~\R~u', "\n", $params['config']); + + // Use defaultSettings as a whitelist so we don't just insert any old junk into the file + foreach ($this->defaultSettings as $key => $default) { + if (isset($params[$key]) && strlen($params[$key])) { + $pos = strrpos($config, '};'); + $setting = "\n\tconfig.$key = '{$params[$key]}';\n"; + $config = substr_replace($config, $setting, $pos, 0); + } + } + self::saveConfigFile($config); + if (!empty($params['save'])) { + CRM_Core_Session::setStatus(ts("You may need to clear your browser's cache to see the changes in CiviCRM."), ts('CKEditor Saved'), 'success'); + } + } + + /** + * @return array + */ + private function getCKPlugins() { + $plugins = array(); + global $civicrm_root; + $pluginDir = CRM_Utils_file::addTrailingSlash($civicrm_root, '/') . 'bower_components/ckeditor/plugins'; + + foreach (glob($pluginDir . '/*', GLOB_ONLYDIR) as $dir) { + $dir = rtrim(str_replace('\\', '/', $dir), '/'); + $name = substr($dir, strrpos($dir, '/') + 1); + $dir = CRM_Utils_file::addTrailingSlash($dir, '/'); + if (is_file($dir . 'plugin.js')) { + $plugins[$name] = array( + 'id' => $name, + 'text' => ucfirst($name), + 'icon' => NULL, + ); + if (is_dir($dir . "icons")) { + if (is_file($dir . "icons/$name.png")) { + $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/$name.png"; + } + elseif (glob($dir . "icons/*.png")) { + $icon = CRM_Utils_Array::first(glob($dir . "icons/*.png")); + $icon = rtrim(str_replace('\\', '/', $icon), '/'); + $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/" . substr($icon, strrpos($icon, '/') + 1); + } + } + } + } + + return $plugins; + } + + /** + * @return array + */ + private function getCKSkins() { + $skins = array(); + global $civicrm_root; + $skinDir = CRM_Utils_file::addTrailingSlash($civicrm_root, '/') . 'bower_components/ckeditor/skins'; + foreach (glob($skinDir . '/*', GLOB_ONLYDIR) as $dir) { + $dir = rtrim(str_replace('\\', '/', $dir), '/'); + $skins[] = substr($dir, strrpos($dir, '/') + 1); + } + return $skins; + } + + /** + * @param $setting + * @return string + */ + private function getConfigSetting($setting) { + $value = CRM_Utils_Array::value($setting, $this->defaultSettings, ''); + $file = self::getConfigFile(); + if ($file) { + $contents = file_get_contents($file); + $matches = array(); + preg_match("/\sconfig\.$setting\s?=\s?'([^']*)'/", $contents, $matches); + if ($matches) { + $value = $matches[1]; + } + } + return $value; + } + + /** + * @return null|string + */ + public static function getConfigUrl() { + if (self::getConfigFile()) { + // FIXME: Basing file path off imageUploadURL sucks, but it's all we got + $url = CRM_Utils_file::addTrailingSlash(CRM_Core_Config::singleton()->imageUploadURL, '/'); + $url = str_replace('/persist/contribute/', '/persist/', $url); + return $url . SELF::CONFIG_FILENAME; + } + return NULL; + } + + /** + * @param bool $checkIfFileExists + * If false, this fn will return fileName even if it doesn't exist + * + * @return null|string + */ + public static function getConfigFile($checkIfFileExists = TRUE) { + // FIXME: Basing file path off imageUploadDir sucks, but it's all we got + $dir = CRM_Core_Config::singleton()->imageUploadDir; + $dir = CRM_Utils_file::addTrailingSlash(str_replace('\\', '/', $dir), '/'); + $dir = str_replace('/persist/contribute/', '/persist/', $dir); + $fileName = $dir . SELF::CONFIG_FILENAME; + return !$checkIfFileExists || is_file($fileName) ? $fileName : NULL; + } + + /** + * @param string $contents + */ + public static function saveConfigFile($contents) { + $file = self::getConfigFile(FALSE); + file_put_contents($file, $contents); + } + + /** + * Delete SELF::CONFIG_FILENAME + */ + public static function deleteConfigFile() { + $file = self::getConfigFile(); + if ($file) { + unlink($file); + } + } + +} diff --git a/CRM/Core/Resources.php b/CRM/Core/Resources.php index cd2945b5c5..67034a9e6e 100644 --- a/CRM/Core/Resources.php +++ b/CRM/Core/Resources.php @@ -582,14 +582,17 @@ class CRM_Core_Resources { // Add resources from coreResourceList $jsWeight = -9999; - foreach ($this->coreResourceList() as $file) { - if (substr($file, -2) == 'js') { + foreach ($this->coreResourceList() as $item) { + if (is_array($item)) { + $this->addSetting($item); + } + elseif (substr($item, -2) == 'js') { // Don't bother looking for ts() calls in packages, there aren't any - $translate = (substr($file, 0, 3) == 'js/'); - $this->addScriptFile('civicrm', $file, $jsWeight++, $region, $translate); + $translate = (substr($item, 0, 3) == 'js/'); + $this->addScriptFile('civicrm', $item, $jsWeight++, $region, $translate); } else { - $this->addStyleFile('civicrm', $file, -100, $region); + $this->addStyleFile('civicrm', $item, -100, $region); } } @@ -717,6 +720,10 @@ class CRM_Core_Resources { if ($editor == "CKEditor") { $items[] = "bower_components/ckeditor/ckeditor.js"; $items[] = "js/wysiwyg/crm.ckeditor.js"; + $ckConfig = CRM_Admin_Page_CKEditorConfig::getConfigUrl(); + if ($ckConfig) { + $items[] = array('config' => array('CKEditorCustomConfig' => $ckConfig)); + } } // These scripts are only needed by back-office users diff --git a/CRM/Core/xml/Menu/Admin.xml b/CRM/Core/xml/Menu/Admin.xml index 632d87913e..971f80849c 100644 --- a/CRM/Core/xml/Menu/Admin.xml +++ b/CRM/Core/xml/Menu/Admin.xml @@ -759,4 +759,10 @@ CRM_Badge_Form_Layout administer CiviCRM + + civicrm/admin/ckeditor + Configure CKEditor + CRM_Admin_Page_CKEditorConfig + administer CiviCRM + diff --git a/CRM/Logging/ReportDetail.php b/CRM/Logging/ReportDetail.php index 921c35bf94..d76664fb78 100644 --- a/CRM/Logging/ReportDetail.php +++ b/CRM/Logging/ReportDetail.php @@ -239,7 +239,7 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form { $q = "reset=1&log_conn_id={$this->log_conn_id}&log_date={$this->log_date}"; $this->assign('revertURL', CRM_Report_Utils_Report::getNextUrl($this->detail, "$q&revert=1", FALSE, TRUE)); - $this->assign('revertConfirm', ts('Are you sure you want to revert all these changes?')); + $this->assign('revertConfirm', ts('Are you sure you want to revert all changes?')); } } diff --git a/js/wysiwyg/admin.ckeditor-configurator.js b/js/wysiwyg/admin.ckeditor-configurator.js new file mode 100644 index 0000000000..0ebbb59373 --- /dev/null +++ b/js/wysiwyg/admin.ckeditor-configurator.js @@ -0,0 +1,65 @@ +// https://civicrm.org/licensing +(function($, _) { + 'use strict'; + + // Weird conflict with drupal styles + $('body').removeClass('toolbar'); + + function format(item) { + var icon = ''; + if (item.icon) { + icon = ''; + } + return icon + ' ' + item.text; + } + + $('#extraPlugins').crmSelect2({ + multiple: true, + closeOnSelect: false, + data: CRM.vars.ckConfig.plugins, + escapeMarkup: _.identity, + formatResult: format, + formatSelection: format + }); + + var toolbarModifier = new ToolbarConfigurator.ToolbarModifier( 'editor-basic' ); + + toolbarModifier.init(_.noop); + + CKEDITOR.document.getById( 'toolbarModifierWrapper' ).append( toolbarModifier.mainContainer ); + + $('.toolbar button:last', '#toolbarModifierWrapper').hide(); + + $(function() { + var selectorOpen = false, + changedWhileOpen = false; + + $('#toolbarModifierForm') + // The buttons in the configurator are not submit buttons! + .on('click', 'button', function(e) { + e.preventDefault(); + }) + .on('submit', function(e) { + $('.toolbar button:last', '#toolbarModifierWrapper')[0].click(); + $('.configContainer textarea', '#toolbarModifierWrapper').attr('name', 'config'); + }) + .on('change', '.config-param', function(e) { + changedWhileOpen = true; + if (!selectorOpen) { + $('#toolbarModifierForm').submit().block(); + } + }) + // Debounce the change event so it only fires after the multiselect is closed + .on('select2-open', 'input.config-param', function(e) { + selectorOpen = true; + changedWhileOpen = false; + }) + .on('select2-close', 'input.config-param', function(e) { + selectorOpen = false; + if (changedWhileOpen) { + $(this).change(); + } + }); + }); + +})(CRM.$, CRM._); \ No newline at end of file diff --git a/js/wysiwyg/crm.ckeditor.js b/js/wysiwyg/crm.ckeditor.js index fe7fc499d8..5d418e57b3 100644 --- a/js/wysiwyg/crm.ckeditor.js +++ b/js/wysiwyg/crm.ckeditor.js @@ -16,15 +16,17 @@ browseUrl = CRM.config.userFrameworkResourceURL + "packages/kcfinder/browse.php?cms=civicrm", uploadUrl = CRM.config.userFrameworkResourceURL + "packages/kcfinder/upload.php?cms=civicrm"; if ($(item).length) { - editor = CKEDITOR.replace($(item)[0]); + editor = CKEDITOR.replace($(item)[0], { + filebrowserBrowseUrl: browseUrl + '&type=files', + filebrowserImageBrowseUrl: browseUrl + '&type=images', + filebrowserFlashBrowseUrl: browseUrl + '&type=flash', + filebrowserUploadUrl: uploadUrl + '&type=files', + filebrowserImageUploadUrl: uploadUrl + '&type=images', + filebrowserFlashUploadUrl: uploadUrl + '&type=flash', + customConfig: CRM.config.CKEditorCustomConfig + }); } if (editor) { - editor.config.filebrowserBrowseUrl = browseUrl + '&type=files'; - editor.config.filebrowserImageBrowseUrl = browseUrl + '&type=images'; - editor.config.filebrowserFlashBrowseUrl = browseUrl + '&type=flash'; - editor.config.filebrowserUploadUrl = uploadUrl + '&type=files'; - editor.config.filebrowserImageUploadUrl = uploadUrl + '&type=images'; - editor.config.filebrowserFlashUploadUrl = uploadUrl + '&type=flash'; editor.on('focus', function() { $(item).trigger('focus'); }); diff --git a/templates/CRM/Admin/Form/Preferences/Display.tpl b/templates/CRM/Admin/Form/Preferences/Display.tpl index de6c92fb27..7ae0f08fb8 100644 --- a/templates/CRM/Admin/Form/Preferences/Display.tpl +++ b/templates/CRM/Admin/Form/Preferences/Display.tpl @@ -155,7 +155,14 @@ {$form.editor_id.label} - {$form.editor_id.html} + + {$form.editor_id.html} +   + + + {$form.ckeditor_config.html} + +   @@ -228,6 +235,12 @@ placeholder: 'ui-state-highlight', update: getSorting }); + + function showCKEditorConfig() { + console.log($(this).val()); + $('.crm-preferences-display-form-block-editor_id .crm-button').toggle($(this).val() == '2'); + } + $('select[name=editor_id]').each(showCKEditorConfig).change(showCKEditorConfig); }); {/literal} diff --git a/templates/CRM/Admin/Page/CKEditorConfig.tpl b/templates/CRM/Admin/Page/CKEditorConfig.tpl new file mode 100644 index 0000000000..049fbcc894 --- /dev/null +++ b/templates/CRM/Admin/Page/CKEditorConfig.tpl @@ -0,0 +1,77 @@ +{* + +--------------------------------------------------------------------+ + | CiviCRM version 4.6 | + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC (c) 2004-2015 | + +--------------------------------------------------------------------+ + | This file is a part of CiviCRM. | + | | + | CiviCRM is free software; you can copy, modify, and distribute it | + | under the terms of the GNU Affero General Public License | + | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | + | | + | CiviCRM is distributed in the hope that it will be useful, but | + | WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | + | See the GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public | + | License and the CiviCRM Licensing Exception along | + | with this program; if not, contact CiviCRM LLC | + | at info[AT]civicrm[DOT]org. If you have questions about the | + | GNU Affero General Public License or the licensing of CiviCRM, | + | see the CiviCRM license FAQ at http://civicrm.org/licensing | + +--------------------------------------------------------------------+ +*} + +{* Force the custom config file to reload by appending a new query string *} + + +
+
+ + +    + + +
+ +
+
+
+
+ +
+
+
+
+
+ +
+ + + + + + +
+
-- 2.25.1