Extract CKEditor into its own core extension
[civicrm-core.git] / ext / ckeditor4 / CRM / Ckeditor4 / Form / CKEditorConfig.php
... / ...
CommitLineData
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18use CRM_Ckeditor4_ExtensionUtil as E;
19
20/**
21 * Form for configuring CKEditor options.
22 */
23class CRM_Ckeditor4_Form_CKEditorConfig extends CRM_Core_Form {
24
25 const CONFIG_FILEPATH = '[civicrm.files]/persist/crm-ckeditor-';
26
27 /**
28 * @var bool
29 */
30 public $submitOnce = TRUE;
31
32 /**
33 * Settings that cannot be configured in "advanced options"
34 *
35 * @var array
36 */
37 public $blackList = [
38 'on',
39 'skin',
40 'extraPlugins',
41 'toolbarGroups',
42 'removeButtons',
43 'customConfig',
44 'filebrowserBrowseUrl',
45 'filebrowserImageBrowseUrl',
46 'filebrowserFlashBrowseUrl',
47 'filebrowserUploadUrl',
48 'filebrowserImageUploadUrl',
49 'filebrowserFlashUploadUrl',
50 ];
51
52 /**
53 * Prepare form
54 */
55 public function preProcess() {
56 CRM_Utils_Request::retrieve('preset', 'String', $this, FALSE, 'default', 'GET');
57
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() {
77 $settings = $this->getConfigSettings();
78
79 CRM_Core_Resources::singleton()
80 ->addScriptFile('civicrm', 'bower_components/ckeditor/ckeditor.js', 0, 'page-header')
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)
84 ->addScriptFile('ckeditor4', 'js/admin.ckeditor-configurator.js', 10)
85 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/css/fontello.css')
86 ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/css/samples.css')
87 ->addVars('ckConfig', [
88 'plugins' => array_values($this->getCKPlugins()),
89 'blacklist' => $this->blackList,
90 'settings' => $settings,
91 ]);
92
93 $configUrl = self::getConfigUrl($this->get('preset')) ?: self::getConfigUrl('default');
94
95 $this->assign('preset', $this->get('preset'));
96 $this->assign('presets', CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'));
97 $this->assign('skins', $this->getCKSkins());
98 $this->assign('skin', CRM_Utils_Array::value('skin', $settings));
99 $this->assign('extraPlugins', CRM_Utils_Array::value('extraPlugins', $settings));
100 $this->assign('configUrl', $configUrl);
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 ],
113 // Hidden button used to refresh form
114 [
115 'type' => 'submit',
116 'class' => 'hiddenElement',
117 'name' => ts('Save'),
118 ],
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 }
131
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);
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]));
152 }
153 }
154 }
155
156 /**
157 * Generate the config js file based on posted data.
158 *
159 * @param array $params
160 */
161 public function save($params) {
162 $config = self::fileHeader()
163 // Standardize line-endings
164 . preg_replace('~\R~u', "\n", $params['config']);
165
166 // Generate a whitelist of allowed config params
167 $allOptions = json_decode(file_get_contents(E::path('js/ck-options.json')), TRUE);
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_
174 foreach ($params as $key => $val) {
175 $val = trim($val);
176 if (strpos($key, 'config_') === 0 && strlen($val) && in_array(substr($key, 7), $whiteList)) {
177 if ($val != 'true' && $val != 'false' && $val != 'null' && $val[0] != '{' && $val[0] != '[' && !is_numeric($val)) {
178 $val = '"' . $val . '"';
179 }
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);
186 }
187 catch (CRM_Core_Exception $e) {
188 CRM_Core_Session::setStatus(ts("Error saving %1.", [1 => $key]), ts('Invalid Value'), 'error');
189 }
190 }
191 }
192 self::saveConfigFile($this->get('preset'), $config);
193 }
194
195 /**
196 * Get available CKEditor plugin list.
197 *
198 * @return array
199 */
200 private function getCKPlugins() {
201 $plugins = [];
202 $pluginDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/plugins');
203
204 foreach (glob($pluginDir . '/*', GLOB_ONLYDIR) as $dir) {
205 $dir = rtrim(str_replace('\\', '/', $dir), '/');
206 $name = substr($dir, strrpos($dir, '/') + 1);
207 $dir = CRM_Utils_File::addTrailingSlash($dir, '/');
208 if (is_file($dir . 'plugin.js')) {
209 $plugins[$name] = [
210 'id' => $name,
211 'text' => ucfirst($name),
212 'icon' => NULL,
213 ];
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 /**
231 * Get available CKEditor skins.
232 *
233 * @return array
234 */
235 private function getCKSkins() {
236 $skins = [];
237 $skinDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/skins');
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 /**
246 * @return array
247 */
248 private function getConfigSettings() {
249 $matches = $result = [];
250 $file = self::getConfigFile($this->get('preset')) ?: self::getConfigFile('default');
251 $result['skin'] = 'moono';
252 if ($file) {
253 $contents = file_get_contents($file);
254 preg_match_all("/\sconfig\.(\w+)\s?=\s?([^;]*);/", $contents, $matches);
255 foreach ($matches[1] as $i => $match) {
256 $result[$match] = trim($matches[2][$i], ' "\'');
257 }
258 }
259 return $result;
260 }
261
262 /**
263 * @param string $preset
264 * Omit to get an array of all presets
265 * @return array|null|string
266 */
267 public static function getConfigUrl($preset = NULL) {
268 $items = [];
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 }
274 }
275 return $preset ? CRM_Utils_Array::value($preset, $items) : $items;
276 }
277
278 /**
279 * @param string $preset
280 *
281 * @return null|string
282 */
283 public static function getConfigFile($preset = 'default') {
284 $fileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
285 return is_file($fileName) ? $fileName : NULL;
286 }
287
288 /**
289 * @param string $preset
290 * @param string $contents
291 */
292 public static function saveConfigFile($preset, $contents) {
293 $file = Civi::paths()->getPath(self::CONFIG_FILEPATH . $preset . '.js');
294 file_put_contents($file, $contents);
295 }
296
297 /**
298 * Delete config file.
299 */
300 public static function deleteConfigFile($preset) {
301 $file = self::getConfigFile($preset);
302 if ($file) {
303 unlink($file);
304 }
305 }
306
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 }
317 $newFileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . 'default.js');
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
334}