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