Completed
Push — master ( 7337f2...f0906f )
by Ingo
11:09
created

TinyMCEConfig::getScriptURL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 18
rs 9.4285
cc 2
eloc 13
nc 2
nop 0
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
		'language' => 'en',
38
    );
39
40
	/**
41
	 * Holder list of enabled plugins
42
     *
43
     * @var array
44
	 */
45
	protected $plugins = array(
46
		'table' => null,
47
		'emoticons' => null,
48
		'paste' => null,
49
		'code' => null,
50
		'link' => null,
51
		'importcss' => null,
52
	);
53
54
	/**
55
	 * Theme name
56
	 *
57
	 * @var string
58
	 */
59
	protected $theme = 'modern';
60
61
	/**
62
	 * Get the theme
63
	 *
64
	 * @return string
65
	 */
66
	public function getTheme() {
67
		return $this->theme;
68
	}
69
70
	/**
71
	 * Set the theme name
72
	 *
73
	 * @param string $theme
74
	 * @return $this
75
	 */
76
	public function setTheme($theme) {
77
		$this->theme = $theme;
78
		return $this;
79
	}
80
81
	/**
82
	 * Holder list of buttons, organised by line. This array is 1-based indexed array
83
     *
84
     * {@link https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols}
85
     *
86
     * @var array
87
	 */
88
	protected $buttons = array(
89
		1 => array(
90
            'bold', 'italic', 'underline', 'removeformat', '|',
91
			'alignleft', 'aligncenter', 'alignright', 'alignjustify', '|',
92
			'bullist', 'numlist', 'outdent', 'indent',
93
        ),
94
		2 => array(
95
            'formatselect', '|',
96
			'paste', 'pastetext', '|',
97
			'table', 'ssmedia', 'sslink', 'unlink', '|',
98
			'code'
99
        ),
100
		3 => array()
101
	);
102
103
	public function getOption($key) {
104
		if(isset($this->settings[$key])) {
105
            return $this->settings[$key];
106
        }
107
        return null;
108
	}
109
110
	public function setOption($key,$value) {
111
		$this->settings[$key] = $value;
112
		return $this;
113
	}
114
115
	public function setOptions($options) {
116
		foreach ($options as $key => $value) {
117
			$this->settings[$key] = $value;
118
		}
119
		return $this;
120
	}
121
122
    /**
123
     * Get all settings
124
     *
125
     * @return array
126
     */
127
    protected function getSettings() {
128
        return $this->settings;
129
    }
130
131
    public function getAttributes() {
132
        return [
133
            'data-editor' => 'tinyMCE', // Register ss.editorWrappers.tinyMCE
134
            'data-config' => Convert::array2json($this->getConfig())
135
        ];
136
    }
137
138
	/**
139
	 * Enable one or several plugins. Will maintain unique list if already
140
	 * enabled plugin is re-passed. If passed in as a map of plugin-name to path,
141
	 * the plugin will be loaded by tinymce.PluginManager.load() instead of through tinyMCE.init().
142
	 * Keep in mind that these externals plugins require a dash-prefix in their name.
143
	 *
144
	 * @see http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.PluginManager/load
145
	 *
146
	 * If passing in a non-associative array, the plugin name should be located in the standard tinymce
147
	 * plugins folder.
148
	 *
149
	 * If passing in an associative array, the key of each item should be the plugin name.
150
	 * The value of each item is one of:
151
	 *  - null - Will be treated as a stardard plugin in the standard location
152
	 *  - relative path - Will be treated as a relative url
153
	 *  - absolute url - Some url to an external plugin
154
	 *
155
	 * @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...
156
	 * @return $this
157
	 */
158
	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...
159
		$plugins = func_get_args();
160
		if (is_array(current($plugins))) {
161
            $plugins = current($plugins);
162
        }
163
		foreach ($plugins as $name => $path) {
164
			// if plugins are passed without a path
165
			if(is_numeric($name)) {
166
				$name = $path;
167
				$path = null;
168
			}
169
			if (!array_key_exists($name, $this->plugins)) {
170
                $this->plugins[$name] = $path;
171
            }
172
		}
173
		return $this;
174
	}
175
176
	/**
177
	 * Enable one or several plugins. Will properly handle being passed a plugin that is already disabled
178
	 * @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...
179
	 * @return $this
180
	 */
181
	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...
182
		$plugins = func_get_args();
183
		if (is_array(current($plugins))) {
184
            $plugins = current($plugins);
185
        }
186
		foreach ($plugins as $name) {
187
			unset($this->plugins[$name]);
188
		}
189
		return $this;
190
	}
191
192
	/**
193
     * Gets the list of all enabled plugins as an associative array.
194
     * Array keys are the plugin names, and values are potentially the plugin location
195
     *
196
	 * @return array
197
	 */
198
	public function getPlugins() {
199
		return $this->plugins;
200
	}
201
202
	/**
203
	 * Get list of plugins without custom locations, which is the set of
204
	 * plugins which can be loaded via the standard plugin path, and could
205
	 * potentially be minified
206
	 *
207
	 * @return array
208
	 */
209
	public function getInternalPlugins() {
210
		// Return only plugins with no custom url
211
		$plugins = [];
212
		foreach($this->getPlugins() as $name => $url) {
213
			if(empty($url)) {
214
				$plugins[] = $name;
215
			}
216
		}
217
		return $plugins;
218
	}
219
220
    /**
221
     * Get all button rows, skipping empty rows
222
     *
223
     * @return array
224
     */
225
    public function getButtons() {
226
        return array_filter($this->buttons);
227
    }
228
229
	/**
230
	 * Totally re-set the buttons on a given line
231
	 *
232
	 * @param int $line The line number to redefine, from 1 to 3
233
	 * @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...
234
     * The button names to assign to this line.
235
	 * @return $this
236
	 */
237
	public function setButtonsForLine($line, $buttons) {
238
		if (func_num_args() > 2) {
239
			$buttons = func_get_args();
240
			array_shift($buttons);
241
		}
242
		$this->buttons[$line] = is_array($buttons) ? $buttons : array($buttons);
243
		return $this;
244
	}
245
246
	/**
247
	 * Add buttons to the end of a line
248
	 * @param int $line The line number to redefine, from 1 to 3
249
	 * @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...
250
     * The button names to add to this line
251
	 * @return $this
252
	 */
253
	public function addButtonsToLine($line, $buttons) {
254
        if(func_num_args() > 2) {
255
            $buttons = func_get_args();
256
            array_shift($buttons);
257
        }
258
        if(!is_array($buttons)) {
259
            $buttons = [$buttons];
260
        }
261
		foreach ($buttons as $button) {
262
			$this->buttons[$line][] = $button;
263
		}
264
		return $this;
265
	}
266
267
	/**
268
	 * Internal function for adding and removing buttons related to another button
269
	 * @param string $name The name of the button to modify
270
	 * @param int $offset The offset relative to that button to perform an array_splice at.
271
     * 0 for before $name, 1 for after.
272
	 * @param int $del The number of buttons to remove at the position given by index(string) + offset
273
	 * @param mixed $add An array or single item to insert at the position given by index(string) + offset,
274
	 * or null for no insertion
275
	 * @return bool True if $name matched a button, false otherwise
276
	 */
277
	protected function modifyButtons($name, $offset, $del=0, $add=null) {
278
		foreach ($this->buttons as &$buttons) {
279
			if (($idx = array_search($name, $buttons)) !== false) {
280
				if ($add) {
281
                    array_splice($buttons, $idx + $offset, $del, $add);
282
                } else {
283
                    array_splice($buttons, $idx + $offset, $del);
284
                }
285
				return true;
286
			}
287
		}
288
		return false;
289
	}
290
291
	/**
292
	 * Insert buttons before the first occurance of another button
293
	 * @param string $before the name of the button to insert other buttons before
294
	 * @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...
295
     * The button names to insert before that button
296
	 * @return bool True if insertion occured, false if it did not (because the given button name was not found)
297
	 */
298
	public function insertButtonsBefore($before, $buttons) {
299
        if(func_num_args() > 2) {
300
            $buttons = func_get_args();
301
            array_shift($buttons);
302
        }
303
        if(!is_array($buttons)) {
304
            $buttons = [$buttons];
305
        }
306
		return $this->modifyButtons($before, 0, 0, $buttons);
307
	}
308
309
	/**
310
	 * Insert buttons after the first occurance of another button
311
	 * @param string $after the name of the button to insert other buttons before
312
	 * @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...
313
     * The button names to insert after that button
314
	 * @return bool True if insertion occured, false if it did not (because the given button name was not found)
315
	 */
316
	public function insertButtonsAfter($after, $buttons) {
317
		if(func_num_args() > 2) {
318
            $buttons = func_get_args();
319
            array_shift($buttons);
320
        }
321
        if(!is_array($buttons)) {
322
            $buttons = [$buttons];
323
        }
324
		return $this->modifyButtons($after, 1, 0, $buttons);
325
	}
326
327
	/**
328
	 * Remove the first occurance of buttons
329
	 * @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...
330
	 * @return null
331
	 */
332
	public function removeButtons($buttons) {
333
        if(func_num_args() > 1) {
334
            $buttons = func_get_args();
335
        }
336
        if(!is_array($buttons)) {
337
            $buttons = [$buttons];
338
        }
339
		foreach ($buttons as $button) {
340
			$this->modifyButtons($button, 0, 1);
341
		}
342
	}
343
344
	/**
345
	 * Generate the JavaScript that will set TinyMCE's configuration:
346
	 * - Parse all configurations into JSON objects to be used in JavaScript
347
	 * - Includes TinyMCE and configurations using the {@link Requirements} system
348
     *
349
     * @return array
350
	 */
351
	protected function getConfig() {
352
        $settings = $this->getSettings();
353
354
		// https://www.tinymce.com/docs/configure/url-handling/#document_base_url
355
        $settings['document_base_url'] = Director::absoluteBaseURL();
356
357
		// https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL
358
		$tinyMCEBaseURL = Controller::join_links(
359
			Director::absoluteBaseURL(),
360
			$this->config()->base_dir
361
		);
362
		$settings['baseURL'] = $tinyMCEBaseURL;
363
364
        // map all plugins to absolute urls for loading
365
		$plugins = array();
366
        foreach($this->getPlugins() as $plugin => $path) {
367
			if(!$path) {
368
				// Empty paths: Convert to urls in standard base url
369
				$path = Controller::join_links(
370
					$tinyMCEBaseURL,
371
					"plugins/{$plugin}/plugin.min.js"
372
				);
373
			} elseif(!Director::is_absolute_url($path)) {
374
				// Non-absolute urls are made absolute
375
				$path = Director::absoluteURL($path);
376
			}
377
            $plugins[$plugin] = $path;
378
        }
379
380
        // https://www.tinymce.com/docs/configure/integration-and-setup/#external_plugins
381
        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...
382
            $settings['external_plugins'] = $plugins;
383
        }
384
385
        // https://www.tinymce.com/docs/configure/editor-appearance/#groupingtoolbarcontrols
386
        $buttons = $this->getButtons();
387
        $settings['toolbar'] = [];
388
        foreach($buttons as $rowButtons) {
389
            $row = implode(' ', $rowButtons);
390
            if(count($buttons) > 1) {
391
                $settings['toolbar'][] = $row;
392
            } else {
393
                $settings['toolbar'] = $row;
394
            }
395
        }
396
397
        // https://www.tinymce.com/docs/configure/content-appearance/#content_css
398
		$settings['content_css'] = $this->getEditorCSS();
399
400
		// https://www.tinymce.com/docs/configure/editor-appearance/#theme_url
401
		$theme = $this->getTheme();
402
		if(!Director::is_absolute_url($theme)) {
403
			$theme = Controller::join_links($tinyMCEBaseURL, "themes/{$theme}/theme.min.js");
404
		}
405
		$settings['theme_url'] = $theme;
406
407
        // Send back
408
        return $settings;
409
	}
410
411
    /**
412
     * Get location of all editor.css files
413
     *
414
     * @return array
415
     */
416
    protected function getEditorCSS() {
417
        $editor = array();
418
		$editor[] = Controller::join_links(
419
			Director::absoluteBaseURL(),
420
			FRAMEWORK_ADMIN_DIR . '/css/editor.css'
421
		);
422
		if($theme = SSViewer::get_theme_folder()) {
423
			$editorDir = $theme . '/css/editor.css';
424
			if(file_exists(BASE_PATH . '/' . $editorDir)) {
425
				$editor[] = Controller::join_links(
426
					Director::absoluteBaseURL(),
427
					$editorDir
428
				);
429
			}
430
		}
431
		return $editor;
432
	}
433
434
	/**
435
	 * Generate gzipped TinyMCE configuration including plugins and languages.
436
	 * This ends up "pre-loading" TinyMCE bundled with the required plugins
437
	 * so that multiple HTTP requests on the client don't need to be made.
438
	 *
439
	 * @return string
440
	 */
441
	public function getScriptURL() {
442
		// If gzip is disabled just return core script url
443
		$useGzip = Config::inst()->get('HtmlEditorField', 'use_gzip');
444
		if(!$useGzip) {
445
			return THIRDPARTY_DIR . '/tinymce/tinymce.min.js';
446
		}
447
448
		// tinyMCE JS requirement
449
		require_once THIRDPARTY_PATH . '/tinymce/tiny_mce_gzip.php';
450
		$tag = TinyMCE_Compressor::renderTag(array(
451
			'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
452
			'plugins' => implode(',', $this->getInternalPlugins()),
453
			'themes' => $this->getTheme(),
454
			'languages' => $this->getOption('language')
455
		), true);
456
		preg_match('/src="([^"]*)"/', $tag, $matches);
457
		return html_entity_decode($matches[1]);
458
	}
459
460
	public function init() {
461
		// These should be 'provides' by bundle-dist.js
462
		Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/jquery/jquery.js");
463
		Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
464
		Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
465
		Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/ssui.core.js');
466
467
		// include TinyMCE Javascript
468
		Requirements::javascript($this->getScriptURL());
469
		Requirements::javascript(FRAMEWORK_DIR ."/javascript/dist/HtmlEditorField.js");
470
471
		Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
472
	}
473
}
474