Completed
Pull Request — master (#5258)
by Sean
29:40 queued 19:27
created

TinyMCEConfig   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 454
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 55
c 1
b 0
f 0
lcom 1
cbo 8
dl 0
loc 454
rs 6.8

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getTheme() 0 3 1
A setTheme() 0 4 1
A getOption() 0 6 2
A setOption() 0 4 1
A setOptions() 0 6 2
A getSettings() 0 3 1
A getAttributes() 0 6 1
B enablePlugins() 0 17 5
A disablePlugins() 0 10 3
A getPlugins() 0 3 1
A getButtons() 0 3 1
A setButtonsForLine() 0 8 3
A addButtonsToLine() 0 13 4
A modifyButtons() 0 13 4
A insertButtonsBefore() 0 10 3
A insertButtonsAfter() 0 10 3
A removeButtons() 0 11 4
B getConfig() 0 59 8
A getEditorCSS() 0 17 3
B requireJS() 0 25 3
A init() 0 13 1

How to fix   Complexity   

Complex Class

Complex classes like TinyMCEConfig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TinyMCEConfig, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Default configuration for HtmlEditor specific to tinymce
5
 */
6
class TinyMCEConfig extends HtmlEditorConfig {
7
8
	/**
9
	 * Location of module relative to BASE_DIR. This must contain the following dirs
10
	 * - plugins
11
	 * - themes
12
	 * - skins
13
	 *
14
	 * @config
15
	 * @var string
16
	 */
17
	private static $base_dir = 'framework/thirdparty/tinymce';
18
19
	/**
20
	 * TinyMCE JS settings
21
	 *
22
	 * @link https://www.tinymce.com/docs/configure/
23
	 *
24
	 * @var array
25
	 */
26
    protected $settings = array(
27
		'fix_list_elements' => true, // https://www.tinymce.com/docs/configure/content-filtering/#fix_list_elements
28
		'friendly_name' => '(Please set a friendly name for this config)',
29
		'priority' => 0, // used for Per-member config override
30
		'browser_spellcheck' => true,
31
		'body_class' => 'typography',
32
        'elementpath' => false, // https://www.tinymce.com/docs/configure/editor-appearance/#elementpath
33
		'relative_urls' => true,
34
		'remove_script_host' => true,
35
		'convert_urls' => false, // Prevent site-root images being rewritten to base relative
36
		'menubar' => false,
37
    );
38
39
	/**
40
	 * Holder list of enabled plugins
41
     *
42
     * @var array
43
	 */
44
	protected $plugins = array(
45
		'table' => null,
46
		'emoticons' => null,
47
		'paste' => null,
48
		'code' => null,
49
		'link' => null,
50
		'importcss' => null,
51
	);
52
53
	/**
54
	 * Theme name
55
	 *
56
	 * @var string
57
	 */
58
	protected $theme = 'modern';
59
60
	/**
61
	 * Get the theme
62
	 *
63
	 * @return string
64
	 */
65
	public function getTheme() {
66
		return $this->theme;
67
	}
68
69
	/**
70
	 * Set the theme name
71
	 *
72
	 * @param string $theme
73
	 * @return $this
74
	 */
75
	public function setTheme($theme) {
76
		$this->theme = $theme;
77
		return $this;
78
	}
79
80
	/**
81
	 * Holder list of buttons, organised by line. This array is 1-based indexed array
82
     *
83
     * {@link https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols}
84
     *
85
     * @var array
86
	 */
87
	protected $buttons = array(
88
		1 => array(
89
            'bold', 'italic', 'underline', 'removeformat', '|',
90
			'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|',
91
			'bullist', 'numlist', 'outdent', 'indent',
92
        ),
93
		2 => array(
94
            'formatselect', '|',
95
			'paste', 'pastetext', '|',
96
			'table', 'ssmedia', 'sslink', 'unlink', '|',
97
			'code'
98
        ),
99
		3 => array()
100
	);
101
102
	public function getOption($key) {
103
		if(isset($this->settings[$key])) {
104
            return $this->settings[$key];
105
        }
106
        return null;
107
	}
108
109
	public function setOption($key,$value) {
110
		$this->settings[$key] = $value;
111
		return $this;
112
	}
113
114
	public function setOptions($options) {
115
		foreach ($options as $key => $value) {
116
			$this->settings[$key] = $value;
117
		}
118
		return $this;
119
	}
120
121
    /**
122
     * Get all settings
123
     *
124
     * @return array
125
     */
126
    protected function getSettings() {
127
        return $this->settings;
128
    }
129
130
    public function getAttributes() {
131
        return [
132
            'data-editor' => 'tinyMCE', // Register ss.editorWrappers.tinyMCE
133
            'data-config' => Convert::array2json($this->getConfig())
134
        ];
135
    }
136
137
	/**
138
	 * Enable one or several plugins. Will maintain unique list if already
139
	 * enabled plugin is re-passed. If passed in as a map of plugin-name to path,
140
	 * the plugin will be loaded by tinymce.PluginManager.load() instead of through tinyMCE.init().
141
	 * Keep in mind that these externals plugins require a dash-prefix in their name.
142
	 *
143
	 * @see http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.PluginManager/load
144
	 *
145
	 * If passing in a non-associative array, the plugin name should be located in the standard tinymce
146
	 * plugins folder.
147
	 *
148
	 * If passing in an associative array, the key of each item should be the plugin name.
149
	 * The value of each item is one of:
150
	 *  - null - Will be treated as a stardard plugin in the standard location
151
	 *  - relative path - Will be treated as a relative url
152
	 *  - absolute url - Some url to an external plugin
153
	 *
154
	 * @param string $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
0 ignored issues
show
Documentation introduced by
There is no parameter named $plugin,.... Did you maybe mean $plugin?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
155
	 * @return $this
156
	 */
157
	public function enablePlugins($plugin) {
0 ignored issues
show
Unused Code introduced by
The parameter $plugin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
158
		$plugins = func_get_args();
159
		if (is_array(current($plugins))) {
160
            $plugins = current($plugins);
161
        }
162
		foreach ($plugins as $name => $path) {
163
			// if plugins are passed without a path
164
			if(is_numeric($name)) {
165
				$name = $path;
166
				$path = null;
167
			}
168
			if (!array_key_exists($name, $this->plugins)) {
169
                $this->plugins[$name] = $path;
170
            }
171
		}
172
		return $this;
173
	}
174
175
	/**
176
	 * Enable one or several plugins. Will properly handle being passed a plugin that is already disabled
177
	 * @param string $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
0 ignored issues
show
Documentation introduced by
There is no parameter named $plugin,.... Did you maybe mean $plugin?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
178
	 * @return $this
179
	 */
180
	public function disablePlugins($plugin) {
0 ignored issues
show
Unused Code introduced by
The parameter $plugin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
181
		$plugins = func_get_args();
182
		if (is_array(current($plugins))) {
183
            $plugins = current($plugins);
184
        }
185
		foreach ($plugins as $name) {
186
			unset($this->plugins[$name]);
187
		}
188
		return $this;
189
	}
190
191
	/**
192
     * Gets the list of all enabled plugins as an associative array.
193
     * Array keys are the plugin names, and values are potentially the plugin location
194
     *
195
	 * @return array
196
	 */
197
	public function getPlugins() {
198
		return $this->plugins;
199
	}
200
201
    /**
202
     * Get all button rows, skipping empty rows
203
     *
204
     * @return array
205
     */
206
    public function getButtons() {
207
        return array_filter($this->buttons);
208
    }
209
210
	/**
211
	 * Totally re-set the buttons on a given line
212
	 *
213
	 * @param int $line The line number to redefine, from 1 to 3
214
	 * @param string $buttons,... A string or several strings, or a single array of strings.
0 ignored issues
show
Documentation introduced by
There is no parameter named $buttons,.... Did you maybe mean $buttons?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
215
     * The button names to assign to this line.
216
	 * @return $this
217
	 */
218
	public function setButtonsForLine($line, $buttons) {
219
		if (func_num_args() > 2) {
220
			$buttons = func_get_args();
221
			array_shift($buttons);
222
		}
223
		$this->buttons[$line] = is_array($buttons) ? $buttons : array($buttons);
224
		return $this;
225
	}
226
227
	/**
228
	 * Add buttons to the end of a line
229
	 * @param int $line The line number to redefine, from 1 to 3
230
	 * @param string $buttons,... A string or several strings, or a single array of strings.
0 ignored issues
show
Documentation introduced by
There is no parameter named $buttons,.... Did you maybe mean $buttons?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
231
     * The button names to add to this line
232
	 * @return $this
233
	 */
234
	public function addButtonsToLine($line, $buttons) {
235
        if(func_num_args() > 2) {
236
            $buttons = func_get_args();
237
            array_shift($buttons);
238
        }
239
        if(!is_array($buttons)) {
240
            $buttons = [$buttons];
241
        }
242
		foreach ($buttons as $button) {
243
			$this->buttons[$line][] = $button;
244
		}
245
		return $this;
246
	}
247
248
	/**
249
	 * Internal function for adding and removing buttons related to another button
250
	 * @param string $name The name of the button to modify
251
	 * @param int $offset The offset relative to that button to perform an array_splice at.
252
     * 0 for before $name, 1 for after.
253
	 * @param int $del The number of buttons to remove at the position given by index(string) + offset
254
	 * @param mixed $add An array or single item to insert at the position given by index(string) + offset,
255
	 * or null for no insertion
256
	 * @return bool True if $name matched a button, false otherwise
257
	 */
258
	protected function modifyButtons($name, $offset, $del=0, $add=null) {
259
		foreach ($this->buttons as &$buttons) {
260
			if (($idx = array_search($name, $buttons)) !== false) {
261
				if ($add) {
262
                    array_splice($buttons, $idx + $offset, $del, $add);
263
                } else {
264
                    array_splice($buttons, $idx + $offset, $del);
265
                }
266
				return true;
267
			}
268
		}
269
		return false;
270
	}
271
272
	/**
273
	 * Insert buttons before the first occurance of another button
274
	 * @param string $before the name of the button to insert other buttons before
275
	 * @param string $buttons,... a string, or several strings, or a single array of strings.
0 ignored issues
show
Documentation introduced by
There is no parameter named $buttons,.... Did you maybe mean $buttons?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
276
     * The button names to insert before that button
277
	 * @return bool True if insertion occured, false if it did not (because the given button name was not found)
278
	 */
279
	public function insertButtonsBefore($before, $buttons) {
280
        if(func_num_args() > 2) {
281
            $buttons = func_get_args();
282
            array_shift($buttons);
283
        }
284
        if(!is_array($buttons)) {
285
            $buttons = [$buttons];
286
        }
287
		return $this->modifyButtons($before, 0, 0, $buttons);
288
	}
289
290
	/**
291
	 * Insert buttons after the first occurance of another button
292
	 * @param string $after the name of the button to insert other buttons before
293
	 * @param string $buttons,... a string, or several strings, or a single array of strings.
0 ignored issues
show
Documentation introduced by
There is no parameter named $buttons,.... Did you maybe mean $buttons?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
294
     * The button names to insert after that button
295
	 * @return bool True if insertion occured, false if it did not (because the given button name was not found)
296
	 */
297
	public function insertButtonsAfter($after, $buttons) {
298
		if(func_num_args() > 2) {
299
            $buttons = func_get_args();
300
            array_shift($buttons);
301
        }
302
        if(!is_array($buttons)) {
303
            $buttons = [$buttons];
304
        }
305
		return $this->modifyButtons($after, 1, 0, $buttons);
306
	}
307
308
	/**
309
	 * Remove the first occurance of buttons
310
	 * @param string $buttons,... one or more strings - the name of the buttons to remove
0 ignored issues
show
Documentation introduced by
There is no parameter named $buttons,.... Did you maybe mean $buttons?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
311
	 * @return null
312
	 */
313
	public function removeButtons($buttons) {
314
        if(func_num_args() > 1) {
315
            $buttons = func_get_args();
316
        }
317
        if(!is_array($buttons)) {
318
            $buttons = [$buttons];
319
        }
320
		foreach ($buttons as $button) {
321
			$this->modifyButtons($button, 0, 1);
322
		}
323
	}
324
325
	/**
326
	 * Generate the JavaScript that will set TinyMCE's configuration:
327
	 * - Parse all configurations into JSON objects to be used in JavaScript
328
	 * - Includes TinyMCE and configurations using the {@link Requirements} system
329
     *
330
     * @return array
331
	 */
332
	protected function getConfig() {
333
        $settings = $this->getSettings();
334
335
		// https://www.tinymce.com/docs/configure/url-handling/#document_base_url
336
        $settings['document_base_url'] = Director::absoluteBaseURL();
337
338
		// https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL
339
		$tinyMCEBaseURL = Controller::join_links(
340
			Director::absoluteBaseURL(),
341
			$this->config()->base_dir
342
		);
343
		$settings['baseURL'] = $tinyMCEBaseURL;
344
345
        // map all plugins to absolute urls for loading
346
		$plugins = array();
347
        foreach($this->getPlugins() as $plugin => $path) {
348
			if(!$path) {
349
				// Empty paths: Convert to urls in standard base url
350
				$path = Controller::join_links(
351
					$tinyMCEBaseURL,
352
					"plugins/{$plugin}/plugin.min.js"
353
				);
354
			} elseif(!Director::is_absolute_url($path)) {
355
				// Non-absolute urls are made absolute
356
				$path = Director::absoluteURL($path);
357
			}
358
            $plugins[$plugin] = $path;
359
        }
360
361
        // https://www.tinymce.com/docs/configure/integration-and-setup/#external_plugins
362
        if($plugins) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plugins of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
363
            $settings['external_plugins'] = $plugins;
364
        }
365
366
        // https://www.tinymce.com/docs/configure/editor-appearance/#groupingtoolbarcontrols
367
        $buttons = $this->getButtons();
368
        $settings['toolbar'] = [];
369
        foreach($buttons as $rowButtons) {
370
            $row = implode(' ', $rowButtons);
371
            if(count($buttons) > 1) {
372
                $settings['toolbar'][] = $row;
373
            } else {
374
                $settings['toolbar'] = $row;
375
            }
376
        }
377
378
        // https://www.tinymce.com/docs/configure/content-appearance/#content_css
379
		$settings['content_css'] = $this->getEditorCSS();
380
381
		// https://www.tinymce.com/docs/configure/editor-appearance/#theme_url
382
		$theme = $this->getTheme();
383
		if(!Director::is_absolute_url($theme)) {
384
			$theme = Controller::join_links($tinyMCEBaseURL, "themes/{$theme}/theme.min.js");
385
		}
386
		$settings['theme_url'] = $theme;
387
388
        // Send back
389
        return $settings;
390
	}
391
392
    /**
393
     * Get location of all editor.css files
394
     *
395
     * @return array
396
     */
397
    protected function getEditorCSS() {
398
        $editor = array();
399
		$editor[] = Controller::join_links(
400
			Director::absoluteBaseURL(),
401
			FRAMEWORK_ADMIN_DIR . '/css/editor.css'
402
		);
403
		if($theme = SSViewer::get_theme_folder()) {
404
			$editorDir = $theme . '/css/editor.css';
405
			if(file_exists(BASE_PATH . '/' . $editorDir)) {
406
				$editor[] = Controller::join_links(
407
					Director::absoluteBaseURL(),
408
					$editorDir
409
				);
410
			}
411
		}
412
		return $editor;
413
	}
414
415
	/**
416
	 * Generate gzipped TinyMCE configuration including plugins and languages.
417
	 * This ends up "pre-loading" TinyMCE bundled with the required plugins
418
	 * so that multiple HTTP requests on the client don't need to be made.
419
	 */
420
	public function requireJS() {
421
		require_once THIRDPARTY_PATH . '/tinymce/tiny_mce_gzip.php';
422
423
		$useGzip = Config::inst()->get('HtmlEditorField', 'use_gzip');
424
		$languages = array();
425
426
		foreach(self::$configs as $configID => $config) {
427
			$languages[] = $config->getOption('language');
428
		}
429
430
		// tinyMCE JS requirement
431
		if($useGzip) {
432
			$tag = TinyMCE_Compressor::renderTag(array(
433
				'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
434
				'plugins' => implode(',', array_keys($this->getPlugins())),
435
				'themes' => $this->getTheme(),
436
				'languages' => implode(',', array_filter($languages))
437
			), true);
438
			preg_match('/src="([^"]*)"/', $tag, $matches);
439
440
			Requirements::javascript(html_entity_decode($matches[1]));
441
		} else {
442
			Requirements::javascript(THIRDPARTY_DIR . '/tinymce/tinymce.min.js');
443
		}
444
	}
445
446
	public function init() {
447
		// These should be 'provides' by bundle-dist.js
448
		Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/jquery/jquery.js");
449
		Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
450
		Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
451
		Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/ssui.core.js');
452
453
		// include TinyMCE Javascript
454
		$this->requireJS();
455
		Requirements::javascript(FRAMEWORK_DIR ."/javascript/dist/HtmlEditorField.js");
456
457
		Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
458
	}
459
}
460