Extract CKEditor into its own core extension
[civicrm-core.git] / ext / ckeditor4 / CRM / Ckeditor4 / Form / 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
301b8b4b
SL
18use CRM_Ckeditor4_ExtensionUtil as E;
19
7266e09b 20/**
f7d20926 21 * Form for configuring CKEditor options.
7266e09b 22 */
301b8b4b 23class CRM_Ckeditor4_Form_CKEditorConfig extends CRM_Core_Form {
7266e09b 24
7ad5ae6a 25 const CONFIG_FILEPATH = '[civicrm.files]/persist/crm-ckeditor-';
7266e09b 26
88aae6d4
A
27 /**
28 * @var bool
29 */
30 public $submitOnce = TRUE;
31
7266e09b 32 /**
3b7ceeb2 33 * Settings that cannot be configured in "advanced options"
7266e09b
CW
34 *
35 * @var array
36 */
be2fb01f 37 public $blackList = [
3b7ceeb2
CW
38 'on',
39 'skin',
40 'extraPlugins',
41 'toolbarGroups',
42 'removeButtons',
1ea6de73 43 'customConfig',
3b7ceeb2
CW
44 'filebrowserBrowseUrl',
45 'filebrowserImageBrowseUrl',
46 'filebrowserFlashBrowseUrl',
47 'filebrowserUploadUrl',
48 'filebrowserImageUploadUrl',
49 'filebrowserFlashUploadUrl',
be2fb01f 50 ];
7266e09b
CW
51
52 /**
cd2d6fc2 53 * Prepare form
7266e09b 54 */
f7d20926 55 public function preProcess() {
cd2d6fc2 56 CRM_Utils_Request::retrieve('preset', 'String', $this, FALSE, 'default', 'GET');
7266e09b 57
5d52684a
CW
58 CRM_Utils_System::appendBreadCrumb([
59 [
60 'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'),
61 'title' => ts('Display Preferences'),
62 ],
63 ]);
64
65 // Initial build
66 if (empty($_POST['qfKey'])) {
67 $this->addResources();
68 }
69 }
70
71 /**
72 * Add resources during initial build or rebuild
73 *
74 * @throws CRM_Core_Exception
75 */
76 public function addResources() {
3b7ceeb2
CW
77 $settings = $this->getConfigSettings();
78
7266e09b 79 CRM_Core_Resources::singleton()
68e7ff1c 80 ->addScriptFile('civicrm', 'bower_components/ckeditor/ckeditor.js', 0, 'page-header')
7266e09b
CW
81 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js', 1)
82 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/abstracttoolbarmodifier.js', 2)
83 ->addScriptFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js', 3)
301b8b4b 84 ->addScriptFile('ckeditor4', 'js/admin.ckeditor-configurator.js', 10)
7266e09b
CW
85 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/css/fontello.css')
86 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/css/samples.css')
be2fb01f 87 ->addVars('ckConfig', [
7266e09b 88 'plugins' => array_values($this->getCKPlugins()),
3b7ceeb2
CW
89 'blacklist' => $this->blackList,
90 'settings' => $settings,
be2fb01f 91 ]);
7266e09b 92
cd2d6fc2 93 $configUrl = self::getConfigUrl($this->get('preset')) ?: self::getConfigUrl('default');
7ad5ae6a 94
cd2d6fc2 95 $this->assign('preset', $this->get('preset'));
7ad5ae6a 96 $this->assign('presets', CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'));
7266e09b 97 $this->assign('skins', $this->getCKSkins());
3b7ceeb2
CW
98 $this->assign('skin', CRM_Utils_Array::value('skin', $settings));
99 $this->assign('extraPlugins', CRM_Utils_Array::value('extraPlugins', $settings));
7ad5ae6a 100 $this->assign('configUrl', $configUrl);
cd2d6fc2
CW
101 }
102
103 /**
104 * Build form
105 */
106 public function buildQuickForm() {
107 $revertConfirm = json_encode(ts('Are you sure you want to revert all changes?'));
108 $this->addButtons([
109 [
110 'type' => 'next',
111 'name' => ts('Save'),
112 ],
5d52684a
CW
113 // Hidden button used to refresh form
114 [
115 'type' => 'submit',
116 'class' => 'hiddenElement',
117 'name' => ts('Save'),
118 ],
cd2d6fc2
CW
119 [
120 'type' => 'cancel',
121 'name' => ts('Cancel'),
122 ],
123 [
124 'type' => 'refresh',
125 'name' => ts('Revert to Default'),
126 'icon' => 'fa-undo',
127 'js' => ['onclick' => "return confirm($revertConfirm);"],
128 ],
129 ]);
130 }
7266e09b 131
cd2d6fc2
CW
132 /**
133 * Handle form submission
134 */
135 public function postProcess() {
136 if (!empty($_POST[$this->getButtonName('refresh')])) {
137 self::deleteConfigFile($this->get('preset'));
138 self::setConfigDefault();
139 }
140 else {
141 if (!empty($_POST[$this->getButtonName('next')])) {
142 $this->save($_POST);
5d52684a
CW
143 CRM_Core_Session::setStatus(ts("You may need to clear your browser's cache to see the changes in CiviCRM."), ts('CKEditor Saved'), 'success');
144 }
145 // The "submit" hidden button saves but does not redirect
146 if (!empty($_POST[$this->getButtonName('submit')])) {
147 $this->save($_POST);
148 $this->addResources();
149 }
150 else {
151 CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin/ckeditor', ['reset' => 1]));
cd2d6fc2 152 }
cd2d6fc2 153 }
7266e09b
CW
154 }
155
156 /**
157 * Generate the config js file based on posted data.
158 *
159 * @param array $params
160 */
161 public function save($params) {
5e0b4b77 162 $config = self::fileHeader()
7266e09b
CW
163 // Standardize line-endings
164 . preg_replace('~\R~u', "\n", $params['config']);
165
1ea6de73 166 // Generate a whitelist of allowed config params
301b8b4b 167 $allOptions = json_decode(file_get_contents(E::path('js/ck-options.json')), TRUE);
1ea6de73
CW
168 // These two aren't really blacklisted they're just in a different part of the form
169 $blackList = array_diff($this->blackList, ['skin', 'extraPlugins']);
170 // All options minus blacklist = whitelist
171 $whiteList = array_diff(array_column($allOptions, 'id'), $blackList);
172
173 // Save whitelisted params starting with config_
3b7ceeb2
CW
174 foreach ($params as $key => $val) {
175 $val = trim($val);
1ea6de73 176 if (strpos($key, 'config_') === 0 && strlen($val) && in_array(substr($key, 7), $whiteList)) {
b3e9532a 177 if ($val != 'true' && $val != 'false' && $val != 'null' && $val[0] != '{' && $val[0] != '[' && !is_numeric($val)) {
1ea6de73 178 $val = '"' . $val . '"';
3b7ceeb2 179 }
1ea6de73
CW
180 try {
181 $val = CRM_Utils_JS::encode(CRM_Utils_JS::decode($val, TRUE));
182 $pos = strrpos($config, '};');
183 $key = preg_replace('/^config_/', 'config.', $key);
184 $setting = "\n\t{$key} = {$val};\n";
185 $config = substr_replace($config, $setting, $pos, 0);
cd2d6fc2
CW
186 }
187 catch (CRM_Core_Exception $e) {
188 CRM_Core_Session::setStatus(ts("Error saving %1.", [1 => $key]), ts('Invalid Value'), 'error');
4ce3e897 189 }
7266e09b
CW
190 }
191 }
cd2d6fc2 192 self::saveConfigFile($this->get('preset'), $config);
7266e09b
CW
193 }
194
195 /**
47b59d65 196 * Get available CKEditor plugin list.
ce064e4f 197 *
7266e09b
CW
198 * @return array
199 */
200 private function getCKPlugins() {
be2fb01f 201 $plugins = [];
47b59d65 202 $pluginDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/plugins');
7266e09b
CW
203
204 foreach (glob($pluginDir . '/*', GLOB_ONLYDIR) as $dir) {
205 $dir = rtrim(str_replace('\\', '/', $dir), '/');
206 $name = substr($dir, strrpos($dir, '/') + 1);
c33f1df1 207 $dir = CRM_Utils_File::addTrailingSlash($dir, '/');
7266e09b 208 if (is_file($dir . 'plugin.js')) {
be2fb01f 209 $plugins[$name] = [
7266e09b
CW
210 'id' => $name,
211 'text' => ucfirst($name),
212 'icon' => NULL,
be2fb01f 213 ];
7266e09b
CW
214 if (is_dir($dir . "icons")) {
215 if (is_file($dir . "icons/$name.png")) {
216 $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/$name.png";
217 }
218 elseif (glob($dir . "icons/*.png")) {
219 $icon = CRM_Utils_Array::first(glob($dir . "icons/*.png"));
220 $icon = rtrim(str_replace('\\', '/', $icon), '/');
221 $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/" . substr($icon, strrpos($icon, '/') + 1);
222 }
223 }
224 }
225 }
226
227 return $plugins;
228 }
229
230 /**
47b59d65 231 * Get available CKEditor skins.
ce064e4f 232 *
7266e09b
CW
233 * @return array
234 */
235 private function getCKSkins() {
be2fb01f 236 $skins = [];
47b59d65 237 $skinDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/skins');
7266e09b
CW
238 foreach (glob($skinDir . '/*', GLOB_ONLYDIR) as $dir) {
239 $dir = rtrim(str_replace('\\', '/', $dir), '/');
240 $skins[] = substr($dir, strrpos($dir, '/') + 1);
241 }
242 return $skins;
243 }
244
245 /**
3b7ceeb2 246 * @return array
7266e09b 247 */
3b7ceeb2 248 private function getConfigSettings() {
be2fb01f 249 $matches = $result = [];
cd2d6fc2 250 $file = self::getConfigFile($this->get('preset')) ?: self::getConfigFile('default');
3b7ceeb2 251 $result['skin'] = 'moono';
7266e09b
CW
252 if ($file) {
253 $contents = file_get_contents($file);
3b7ceeb2
CW
254 preg_match_all("/\sconfig\.(\w+)\s?=\s?([^;]*);/", $contents, $matches);
255 foreach ($matches[1] as $i => $match) {
256 $result[$match] = trim($matches[2][$i], ' "\'');
7266e09b
CW
257 }
258 }
3b7ceeb2 259 return $result;
7266e09b
CW
260 }
261
262 /**
7ad5ae6a
CW
263 * @param string $preset
264 * Omit to get an array of all presets
265 * @return array|null|string
7266e09b 266 */
7ad5ae6a 267 public static function getConfigUrl($preset = NULL) {
be2fb01f 268 $items = [];
7ad5ae6a
CW
269 $presets = CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'name');
270 foreach ($presets as $key => $name) {
271 if (self::getConfigFile($name)) {
272 $items[$name] = Civi::paths()->getUrl(self::CONFIG_FILEPATH . $name . '.js', 'absolute');
273 }
7266e09b 274 }
7ad5ae6a 275 return $preset ? CRM_Utils_Array::value($preset, $items) : $items;
7266e09b
CW
276 }
277
278 /**
7ad5ae6a 279 * @param string $preset
7266e09b
CW
280 *
281 * @return null|string
282 */
7ad5ae6a
CW
283 public static function getConfigFile($preset = 'default') {
284 $fileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
285 return is_file($fileName) ? $fileName : NULL;
7266e09b
CW
286 }
287
288 /**
62d3ee27 289 * @param string $preset
7266e09b
CW
290 * @param string $contents
291 */
7ad5ae6a
CW
292 public static function saveConfigFile($preset, $contents) {
293 $file = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
7266e09b
CW
294 file_put_contents($file, $contents);
295 }
296
297 /**
ce064e4f 298 * Delete config file.
7266e09b 299 */
7ad5ae6a
CW
300 public static function deleteConfigFile($preset) {
301 $file = self::getConfigFile($preset);
7266e09b
CW
302 if ($file) {
303 unlink($file);
304 }
305 }
306
5e0b4b77
CW
307 /**
308 * Create default config file if it doesn't exist
309 */
310 public static function setConfigDefault() {
311 if (!self::getConfigFile()) {
312 $config = self::fileHeader() . "CKEDITOR.editorConfig = function( config ) {\n\tconfig.allowedContent = true;\n};\n";
313 // Make sure directories exist
314 if (!is_dir(Civi::paths()->getPath('[civicrm.files]/persist'))) {
315 mkdir(Civi::paths()->getPath('[civicrm.files]/persist'));
316 }
6aa27904 317 $newFileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . 'default.js');
5e0b4b77
CW
318 file_put_contents($newFileName, $config);
319 }
320 }
321
322 /**
323 * @return string
324 */
325 public static function fileHeader() {
326 return "/**\n"
327 . " * CKEditor config file auto-generated by CiviCRM (" . date('Y-m-d H:i:s') . ").\n"
328 . " *\n"
329 . " * Note: This file will be overwritten if settings are modified at:\n"
330 . " * @link " . CRM_Utils_System::url('civicrm/admin/ckeditor', NULL, TRUE, NULL, FALSE) . "\n"
331 . " */\n";
332 }
333
7266e09b 334}