Completed
Push — fix-for-add-folder ( c1d355...a17c5c )
by Sam
07:30
created

TinyMCEConfig   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 454
Duplicated Lines 6.83 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 55
c 1
b 0
f 0
lcom 1
cbo 7
dl 31
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() 10 10 3
A insertButtonsAfter() 10 10 3
A removeButtons() 11 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   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 View Code Duplication
	public function insertButtonsBefore($before, $buttons) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function insertButtonsAfter($after, $buttons) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function removeButtons($buttons) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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