Passed
Push — development ( a254f4...4fadb5 )
by Spuds
01:18 queued 34s
created

loadEditorPlugins()   D

Complexity

Conditions 10
Paths 384

Size

Total Lines 65
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 12.7818

Importance

Changes 0
Metric Value
cc 10
eloc 32
c 0
b 0
f 0
nc 384
nop 1
dl 0
loc 65
ccs 23
cts 33
cp 0.6969
crap 12.7818
rs 4.5333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains functions specific to the editing box and is
5
 * generally used for WYSIWYG type functionality.
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 Beta 1
15
 *
16
 */
17
18
use ElkArte\Cache\Cache;
19
use ElkArte\Languages\Txt;
20
21
/**
22
 * Creates a box that can be used for richedit stuff like BBC, Smileys etc.
23
 *
24
 * @param array $editorOptions associative array of options => value
25
 *  Must contain:
26
 *   - id => unique id for the css
27
 *   - value => text for the editor or blank
28
 *   - smiley_container => ID for where the smileys will be placed
29
 *   - bbc_container => ID for where the toolbar will be placed
30
 * Optionally:
31
 *   - height => height of the initial box
32
 *   - width => width of the box (100%)
33
 *   - force_rich => force wysiwyg to be enabled
34
 *   - disable_smiley_box => boolean to turn off the smiley box
35
 *   - labels => array(
36
 *       - 'post_button' => $txt['for post button'],
37
 *     ),
38
 *   - preview_type => 2 how to act on preview click, see template_control_richedit_buttons
39
 *
40
 * @event integrate_editor_plugins
41
 * @uses GenericControls template
42
 * @uses Post language
43
 */
44
function create_control_richedit($editorOptions)
45
{
46
	global $txt, $options, $context, $settings, $scripturl;
47
48
	// Load the Post language file... for the moment at least.
49
	Txt::load('Post');
50
51
	// Every control must have an ID!
52
	assert(isset($editorOptions['id']));
53
	assert(isset($editorOptions['value']));
54
55
	// Is this the first richedit - if so we need to ensure things are initialised and that we load all needed files
56
	if (empty($context['controls']['richedit']))
57
	{
58
		// Store the name / ID we are creating for template compatibility.
59
		$context['post_box_name'] = $editorOptions['id'];
60
		$context['smiley_box_name'] = $editorOptions['smiley_container'] ?? null;
61
		$context['bbc_box_name'] = $editorOptions['bbc_container'] ?? null;
62
63
		// Don't show the smileys if they are off or not wanted.
64
		$editorOptions['disable_smiley_box'] = !empty($editorOptions['disable_smiley_box'])
65
			|| $GLOBALS['context']['smiley_set'] === 'none'
66
			|| !empty($GLOBALS['options']['show_no_smileys'])
67
			|| empty($editorOptions['smiley_container']);
68
69
		// This really has some WYSIWYG stuff.
70
		theme()->getTemplates()->load('GenericControls');
71
		loadCSSFile('jquery.sceditor.css');
72
		if (!empty($context['theme_variant']) && file_exists($settings['theme_dir'] . '/css/' . $context['theme_variant'] . '/jquery.sceditor.elk' . $context['theme_variant'] . '.css'))
73
		{
74
			loadCSSFile($context['theme_variant'] . '/jquery.sceditor.elk' . $context['theme_variant'] . '.css');
75
		}
76
77
		// JS makes the editor go round
78
		loadJavascriptFile([
79
			'editor/jquery.sceditor.bbcode.min.js',
80
			'editor/jquery.sceditor.elkarte.js',
81
			'post.js',
82
			'editor/dropAttachments.js',
83
			'editor/ilaAttachments.js',
84
			'editor/chunkUpload.js'
85
		]);
86
87
		theme()->addJavascriptVar([
88
			'post_box_name' => $editorOptions['id'],
89
			'elk_smileys_url' => $context['smiley_path'],
90
			'elk_emoji_url' => $context['emoji_path'],
91
			'bbc_quote_from' => $txt['quote_from'],
92
			'bbc_quote' => $txt['quote'],
93
			'bbc_search_on' => $txt['search_on'],
94
			'ila_filename' => $txt['file'] . ' ' . $txt['name']], true
95
		);
96
97
		// Editor language file
98
		if (!empty($txt['lang_locale']))
99
		{
100
			loadJavascriptFile($scripturl . '?action=jslocale;sa=sceditor', ['defer' => true], 'sceditor_language');
101
		}
102
103
		// Our not so concise shortcut line
104
		$context['shortcuts_text'] = $context['shortcuts_text'] ?? $txt['shortcuts'];
105
	}
106
107
	// Start off the editor...
108
	$context['controls']['richedit'][$editorOptions['id']] = [
109
		'id' => $editorOptions['id'],
110
		'value' => $editorOptions['value'],
111
		'rich_active' => !empty($options['wysiwyg_default']) || !empty($editorOptions['force_rich']) || !empty($_REQUEST[$editorOptions['id'] . '_mode']),
112
		'disable_smiley_box' => $editorOptions['disable_smiley_box'],
113
		'width' => $editorOptions['width'] ?? '100%',
114
		'height' => $editorOptions['height'] ?? '250px',
115
		'form' => $editorOptions['form'] ?? 'postmodify',
116
		'preview_type' => isset($editorOptions['preview_type']) ? (int) $editorOptions['preview_type'] : 1,
117
		'labels' => !empty($editorOptions['labels']) ? $editorOptions['labels'] : [],
118
		'locale' => !empty($txt['lang_locale']) ? $txt['lang_locale'] : 'en_US',
119
		'plugin_addons' => !empty($editorOptions['plugin_addons']) ? $editorOptions['plugin_addons'] : [],
120 2
		'plugin_options' => !empty($editorOptions['plugin_options']) ? $editorOptions['plugin_options'] : [],
121 2
		'buttons' => !empty($editorOptions['buttons']) ? $editorOptions['buttons'] : [],
122
		'hidden_fields' => !empty($editorOptions['hidden_fields']) ? $editorOptions['hidden_fields'] : [],
123 2
	];
124
125
	// Allow addons an easy way to add plugins, initialization objects, etc. to the editor control
126 2
	call_integration_hook('integrate_editor_plugins', [$editorOptions['id']]);
127
128
	// Switch between default images and back... mostly in case you don't have a PersonalMessage template, but do have a Post template.
129 2
	$use_defaults = isset($settings['use_default_images'], $settings['default_template']) && $settings['use_default_images'] === 'defaults';
130 2
	if ($use_defaults)
131
	{
132
		$temp = [];
133 2
		$temp[] = $settings['theme_url'];
134
		$temp[] = $settings['images_url'];
135
		$temp[] = $settings['theme_dir'];
136 2
137
		$settings['theme_url'] = $settings['default_theme_url'];
138
		$settings['images_url'] = $settings['default_images_url'];
139 2
		$settings['theme_dir'] = $settings['default_theme_dir'];
140
	}
141
142 2
	// Set up the toolbar, smileys, plugins
143 2
	$context['bbc_toolbar'] = loadEditorToolbar();
144 2
	$context['editor_bbc_toolbar'] = buildBBCToolbar($context['bbc_box_name']);
145
	$context['smileys'] = empty($editorOptions['disable_smiley_box']) ? loadEditorSmileys($context['controls']['richedit'][$editorOptions['id']]) : '';
146
	$context['editor_smileys_toolbar'] = buildSmileyToolbar(empty($editorOptions['disable_smiley_box']));
147
	$context['plugins'] = loadEditorPlugins($context['controls']['richedit'][$editorOptions['id']]);
148
	$context['plugin_options'] = getPluginOptions($context['controls']['richedit'][$editorOptions['id']], $editorOptions['id']);
149
150 2
	// Switch the URLs back... now we're back to whatever the main sub template is (like folder in PersonalMessage.)
151 2
	if ($use_defaults)
152 2
	{
153 2
		list($settings['theme_url'], $settings['images_url'], $settings['theme_dir']) = $temp;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $temp does not seem to be defined for all execution paths leading up to this point.
Loading history...
154 2
	}
155 2
156 2
	// Provide some dynamic error checking (no subject, no message, no service!)
157 2
	if (!empty($editorOptions['live_errors']))
158
	{
159
		Txt::load('Errors');
160 2
		theme()->addInlineJavascript('
161
		
162 2
	error_txts[\'no_subject\'] = ' . JavaScriptEscape($txt['error_no_subject']) . ';
163
	error_txts[\'no_message\'] = ' . JavaScriptEscape($txt['error_no_message']) . ';
164
		', true);
165
	}
166 2
}
167
168
/**
169
 *  Defines what editor plugins to load
170
 *
171
 *  What it does:
172 2
 *  - loads the JS needed by core plugins.
173
 *  - loads the CSS needed by core plugins
174 2
 *  - Will merge in plugins added by addons.
175
 *  - The editor will load and init the plugins
176
 *
177
 * @param array $editor_context The editor context containing the plugin addons.
178 2
 * @return array The list of loaded plugins.
179 2
 */
180
function loadEditorPlugins($editor_context)
181
{
182
	global $modSettings;
183
184
	$plugins = [];
185
	$neededJS = [];
186
	$neededCSS = [];
187
188
	$plugins[] = 'initialLoad';
189
	$neededJS[] = 'editor/initialLoad.plugin.js';
190
191
	if (!empty($modSettings['enableSplitTag']))
192
	{
193
		$plugins[] = 'splittag';
194
		$neededJS[] = 'editor/splittag.plugin.js';
195 2
	}
196 2
197 2
	if (!empty($modSettings['enableUndoRedo']))
198 2
	{
199 2
		$plugins[] = 'undo';
200 2
		$neededJS[] = 'editor/undo.plugin.min.js';
201 2
	}
202 2
203 2
	if (!empty($modSettings['mentions_enabled']))
204 2
	{
205 2
		$plugins[] = 'mention';
206 2
		$neededJS = array_merge($neededJS, ['editor/jquery.atwho.min.js', 'editor/jquery.caret.min.js', 'editor/mentioning.plugin.js']);
207 2
	}
208 2
209 2
	if (!empty($modSettings['enableGiphy']))
210 2
	{
211 2
		$plugins[] = 'giphy';
212 2
		$neededJS[] = 'editor/giphy.plugin.js';
213
		$neededCSS[] = 'sceditor.giphy.css';
214
	}
215
216 2
	if (!empty($modSettings['enableTenor']))
217
	{
218
		$plugins[] = 'tenor';
219 2
		$neededJS[] = 'editor/tenor.plugin.js';
220
		$neededCSS[] = 'sceditor.tenor.css';
221
	}
222
223
	if (!empty($neededJS))
224
	{
225
		loadJavascriptFile($neededJS, ['defer' => true]);
226
	}
227
228
	if (!empty($neededCSS))
229
	{
230
		loadCSSFile($neededCSS);
231 2
	}
232
233
	// Merge with other plugins added by core features or addons which have loaded JS/CSS as needed
234
	if (!empty($editor_context['plugin_addons']))
235
	{
236
		if (!is_array($editor_context['plugin_addons']))
237
		{
238 2
			$editor_context['plugin_addons'] = [$editor_context['plugin_addons']];
239
		}
240
241
		$plugins = array_filter(array_merge($plugins, $editor_context['plugin_addons']));
242
	}
243 2
244
	return $plugins;
245
}
246
247
/**
248
 * Loads any built in plugin options and merges in any addon defined ones.
249
 *
250
 * @param array $editor_context
251
 * @param string $editor_id
252 2
 * @return array
253
 */
254
function getPluginOptions($editor_context, $editor_id)
255 2
{
256
	global $modSettings;
257
258 2
	$plugin_options = [];
259 2
260
	if (!empty($modSettings['mentions_enabled']))
261
	{
262
		$plugin_options[] = '
263
			mentionOptions: {
264
				editor_id: \'' . $editor_id . '\',
265 2
				cache: {
266
					mentions: [],
267
					queries: [],
268 2
					names: []
269
				}
270
			}';
271
	}
272
273
	// Allow addons to insert additional editor objects
274
	if (!empty($editor_context['plugin_options']) && is_array($editor_context['plugin_options']))
275
	{
276
		$plugin_options = array_merge($plugin_options, $editor_context['plugin_options']);
277
	}
278
279
	return $plugin_options;
280
}
281
282
/**
283
 * Loads the editor toolbar with just the enabled commands
284
 *
285
 * Each row will be a comma seperated list of commands that the editor knows (or should)
286 2
 * bold,italic,quote,.....
287 2
 *
288
 * @return array
289 2
 */
290
function loadEditorToolbar()
291 2
{
292
	$bbc_tags = loadToolbarDefaults();
293
	$disabledToolbar = getDisabledBBC();
294 2
295
	// Build our toolbar, taking into account any bbc codes from integration
296
	$bbcToolbar = [];
297 2
	foreach ($bbc_tags as $row => $tagRow)
298
	{
299 2
		$bbcToolbar[$row] = $bbcToolbar[$row] ?? [];
300
		$tagsRow = [];
301
302 2
		// For each row of buttons defined, lets build our tags
303
		foreach ($tagRow as $tags)
304 2
		{
305
			foreach ($tags as $tag)
306
			{
307
				// Just add this code in the existing grouping
308
				if (!isset($disabledToolbar[$tag]))
309 2
				{
310
					$tagsRow[] = $tag;
311 2
				}
312
			}
313
314
			// If the row is not empty, and the last added tag is not a space, add a space.
315
			if (!empty($tagsRow) && $tagsRow[count($tagsRow) - 1] !== 'space')
316 2
			{
317
				$tagsRow[] = 'space';
318 2
			}
319
		}
320
321
		// Build that beautiful button row
322
		if (!empty($tagsRow))
323
		{
324 2
			$bbcToolbar[$row][] = implode(',', $tagsRow);
325
		}
326 2
	}
327
328
	return $bbcToolbar;
329
}
330
331
/**
332 2
 * Loads disabled BBC tags from the DB as defined in the ACP.  It
333
 * will then translate BBC to editor commands (b => bold) such that
334
 * disabled BBC will also disable the associated editor command button.
335
 *
336
 * @return array
337
 */
338
function getDisabledBBC()
339
{
340
	global $modSettings;
341
342
	// Generate a list of buttons that shouldn't be shown
343
	$disabled_tags = [];
344
	$disabledToolbar = [];
345
346
	if (!empty($modSettings['disabledBBC']))
347
	{
348
		$disabled_tags = explode(',', $modSettings['disabledBBC']);
349
	}
350
351
	// Map bbc codes to editor toolbar tags
352
	$translate_tags_to_code = ['b' => 'bold', 'i' => 'italic', 'u' => 'underline', 's' => 'strike',
353
							   'img' => 'image', 'url' => 'link', 'sup' => 'superscript', 'sub' => 'subscript', 'hr' => 'horizontalrule'];
354
355
	// Remove the toolbar buttons for any bbc tags that have been turned off in the ACP
356
	foreach ($disabled_tags as $tag)
357
	{
358
		// list is special, its prevents two tags
359
		if ($tag === 'list')
360
		{
361
			$disabledToolbar['bulletlist'] = true;
362
			$disabledToolbar['orderedlist'] = true;
363
		}
364
		elseif (isset($translate_tags_to_code[$tag]))
365
		{
366
			$disabledToolbar[$translate_tags_to_code[$tag]] = true;
367
		}
368
369
		// Tag is the same as the code, like font, color, size etc
370
		$disabledToolbar[trim($tag)] = true;
371
	}
372
373
	return $disabledToolbar;
374
}
375
376
/**
377
 * Loads the toolbar default buttons which defines what editor buttons might show
378
 *
379
 * @event integrate_bbc_buttons
380
 * @return array|mixed
381
 */
382
function loadToolbarDefaults()
383
{
384
	$bbc_tags = [];
385
386
	// The below array is used to show a command button in the editor, the execution
387
	// and display details of any added buttons must be defined in the javascript files
388
	// see jquery.sceditor.elkarte.js under the sceditor.formats.bbcode area
389
	// for examples of how to use the .set command to add codes.  Include your new
390
	// JS with addInlineJavascript() or loadJavascriptFile() in your addon
391
	$bbc_tags['row1'] = [
392
		['bold', 'italic', 'underline', 'strike'],
393
		['left', 'center', 'right', 'pre'],
394
		['image', 'link', 'giphy', 'tenor'],
395
		['bulletlist', 'orderedlist'],
396
		['source', 'expand'],
397
	];
398
399
	$bbc_tags['row2'] = [
400
		['tt', 'superscript', 'subscript'],
401
		['spoiler', 'footnote', 'removeformat'],
402
		['quote', 'code', 'table', 'horizontalrule', 'email'],
403
		['font', 'size', 'color'],
404
		['splittag', 'undo', 'redo'],
405
	];
406
407
	// Allow mods to add BBC buttons to the toolbar, actions are defined in the JS
408
	call_integration_hook('integrate_bbc_buttons', [&$bbc_tags]);
409
410
	return $bbc_tags;
411
}
412
413
/**
414
 * Loads the smileys for use in the required locations (postform or popup)
415
 *
416
 * What it does:
417
 * - Will load the default smileys or custom smileys as defined in ACP
418
 * - Caches the DB call for 10 mins
419
 * - Sorts smileys to proper array positions removing hidden ones
420
 *
421
 * @return array|array[]|mixed
422
 */
423
function loadEditorSmileys($editorOptions)
424
{
425
	global $context, $modSettings;
426
427
	$smileys = [
428
		'postform' => [],
429
		'popup' => [],
430
	];
431 2
432
	// Initialize smiley array... if not loaded before.
433
	if (empty($context['smileys']) && empty($editorOptions['disable_smiley_box']))
434
	{
435
		$temp = [];
436
		if (!Cache::instance()->getVar($temp, 'posting_smileys', 600))
437
		{
438
			require_once(SUBSDIR . '/Smileys.subs.php');
439
			$smileys = getEditorSmileys();
440
			foreach ($smileys as $section => $smileyRows)
441
			{
442
				$last_row = null;
443
				foreach ($smileyRows as $rowIndex => $smileRow)
444
				{
445
					$smileys[$section][$rowIndex]['smileys'][count($smileRow['smileys']) - 1]['isLast'] = true;
446
					$last_row = $rowIndex;
447
				}
448
449
				if ($last_row !== null)
450
				{
451
					$smileys[$section][$last_row]['isLast'] = true;
452
				}
453
			}
454
455
			Cache::instance()->put('posting_smileys', $smileys, 600);
456
		}
457
		else
458
		{
459
			$smileys = $temp;
460
		}
461
462
		// The smiley popup may take advantage of Jquery UI ....
463
		if (!empty($smileys['popup']))
464
		{
465
			$modSettings['jquery_include_ui'] = true;
466
		}
467
468
		return $smileys;
469
	}
470
471
	return empty($context['smileys']) ? $smileys : $context['smileys'];
472
}
473
474
function buildSmileyToolbar($useSmileys)
475
{
476
	global $context;
477
478
	if (!$useSmileys)
479
	{
480
		return ', emoticons: {}';
481
	}
482
483
	$emoticons = ',
484
		emoticons: {';
485 2
486
	$countLocations = count($context['smileys']);
487
	foreach ($context['smileys'] as $location => $smileyRows)
488
	{
489
		$countLocations--;
490
		if ($location === 'postform')
491
		{
492 2
			$emoticons .= '
493
				dropdown: {';
494
		}
495
496
		if ($location === 'popup')
497
		{
498
			$emoticons .= '
499
				popup: {';
500
		}
501
502
		$numRows = count($smileyRows);
503
504
		// This is needed because otherwise the editor will remove all the duplicate (empty)
505
		// keys and leave only 1 additional line
506
		$emptyPlaceholder = 0;
507
		foreach ($smileyRows as $smileyRow)
508
		{
509
			foreach ($smileyRow['smileys'] as $smiley)
510
			{
511
				$emoticons .= '
512
					' . JavaScriptEscape($smiley['code']) . ': {url: ' . (JavaScriptEscape((isset($smiley['emoji']) ? $context['emoji_path'] : $context['smiley_path']) . $smiley['filename'])) . ', tooltip: ' . JavaScriptEscape($smiley['description']) . '}' . (empty($smiley['isLast']) ? ',' : '');
513
			}
514
515
			if (empty($smileyRow['isLast']) && $numRows !== 1)
516
			{
517
				$emoticons .= ",'-" . $emptyPlaceholder++ . "': '',";
518
			}
519
		}
520
521
		$emoticons .= '
522
			}' . ($countLocations !== 0 ? ',' : '');
523
	}
524
525
	return $emoticons . '
526
	}';
527
}
528
529
/**
530
 *  Builds the BBC toolbar configuration for the editor.
531
 *
532
 *  What it does:
533
 *  - If the $bbcContainer parameter is null, it returns the configuration for the source mode toolbar.
534 2
 *  - Otherwise, it builds the toolbar configuration based on the buttons in the $context['bbc_toolbar'] array.
535
 *
536
 * @param mixed|null $bbcContainer The container of the editor. Null for source mode, otherwise the container element.
537
 * @return string The configuration for the BBC toolbar.
538
 */
539
function buildBBCToolbar($bbcContainer)
540
{
541
	global $context;
542
543
	if ($bbcContainer === null)
544
	{
545
		return ', 
546
			toolbar: "source"';
547
	}
548
549
	// Show all the editor command buttons
550
	$toolbar = ',
551
		toolbar: "';
552
553
	// Create the tooltag rows to display the buttons in the editor
554
	foreach ($context['bbc_toolbar'] as $buttonRow)
555
	{
556
		$toolbar .= $buttonRow[0] . '|';
557
	}
558
559
	$toolbar .= '"';
560
561
	return $toolbar;
562
}
563