Merge pull request #17209 from civicrm/5.25
[civicrm-core.git] / CRM / Admin / Page / CKEditorConfig.php
CommitLineData
7266e09b
CW
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
7266e09b 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
7266e09b
CW
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
7266e09b
CW
16 */
17
18/**
ce064e4f 19 * Page for configuring CKEditor options.
7266e09b
CW
20 *
21 * Note that while this is implemented as a CRM_Core_Page, it is actually a form.
47b59d65 22 * Because the form needs to be submitted and refreshed via javascript, it seemed like
7266e09b
CW
23 * Quickform and CRM_Core_Form/Controller might get in the way.
24 */
25class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page {
26
7ad5ae6a 27 const CONFIG_FILEPATH = '[civicrm.files]/persist/crm-ckeditor-';
7266e09b
CW
28
29 /**
3b7ceeb2 30 * Settings that cannot be configured in "advanced options"
7266e09b
CW
31 *
32 * @var array
33 */
be2fb01f 34 public $blackList = [
3b7ceeb2
CW
35 'on',
36 'skin',
37 'extraPlugins',
38 'toolbarGroups',
39 'removeButtons',
40 'filebrowserBrowseUrl',
41 'filebrowserImageBrowseUrl',
42 'filebrowserFlashBrowseUrl',
43 'filebrowserUploadUrl',
44 'filebrowserImageUploadUrl',
45 'filebrowserFlashUploadUrl',
be2fb01f 46 ];
7266e09b 47
7ad5ae6a
CW
48 public $preset;
49
7266e09b 50 /**
ce064e4f 51 * Run page.
52 *
7266e09b
CW
53 * @return string
54 */
55 public function run() {
7ad5ae6a
CW
56 $this->preset = CRM_Utils_Array::value('preset', $_REQUEST, 'default');
57
7266e09b
CW
58 // If the form was submitted, take appropriate action.
59 if (!empty($_POST['revert'])) {
7ad5ae6a 60 self::deleteConfigFile($this->preset);
5e0b4b77 61 self::setConfigDefault();
7266e09b
CW
62 }
63 elseif (!empty($_POST['config'])) {
64 $this->save($_POST);
65 }
66
3b7ceeb2
CW
67 $settings = $this->getConfigSettings();
68
7266e09b 69 CRM_Core_Resources::singleton()
68e7ff1c 70 ->addScriptFile('civicrm', 'bower_components/ckeditor/ckeditor.js', 0, 'page-header')
7266e09b
CW
71 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js', 1)
72 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/abstracttoolbarmodifier.js', 2)
73 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js', 3)
74 ->addScriptFile('civicrm', 'js/wysiwyg/admin.ckeditor-configurator.js', 10)
75 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/css/fontello.css')
76 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/css/samples.css')
be2fb01f 77 ->addVars('ckConfig', [
7266e09b 78 'plugins' => array_values($this->getCKPlugins()),
3b7ceeb2
CW
79 'blacklist' => $this->blackList,
80 'settings' => $settings,
be2fb01f 81 ]);
7266e09b 82
6aa27904 83 $configUrl = self::getConfigUrl($this->preset) ?: self::getConfigUrl('default');
7ad5ae6a
CW
84
85 $this->assign('preset', $this->preset);
86 $this->assign('presets', CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'));
7266e09b 87 $this->assign('skins', $this->getCKSkins());
3b7ceeb2
CW
88 $this->assign('skin', CRM_Utils_Array::value('skin', $settings));
89 $this->assign('extraPlugins', CRM_Utils_Array::value('extraPlugins', $settings));
7ad5ae6a 90 $this->assign('configUrl', $configUrl);
be2fb01f 91 $this->assign('revertConfirm', htmlspecialchars(ts('Are you sure you want to revert all changes?', ['escape' => 'js'])));
7266e09b 92
5d4fcf54
TO
93 CRM_Utils_System::appendBreadCrumb([
94 [
95 'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'),
96 'title' => ts('Display Preferences'),
97 ],
98 ]);
7266e09b
CW
99
100 return parent::run();
101 }
102
103 /**
104 * Generate the config js file based on posted data.
105 *
106 * @param array $params
107 */
108 public function save($params) {
5e0b4b77 109 $config = self::fileHeader()
7266e09b
CW
110 // Standardize line-endings
111 . preg_replace('~\R~u', "\n", $params['config']);
112
3b7ceeb2
CW
113 // Use all params starting with config_
114 foreach ($params as $key => $val) {
115 $val = trim($val);
116 if (strpos($key, 'config_') === 0 && strlen($val)) {
b3e9532a 117 if ($val != 'true' && $val != 'false' && $val != 'null' && $val[0] != '{' && $val[0] != '[' && !is_numeric($val)) {
7b36b801 118 $val = json_encode($val, JSON_UNESCAPED_SLASHES);
3b7ceeb2 119 }
4ce3e897
CW
120 elseif ($val[0] == '{' || $val[0] == '[') {
121 if (!is_array(json_decode($val, TRUE))) {
122 // Invalid JSON. Do not save.
123 continue;
124 }
125 }
7266e09b 126 $pos = strrpos($config, '};');
3b7ceeb2
CW
127 $key = preg_replace('/^config_/', 'config.', $key);
128 $setting = "\n\t{$key} = {$val};\n";
7266e09b
CW
129 $config = substr_replace($config, $setting, $pos, 0);
130 }
131 }
7ad5ae6a 132 self::saveConfigFile($this->preset, $config);
7266e09b
CW
133 if (!empty($params['save'])) {
134 CRM_Core_Session::setStatus(ts("You may need to clear your browser's cache to see the changes in CiviCRM."), ts('CKEditor Saved'), 'success');
135 }
136 }
137
138 /**
47b59d65 139 * Get available CKEditor plugin list.
ce064e4f 140 *
7266e09b
CW
141 * @return array
142 */
143 private function getCKPlugins() {
be2fb01f 144 $plugins = [];
47b59d65 145 $pluginDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/plugins');
7266e09b
CW
146
147 foreach (glob($pluginDir . '/*', GLOB_ONLYDIR) as $dir) {
148 $dir = rtrim(str_replace('\\', '/', $dir), '/');
149 $name = substr($dir, strrpos($dir, '/') + 1);
c33f1df1 150 $dir = CRM_Utils_File::addTrailingSlash($dir, '/');
7266e09b 151 if (is_file($dir . 'plugin.js')) {
be2fb01f 152 $plugins[$name] = [
7266e09b
CW
153 'id' => $name,
154 'text' => ucfirst($name),
155 'icon' => NULL,
be2fb01f 156 ];
7266e09b
CW
157 if (is_dir($dir . "icons")) {
158 if (is_file($dir . "icons/$name.png")) {
159 $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/$name.png";
160 }
161 elseif (glob($dir . "icons/*.png")) {
162 $icon = CRM_Utils_Array::first(glob($dir . "icons/*.png"));
163 $icon = rtrim(str_replace('\\', '/', $icon), '/');
164 $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/" . substr($icon, strrpos($icon, '/') + 1);
165 }
166 }
167 }
168 }
169
170 return $plugins;
171 }
172
173 /**
47b59d65 174 * Get available CKEditor skins.
ce064e4f 175 *
7266e09b
CW
176 * @return array
177 */
178 private function getCKSkins() {
be2fb01f 179 $skins = [];
47b59d65 180 $skinDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/skins');
7266e09b
CW
181 foreach (glob($skinDir . '/*', GLOB_ONLYDIR) as $dir) {
182 $dir = rtrim(str_replace('\\', '/', $dir), '/');
183 $skins[] = substr($dir, strrpos($dir, '/') + 1);
184 }
185 return $skins;
186 }
187
188 /**
3b7ceeb2 189 * @return array
7266e09b 190 */
3b7ceeb2 191 private function getConfigSettings() {
be2fb01f 192 $matches = $result = [];
6aa27904 193 $file = self::getConfigFile($this->preset) ?: self::getConfigFile('default');
3b7ceeb2 194 $result['skin'] = 'moono';
7266e09b
CW
195 if ($file) {
196 $contents = file_get_contents($file);
3b7ceeb2
CW
197 preg_match_all("/\sconfig\.(\w+)\s?=\s?([^;]*);/", $contents, $matches);
198 foreach ($matches[1] as $i => $match) {
199 $result[$match] = trim($matches[2][$i], ' "\'');
7266e09b
CW
200 }
201 }
3b7ceeb2 202 return $result;
7266e09b
CW
203 }
204
205 /**
7ad5ae6a
CW
206 * @param string $preset
207 * Omit to get an array of all presets
208 * @return array|null|string
7266e09b 209 */
7ad5ae6a 210 public static function getConfigUrl($preset = NULL) {
be2fb01f 211 $items = [];
7ad5ae6a
CW
212 $presets = CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'name');
213 foreach ($presets as $key => $name) {
214 if (self::getConfigFile($name)) {
215 $items[$name] = Civi::paths()->getUrl(self::CONFIG_FILEPATH . $name . '.js', 'absolute');
216 }
7266e09b 217 }
7ad5ae6a 218 return $preset ? CRM_Utils_Array::value($preset, $items) : $items;
7266e09b
CW
219 }
220
221 /**
7ad5ae6a 222 * @param string $preset
7266e09b
CW
223 *
224 * @return null|string
225 */
7ad5ae6a
CW
226 public static function getConfigFile($preset = 'default') {
227 $fileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
228 return is_file($fileName) ? $fileName : NULL;
7266e09b
CW
229 }
230
231 /**
62d3ee27 232 * @param string $preset
7266e09b
CW
233 * @param string $contents
234 */
7ad5ae6a
CW
235 public static function saveConfigFile($preset, $contents) {
236 $file = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
7266e09b
CW
237 file_put_contents($file, $contents);
238 }
239
240 /**
ce064e4f 241 * Delete config file.
7266e09b 242 */
7ad5ae6a
CW
243 public static function deleteConfigFile($preset) {
244 $file = self::getConfigFile($preset);
7266e09b
CW
245 if ($file) {
246 unlink($file);
247 }
248 }
249
5e0b4b77
CW
250 /**
251 * Create default config file if it doesn't exist
252 */
253 public static function setConfigDefault() {
254 if (!self::getConfigFile()) {
255 $config = self::fileHeader() . "CKEDITOR.editorConfig = function( config ) {\n\tconfig.allowedContent = true;\n};\n";
256 // Make sure directories exist
257 if (!is_dir(Civi::paths()->getPath('[civicrm.files]/persist'))) {
258 mkdir(Civi::paths()->getPath('[civicrm.files]/persist'));
259 }
6aa27904 260 $newFileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . 'default.js');
5e0b4b77
CW
261 file_put_contents($newFileName, $config);
262 }
263 }
264
265 /**
266 * @return string
267 */
268 public static function fileHeader() {
269 return "/**\n"
270 . " * CKEditor config file auto-generated by CiviCRM (" . date('Y-m-d H:i:s') . ").\n"
271 . " *\n"
272 . " * Note: This file will be overwritten if settings are modified at:\n"
273 . " * @link " . CRM_Utils_System::url('civicrm/admin/ckeditor', NULL, TRUE, NULL, FALSE) . "\n"
274 . " */\n";
275 }
276
7266e09b 277}