Completed
Push — 3.0 ( a46cca...861f1c )
by Jeroen
204:32 queued 99:44
created

engine/lib/views.php (1 issue)

Severity
Code
1
<?php
2
/**
3
 * Elgg's view system.
4
 *
5
 * The view system is the primary templating engine in Elgg and renders
6
 * all output.  Views are short, parameterised PHP scripts for displaying
7
 * output that can be regsitered, overridden, or extended.  The view type
8
 * determines the output format and location of the files that renders the view.
9
 *
10
 * Elgg uses a two step process to render full output: first
11
 * content-specific elements are rendered, then the resulting
12
 * content is inserted into a layout and displayed.  This makes it
13
 * easy to maintain a consistent look on all pages.
14
 *
15
 * A view corresponds to a single file on the filesystem and the views
16
 * name is its directory structure.  A file in
17
 * <code>mod/plugins/views/default/myplugin/example.php</code>
18
 * is called by saying (with the default viewtype):
19
 * <code>echo elgg_view('myplugin/example');</code>
20
 *
21
 * View names that are registered later override those that are
22
 * registered earlier.  For plugins this corresponds directly
23
 * to their load order: views in plugins lower in the list override
24
 * those higher in the list.
25
 *
26
 * Plugin views belong in the views/ directory under an appropriate
27
 * viewtype.  Views are automatically registered.
28
 *
29
 * Views can be embedded-you can call a view from within a view.
30
 * Views can also be prepended or extended by any other view.
31
 *
32
 * Any view can extend any other view if registered with
33
 * {@link elgg_extend_view()}.
34
 *
35
 * Viewtypes are set by passing $_REQUEST['view'].  The viewtype
36
 * 'default' is a standard HTML view.  Types can be defined on the fly
37
 * and you can get the current viewtype with {@link elgg_get_viewtype()}.
38
 *
39
 * @note Internal: Plugin views are autoregistered before their init functions
40
 * are called, so the init order doesn't affect views.
41
 *
42
 * @note Internal: The file that determines the output of the view is the last
43
 * registered by {@link elgg_set_view_location()}.
44
 *
45
 * @package Elgg.Core
46
 * @subpackage Views
47
 */
48
49
use Elgg\Menu\Menu;
50
use Elgg\Menu\UnpreparedMenu;
51
use Elgg\Project\Paths;
52
53
/**
54
 * Manually set the viewtype.
55
 *
56
 * View types are detected automatically.  This function allows
57
 * you to force subsequent views to use a different viewtype.
58
 *
59
 * @tip Call elgg_set_viewtype() with no parameter to reset.
60
 *
61
 * @param string $viewtype The view type, e.g. 'rss', or 'default'.
62
 *
63
 * @return bool
64
 */
65
function elgg_set_viewtype($viewtype = '') {
66 36
	return _elgg_services()->views->setViewtype($viewtype);
67
}
68
69
/**
70
 * Return the current view type.
71
 *
72
 * Viewtypes are automatically detected and can be set with $_REQUEST['view']
73
 * or {@link elgg_set_viewtype()}.
74
 *
75
 * @return string The viewtype
76
 * @see elgg_set_viewtype()
77
 */
78
function elgg_get_viewtype() {
79 320
	return _elgg_services()->views->getViewtype();
80
}
81
82
/**
83
 * Checks if $viewtype is a string suitable for use as a viewtype name
84
 *
85
 * @param string $viewtype Potential viewtype name. Alphanumeric chars plus _ allowed.
86
 *
87
 * @return bool
88
 * @internal
89
 * @since 1.9
90
 */
91
function _elgg_is_valid_viewtype($viewtype) {
92 1
	return _elgg_services()->views->isValidViewtype($viewtype);
93
}
94
95
/**
96
 * Register a viewtype to fall back to a default view if a view isn't
97
 * found for that viewtype.
98
 *
99
 * @tip This is useful for alternate html viewtypes (such as for mobile devices).
100
 *
101
 * @param string $viewtype The viewtype to register
102
 *
103
 * @return void
104
 * @since 1.7.2
105
 */
106
function elgg_register_viewtype_fallback($viewtype) {
107
	_elgg_services()->views->registerViewtypeFallback($viewtype);
108
}
109
110
/**
111
 * Checks if a viewtype falls back to default.
112
 *
113
 * @param string $viewtype Viewtype
114
 *
115
 * @return boolean
116
 * @since 1.7.2
117
 */
118
function elgg_does_viewtype_fallback($viewtype) {
119
	return _elgg_services()->views->doesViewtypeFallback($viewtype);
120
}
121
122
/**
123
 * Register a view to be available for ajax calls
124
 *
125
 * @warning Only views that begin with 'js/' and 'css/' have their content
126
 * type set to 'text/javascript' and 'text/css'. Other views are served as
127
 * 'text/html'.
128
 *
129
 * @param string $view The view name
130
 * @return void
131
 * @since 1.8.3
132
 */
133
function elgg_register_ajax_view($view) {
134 94
	elgg_register_external_view($view, false);
135 94
}
136
137
/**
138
 * Unregister a view for ajax calls
139
 *
140
 * @param string $view The view name
141
 * @return void
142
 * @since 1.8.3
143
 */
144
function elgg_unregister_ajax_view($view) {
145
	elgg_unregister_external_view($view);
146
}
147
148
/**
149
 * Registers a view as being available externally (i.e. via URL).
150
 *
151
 * @param string  $view      The name of the view.
152
 * @param boolean $cacheable Whether this view can be cached.
153
 * @return void
154
 * @since 1.9.0
155
 */
156
function elgg_register_external_view($view, $cacheable = false) {
157
158 94
	_elgg_services()->ajax->registerView($view);
159
160 94
	if ($cacheable) {
161
		_elgg_services()->views->registerCacheableView($view);
162
	}
163 94
}
164
165
/**
166
 * Unregister a view for ajax calls
167
 *
168
 * @param string $view The view name
169
 * @return void
170
 * @since 1.9.0
171
 */
172
function elgg_unregister_external_view($view) {
173
	_elgg_services()->ajax->unregisterView($view);
174
}
175
176
/**
177
 * Set an alternative base location for a view.
178
 *
179
 * Views are expected to be in plugin_name/views/.  This function can
180
 * be used to change that location.
181
 *
182
 * @tip This is useful to optionally register views in a plugin.
183
 *
184
 * @param string $view     The name of the view
185
 * @param string $location The full path to the view
186
 * @param string $viewtype The view type
187
 *
188
 * @return void
189
 */
190
function elgg_set_view_location($view, $location, $viewtype = '') {
191
	_elgg_services()->views->setViewDir($view, $location, $viewtype);
192
}
193
194
/**
195
 * Returns whether the specified view exists
196
 *
197
 * @note If $recurse is true, also checks if a view exists only as an extension.
198
 *
199
 * @param string $view     The view name
200
 * @param string $viewtype If set, forces the viewtype
201
 * @param bool   $recurse  If false, do not check extensions
202
 *
203
 * @return bool
204
 */
205
function elgg_view_exists($view, $viewtype = '', $recurse = true) {
206 275
	return _elgg_services()->views->viewExists($view, $viewtype, $recurse);
207
}
208
209
/**
210
 * List all views in a viewtype
211
 *
212
 * @param string $viewtype Viewtype
213
 *
214
 * @return string[]
215
 *
216
 * @since 2.0
217
 */
218
function elgg_list_views($viewtype = 'default') {
219
	return _elgg_services()->views->listViews($viewtype);
220
}
221
222
/**
223
 * Return a parsed view.
224
 *
225
 * Views are rendered by a template handler and returned as strings.
226
 *
227
 * Views are called with a special $vars variable set,
228
 * which includes any variables passed as the second parameter.
229
 *
230
 * The input of views can be intercepted by registering for the
231
 * view_vars, $view_name plugin hook.
232
 *
233
 * If the input contains the key "__view_output", the view will output this value as a string.
234
 * No extensions are used, and the "view" hook is not triggered).
235
 *
236
 * The output of views can be intercepted by registering for the
237
 * view, $view_name plugin hook.
238
 *
239
 * @param string $view     The name and location of the view to use
240
 * @param array  $vars     Variables to pass to the view.
241
 * @param string $viewtype If set, forces the viewtype for the elgg_view call to be
242
 *                          this value (default: standard detection)
243
 *
244
 * @return string The parsed view
245
 */
246
function elgg_view($view, $vars = [], $viewtype = '') {
247 420
	if (func_num_args() == 5) {
248
		elgg_log(__FUNCTION__ . ' now has only 3 arguments. Update your usage.', 'ERROR');
249
		$viewtype = func_get_arg(4);
250
	}
251 420
	return _elgg_services()->views->renderView($view, $vars, $viewtype);
252
}
253
254
/**
255
 * Display a view with a deprecation notice. No missing view NOTICE is logged
256
 *
257
 * @param string $view       The name and location of the view to use
258
 * @param array  $vars       Variables to pass to the view
259
 * @param string $suggestion Suggestion with the deprecation message
260
 * @param string $version    Human-readable *release* version: 1.7, 1.8, ...
261
 *
262
 * @return string The parsed view
263
 *
264
 * @see elgg_view()
265
 */
266
function elgg_view_deprecated($view, array $vars, $suggestion, $version) {
267
	return _elgg_services()->views->renderDeprecatedView($view, $vars, $suggestion, $version);
268
}
269
270
/**
271
 * Extends a view with another view.
272
 *
273
 * The output of any view can be prepended or appended to any other view.
274
 *
275
 * The default action is to append a view.  If the priority is less than 500,
276
 * the output of the extended view will be appended to the original view.
277
 *
278
 * Views can be extended multiple times, and extensions are not checked for
279
 * uniqueness. Use {@link elgg_unextend_view()} to help manage duplicates.
280
 *
281
 * Priority can be specified and affects the order in which extensions
282
 * are appended or prepended.
283
 *
284
 * @see elgg_prepend_css_urls() If the extension is CSS, you may need to use this to fix relative URLs.
285
 *
286
 * @param string $view           The view to extend.
287
 * @param string $view_extension This view is added to $view
288
 * @param int    $priority       The priority, from 0 to 1000, to add at (lowest numbers displayed first)
289
 *
290
 * @return void
291
 * @since 1.7.0
292
 */
293
function elgg_extend_view($view, $view_extension, $priority = 501) {
294 86
	_elgg_services()->views->extendView($view, $view_extension, $priority);
295 86
}
296
297
/**
298
 * Unextends a view.
299
 *
300
 * @param string $view           The view that was extended.
301
 * @param string $view_extension This view that was added to $view
302
 *
303
 * @return bool
304
 * @since 1.7.2
305
 */
306
function elgg_unextend_view($view, $view_extension) {
307 43
	return _elgg_services()->views->unextendView($view, $view_extension);
308
}
309
310
/**
311
 * Get the views (and priorities) that extend a view.
312
 *
313
 * @note extensions may change anytime, especially during the [init, system] event
314
 *
315
 * @param string $view View name
316
 *
317
 * @return string[] Keys returned are view priorities.
318
 * @since 2.3
319
 */
320
function elgg_get_view_extensions($view) {
321
	$list = _elgg_services()->views->getViewList($view);
322
	unset($list[500]);
323
	return $list;
324
}
325
326
/**
327
 * In CSS content, prepend a path to relative URLs.
328
 *
329
 * This is useful to process a CSS view being used as an extension.
330
 *
331
 * @param string $css  CSS
332
 * @param string $path Path to prepend. E.g. "foo/bar/" or "../"
333
 *
334
 * @return string
335
 * @since 2.2
336
 */
337
function elgg_prepend_css_urls($css, $path) {
338 1
	return Minify_CSS_UriRewriter::prepend($css, $path);
339
}
340
341
/**
342
 * Assembles and outputs a full page.
343
 *
344
 * A "page" in Elgg is determined by the current view type and
345
 * can be HTML for a browser, RSS for a feed reader, or
346
 * Javascript, PHP and a number of other formats.
347
 *
348
 * For HTML pages, use the 'head', 'page' plugin hook for setting meta elements
349
 * and links.
350
 *
351
 * @param string $title      Title
352
 * @param string $body       Body
353
 * @param string $page_shell Optional page shell to use. See page/shells view directory
354
 * @param array  $vars       Optional vars array to pass to the page
355
 *                           shell. Automatically adds title, body, head, and sysmessages
356
 *
357
 * @return string The contents of the page
358
 * @since  1.8
359
 */
360
function elgg_view_page($title, $body, $page_shell = 'default', $vars = []) {
361 52
	$timer = _elgg_services()->timer;
362 52
	if (!$timer->hasEnded(['build page'])) {
363 9
		$timer->end(['build page']);
364
	}
365 52
	$timer->begin([__FUNCTION__]);
366
367 52
	$params = [];
368 52
	$params['identifier'] = _elgg_services()->request->getFirstUrlSegment();
369 52
	$params['segments'] = _elgg_services()->request->getUrlSegments();
370 52
	array_shift($params['segments']);
371 52
	$page_shell = elgg_trigger_plugin_hook('shell', 'page', $params, $page_shell);
372
373
374 52
	$system_messages = _elgg_services()->systemMessages;
375
376 52
	$messages = null;
377 52
	if ($system_messages->count()) {
378
		$messages = $system_messages->dumpRegister();
379
380
		if (isset($messages['error'])) {
381
			// always make sure error is the first type
382
			$errors = [
383
				'error' => $messages['error']
384
			];
385
386
			unset($messages['error']);
387
			$messages = array_merge($errors, $messages);
388
		}
389
	}
390
391 52
	$vars['title'] = $title;
392 52
	$vars['body'] = $body;
393 52
	$vars['sysmessages'] = $messages;
394 52
	$vars['page_shell'] = $page_shell;
395
396
	// head has keys 'title', 'metas', 'links'
397 52
	$head_params = _elgg_views_prepare_head($title);
398
399 52
	$vars['head'] = elgg_trigger_plugin_hook('head', 'page', $vars, $head_params);
400
401 52
	$vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars);
402
403 52
	$output = elgg_view("page/$page_shell", $vars);
404
405
406
	// Allow plugins to modify the output
407 52
	$output = elgg_trigger_plugin_hook('output', 'page', $vars, $output);
408
409 52
	$timer->end([__FUNCTION__]);
410 52
	return $output;
411
}
412
413
/**
414
 * Render a resource view. Use this in your page handler to hand off page rendering to
415
 * a view in "resources/". If not found in the current viewtype, we try the "default" viewtype.
416
 *
417
 * @param string $name The view name without the leading "resources/"
418
 * @param array  $vars Arguments passed to the view
419
 *
420
 * @return string
421
 * @throws \Elgg\PageNotFoundException
422
 */
423
function elgg_view_resource($name, array $vars = []) {
424 70
	$view = "resources/$name";
425
426 70
	if (elgg_view_exists($view)) {
427 70
		return _elgg_services()->views->renderView($view, $vars);
428
	}
429
430
	if (elgg_get_viewtype() !== 'default' && elgg_view_exists($view, 'default')) {
431
		return _elgg_services()->views->renderView($view, $vars, 'default');
432
	}
433
434
	_elgg_services()->logger->error("The view $view is missing.");
435
436
	// only works for default viewtype
437
	throw new \Elgg\PageNotFoundException();
438
}
439
440
/**
441
 * Prepare the variables for the html head
442
 *
443
 * @param string $title Page title for <head>
444
 * @return array
445
 * @internal
446
 */
447
function _elgg_views_prepare_head($title) {
448
	$params = [
449 52
		'links' => [],
450
		'metas' => [],
451
	];
452
453 52
	if (empty($title)) {
454 5
		$params['title'] = _elgg_config()->sitename;
455
	} else {
456 47
		$params['title'] = $title . ' : ' . _elgg_config()->sitename;
457
	}
458
459 52
	$params['metas']['content-type'] = [
460
		'http-equiv' => 'Content-Type',
461
		'content' => 'text/html; charset=utf-8',
462
	];
463
464 52
	$params['metas']['description'] = [
465 52
		'name' => 'description',
466 52
		'content' => _elgg_config()->sitedescription
467
	];
468
469
	// https://developer.chrome.com/multidevice/android/installtohomescreen
470 52
	$params['metas']['viewport'] = [
471
		'name' => 'viewport',
472
		'content' => 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0',
473
	];
474 52
	$params['metas']['mobile-web-app-capable'] = [
475
		'name' => 'mobile-web-app-capable',
476
		'content' => 'yes',
477
	];
478 52
	$params['metas']['apple-mobile-web-app-capable'] = [
479
		'name' => 'apple-mobile-web-app-capable',
480
		'content' => 'yes',
481
	];
482
483
	// RSS feed link
484 52
	if (_elgg_has_rss_link()) {
485 27
		$url = current_page_url();
486 27
		if (substr_count($url, '?')) {
487 1
			$url .= "&view=rss";
488
		} else {
489 26
			$url .= "?view=rss";
490
		}
491 27
		$params['links']['rss'] = [
492 27
			'rel' => 'alternative',
493 27
			'type' => 'application/rss+xml',
494 27
			'title' => 'RSS',
495 27
			'href' => $url,
496
		];
497
	}
498
499 52
	return $params;
500
}
501
502
503
/**
504
 * Add favicon link tags to HTML head
505
 *
506
 * @param string $hook        "head"
507
 * @param string $type        "page"
508
 * @param array  $head_params Head params
509
 *                            <code>
510
 *                               [
511
 *                                  'title' => '',
512
 *                                  'metas' => [],
513
 *                                  'links' => [],
514
 *                               ]
515
 *                            </code>
516
 * @param array  $params      Hook params
517
 * @return array
518
 */
519
function _elgg_views_prepare_favicon_links($hook, $type, $head_params, $params) {
520
521 10
	$head_params['links']['apple-touch-icon'] = [
522 10
		'rel' => 'apple-touch-icon',
523 10
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
524
	];
525
526
	// favicons
527 10
	$head_params['links']['icon-ico'] = [
528 10
		'rel' => 'icon',
529 10
		'href' => elgg_get_simplecache_url('graphics/favicon.ico'),
530
	];
531 10
	$head_params['links']['icon-vector'] = [
532 10
		'rel' => 'icon',
533 10
		'sizes' => '16x16 32x32 48x48 64x64 128x128',
534 10
		'type' => 'image/svg+xml',
535 10
		'href' => elgg_get_simplecache_url('graphics/favicon.svg'),
536
	];
537 10
	$head_params['links']['icon-16'] = [
538 10
		'rel' => 'icon',
539 10
		'sizes' => '16x16',
540 10
		'type' => 'image/png',
541 10
		'href' => elgg_get_simplecache_url('graphics/favicon-16.png'),
542
	];
543 10
	$head_params['links']['icon-32'] = [
544 10
		'rel' => 'icon',
545 10
		'sizes' => '32x32',
546 10
		'type' => 'image/png',
547 10
		'href' => elgg_get_simplecache_url('graphics/favicon-32.png'),
548
	];
549 10
	$head_params['links']['icon-64'] = [
550 10
		'rel' => 'icon',
551 10
		'sizes' => '64x64',
552 10
		'type' => 'image/png',
553 10
		'href' => elgg_get_simplecache_url('graphics/favicon-64.png'),
554
	];
555 10
	$head_params['links']['icon-128'] = [
556 10
		'rel' => 'icon',
557 10
		'sizes' => '128x128',
558 10
		'type' => 'image/png',
559 10
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
560
	];
561
562 10
	return $head_params;
563
}
564
565
/**
566
 * Displays a layout with optional parameters.
567
 *
568
 * Layouts are templates provide consistency by organizing blocks of content on the page.
569
 *
570
 * Plugins should use one of the core layouts:
571
 *  - default     Primary template with one, two or no sidebars
572
 *  - admin       Admin page template
573
 *  - error       Error page template
574
 *  - widgets     Widgets canvas
575
 *
576
 * Plugins can create and use custom layouts by placing a layout view
577
 * in "page/layouts/<layout_name>" and calling elgg_view_layout(<layout_name>).
578
 *
579
 * For a full list of parameters supported by each of these layouts see
580
 * corresponding layout views.
581
 *
582
 * @param string $layout_name Layout name
583
 *                            Corresponds to a view in "page/layouts/<layout_name>".
584
 * @param array  $vars        Layout parameters
585
 *                            An associative array of parameters to pass to
586
 *                            the layout hooks and views.
587
 *                            Route 'identifier' and 'segments' of the page being
588
 *                            rendered will be added to this array automatially,
589
 *                            allowing plugins to alter layout views and subviews
590
 *                            based on the current route.
591
 * @return string
592
 */
593
function elgg_view_layout($layout_name, $vars = []) {
594 43
	$timer = _elgg_services()->timer;
595 43
	if (!$timer->hasEnded(['build page'])) {
596 42
		$timer->end(['build page']);
597
	}
598 43
	$timer->begin([__FUNCTION__]);
599
600
	// Help plugins transition without breaking them
601 43
	switch ($layout_name) {
602 43
		case 'content' :
603 12
			$layout_name = 'default';
604 12
			$vars = _elgg_normalize_content_layout_vars($vars);
605 12
			break;
606
607 31
		case 'one_sidebar' :
608 1
			$layout_name = 'default';
609 1
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
610 1
			$vars['sidebar_alt'] = false;
611 1
			break;
612
613 30
		case 'one_column' :
614
			$layout_name = 'default';
615
			$vars['sidebar'] = false;
616
			$vars['sidebar_alt'] = false;
617
			break;
618
619 30
		case 'two_sidebar' :
620
			$layout_name = 'default';
621
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
622
			$vars['sidebar_alt'] = elgg_extract('sidebar_alt', $vars, '', false);
623
			break;
624
625 30
		case 'default' :
626 23
			$filter_id = elgg_extract('filter_id', $vars, 'filter');
627 23
			$filter_context = elgg_extract('filter_value', $vars);
628 23
			if (isset($filter_context) && $filter_id === 'filter') {
629 11
				$context = elgg_extract('context', $vars, elgg_get_context());
630 11
				$vars['filter'] = elgg_get_filter_tabs($context, $filter_context, null, $vars);
631 11
				$vars['filter_id'] = $filter_id;
632 11
				$vars['filter_value'] = $filter_context;
633
			}
634 23
			break;
635
	}
636
637 43
	if (isset($vars['nav'])) {
638
		// Temporary helper until all core views are updated
639
		$vars['breadcrumbs'] = $vars['nav'];
640
		unset($vars['nav']);
641
	}
642
643 43
	$vars['identifier'] = _elgg_services()->request->getFirstUrlSegment();
644 43
	$vars['segments'] = _elgg_services()->request->getUrlSegments();
645 43
	array_shift($vars['segments']);
646
647 43
	$layout_name = elgg_trigger_plugin_hook('layout', 'page', $vars, $layout_name);
648
649 43
	$vars['layout'] = $layout_name;
650
651
	$layout_views = [
652 43
		"page/layouts/$layout_name",
653 43
		"page/layouts/default",
654
	];
655
656 43
	$output = '';
657 43
	foreach ($layout_views as $layout_view) {
658 43
		if (elgg_view_exists($layout_view)) {
659 10
			$output = elgg_view($layout_view, $vars);
660 43
			break;
661
		}
662
	}
663
664 43
	$timer->end([__FUNCTION__]);
665 43
	return $output;
666
}
667
668
/**
669
 * Normalizes deprecated content layout $vars for use in default layout
670
 * Helper function to assist plugins transitioning to 3.0
671
 *
672
 * @param array $vars Vars
673
 * @return array
674
 * @internal
675
 */
676
function _elgg_normalize_content_layout_vars(array $vars = []) {
677
678 12
	$context = elgg_extract('context', $vars, elgg_get_context());
679
680 12
	$vars['title'] = elgg_extract('title', $vars, '');
681 12
	if (!$vars['title'] && $vars['title'] !== false) {
682 1
		$vars['title'] = elgg_echo($context);
683
	}
684
685
	// 1.8 supported 'filter_override'
686 12
	if (isset($vars['filter_override'])) {
687
		$vars['filter'] = $vars['filter_override'];
688
	}
689
690
	// register the default content filters
691 12
	if (!isset($vars['filter']) && $context) {
692
		$selected = elgg_extract('filter_context', $vars);
693
		$vars['filter'] = elgg_get_filter_tabs($context, $selected, null, $vars);
694
		$vars['filter_id'] = $context;
695
		$vars['filter_value'] = $selected;
696
	}
697
698 12
	return $vars;
699
}
700
701
/**
702
 * Render a menu
703
 *
704
 * @see elgg_register_menu_item() for documentation on adding menu items and
705
 * navigation.php for information on the different menus available.
706
 *
707
 * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables
708
 * plugins to add menu items just before a menu is rendered. This is used by
709
 * dynamic menus (menus that change based on some input such as the user hover
710
 * menu). Using elgg_register_menu_item() in response to the hook can cause
711
 * incorrect links to show up. See the blog plugin's blog_owner_block_menu()
712
 * for an example of using this plugin hook.
713
 *
714
 * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins
715
 * to modify the structure of the menu (sort it, remove items, set variables on
716
 * the menu items).
717
 *
718
 * Preset (unprepared) menu items passed to the this function with the $vars
719
 * argument, will be merged with the registered items (registered with
720
 * elgg_register_menu_item()). The combined set of menu items will be passed
721
 * to 'register', 'menu:<menu_name>' hook.
722
 *
723
 * Plugins that pass preset menu items to this function and do not wish to be
724
 * affected by plugin hooks (e.g. if you are displaying multiple menus with
725
 * the same name on the page) should instead choose a unqie menu name
726
 * and define a menu_view argument to render menus consistently.
727
 * For example, if you have multiple 'filter' menus on the page:
728
 * <code>
729
 *    elgg_view_menu("filter:$uid", [
730
 *        'items' => $items,
731
 *        'menu_view' => 'navigation/menu/filter',
732
 *    ]);
733
 * </code>
734
 *
735
 * elgg_view_menu() uses views in navigation/menu
736
 *
737
 * @param string|Menu|UnpreparedMenu $menu Menu name (or object)
738
 * @param array                      $vars An associative array of display options for the menu.
739
 *
740
 *                          Options include:
741
 *                              items => an array of unprepared menu items
742
 *                                       as ElggMenuItem or menu item factory options
743
 *                              sort_by => string or php callback
744
 *                                  string options: 'name', 'priority' (default), 'text'
745
 *                                  or a php callback (a compare function for usort)
746
 *                              handler: string the page handler to build action URLs
747
 *                              entity: \ElggEntity to use to build action URLs
748
 *                              class: string the class for the entire menu.
749
 *                              menu_view: name of the view to be used to render the menu
750
 *                              show_section_headers: bool show headers before menu sections.
751
 *
752
 * @return string
753
 * @since 1.8.0
754
 */
755
function elgg_view_menu($menu, array $vars = []) {
756
757 54
	$menu_view = elgg_extract('menu_view', $vars);
758 54
	unset($vars['menu_view']);
759
760 54
	if (is_string($menu)) {
761 54
		$menu = _elgg_services()->menus->getMenu($menu, $vars);
762
	} elseif ($menu instanceof UnpreparedMenu) {
763
		$menu = _elgg_services()->menus->prepareMenu($menu);
764
	}
765
766 54
	if (!$menu instanceof Menu) {
1 ignored issue
show
$menu is always a sub-type of Elgg\Menu\Menu.
Loading history...
767
		throw new \InvalidArgumentException('$menu must be a menu name, a Menu, or UnpreparedMenu');
768
	}
769
770 54
	$name = $menu->getName();
771 54
	$params = $menu->getParams();
772
773
	$views = [
774 54
		$menu_view,
775 54
		"navigation/menu/$name",
776 54
		'navigation/menu/default',
777
	];
778
779 54
	foreach ($views as $view) {
780 54
		if (elgg_view_exists($view)) {
781 54
			return elgg_view($view, $params);
782
		}
783
	}
784 30
}
785
786
/**
787
 * Render a menu item (usually as a link)
788
 *
789
 * @param \ElggMenuItem $item The menu item
790
 * @param array         $vars Options to pass to output/url if a link
791
 * @return string
792
 * @since 1.9.0
793
 */
794
function elgg_view_menu_item(\ElggMenuItem $item, array $vars = []) {
795
796 21
	$vars = array_merge($item->getValues(), $vars);
797 21
	$vars['class'] = elgg_extract_class($vars, ['elgg-menu-content']);
798
799 21
	if ($item->getLinkClass()) {
800 11
		$vars['class'][] = $item->getLinkClass();
801
	}
802
803 21
	if ($item->getHref() === false || $item->getHref() === null) {
804 6
		$vars['class'][] = 'elgg-non-link';
805
	}
806
807 21
	if (!isset($vars['rel']) && !isset($vars['is_trusted'])) {
808 21
		$vars['is_trusted'] = true;
809
	}
810
811 21
	if ($item->getConfirmText()) {
812
		$vars['confirm'] = $item->getConfirmText();
813
	}
814
815 21
	return elgg_view('output/url', $vars);
816
}
817
818
/**
819
 * Returns a string of a rendered entity.
820
 *
821
 * Entity views are either determined by setting the view property on the entity
822
 * or by having a view named after the entity $type/$subtype.  Entities that have
823
 * neither a view property nor a defined $type/$subtype view will fall back to
824
 * using the $type/default view.
825
 *
826
 * The entity view is called with the following in $vars:
827
 *  - \ElggEntity 'entity' The entity being viewed
828
 *
829
 * @tip This function can automatically appends annotations to entities if in full
830
 * view and a handler is registered for the entity:annotate.  See https://github.com/Elgg/Elgg/issues/964 and
831
 * {@link elgg_view_entity_annotations()}.
832
 *
833
 * @param \ElggEntity $entity The entity to display
834
 * @param array       $vars   Array of variables to pass to the entity view.
835
 *      'full_view'           Whether to show a full or condensed view. (Default: true)
836
 *      'item_view'           Alternative view used to render this entity
837
 *      'register_rss_link'   Register the rss link availability (default: depending on full_view)
838
 *
839
 * @return false|string HTML to display or false
840
 * @todo The annotation hook might be better as a generic plugin hook to append content.
841
 */
842
function elgg_view_entity(\ElggEntity $entity, array $vars = []) {
843
844
	$defaults = [
845 13
		'full_view' => true,
846
	];
847
848 13
	$vars = array_merge($defaults, $vars);
849
	
850 13
	if (elgg_extract('register_rss_link', $vars, elgg_extract('full_view', $vars))) {
851 11
		elgg_register_rss_link();
852
	}
853
854 13
	$vars['entity'] = $entity;
855
856 13
	$entity_type = $entity->getType();
857 13
	$entity_subtype = $entity->getSubtype();
858
859
	$entity_views = [
860 13
		elgg_extract('item_view', $vars, ''),
861 13
		"$entity_type/$entity_subtype",
862 13
		"$entity_type/default",
863
	];
864
865 13
	$contents = '';
866 13
	foreach ($entity_views as $view) {
867 13
		if (elgg_view_exists($view)) {
868 13
			$contents = elgg_view($view, $vars);
869 13
			break;
870
		}
871
	}
872
873
	// Marcus Povey 20090616 : Speculative and low impact approach for fixing #964
874 13
	if ($vars['full_view']) {
875 11
		$annotations = elgg_view_entity_annotations($entity, $vars['full_view']);
876
877 11
		if ($annotations) {
878
			$contents .= $annotations;
879
		}
880
	}
881 13
	return $contents;
882
}
883
884
/**
885
 * View the icon of an entity
886
 *
887
 * Entity views are determined by having a view named after the entity $type/$subtype.
888
 * Entities that do not have a defined icon/$type/$subtype view will fall back to using
889
 * the icon/$type/default view.
890
 *
891
 * @param \ElggEntity $entity The entity to display
892
 * @param string      $size   The size: tiny, small, medium, large
893
 * @param array       $vars   An array of variables to pass to the view. Some possible
894
 *                            variables are img_class and link_class. See the
895
 *                            specific icon view for more parameters.
896
 *
897
 * @return string HTML to display or false
898
 */
899
function elgg_view_entity_icon(\ElggEntity $entity, $size = 'medium', $vars = []) {
900
901 6
	$vars['entity'] = $entity;
902 6
	$vars['size'] = $size;
903
904 6
	$entity_type = $entity->getType();
905
906 6
	$subtype = $entity->getSubtype();
907
908 6
	$contents = '';
909 6
	if (elgg_view_exists("icon/$entity_type/$subtype")) {
910
		$contents = elgg_view("icon/$entity_type/$subtype", $vars);
911
	}
912 6
	if (empty($contents) && elgg_view_exists("icon/$entity_type/default")) {
913 1
		$contents = elgg_view("icon/$entity_type/default", $vars);
914
	}
915 6
	if (empty($contents)) {
916 5
		$contents = elgg_view("icon/default", $vars);
917
	}
918
919 6
	return $contents;
920
}
921
922
/**
923
 * Returns a string of a rendered annotation.
924
 *
925
 * Annotation views are expected to be in annotation/$annotation_name.
926
 * If a view is not found for $annotation_name, the default annotation/default
927
 * will be used.
928
 *
929
 * @warning annotation/default is not currently defined in core.
930
 *
931
 * The annotation view is called with the following in $vars:
932
 *  - \ElggEntity 'annotation' The annotation being viewed.
933
 *
934
 * @param \ElggAnnotation $annotation The annotation to display
935
 * @param array           $vars       Variable array for view.
936
 *      'item_view'  Alternative view used to render an annotation
937
 *
938
 * @return string|false Rendered annotation
939
 */
940
function elgg_view_annotation(\ElggAnnotation $annotation, array $vars = []) {
941
	$defaults = [
942
		'full_view' => true,
943
	];
944
945
	$vars = array_merge($defaults, $vars);
946
	$vars['annotation'] = $annotation;
947
948
	$name = $annotation->name;
949
	if (empty($name)) {
950
		return false;
951
	}
952
953
	$annotation_views = [
954
		elgg_extract('item_view', $vars, ''),
955
		"annotation/$name",
956
		"annotation/default",
957
	];
958
959
	$contents = '';
960
	foreach ($annotation_views as $view) {
961
		if (elgg_view_exists($view)) {
962
			$contents = elgg_view($view, $vars);
963
			break;
964
		}
965
	}
966
967
	return $contents;
968
}
969
970
/**
971
 * Returns a rendered list of entities with pagination. This function should be
972
 * called by wrapper functions.
973
 *
974
 * @see elgg_list_entities()
975
 *
976
 * @param array $entities Array of entities
977
 * @param array $vars     Display variables
978
 *      'count'            The total number of entities across all pages
979
 *      'offset'           The current indexing offset
980
 *      'limit'            The number of entities to display per page (default from settings)
981
 *      'full_view'        Display the full view of the entities?
982
 *      'list_class'       CSS class applied to the list
983
 *      'item_class'       CSS class applied to the list items
984
 *      'item_view'        Alternative view to render list items content
985
 *      'list_item_view'   Alternative view to render list items
986
 *      'pagination'       Display pagination?
987
 *      'base_url'         Base URL of list (optional)
988
 *      'url_fragment'     URL fragment to add to links if not present in base_url (optional)
989
 *      'position'         Position of the pagination: before, after, or both
990
 *      'list_type'        List type: 'list' (default), 'gallery'
991
 *      'list_type_toggle' Display the list type toggle?
992
 *      'no_results'       Message to display if no results (string|true|Closure)
993
 *
994
 * @return string The rendered list of entities
995
 */
996
function elgg_view_entity_list($entities, array $vars = []) {
997 24
	$offset = (int) get_input('offset', 0);
998
999
	// list type can be passed as request parameter
1000 24
	$list_type = get_input('list_type', 'list');
1001
1002
	$defaults = [
1003 24
		'items' => $entities,
1004 24
		'list_class' => 'elgg-list-entity',
1005
		'full_view' => true,
1006
		'pagination' => true,
1007 24
		'list_type' => $list_type,
1008
		'list_type_toggle' => false,
1009 24
		'offset' => $offset,
1010
		'limit' => null,
1011
	];
1012
1013 24
	$vars = array_merge($defaults, $vars);
1014
1015 24
	if (!$vars["limit"] && !$vars["offset"]) {
1016
		// no need for pagination if listing is unlimited
1017
		$vars["pagination"] = false;
1018
	}
1019
1020 24
	$view = "page/components/{$vars['list_type']}";
1021 24
	if (!elgg_view_exists($view)) {
1022 20
		$view = 'page/components/list';
1023
	}
1024
	
1025 24
	return elgg_view($view, $vars);
1026
}
1027
1028
/**
1029
 * Returns a rendered list of annotations, plus pagination. This function
1030
 * should be called by wrapper functions.
1031
 *
1032
 * @param array $annotations Array of annotations
1033
 * @param array $vars        Display variables
1034
 *      'count'      The total number of annotations across all pages
1035
 *      'offset'     The current indexing offset
1036
 *      'limit'      The number of annotations to display per page
1037
 *      'full_view'  Display the full view of the annotation?
1038
 *      'list_class' CSS Class applied to the list
1039
 *      'item_view'  Alternative view to render list items
1040
 *      'offset_key' The url parameter key used for offset
1041
 *      'no_results' Message to display if no results (string|true|Closure)
1042
 *
1043
 * @return string The list of annotations
1044
 * @internal
1045
 */
1046
function elgg_view_annotation_list($annotations, array $vars = []) {
1047
	$defaults = [
1048
		'items' => $annotations,
1049
		'offset' => null,
1050
		'limit' => null,
1051
		'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9
1052
		'full_view' => true,
1053
		'offset_key' => 'annoff',
1054
	];
1055
1056
	$vars = array_merge($defaults, $vars);
1057
1058
	if (!$vars["limit"] && !$vars["offset"]) {
1059
		// no need for pagination if listing is unlimited
1060
		$vars["pagination"] = false;
1061
	}
1062
1063
	return elgg_view('page/components/list', $vars);
1064
}
1065
1066
/**
1067
 * Display a plugin-specified rendered list of annotations for an entity.
1068
 *
1069
 * This displays the output of functions registered to the entity:annotation,
1070
 * $entity_type plugin hook.
1071
 *
1072
 * This is called automatically by the framework from {@link elgg_view_entity()}
1073
 *
1074
 * @param \ElggEntity $entity    Entity
1075
 * @param bool        $full_view Display full view?
1076
 *
1077
 * @return mixed string or false on failure
1078
 * @todo Change the hook name.
1079
 */
1080
function elgg_view_entity_annotations(\ElggEntity $entity, $full_view = true) {
1081
	
1082 11
	$entity_type = $entity->getType();
1083
1084 11
	$annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type,
1085
		[
1086 11
			'entity' => $entity,
1087 11
			'full_view' => $full_view,
1088
		]
1089
	);
1090
1091 11
	return $annotations;
1092
}
1093
1094
/**
1095
 * Renders a title.
1096
 *
1097
 * This is a shortcut for {@elgg_view page/elements/title}.
1098
 *
1099
 * @param string $title The page title
1100
 * @param array  $vars  View variables (was submenu be displayed? (deprecated))
1101
 *
1102
 * @return string The HTML (etc)
1103
 */
1104
function elgg_view_title($title, array $vars = []) {
1105 8
	$vars['title'] = $title;
1106
1107 8
	return elgg_view('page/elements/title', $vars);
1108
}
1109
1110
/**
1111
 * Displays a UNIX timestamp in a friendly way
1112
 *
1113
 * @see elgg_get_friendly_time()
1114
 *
1115
 * @param int $time A UNIX epoch timestamp
1116
 *
1117
 * @return string The friendly time HTML
1118
 * @since 1.7.2
1119
 */
1120
function elgg_view_friendly_time($time) {
1121 3
	$view = 'output/friendlytime';
1122 3
	$vars = ['time' => $time];
1123 3
	$viewtype = elgg_view_exists($view) ? '' : 'default';
1124
1125 3
	return _elgg_view_under_viewtype($view, $vars, $viewtype);
1126
}
1127
1128
/**
1129
 * Returns rendered comments and a comment form for an entity.
1130
 *
1131
 * @tip Plugins can override the output by registering a handler
1132
 * for the comments, $entity_type hook.  The handler is responsible
1133
 * for formatting the comments and the add comment form.
1134
 *
1135
 * @param \ElggEntity $entity      The entity to view comments of
1136
 * @param bool        $add_comment Include a form to add comments?
1137
 * @param array       $vars        Variables to pass to comment view
1138
 *
1139
 * @return string|false Rendered comments or false on failure
1140
 */
1141
function elgg_view_comments($entity, $add_comment = true, array $vars = []) {
1142
	
1143 1
	if (!$entity instanceof \ElggEntity) {
1144
		return false;
1145
	}
1146
1147 1
	$vars['entity'] = $entity;
1148 1
	$vars['show_add_form'] = $add_comment;
1149 1
	$vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments");
1150
1151 1
	$output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false);
1152 1
	if ($output !== false) {
1153
		return $output;
1154
	}
1155
	
1156 1
	return elgg_view('page/elements/comments', $vars);
1157
}
1158
1159
/**
1160
 * Wrapper function for the image block display pattern.
1161
 *
1162
 * Fixed width media on the side (image, icon, flash, etc.).
1163
 * Descriptive content filling the rest of the column.
1164
 *
1165
 * @note Use the $vars "image_alt" key to set an image on the right. If you do, you may pass
1166
 *       in an empty string for $image to have only the right image.
1167
 *
1168
 * This is a shortcut for {@elgg_view page/components/image_block}.
1169
 *
1170
 * @param string $image The icon and other information
1171
 * @param string $body  Description content
1172
 * @param array  $vars  Additional parameters for the view
1173
 *
1174
 * @return string
1175
 * @since 1.8.0
1176
 */
1177
function elgg_view_image_block($image, $body, $vars = []) {
1178 5
	$vars['image'] = $image;
1179 5
	$vars['body'] = $body;
1180 5
	return elgg_view('page/components/image_block', $vars);
1181
}
1182
1183
/**
1184
 * Wrapper function for the module display pattern.
1185
 *
1186
 * Box with header, body, footer
1187
 *
1188
 * This is a shortcut for {@elgg_view page/components/module}.
1189
 *
1190
 * @param string $type  The type of module (main, info, popup, aside, etc.)
1191
 * @param string $title A title to put in the header
1192
 * @param string $body  Content of the module
1193
 * @param array  $vars  Additional parameters for the module
1194
 *
1195
 * @return string
1196
 * @since 1.8.0
1197
 */
1198
function elgg_view_module($type, $title, $body, array $vars = []) {
1199 21
	$vars['type'] = $type;
1200 21
	$vars['title'] = $title;
1201 21
	$vars['body'] = $body;
1202 21
	return elgg_view('page/components/module', $vars);
1203
}
1204
1205
/**
1206
 * Wrapper function for the message display pattern.
1207
 *
1208
 * Box with header, body
1209
 *
1210
 * This is a shortcut for {@elgg_view page/components/message}.
1211
 *
1212
 * @param string $type The type of message (error, success, warning, help, notice)
1213
 * @param string $body Content of the message
1214
 * @param array  $vars Additional parameters for the message
1215
 *
1216
 * @return string
1217
 * @since 3.0.0
1218
 */
1219
function elgg_view_message($type, $body, array $vars = []) {
1220 16
	$vars['type'] = $type;
1221 16
	$vars['body'] = $body;
1222 16
	return elgg_view('page/components/message', $vars);
1223
}
1224
1225
/**
1226
 * Renders a human-readable representation of a river item
1227
 *
1228
 * @param \ElggRiverItem $item A river item object
1229
 * @param array          $vars An array of variables for the view
1230
 *      'item_view'         Alternative view to render the item
1231
 *      'register_rss_link' Register the rss link availability (default: false)
1232
 * @return string returns empty string if could not be rendered
1233
 */
1234
function elgg_view_river_item($item, array $vars = []) {
1235
1236
	if (!($item instanceof \ElggRiverItem)) {
1237
		return '';
1238
	}
1239
1240
	// checking default viewtype since some viewtypes do not have unique views per item (rss)
1241
	$view = $item->getView();
1242
1243
	$subject = $item->getSubjectEntity();
1244
	$object = $item->getObjectEntity();
1245
	if (!$subject || !$object) {
1246
		// subject is disabled or subject/object deleted
1247
		return '';
1248
	}
1249
	
1250
	if (elgg_extract('register_rss_link', $vars)) {
1251
		elgg_register_rss_link();
1252
	}
1253
1254
	$vars['item'] = $item;
1255
1256
	// create river view logic
1257
	$type = $object->getType();
1258
	$subtype = $object->getSubtype();
1259
	$action = $item->action_type;
1260
1261
	$river_views = [
1262
		elgg_extract('item_view', $vars, ''),
1263
		'river/item', // important for other viewtypes, e.g. "rss"
1264
		$view,
1265
		"river/{$type}/{$subtype}/{$action}",
1266
		"river/{$type}/{$subtype}/default",
1267
		"river/{$type}/{$action}",
1268
		"river/{$type}/default",
1269
		'river/elements/layout',
1270
	];
1271
1272
	$contents = '';
1273
	foreach ($river_views as $view) {
1274
		if (elgg_view_exists($view)) {
1275
			$contents = elgg_view($view, $vars);
1276
			break;
1277
		}
1278
	}
1279
1280
	return $contents;
1281
}
1282
1283
/**
1284
 * Convenience function for generating a form from a view in a standard location.
1285
 *
1286
 * This function assumes that the body of the form is located at "forms/$action" and
1287
 * sets the action by default to "action/$action".  Automatically wraps the forms/$action
1288
 * view with a <form> tag and inserts the anti-csrf security tokens.
1289
 *
1290
 * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any
1291
 * slashes with dashes (blog/save becomes elgg-form-blog-save)
1292
 *
1293
 * @example
1294
 * <code>echo elgg_view_form('login');</code>
1295
 *
1296
 * This would assume a "login" form body to be at "forms/login" and would set the action
1297
 * of the form to "http://yoursite.com/action/login".
1298
 *
1299
 * If elgg_view('forms/login') is:
1300
 * <input type="text" name="username" />
1301
 * <input type="password" name="password" />
1302
 *
1303
 * Then elgg_view_form('login') generates:
1304
 * <form action="http://yoursite.com/action/login" method="post">
1305
 *     ...security tokens...
1306
 *     <input type="text" name="username" />
1307
 *     <input type="password" name="password" />
1308
 * </form>
1309
 *
1310
 * @param string $action    The name of the action. An action name does not include
1311
 *                          the leading "action/". For example, "login" is an action name.
1312
 * @param array  $form_vars $vars environment passed to the "input/form" view
1313
 *                           - 'ajax' bool If true, the form will be submitted with an Ajax request
1314
 * @param array  $body_vars $vars environment passed to the "forms/$action" view
1315
 *
1316
 * @return string The complete form
1317
 */
1318
function elgg_view_form($action, $form_vars = [], $body_vars = []) {
1319 31
	return _elgg_services()->forms->render($action, $form_vars, $body_vars);
1320
}
1321
1322
/**
1323
 * Sets form footer and defers its rendering until the form view and extensions have been rendered.
1324
 * Deferring footer rendering allows plugins to extend the form view while maintaining
1325
 * logical DOM structure.
1326
 * Footer will be rendered using 'elements/forms/footer' view after form body has finished rendering
1327
 *
1328
 * @param string $footer Footer
1329
 * @return bool
1330
 */
1331
function elgg_set_form_footer($footer = '') {
1332 22
	return _elgg_services()->forms->setFooter($footer);
1333
}
1334
1335
/**
1336
 * Returns currently set footer, or false if not in the form rendering stack
1337
 * @return string|false
1338
 */
1339
function elgg_get_form_footer() {
1340
	return _elgg_services()->forms->getFooter();
1341
}
1342
1343
/**
1344
 * Split array of vars into subarrays based on property prefixes
1345
 *
1346
 * @see elgg_view_field()
1347
 *
1348
 * @param array $vars     Vars to split
1349
 * @param array $prefixes Prefixes to split
1350
 *
1351
 * @return array
1352
 */
1353
function _elgg_split_vars(array $vars = [], array $prefixes = null) {
1354
1355 9
	if (!isset($prefixes)) {
1356 9
		$prefixes = ['#'];
1357
	}
1358
1359 9
	$return = [];
1360
1361 9
	foreach ($vars as $key => $value) {
1362 9
		foreach ($prefixes as $prefix) {
1363 9
			if (substr($key, 0, 1) === $prefix) {
1364 9
				$key = substr($key, 1);
1365 9
				$return[$prefix][$key] = $value;
1366 9
				break;
1367
			} else {
1368 9
				$return[''][$key] = $value;
1369
			}
1370
		}
1371
	}
1372
1373 9
	return $return;
1374
}
1375
1376
/**
1377
 * Renders a form field, usually with a wrapper element, a label, help text, etc.
1378
 *
1379
 * @param array $params Field parameters and variables for the input view.
1380
 *                      Keys not prefixed with hash (#) are passed to the input view as $vars.
1381
 *                      Keys prefixed with a hash specify the field wrapper (.elgg-view-field) output.
1382
 *                       - #type: specifies input view. E.g. "text" uses the view "input/text".
1383
 *                       - #label: field label HTML
1384
 *                       - #help: field help HTML
1385
 *                       - #class: field class name
1386
 *                       - #view: custom view to use to render the field
1387
 *                       - #html: can be used to render custom HTML instead of in put field, helpful when you need to add a help paragraph or similar
1388
 *                      Note: Both #label and #help are printed unescaped within their wrapper element.
1389
 *                      Note: Some fields (like input/checkbox) need special attention because #label and label serve different purposes
1390
 *                      "#label" will be used as a label in the field wrapper but "label" will be used in the input view
1391
 *
1392
 * @return string
1393
 * @since 2.3
1394
 */
1395
function elgg_view_field(array $params = []) {
1396
1397 26
	if (!empty($params['#html'])) {
1398
		return $params['#html'];
1399
	}
1400
	
1401 26
	if (empty($params['#type'])) {
1402
		_elgg_services()->logger->error(__FUNCTION__ . '(): $params["#type"] is required.');
1403
		return '';
1404
	}
1405
1406 26
	$input_type = $params['#type'];
1407 26
	if (!elgg_view_exists("input/$input_type")) {
1408 17
		return '';
1409
	}
1410
1411 9
	$hidden_types = ['hidden', 'securitytoken'];
1412 9
	if (in_array($input_type, $hidden_types)) {
1413 9
		$params = _elgg_split_vars($params);
1414 9
		return elgg_view("input/$input_type", $params['']);
1415
	}
1416
1417 9
	$id = elgg_extract('id', $params);
1418 9
	if (!$id) {
1419 9
		$id = "elgg-field-" . base_convert(mt_rand(), 10, 36);
1420 9
		$params['id'] = $id;
1421
	}
1422
1423 9
	$make_special_checkbox_label = false;
1424 9
	if ($input_type == 'checkbox' && (isset($params['label']) || isset($params['#label']))) {
1425
		if (isset($params['#label']) && isset($params['label'])) {
1426
			$params['label_tag'] = 'div';
1427
		} else {
1428
			$label = elgg_extract('label', $params);
1429
			$label = elgg_extract('#label', $params, $label);
1430
1431
			$params['#label'] = $label;
1432
			unset($params['label']);
1433
1434
			// Single checkbox input view gets special treatment
1435
			// We don't want the field label to appear a checkbox without a label
1436
			$make_special_checkbox_label = true;
1437
		}
1438
	}
1439
1440
	// Need to set defaults to prevent input keys with same name ending up in element vars if not provided
1441
	$defaults = [
1442 9
		'#class' => ELGG_ENTITIES_ANY_VALUE,
1443
		'#help' => ELGG_ENTITIES_ANY_VALUE,
1444
		'#label' => ELGG_ENTITIES_ANY_VALUE,
1445
		'#view' => ELGG_ENTITIES_ANY_VALUE,
1446
	];
1447 9
	$params = array_merge($defaults, $params);
1448
	
1449
	// first pass non-hash keys into both
1450 9
	$split_params = _elgg_split_vars($params);
1451
1452
	// $vars passed to input/$input_name
1453 9
	$input_vars = $split_params[''];
1454
	
1455
	// $vars passed to label, help and field wrapper views
1456 9
	$element_vars = array_merge($split_params[''], $split_params['#']);
1457
1458
	// field input view needs this
1459 9
	$input_vars['input_type'] = $input_type;
1460
1461
	// field views get more data
1462 9
	$element_vars['input_type'] = $input_type;
1463
1464
	// wrap if present
1465 9
	$element_vars['label'] = elgg_view('elements/forms/label', $element_vars);
1466 9
	$element_vars['help'] = elgg_view('elements/forms/help', $element_vars);
1467
1468 9
	if ($make_special_checkbox_label) {
1469
		$input_vars['label'] = $element_vars['label'];
1470
		$input_vars['label_tag'] = 'div';
1471
		unset($element_vars['label']);
1472
	}
1473 9
	$element_vars['input'] = elgg_view("elements/forms/input", $input_vars);
1474
1475 9
	return elgg_view('elements/forms/field', $element_vars);
1476
}
1477
1478
/**
1479
 * Create a tagcloud for viewing
1480
 *
1481
 *
1482
 * @param array $options Any elgg_get_tags() options except:
1483
 *
1484
 * 	type => must be single entity type
1485
 *
1486
 * 	subtype => must be single entity subtype
1487
 *
1488
 * @return string
1489
 *
1490
 * @see elgg_get_tags()
1491
 * @since 1.7.1
1492
 */
1493
function elgg_view_tagcloud(array $options = []) {
1494
1495
	$type = $subtype = '';
1496
	if (isset($options['type'])) {
1497
		$type = $options['type'];
1498
	}
1499
	if (isset($options['subtype'])) {
1500
		$subtype = $options['subtype'];
1501
	}
1502
1503
	$tag_data = elgg_get_tags($options);
1504
	return elgg_view("output/tagcloud", [
1505
		'value' => $tag_data,
1506
		'type' => $type,
1507
		'subtype' => $subtype,
1508
	]);
1509
}
1510
1511
/**
1512
 * View an item in a list
1513
 *
1514
 * @param mixed $item Entity, annotation, river item, or other data
1515
 * @param array $vars Additional parameters for the rendering
1516
 *                    'item_view' - Alternative view used to render list items
1517
 *                                  This parameter is required if rendering
1518
 *                                  list items that are not entity, annotation or river
1519
 * @return false|string
1520
 * @since 1.8.0
1521
 * @internal
1522
 */
1523
function elgg_view_list_item($item, array $vars = []) {
1524
1525 6
	if ($item instanceof \ElggEntity) {
1526 6
		return elgg_view_entity($item, $vars);
1527
	} else if ($item instanceof \ElggAnnotation) {
1528
		return elgg_view_annotation($item, $vars);
1529
	} else if ($item instanceof \ElggRiverItem) {
1530
		return elgg_view_river_item($item, $vars);
1531
	}
1532
1533
	$view = elgg_extract('item_view', $vars);
1534
	if ($view && elgg_view_exists($view)) {
1535
		$vars['item'] = $item;
1536
		return elgg_view($view, $vars);
1537
	}
1538
1539
	return '';
1540
}
1541
1542
/**
1543
 * View an icon glyph
1544
 *
1545
 * @param string $name The specific icon to display
1546
 * @param mixed  $vars The additional classname as a string ('float', 'float-alt' or a custom class)
1547
 *                     or an array of variables (array('class' => 'float')) to pass to the icon view.
1548
 *
1549
 * @return string The html for displaying an icon
1550
 * @throws InvalidArgumentException
1551
 */
1552
function elgg_view_icon($name, $vars = []) {
1553 20
	if (empty($vars)) {
1554 12
		$vars = [];
1555
	}
1556
1557 20
	if (is_string($vars)) {
1558 1
		$vars = ['class' => $vars];
1559
	}
1560
1561 20
	if (!is_array($vars)) {
1562
		throw new \InvalidArgumentException('$vars needs to be a string or an array');
1563
	}
1564
1565 20
	$vars['class'] = elgg_extract_class($vars, "elgg-icon-$name");
1566
1567 20
	return elgg_view("output/icon", $vars);
1568
}
1569
1570
/**
1571
 * Include the RSS icon link and link element in the head
1572
 *
1573
 * @return void
1574
 */
1575
function elgg_register_rss_link() {
1576 33
	_elgg_config()->_elgg_autofeed = true;
1577 33
}
1578
1579
/**
1580
 * Remove the RSS icon link and link element from the head
1581
 *
1582
 * @return void
1583
 */
1584
function elgg_unregister_rss_link() {
1585
	_elgg_config()->_elgg_autofeed = false;
1586
}
1587
1588
/**
1589
 * Should the RSS view of this URL be linked to?
1590
 *
1591
 * @return bool
1592
 * @internal
1593
 */
1594
function _elgg_has_rss_link() {
1595 52
	if (_elgg_config()->disable_rss) {
1596
		return false;
1597
	}
1598
1599 52
	return (bool) _elgg_config()->_elgg_autofeed;
1600
}
1601
1602
/**
1603
 * Auto-registers views from a location.
1604
 *
1605
 * @note Views in plugin/views/ are automatically registered for active plugins.
1606
 * Plugin authors would only need to call this if optionally including
1607
 * an entire views structure.
1608
 *
1609
 * @param string $view_base Optional The base of the view name without the view type.
1610
 * @param string $folder    Required The folder to begin looking in
1611
 * @param string $ignored   This argument is ignored
1612
 * @param string $viewtype  The type of view we're looking at (default, rss, etc)
1613
 *
1614
 * @return bool returns false if folder can't be read
1615
 * @since 1.7.0
1616
 * @see elgg_set_view_location()
1617
 * @internal
1618
 */
1619
function autoregister_views($view_base, $folder, $ignored, $viewtype) {
1620
	return _elgg_services()->views->autoregisterViews($view_base, $folder, $viewtype);
1621
}
1622
1623
/**
1624
 * Minifies simplecache CSS and JS views by handling the "simplecache:generate" hook
1625
 *
1626
 * @param string $hook    The name of the hook
1627
 * @param string $type    View type (css, js, or unknown)
1628
 * @param string $content Content of the view
1629
 * @param array  $params  Array of parameters
1630
 *
1631
 * @return string|null View content minified (if css/js type)
1632
 * @internal
1633
 */
1634
function _elgg_views_minify($hook, $type, $content, $params) {
1635
	if (preg_match('~[\.-]min\.~', $params['view'])) {
1636
		// bypass minification
1637
		return;
1638
	}
1639
1640
	if ($type == 'js') {
1641
		if (_elgg_config()->simplecache_minify_js) {
1642
			return JSMin::minify($content);
1643
		}
1644
	} elseif ($type == 'css') {
1645
		if (_elgg_config()->simplecache_minify_css) {
1646
			$cssmin = new CSSmin();
1647
			return $cssmin->run($content);
1648
		}
1649
	}
1650
}
1651
1652
/**
1653
 * Preprocesses CSS views sent by /cache URLs
1654
 *
1655
 * @param string $hook    The name of the hook "simplecache:generate" or "cache:generate"
1656
 * @param string $type    "css"
1657
 * @param string $content Content of the view
1658
 * @param array  $params  Array of parameters
1659
 *
1660
 * @return string|null View content
1661
 * @internal
1662
 */
1663
function _elgg_views_preprocess_css($hook, $type, $content, $params) {
1664 6
	$options = elgg_extract('compiler_options', $params, []);
1665 6
	return _elgg_services()->cssCompiler->compile($content, $options);
1666
}
1667
1668
/**
1669
 * Inserts module names into anonymous modules by handling the "simplecache:generate" and "cache:generate" hook.
1670
 *
1671
 * @param string $hook    The name of the hook
1672
 * @param string $type    View type (css, js, or unknown)
1673
 * @param string $content Content of the view
1674
 * @param array  $params  Array of parameters
1675
 *
1676
 * @return string|null View content minified (if css/js type)
1677
 * @internal
1678
 */
1679
function _elgg_views_amd($hook, $type, $content, $params) {
1680
	$filter = new \Elgg\Amd\ViewFilter();
1681
	return $filter->filter($params['view'], $content);
1682
}
1683
1684
/**
1685
 * Sends X-Frame-Options header on page requests
1686
 *
1687
 * @return void
1688
 *
1689
 * @internal
1690
 */
1691
function _elgg_views_send_header_x_frame_options() {
1692 10
	elgg_set_http_header('X-Frame-Options: SAMEORIGIN');
1693 10
}
1694
1695
/**
1696
 * Is there a chance a plugin is altering this view?
1697
 *
1698
 * @note Must be called after the [init, system] event, ideally as late as possible.
1699
 *
1700
 * @note Always returns true if the view's location is set in /engine/views.php. Elgg does not keep
1701
 *       track of the defaults for those locations.
1702
 *
1703
 * <code>
1704
 * // check a view in core
1705
 * if (_elgg_view_may_be_altered('foo/bar', 'foo/bar.php')) {
1706
 *     // use the view for BC
1707
 * }
1708
 *
1709
 * // check a view in a bundled plugin
1710
 * $dir = __DIR__ . "/views/" . elgg_get_viewtype();
1711
 * if (_elgg_view_may_be_altered('foo.css', "$dir/foo.css.php")) {
1712
 *     // use the view for BC
1713
 * }
1714
 * </code>
1715
 *
1716
 * @param string $view View name. E.g. "elgg/init.js"
1717
 * @param string $path Absolute file path, or path relative to the viewtype directory. E.g. "elgg/init.js.php"
1718
 *
1719
 * @return bool
1720
 * @internal
1721
 */
1722
function _elgg_view_may_be_altered($view, $path) {
1723
	$views = _elgg_services()->views;
1724
1725
	if ($views->viewIsExtended($view) || $views->viewHasHookHandlers($view)) {
1726
		return true;
1727
	}
1728
1729
	$viewtype = elgg_get_viewtype();
1730
1731
	// check location
1732
	if (0 === strpos($path, '/') || preg_match('~^([A-Za-z]\:)?\\\\~', $path)) {
1733
		// absolute path
1734
		$expected_path = $path;
1735
	} else {
1736
		// relative path
1737
		$expected_path = Paths::elgg() . "views/$viewtype/" . ltrim($path, '/\\');
1738
	}
1739
1740
	$view_path = $views->findViewFile($view, $viewtype);
1741
1742
	return realpath($view_path) !== realpath($expected_path);
1743
}
1744
1745
/**
1746
 * Initialize viewtypes on system boot event
1747
 * This ensures simplecache is cleared during upgrades. See #2252
1748
 *
1749
 * @return void
1750
 * @internal
1751
 * @elgg_event_handler boot system
1752
 */
1753
function elgg_views_boot() {
1754 72
	_elgg_services()->viewCacher->registerCoreViews();
1755
1756
	// jQuery and UI must come before require. See #9024
1757 72
	elgg_register_js('jquery', elgg_get_simplecache_url('jquery.js'), 'head');
1758 72
	elgg_load_js('jquery');
1759
1760 72
	elgg_register_js('jquery-ui', elgg_get_simplecache_url('jquery-ui.js'), 'head');
1761 72
	elgg_load_js('jquery-ui');
1762
1763 72
	elgg_register_js('elgg.require_config', elgg_get_simplecache_url('elgg/require_config.js'), 'head');
1764 72
	elgg_load_js('elgg.require_config');
1765
1766 72
	elgg_register_js('require', elgg_get_simplecache_url('require.js'), 'head');
1767 72
	elgg_load_js('require');
1768
1769 72
	elgg_register_js('elgg', elgg_get_simplecache_url('elgg.js'), 'head');
1770 72
	elgg_load_js('elgg');
1771
1772 72
	elgg_register_css('font-awesome', elgg_get_simplecache_url('font-awesome/css/all.min.css'));
1773 72
	elgg_load_css('font-awesome');
1774
1775 72
	elgg_register_css('elgg', elgg_get_simplecache_url('elgg.css'));
1776 72
	elgg_load_css('elgg');
1777
1778 72
	elgg_register_simplecache_view('elgg/init.js');
1779
1780 72
	elgg_extend_view('elgg.css', 'lightbox/elgg-colorbox-theme/colorbox.css');
1781
1782 72
	elgg_define_js('jquery.ui.autocomplete.html', [
1783 72
		'deps' => ['jquery-ui'],
1784
	]);
1785
1786 72
	elgg_register_js('elgg.avatar_cropper', elgg_get_simplecache_url('elgg/ui.avatar_cropper.js'));
1787
1788
	// @deprecated 2.2
1789 72
	elgg_register_js('elgg.ui.river', elgg_get_simplecache_url('elgg/ui.river.js'));
1790
1791 72
	elgg_register_js('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.js'));
1792 72
	elgg_register_css('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.css'));
1793
1794 72
	elgg_register_css('jquery.treeview', elgg_get_simplecache_url('jquery-treeview/jquery.treeview.css'));
1795 72
	elgg_define_js('jquery.treeview', [
1796 72
		'src' => elgg_get_simplecache_url('jquery-treeview/jquery.treeview.js'),
1797 72
		'exports' => 'jQuery.fn.treeview',
1798
		'deps' => ['jquery'],
1799
	]);
1800
1801 72
	elgg_register_ajax_view('languages.js');
1802
1803
	// pre-process CSS regardless of simplecache
1804 72
	elgg_register_plugin_hook_handler('cache:generate', 'css', '_elgg_views_preprocess_css');
1805 72
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_preprocess_css');
1806
1807 72
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_amd');
1808 72
	elgg_register_plugin_hook_handler('cache:generate', 'js', '_elgg_views_amd');
1809 72
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_minify');
1810 72
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_minify');
1811
1812 72
	elgg_register_plugin_hook_handler('output:before', 'page', '_elgg_views_send_header_x_frame_options');
1813
1814 72
	elgg_register_plugin_hook_handler('view_vars', 'elements/forms/help', '_elgg_views_file_help_upload_limit');
1815
1816
	// registered with high priority for BC
1817
	// prior to 2.2 registration used to take place in _elgg_views_prepare_head() before the hook was triggered
1818 72
	elgg_register_plugin_hook_handler('head', 'page', '_elgg_views_prepare_favicon_links', 1);
1819
1820
	// set default icon sizes - can be overridden with plugin
1821 72
	if (!_elgg_config()->hasValue('icon_sizes')) {
1822
		$icon_sizes = [
1823 5
			'topbar' => ['w' => 16, 'h' => 16, 'square' => true, 'upscale' => true],
1824
			'tiny' => ['w' => 25, 'h' => 25, 'square' => true, 'upscale' => true],
1825
			'small' => ['w' => 40, 'h' => 40, 'square' => true, 'upscale' => true],
1826
			'medium' => ['w' => 100, 'h' => 100, 'square' => true, 'upscale' => true],
1827
			'large' => ['w' => 200, 'h' => 200, 'square' => true, 'upscale' => true],
1828
			'master' => ['w' => 10240, 'h' => 10240, 'square' => false, 'upscale' => false, 'crop' => false],
1829
		];
1830 5
		elgg_set_config('icon_sizes', $icon_sizes);
1831
	}
1832
1833
	// Configure lightbox
1834 72
	elgg_register_plugin_hook_handler('elgg.data', 'site', '_elgg_set_lightbox_config');
1835 72
}
1836
1837
/**
1838
 * Get the site data to be merged into "elgg" in elgg.js.
1839
 *
1840
 * Unlike _elgg_get_js_page_data(), the keys returned are literal expressions.
1841
 *
1842
 * @return array
1843
 * @internal
1844
 */
1845
function _elgg_get_js_site_data() {
1846
	$language = _elgg_config()->language;
1847
	if (!$language) {
1848
		$language = 'en';
1849
	}
1850
1851
	return [
1852
		'elgg.data' => (object) elgg_trigger_plugin_hook('elgg.data', 'site', null, []),
1853
		'elgg.version' => elgg_get_version(),
1854
		'elgg.release' => elgg_get_version(true),
1855
		'elgg.config.wwwroot' => elgg_get_site_url(),
1856
1857
		// refresh token 3 times during its lifetime (in microseconds 1000 * 1/3)
1858
		'elgg.security.interval' => (int) elgg()->csrf->getActionTokenTimeout() * 333,
1859
		'elgg.config.language' => $language,
1860
	];
1861
}
1862
1863
/**
1864
 * Get the initial contents of "elgg" client side. Will be extended by elgg.js.
1865
 *
1866
 * @return array
1867
 * @internal
1868
 */
1869
function _elgg_get_js_page_data() {
1870 8
	$data = elgg_trigger_plugin_hook('elgg.data', 'page', null, []);
1871 8
	if (!is_array($data)) {
1872
		elgg_log('"elgg.data" plugin hook handlers must return an array. Returned ' . gettype($data) . '.', 'ERROR');
1873
		$data = [];
1874
	}
1875
1876
	$elgg = [
1877 8
		'config' => [
1878 8
			'lastcache' => (int) _elgg_config()->lastcache,
1879 8
			'viewtype' => elgg_get_viewtype(),
1880 8
			'simplecache_enabled' => (int) elgg_is_simplecache_enabled(),
1881 8
			'current_language' => get_current_language(),
1882
		],
1883
		'security' => [
1884
			'token' => [
1885 8
				'__elgg_ts' => $ts = time(),
1886 8
				'__elgg_token' => generate_action_token($ts),
1887
			],
1888
		],
1889
		'session' => [
1890
			'user' => null,
1891 8
			'token' => _elgg_services()->session->get('__elgg_session'),
1892
		],
1893 8
		'_data' => (object) $data,
1894
	];
1895
1896 8
	if (_elgg_config()->elgg_load_sync_code) {
1897
		$elgg['config']['load_sync_code'] = true;
1898
	}
1899
1900 8
	$page_owner = elgg_get_page_owner_entity();
1901 8
	if ($page_owner instanceof ElggEntity) {
1902
		$elgg['page_owner'] = $page_owner->toObject();
1903
	}
1904
1905 8
	$user = elgg_get_logged_in_user_entity();
1906 8
	if ($user instanceof ElggUser) {
1907
		$user_object = $user->toObject();
1908
		$user_object->admin = $user->isAdmin();
1909
		$elgg['session']['user'] = $user_object;
1910
	}
1911
1912 8
	return $elgg;
1913
}
1914
1915
/**
1916
 * Render a view while the global viewtype is temporarily changed. This makes sure that
1917
 * nested views use the same viewtype.
1918
 *
1919
 * @param string $view     View name
1920
 * @param array  $vars     View vars
1921
 * @param string $viewtype Temporary viewtype ('' to leave current)
1922
 *
1923
 * @return mixed
1924
 * @internal
1925
 */
1926
function _elgg_view_under_viewtype($view, $vars, $viewtype) {
1927 27
	$current_view_type = null;
1928 27
	if ($viewtype) {
1929 21
		$current_view_type = elgg_get_viewtype();
1930 21
		elgg_set_viewtype($viewtype);
1931
	}
1932
1933 27
	$ret = elgg_view($view, $vars);
1934
1935 27
	if (isset($current_view_type)) {
1936 21
		elgg_set_viewtype($current_view_type);
1937
	}
1938
1939 27
	return $ret;
1940
}
1941
1942
/**
1943
 * Set lightbox config
1944
 *
1945
 * @param string $hook   "elgg.data"
1946
 * @param string $type   "site"
1947
 * @param array  $return Data
1948
 * @param array  $params Hook params
1949
 * @return array
1950
 * @internal
1951
 */
1952
function _elgg_set_lightbox_config($hook, $type, $return, $params) {
1953
1954
	$return['lightbox'] = [
1955
		'current' => elgg_echo('js:lightbox:current', ['{current}', '{total}']),
1956
		'previous' => elgg_view_icon('caret-left'),
1957
		'next' => elgg_view_icon('caret-right'),
1958
		'close' => elgg_view_icon('times'),
1959
		'opacity' => 0.5,
1960
		'maxWidth' => '990px',
1961
		'maxHeight' => '990px',
1962
		'initialWidth' => '300px',
1963
		'initialHeight' => '300px',
1964
	];
1965
1966
	return $return;
1967
}
1968
1969
/**
1970
 * Add a help text to input/file about upload limit
1971
 *
1972
 * In order to not show the help text supply 'show_upload_limit' => false to elgg_view_field()
1973
 *
1974
 * @param \Elgg\Hook $hook 'view_vars' 'elements/forms/help'
1975
 *
1976
 * @return void|array
1977
 * @internal
1978
 */
1979
function _elgg_views_file_help_upload_limit(\Elgg\Hook $hook) {
1980
1981 8
	$return = $hook->getValue();
1982 8
	if (elgg_extract('input_type', $return) !== 'file') {
1983 8
		return;
1984
	}
1985
1986
	if (!elgg_extract('show_upload_limit', $return, true)) {
1987
		return;
1988
	}
1989
1990
	$help = elgg_extract('help', $return, '');
1991
1992
	// Get post_max_size and upload_max_filesize
1993
	$post_max_size = elgg_get_ini_setting_in_bytes('post_max_size');
1994
	$upload_max_filesize = elgg_get_ini_setting_in_bytes('upload_max_filesize');
1995
1996
	// Determine the correct value
1997
	$max_upload = $upload_max_filesize > $post_max_size ? $post_max_size : $upload_max_filesize;
1998
1999
	$help .= ' ' . elgg_echo('input:file:upload_limit', [elgg_format_bytes($max_upload)]);
2000
2001
	$return['help'] = trim($help);
2002
2003
	return $return;
2004
}
2005
2006
/**
2007
 * Maps legacy sprite classes and FontAwesome 4 classes to FontAwesome 5 classes
2008
 *
2009
 * @param array $classes     Icon classes
2010
 * @param bool  $map_sprites Map legacy Elgg sprites
2011
 *
2012
 * @return array
2013
 * @internal
2014
 */
2015
function _elgg_map_icon_glyph_class(array $classes, $map_sprites = true) {
2016
2017
	// these 'old' Elgg 1.x sprite icons will be converted to the FontAwesome version
2018
	$legacy_sprites = [
2019 17
		"arrow-two-head" => "arrows-h",
2020
		"attention" => "exclamation-triangle",
2021
		"cell-phone" => "mobile",
2022
		"checkmark" => "check",
2023
		"clip" => "paperclip",
2024
		"cursor-drag-arrow" => "arrows",
2025
		"drag-arrow" => "arrows", // 'old' admin sprite
2026
		"delete-alt" => "times-circle",
2027
		"delete" => "times",
2028
		"facebook" => "facebook-square",
2029
		"grid" => "th",
2030
		"hover-menu" => "caret-down",
2031
		"info" => "info-circle",
2032
		"lock-closed" => "lock",
2033
		"lock-open" => "unlock",
2034
		"mail" => "envelope-o",
2035
		"mail-alt" => "envelope",
2036
		"print-alt" => "print",
2037
		"push-pin" => "thumb-tack",
2038
		"push-pin-alt" => "thumb-tack",
2039
		"redo" => "share",
2040
		"round-arrow-left" => "arrow-circle-left",
2041
		"round-arrow-right" => "arrow-circle-right",
2042
		"round-checkmark" => "check-circle",
2043
		"round-minus" => "minus-circle",
2044
		"round-plus" => "plus-circle",
2045
		"rss" => "rss-square",
2046
		"search-focus" => "search",
2047
		"settings" => "wrench",
2048
		"settings-alt" => "cog",
2049
		"share" => "share-alt-square",
2050
		"shop-cart" => "shopping-cart",
2051
		"speech-bubble" => "comment",
2052
		"speech-bubble-alt" => "comments",
2053
		"star-alt" => "star",
2054
		"star-empty" => "star-o",
2055
		"thumbs-down-alt" => "thumbs-down",
2056
		"thumbs-up-alt" => "thumbs-up",
2057
		"trash" => "trash-o",
2058
		"twitter" => "twitter-square",
2059
		"undo" => "reply",
2060
		"video" => "film"
2061
	];
2062
2063
	$fa5 = [
2064 17
		'address-book-o' => ['address-book', 'far'],
2065
		'address-card-o' => ['address-card', 'far'],
2066
		'area-chart' => ['chart-area', 'fas'],
2067
		'arrow-circle-o-down' => ['arrow-alt-circle-down', 'far'],
2068
		'arrow-circle-o-left' => ['arrow-alt-circle-left', 'far'],
2069
		'arrow-circle-o-right' => ['arrow-alt-circle-right', 'far'],
2070
		'arrow-circle-o-up' => ['arrow-alt-circle-up', 'far'],
2071
		'arrows-alt' => ['expand-arrows-alt', 'fas'],
2072
		'arrows-h' => ['arrows-alt-h', 'fas'],
2073
		'arrows-v' => ['arrows-alt-v', 'fas'],
2074
		'arrows' => ['arrows-alt', 'fas'],
2075
		'asl-interpreting' => ['american-sign-language-interpreting', 'fas'],
2076
		'automobile' => ['car', 'fas'],
2077
		'bank' => ['university', 'fas'],
2078
		'bar-chart-o' => ['chart-bar', 'far'],
2079
		'bar-chart' => ['chart-bar', 'far'],
2080
		'bathtub' => ['bath', 'fas'],
2081
		'battery-0' => ['battery-empty', 'fas'],
2082
		'battery-1' => ['battery-quarter', 'fas'],
2083
		'battery-2' => ['battery-half', 'fas'],
2084
		'battery-3' => ['battery-three-quarters', 'fas'],
2085
		'battery-4' => ['battery-full', 'fas'],
2086
		'battery' => ['battery-full', 'fas'],
2087
		'bell-o' => ['bell', 'far'],
2088
		'bell-slash-o' => ['bell-slash', 'far'],
2089
		'bitbucket-square' => ['bitbucket', 'fab'],
2090
		'bitcoin' => ['btc', 'fab'],
2091
		'bookmark-o' => ['bookmark', 'far'],
2092
		'building-o' => ['building', 'far'],
2093
		'cab' => ['taxi', 'fas'],
2094
		'calendar-check-o' => ['calendar-check', 'far'],
2095
		'calendar-minus-o' => ['calendar-minus', 'far'],
2096
		'calendar-o' => ['calendar', 'far'],
2097
		'calendar-plus-o' => ['calendar-plus', 'far'],
2098
		'calendar-times-o' => ['calendar-times', 'far'],
2099
		'calendar' => ['calendar-alt', 'fas'],
2100
		'caret-square-o-down' => ['caret-square-down', 'far'],
2101
		'caret-square-o-left' => ['caret-square-left', 'far'],
2102
		'caret-square-o-right' => ['caret-square-right', 'far'],
2103
		'caret-square-o-up' => ['caret-square-up', 'far'],
2104
		'cc' => ['closed-captioning', 'far'],
2105
		'chain-broken' => ['unlink', 'fas'],
2106
		'chain' => ['link', 'fas'],
2107
		'check-circle-o' => ['check-circle', 'far'],
2108
		'check-square-o' => ['check-square', 'far'],
2109
		'circle-o-notch' => ['circle-notch', 'fas'],
2110
		'circle-o' => ['circle', 'far'],
2111
		'circle-thin' => ['circle', 'far'],
2112
		'clock-o' => ['clock', 'far'],
2113
		'close' => ['times', 'fas'],
2114
		'cloud-download' => ['cloud-download-alt', 'fas'],
2115
		'cloud-upload' => ['cloud-upload-alt', 'fas'],
2116
		'cny' => ['yen-sign', 'fas'],
2117
		'code-fork' => ['code-branch', 'fas'],
2118
		'comment-o' => ['comment', 'far'],
2119
		'commenting-o' => ['comment-alt', 'far'],
2120
		'commenting' => ['comment-alt', 'fas'],
2121
		'comments-o' => ['comments', 'far'],
2122
		'credit-card-alt' => ['credit-card', 'fas'],
2123
		'cutlery' => ['utensils', 'fas'],
2124
		'dashboard' => ['tachometer-alt', 'fas'],
2125
		'deafness' => ['deaf', 'fas'],
2126
		'dedent' => ['outdent', 'fas'],
2127
		'diamond' => ['gem', 'far'],
2128
		'dollar' => ['dollar-sign', 'fas'],
2129
		'dot-circle-o' => ['dot-circle', 'far'],
2130
		'drivers-license-o' => ['id-card', 'far'],
2131
		'drivers-license' => ['id-card', 'fas'],
2132
		'eercast' => ['sellcast', 'fab'],
2133
		'envelope-o' => ['envelope', 'far'],
2134
		'envelope-open-o' => ['envelope-open', 'far'],
2135
		'eur' => ['euro-sign', 'fas'],
2136
		'euro' => ['euro-sign', 'fas'],
2137
		'exchange' => ['exchange-alt', 'fas'],
2138
		'external-link-square' => ['external-link-square-alt', 'fas'],
2139
		'external-link' => ['external-link-alt', 'fas'],
2140
		'eyedropper' => ['eye-dropper', 'fas'],
2141
		'fa' => ['font-awesome', 'fab'],
2142
		'facebook-f' => ['facebook-f', 'fab'],
2143
		'facebook-official' => ['facebook', 'fab'],
2144
		'facebook' => ['facebook-f', 'fab'],
2145
		'feed' => ['rss', 'fas'],
2146
		'file-archive-o' => ['file-archive', 'far'],
2147
		'file-audio-o' => ['file-audio', 'far'],
2148
		'file-code-o' => ['file-code', 'far'],
2149
		'file-excel-o' => ['file-excel', 'far'],
2150
		'file-image-o' => ['file-image', 'far'],
2151
		'file-movie-o' => ['file-video', 'far'],
2152
		'file-o' => ['file', 'far'],
2153
		'file-pdf-o' => ['file-pdf', 'far'],
2154
		'file-photo-o' => ['file-image', 'far'],
2155
		'file-picture-o' => ['file-image', 'far'],
2156
		'file-powerpoint-o' => ['file-powerpoint', 'far'],
2157
		'file-sound-o' => ['file-audio', 'far'],
2158
		'file-text-o' => ['file-alt', 'far'],
2159
		'file-text' => ['file-alt', 'fas'],
2160
		'file-video-o' => ['file-video', 'far'],
2161
		'file-word-o' => ['file-word', 'far'],
2162
		'file-zip-o' => ['file-archive', 'far'],
2163
		'files-o' => ['copy', 'far'],
2164
		'flag-o' => ['flag', 'far'],
2165
		'flash' => ['bolt', 'fas'],
2166
		'floppy-o' => ['save', 'far'],
2167
		'folder-o' => ['folder', 'far'],
2168
		'folder-open-o' => ['folder-open', 'far'],
2169
		'frown-o' => ['frown', 'far'],
2170
		'futbol-o' => ['futbol', 'far'],
2171
		'gbp' => ['pound-sign', 'fas'],
2172
		'ge' => ['empire', 'fab'],
2173
		'gear' => ['cog', 'fas'],
2174
		'gears' => ['cogs', 'fas'],
2175
		'gittip' => ['gratipay', 'fab'],
2176
		'glass' => ['glass-martini', 'fas'],
2177
		'google-plus-circle' => ['google-plus', 'fab'],
2178
		'google-plus-official' => ['google-plus', 'fab'],
2179
		'google-plus' => ['google-plus-g', 'fab'],
2180
		'group' => ['users', 'fas'],
2181
		'hand-grab-o' => ['hand-rock', 'far'],
2182
		'hand-lizard-o' => ['hand-lizard', 'far'],
2183
		'hand-o-down' => ['hand-point-down', 'far'],
2184
		'hand-o-left' => ['hand-point-left', 'far'],
2185
		'hand-o-right' => ['hand-point-right', 'far'],
2186
		'hand-o-up' => ['hand-point-up', 'far'],
2187
		'hand-paper-o' => ['hand-paper', 'far'],
2188
		'hand-peace-o' => ['hand-peace', 'far'],
2189
		'hand-pointer-o' => ['hand-pointer', 'far'],
2190
		'hand-rock-o' => ['hand-rock', 'far'],
2191
		'hand-scissors-o' => ['hand-scissors', 'far'],
2192
		'hand-spock-o' => ['hand-spock', 'far'],
2193
		'hand-stop-o' => ['hand-paper', 'far'],
2194
		'handshake-o' => ['handshake', 'far'],
2195
		'hard-of-hearing' => ['deaf', 'fas'],
2196
		'hdd-o' => ['hdd', 'far'],
2197
		'header' => ['heading', 'fas'],
2198
		'heart-o' => ['heart', 'far'],
2199
		'hospital-o' => ['hospital', 'far'],
2200
		'hotel' => ['bed', 'fas'],
2201
		'hourglass-1' => ['hourglass-start', 'fas'],
2202
		'hourglass-2' => ['hourglass-half', 'fas'],
2203
		'hourglass-3' => ['hourglass-end', 'fas'],
2204
		'hourglass-o' => ['hourglass', 'far'],
2205
		'id-card-o' => ['id-card', 'far'],
2206
		'ils' => ['shekel-sign', 'fas'],
2207
		'image' => ['image', 'far'],
2208
		'inr' => ['rupee-sign', 'fas'],
2209
		'institution' => ['university', 'fas'],
2210
		'intersex' => ['transgender', 'fas'],
2211
		'jpy' => ['yen-sign', 'fas'],
2212
		'keyboard-o' => ['keyboard', 'far'],
2213
		'krw' => ['won-sign', 'fas'],
2214
		'legal' => ['gavel', 'fas'],
2215
		'lemon-o' => ['lemon', 'far'],
2216
		'level-down' => ['level-down-alt', 'fas'],
2217
		'level-up' => ['level-up-alt', 'fas'],
2218
		'life-bouy' => ['life-ring', 'far'],
2219
		'life-buoy' => ['life-ring', 'far'],
2220
		'life-saver' => ['life-ring', 'far'],
2221
		'lightbulb-o' => ['lightbulb', 'far'],
2222
		'line-chart' => ['chart-line', 'fas'],
2223
		'linkedin-square' => ['linkedin', 'fab'],
2224
		'linkedin' => ['linkedin-in', 'fab'],
2225
		'long-arrow-down' => ['long-arrow-alt-down', 'fas'],
2226
		'long-arrow-left' => ['long-arrow-alt-left', 'fas'],
2227
		'long-arrow-right' => ['long-arrow-alt-right', 'fas'],
2228
		'long-arrow-up' => ['long-arrow-alt-up', 'fas'],
2229
		'mail-forward' => ['share', 'fas'],
2230
		'mail-reply-all' => ['reply-all', 'fas'],
2231
		'mail-reply' => ['reply', 'fas'],
2232
		'map-marker' => ['map-marker-alt', 'fas'],
2233
		'map-o' => ['map', 'far'],
2234
		'meanpath' => ['font-awesome', 'fab'],
2235
		'meh-o' => ['meh', 'far'],
2236
		'minus-square-o' => ['minus-square', 'far'],
2237
		'mobile-phone' => ['mobile-alt', 'fas'],
2238
		'mobile' => ['mobile-alt', 'fas'],
2239
		'money' => ['money-bill-alt', 'far'],
2240
		'moon-o' => ['moon', 'far'],
2241
		'mortar-board' => ['graduation-cap', 'fas'],
2242
		'navicon' => ['bars', 'fas'],
2243
		'newspaper-o' => ['newspaper', 'far'],
2244
		'paper-plane-o' => ['paper-plane', 'far'],
2245
		'paste' => ['clipboard', 'far'],
2246
		'pause-circle-o' => ['pause-circle', 'far'],
2247
		'pencil-square-o' => ['edit', 'far'],
2248
		'pencil-square' => ['pen-square', 'fas'],
2249
		'pencil' => ['pencil-alt', 'fas'],
2250
		'photo' => ['image', 'far'],
2251
		'picture-o' => ['image', 'far'],
2252
		'pie-chart' => ['chart-pie', 'fas'],
2253
		'play-circle-o' => ['play-circle', 'far'],
2254
		'plus-square-o' => ['plus-square', 'far'],
2255
		'question-circle-o' => ['question-circle', 'far'],
2256
		'ra' => ['rebel', 'fab'],
2257
		'refresh' => ['sync', 'fas'],
2258
		'remove' => ['times', 'fas'],
2259
		'reorder' => ['bars', 'fas'],
2260
		'repeat' => ['redo', 'fas'],
2261
		'resistance' => ['rebel', 'fab'],
2262
		'rmb' => ['yen-sign', 'fas'],
2263
		'rotate-left' => ['undo', 'fas'],
2264
		'rotate-right' => ['redo', 'fas'],
2265
		'rouble' => ['ruble-sign', 'fas'],
2266
		'rub' => ['ruble-sign', 'fas'],
2267
		'ruble' => ['ruble-sign', 'fas'],
2268
		'rupee' => ['rupee-sign', 'fas'],
2269
		's15' => ['bath', 'fas'],
2270
		'scissors' => ['cut', 'fas'],
2271
		'send-o' => ['paper-plane', 'far'],
2272
		'send' => ['paper-plane', 'fas'],
2273
		'share-square-o' => ['share-square', 'far'],
2274
		'shekel' => ['shekel-sign', 'fas'],
2275
		'sheqel' => ['shekel-sign', 'fas'],
2276
		'shield' => ['shield-alt', 'fas'],
2277
		'sign-in' => ['sign-in-alt', 'fas'],
2278
		'sign-out' => ['sign-out-alt', 'fas'],
2279
		'signing' => ['sign-language', 'fas'],
2280
		'sliders' => ['sliders-h', 'fas'],
2281
		'smile-o' => ['smile', 'far'],
2282
		'snowflake-o' => ['snowflake', 'far'],
2283
		'soccer-ball-o' => ['futbol', 'far'],
2284
		'sort-alpha-asc' => ['sort-alpha-down', 'fas'],
2285
		'sort-alpha-desc' => ['sort-alpha-up', 'fas'],
2286
		'sort-amount-asc' => ['sort-amount-down', 'fas'],
2287
		'sort-amount-desc' => ['sort-amount-up', 'fas'],
2288
		'sort-asc' => ['sort-up', 'fas'],
2289
		'sort-desc' => ['sort-down', 'fas'],
2290
		'sort-numeric-asc' => ['sort-numeric-down', 'fas'],
2291
		'sort-numeric-desc' => ['sort-numeric-up', 'fas'],
2292
		'spoon' => ['utensil-spoon', 'fas'],
2293
		'square-o' => ['square', 'far'],
2294
		'star-half-empty' => ['star-half', 'far'],
2295
		'star-half-full' => ['star-half', 'far'],
2296
		'star-half-o' => ['star-half', 'far'],
2297
		'star-o' => ['star', 'far'],
2298
		'sticky-note-o' => ['sticky-note', 'far'],
2299
		'stop-circle-o' => ['stop-circle', 'far'],
2300
		'sun-o' => ['sun', 'far'],
2301
		'support' => ['life-ring', 'far'],
2302
		'tablet' => ['tablet-alt', 'fas'],
2303
		'tachometer' => ['tachometer-alt', 'fas'],
2304
		'television' => ['tv', 'fas'],
2305
		'thermometer-0' => ['thermometer-empty', 'fas'],
2306
		'thermometer-1' => ['thermometer-quarter', 'fas'],
2307
		'thermometer-2' => ['thermometer-half', 'fas'],
2308
		'thermometer-3' => ['thermometer-three-quarters', 'fas'],
2309
		'thermometer-4' => ['thermometer-full', 'fas'],
2310
		'thermometer' => ['thermometer-full', 'fas'],
2311
		'thumb-tack' => ['thumbtack', 'fas'],
2312
		'thumbs-o-down' => ['thumbs-down', 'far'],
2313
		'thumbs-o-up' => ['thumbs-up', 'far'],
2314
		'ticket' => ['ticket-alt', 'fas'],
2315
		'times-circle-o' => ['times-circle', 'far'],
2316
		'times-rectangle-o' => ['window-close', 'far'],
2317
		'times-rectangle' => ['window-close', 'fas'],
2318
		'toggle-down' => ['caret-square-down', 'far'],
2319
		'toggle-left' => ['caret-square-left', 'far'],
2320
		'toggle-right' => ['caret-square-right', 'far'],
2321
		'toggle-up' => ['caret-square-up', 'far'],
2322
		'trash-o' => ['trash-alt', 'far'],
2323
		'trash' => ['trash-alt', 'fas'],
2324
		'try' => ['lira-sign', 'fas'],
2325
		'turkish-lira' => ['lira-sign', 'fas'],
2326
		'unsorted' => ['sort', 'fas'],
2327
		'usd' => ['dollar-sign', 'fas'],
2328
		'user-circle-o' => ['user-circle', 'far'],
2329
		'user-o' => ['user', 'far'],
2330
		'vcard-o' => ['address-card', 'far'],
2331
		'vcard' => ['address-card', 'fas'],
2332
		'video-camera' => ['video', 'fas'],
2333
		'vimeo' => ['vimeo-v', 'fab'],
2334
		'volume-control-phone' => ['phone-volume', 'fas'],
2335
		'warning' => ['exclamation-triangle', 'fas'],
2336
		'wechat' => ['weixin', 'fab'],
2337
		'wheelchair-alt' => ['accessible-icon', 'fab'],
2338
		'window-close-o' => ['window-close', 'far'],
2339
		'won' => ['won-sign', 'fas'],
2340
		'y-combinator-square' => ['hacker-news', 'fab'],
2341
		'yc-square' => ['hacker-news', 'fab'],
2342
		'yc' => ['y-combinator', 'fab'],
2343
		'yen' => ['yen-sign', 'fas'],
2344
		'youtube-play' => ['youtube', 'fab'],
2345
		'youtube-square' => ['youtube', 'fab'],
2346
	];
2347
2348
	$brands = [
2349 17
		'500px',
2350
		'accessible-icon',
2351
		'accusoft',
2352
		'adn',
2353
		'adversal',
2354
		'affiliatetheme',
2355
		'algolia',
2356
		'amazon',
2357
		'amazon-pay',
2358
		'amilia',
2359
		'android',
2360
		'angellist',
2361
		'angrycreative',
2362
		'angular',
2363
		'app-store',
2364
		'app-store-ios',
2365
		'apper',
2366
		'apple',
2367
		'apple-pay',
2368
		'asymmetrik',
2369
		'audible',
2370
		'autoprefixer',
2371
		'avianex',
2372
		'aviato',
2373
		'aws',
2374
		'bandcamp',
2375
		'behance',
2376
		'behance-square',
2377
		'bimobject',
2378
		'bitbucket',
2379
		'bitcoin',
2380
		'bity',
2381
		'black-tie',
2382
		'blackberry',
2383
		'blogger',
2384
		'blogger-b',
2385
		'bluetooth',
2386
		'bluetooth-b',
2387
		'btc',
2388
		'buromobelexperte',
2389
		'buysellads',
2390
		'cc-amazon-pay',
2391
		'cc-amex',
2392
		'cc-apple-pay',
2393
		'cc-diners-club',
2394
		'cc-discover',
2395
		'cc-jcb',
2396
		'cc-mastercard',
2397
		'cc-paypal',
2398
		'cc-stripe',
2399
		'cc-visa',
2400
		'centercode',
2401
		'chrome',
2402
		'cloudscale',
2403
		'cloudsmith',
2404
		'cloudversify',
2405
		'codepen',
2406
		'codiepie',
2407
		'connectdevelop',
2408
		'contao',
2409
		'cpanel',
2410
		'creative-commons',
2411
		'css3',
2412
		'css3-alt',
2413
		'cuttlefish',
2414
		'd-and-d',
2415
		'dashcube',
2416
		'delicious',
2417
		'deploydog',
2418
		'deskpro',
2419
		'deviantart',
2420
		'digg',
2421
		'digital-ocean',
2422
		'discord',
2423
		'discourse',
2424
		'dochub',
2425
		'docker',
2426
		'draft2digital',
2427
		'dribbble',
2428
		'dribbble-square',
2429
		'dropbox',
2430
		'drupal',
2431
		'dyalog',
2432
		'earlybirds',
2433
		'edge',
2434
		'elementor',
2435
		'ember',
2436
		'empire',
2437
		'envira',
2438
		'erlang',
2439
		'ethereum',
2440
		'etsy',
2441
		'expeditedssl',
2442
		'facebook',
2443
		'facebook-f',
2444
		'facebook-messenger',
2445
		'facebook-square',
2446
		'firefox',
2447
		'first-order',
2448
		'firstdraft',
2449
		'flickr',
2450
		'flipboard',
2451
		'fly',
2452
		'font-awesome',
2453
		'font-awesome-alt',
2454
		'font-awesome-flag',
2455
		'fonticons',
2456
		'fonticons-fi',
2457
		'fort-awesome',
2458
		'fort-awesome-alt',
2459
		'forumbee',
2460
		'foursquare',
2461
		'free-code-camp',
2462
		'freebsd',
2463
		'get-pocket',
2464
		'gg',
2465
		'gg-circle',
2466
		'git',
2467
		'git-square',
2468
		'github',
2469
		'github-alt',
2470
		'github-square',
2471
		'gitkraken',
2472
		'gitlab',
2473
		'gitter',
2474
		'glide',
2475
		'glide-g',
2476
		'gofore',
2477
		'goodreads',
2478
		'goodreads-g',
2479
		'google',
2480
		'google-drive',
2481
		'google-play',
2482
		'google-plus',
2483
		'google-plus-g',
2484
		'google-plus-square',
2485
		'google-wallet',
2486
		'gratipay',
2487
		'grav',
2488
		'gripfire',
2489
		'grunt',
2490
		'gulp',
2491
		'hacker-news',
2492
		'hacker-news-square',
2493
		'hips',
2494
		'hire-a-helper',
2495
		'hooli',
2496
		'hotjar',
2497
		'houzz',
2498
		'html5',
2499
		'hubspot',
2500
		'imdb',
2501
		'instagram',
2502
		'internet-explorer',
2503
		'ioxhost',
2504
		'itunes',
2505
		'itunes-note',
2506
		'jenkins',
2507
		'joget',
2508
		'joomla',
2509
		'js',
2510
		'js-square',
2511
		'jsfiddle',
2512
		'keycdn',
2513
		'kickstarter',
2514
		'kickstarter-k',
2515
		'korvue',
2516
		'laravel',
2517
		'lastfm',
2518
		'lastfm-square',
2519
		'leanpub',
2520
		'less',
2521
		'line',
2522
		'linkedin',
2523
		'linkedin-in',
2524
		'linode',
2525
		'linux',
2526
		'lyft',
2527
		'magento',
2528
		'maxcdn',
2529
		'medapps',
2530
		'medium',
2531
		'medium-m',
2532
		'medrt',
2533
		'meetup',
2534
		'microsoft',
2535
		'mix',
2536
		'mixcloud',
2537
		'mizuni',
2538
		'modx',
2539
		'monero',
2540
		'napster',
2541
		'nintendo-switch',
2542
		'node',
2543
		'node-js',
2544
		'npm',
2545
		'ns8',
2546
		'nutritionix',
2547
		'odnoklassniki',
2548
		'odnoklassniki-square',
2549
		'opencart',
2550
		'openid',
2551
		'opera',
2552
		'optin-monster',
2553
		'osi',
2554
		'page4',
2555
		'pagelines',
2556
		'palfed',
2557
		'patreon',
2558
		'paypal',
2559
		'periscope',
2560
		'phabricator',
2561
		'phoenix-framework',
2562
		'php',
2563
		'pied-piper',
2564
		'pied-piper-alt',
2565
		'pied-piper-pp',
2566
		'pinterest',
2567
		'pinterest-p',
2568
		'pinterest-square',
2569
		'playstation',
2570
		'product-hunt',
2571
		'pushed',
2572
		'python',
2573
		'qq',
2574
		'quinscape',
2575
		'quora',
2576
		'ravelry',
2577
		'react',
2578
		'rebel',
2579
		'red-river',
2580
		'reddit',
2581
		'reddit-alien',
2582
		'reddit-square',
2583
		'rendact',
2584
		'renren',
2585
		'replyd',
2586
		'resolving',
2587
		'rocketchat',
2588
		'rockrms',
2589
		'safari',
2590
		'sass',
2591
		'schlix',
2592
		'scribd',
2593
		'searchengin',
2594
		'sellcast',
2595
		'sellsy',
2596
		'servicestack',
2597
		'shirtsinbulk',
2598
		'simplybuilt',
2599
		'sistrix',
2600
		'skyatlas',
2601
		'skype',
2602
		'slack',
2603
		'slack-hash',
2604
		'slideshare',
2605
		'snapchat',
2606
		'snapchat-ghost',
2607
		'snapchat-square',
2608
		'soundcloud',
2609
		'speakap',
2610
		'spotify',
2611
		'stack-exchange',
2612
		'stack-overflow',
2613
		'staylinked',
2614
		'steam',
2615
		'steam-square',
2616
		'steam-symbol',
2617
		'sticker-mule',
2618
		'strava',
2619
		'stripe',
2620
		'stripe-s',
2621
		'studiovinari',
2622
		'stumbleupon',
2623
		'stumbleupon-circle',
2624
		'superpowers',
2625
		'supple',
2626
		'telegram',
2627
		'telegram-plane',
2628
		'tencent-weibo',
2629
		'themeisle',
2630
		'trello',
2631
		'tripadvisor',
2632
		'tumblr',
2633
		'tumblr-square',
2634
		'twitch',
2635
		'twitter',
2636
		'twitter-square',
2637
		'typo3',
2638
		'uber',
2639
		'uikit',
2640
		'uniregistry',
2641
		'untappd',
2642
		'usb',
2643
		'ussunnah',
2644
		'vaadin',
2645
		'viacoin',
2646
		'viadeo',
2647
		'viadeo-square',
2648
		'viber',
2649
		'vimeo',
2650
		'vimeo-square',
2651
		'vimeo-v',
2652
		'vine',
2653
		'vk',
2654
		'vnv',
2655
		'vuejs',
2656
		'weibo',
2657
		'weixin',
2658
		'whatsapp',
2659
		'whatsapp-square',
2660
		'whmcs',
2661
		'wikipedia-w',
2662
		'windows',
2663
		'wordpress',
2664
		'wordpress-simple',
2665
		'wpbeginner',
2666
		'wpexplorer',
2667
		'wpforms',
2668
		'xbox',
2669
		'xing',
2670
		'xing-square',
2671
		'y-combinator',
2672
		'yahoo',
2673
		'yandex',
2674
		'yandex-international',
2675
		'yelp',
2676
		'yoast',
2677
		'youtube',
2678
		'youtube-square',
2679
	];
2680
2681 17
	foreach ($classes as $index => $c) {
2682 17
		if ($c === 'fa') {
2683
			// FontAwesome 5 deprecated the use of fa prefix in favour of fas, far and fab
2684
			unset($classes[$index]);
2685
			continue;
2686
		}
2687
2688 17
		if (preg_match_all('/^elgg-icon-(.+)/i', $c)) {
2689
			// convert
2690 16
			$base_icon = preg_replace('/^elgg-icon-(.+)/i', '$1', $c);
2691
2692 16
			if ($map_sprites) {
2693 16
				if (strpos($base_icon, '-hover') !== false) {
2694
					$base_icon = str_replace('-hover', '', $base_icon);
2695
					$classes[] = 'elgg-state';
2696
					$classes[] = 'elgg-state-notice';
2697
				}
2698
2699 16
				$base_icon = elgg_extract($base_icon, $legacy_sprites, $base_icon);
2700
			}
2701
			
2702
			// map solid/regular/light iconnames to correct classes
2703 16
			if (preg_match('/.*-solid$/', $base_icon)) {
2704
				$base_icon = preg_replace('/(.*)-solid$/', '$1', $base_icon);
2705
				$classes[] = 'fas';
2706 16
			} elseif (preg_match('/.*-regular$/', $base_icon)) {
2707
				$base_icon = preg_replace('/(.*)-regular$/', '$1', $base_icon);
2708
				$classes[] = 'far';
2709 16
			} elseif (preg_match('/.*-light$/', $base_icon)) {
2710
				// currently light is only available in FontAwesome 5 Pro
2711
				$base_icon = preg_replace('/(.*)-light$/', '$1', $base_icon);
2712
				$classes[] = 'fal';
2713
			} else {
2714 16
				if (array_key_exists($base_icon, $fa5)) {
2715 6
					$classes[] = $fa5[$base_icon][1];
2716 6
					$base_icon = $fa5[$base_icon][0];
2717 14
				} else if (in_array($base_icon, $brands)) {
2718
					$classes[] = 'fab';
2719
				} else {
2720 14
					$classes[] = 'fas';
2721
				}
2722
			}
2723
2724 17
			$classes[] = "fa-{$base_icon}";
2725
		}
2726
	}
2727
2728 17
	$classes = array_unique($classes);
2729
2730 17
	return elgg_trigger_plugin_hook('classes', 'icon', null, $classes);
2731
}
2732