Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

engine/lib/views.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Includer;
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 7
	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 11
	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
 * @access private
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 9
	elgg_register_external_view($view, false);
135 9
}
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 9
	_elgg_services()->ajax->registerView($view);
159
160 9
	if ($cacheable) {
161
		_elgg_services()->views->registerCacheableView($view);
162
	}
163 9
}
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 5
	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 21
	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 21
	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
 * @see elgg_view()
258
 *
259
 * @param string  $view       The name and location of the view to use
260
 * @param array   $vars       Variables to pass to the view
261
 * @param string  $suggestion Suggestion with the deprecation message
262
 * @param string  $version    Human-readable *release* version: 1.7, 1.8, ...
263
 *
264
 * @return string The parsed view
265
 * @access private
266
 */
267
function elgg_view_deprecated($view, array $vars, $suggestion, $version) {
268
	return _elgg_services()->views->renderDeprecatedView($view, $vars, $suggestion, $version);
269
}
270
271
/**
272
 * Extends a view with another view.
273
 *
274
 * The output of any view can be prepended or appended to any other view.
275
 *
276
 * The default action is to append a view.  If the priority is less than 500,
277
 * the output of the extended view will be appended to the original view.
278
 *
279
 * Views can be extended multiple times, and extensions are not checked for
280
 * uniqueness. Use {@link elgg_unextend_view()} to help manage duplicates.
281
 *
282
 * Priority can be specified and affects the order in which extensions
283
 * are appended or prepended.
284
 *
285
 * @see elgg_prepend_css_urls() If the extension is CSS, you may need to use this to fix relative URLs.
286
 *
287
 * @param string $view           The view to extend.
288
 * @param string $view_extension This view is added to $view
289
 * @param int    $priority       The priority, from 0 to 1000, to add at (lowest numbers displayed first)
290
 *
291
 * @return void
292
 * @since 1.7.0
293
 */
294
function elgg_extend_view($view, $view_extension, $priority = 501) {
295
	_elgg_services()->views->extendView($view, $view_extension, $priority);
296
}
297
298
/**
299
 * Unextends a view.
300
 *
301
 * @param string $view           The view that was extended.
302
 * @param string $view_extension This view that was added to $view
303
 *
304
 * @return bool
305
 * @since 1.7.2
306
 */
307
function elgg_unextend_view($view, $view_extension) {
308
	return _elgg_services()->views->unextendView($view, $view_extension);
309
}
310
311
/**
312
 * Get the views (and priorities) that extend a view.
313
 *
314
 * @note extensions may change anytime, especially during the [init, system] event
315
 *
316
 * @param string $view View name
317
 *
318
 * @return string[] Keys returned are view priorities.
319
 * @since 2.3
320
 */
321
function elgg_get_view_extensions($view) {
322
	$list = _elgg_services()->views->getViewList($view);
323
	unset($list[500]);
324
	return $list;
325
}
326
327
/**
328
 * In CSS content, prepend a path to relative URLs.
329
 *
330
 * This is useful to process a CSS view being used as an extension.
331
 *
332
 * @param string $css  CSS
333
 * @param string $path Path to prepend. E.g. "foo/bar/" or "../"
334
 *
335
 * @return string
336
 * @since 2.2
337
 */
338
function elgg_prepend_css_urls($css, $path) {
339 1
	return Minify_CSS_UriRewriter::prepend($css, $path);
340
}
341
342
/**
343
 * Assembles and outputs a full page.
344
 *
345
 * A "page" in Elgg is determined by the current view type and
346
 * can be HTML for a browser, RSS for a feed reader, or
347
 * Javascript, PHP and a number of other formats.
348
 *
349
 * For HTML pages, use the 'head', 'page' plugin hook for setting meta elements
350
 * and links.
351
 *
352
 * @param string $title      Title
353
 * @param string $body       Body
354
 * @param string $page_shell Optional page shell to use. See page/shells view directory
355
 * @param array  $vars       Optional vars array to pass to the page
356
 *                           shell. Automatically adds title, body, head, and sysmessages
357
 *
358
 * @return string The contents of the page
359
 * @since  1.8
360
 */
361
function elgg_view_page($title, $body, $page_shell = 'default', $vars = []) {
362 4
	$timer = _elgg_services()->timer;
363 4
	if (!$timer->hasEnded(['build page'])) {
364 1
		$timer->end(['build page']);
365
	}
366 4
	$timer->begin([__FUNCTION__]);
367
368 4
	$params = [];
369 4
	$params['identifier'] = _elgg_services()->request->getFirstUrlSegment();
370 4
	$params['segments'] = _elgg_services()->request->getUrlSegments();
371 4
	array_shift($params['segments']);
372 4
	$page_shell = elgg_trigger_plugin_hook('shell', 'page', $params, $page_shell);
373
374
375 4
	$system_messages = _elgg_services()->systemMessages;
376
377 4
	$messages = null;
378 4
	if ($system_messages->count()) {
379
		$messages = $system_messages->dumpRegister();
380
381
		if (isset($messages['error'])) {
382
			// always make sure error is the first type
383
			$errors = [
384
				'error' => $messages['error']
385
			];
386
387
			unset($messages['error']);
388
			$messages = array_merge($errors, $messages);
389
		}
390
	}
391
392 4
	$vars['title'] = $title;
393 4
	$vars['body'] = $body;
394 4
	$vars['sysmessages'] = $messages;
395 4
	$vars['admin_notices'] = elgg_is_admin_logged_in() ? elgg_get_admin_notices() : [];
396 4
	$vars['page_shell'] = $page_shell;
397
398
	// head has keys 'title', 'metas', 'links'
399 4
	$head_params = _elgg_views_prepare_head($title);
400
401 4
	$vars['head'] = elgg_trigger_plugin_hook('head', 'page', $vars, $head_params);
402
403 4
	$vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars);
404
405 4
	$output = elgg_view("page/$page_shell", $vars);
406
407
408
	// Allow plugins to modify the output
409 4
	$output = elgg_trigger_plugin_hook('output', 'page', $vars, $output);
410
411 4
	$timer->end([__FUNCTION__]);
412 4
	return $output;
413
}
414
415
/**
416
 * Render a resource view. Use this in your page handler to hand off page rendering to
417
 * a view in "resources/". If not found in the current viewtype, we try the "default" viewtype.
418
 *
419
 * @param string $name The view name without the leading "resources/"
420
 * @param array  $vars Arguments passed to the view
421
 *
422
 * @return string
423
 * @throws SecurityException
424
 */
425
function elgg_view_resource($name, array $vars = []) {
426 4
	$view = "resources/$name";
427
428 4
	if (elgg_view_exists($view)) {
429 4
		return _elgg_services()->views->renderView($view, $vars);
430
	}
431
432
	if (elgg_get_viewtype() !== 'default' && elgg_view_exists($view, 'default')) {
433
		return _elgg_services()->views->renderView($view, $vars, 'default');
434
	}
435
436
	_elgg_services()->logger->error("The view $view is missing.");
437
438
	if (elgg_get_viewtype() === 'default') {
439
		// only works for default viewtype
440
		forward('', '404');
441
	} else {
442
		register_error(elgg_echo('error:404:content'));
443
		forward('');
444
	}
445
}
446
447
/**
448
 * Prepare the variables for the html head
449
 *
450
 * @param string $title Page title for <head>
451
 * @return array
452
 * @access private
453
 */
454
function _elgg_views_prepare_head($title) {
455
	$params = [
456 4
		'links' => [],
457
		'metas' => [],
458
	];
459
460 4
	if (empty($title)) {
461
		$params['title'] = _elgg_config()->sitename;
462
	} else {
463 4
		$params['title'] = $title . ' : ' . _elgg_config()->sitename;
464
	}
465
466 4
	$params['metas']['content-type'] = [
467
		'http-equiv' => 'Content-Type',
468
		'content' => 'text/html; charset=utf-8',
469
	];
470
471 4
	$params['metas']['description'] = [
472 4
		'name' => 'description',
473 4
		'content' => _elgg_config()->sitedescription
474
	];
475
476
	// https://developer.chrome.com/multidevice/android/installtohomescreen
477 4
	$params['metas']['viewport'] = [
478
		'name' => 'viewport',
479
		'content' => 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0',
480
	];
481 4
	$params['metas']['mobile-web-app-capable'] = [
482
		'name' => 'mobile-web-app-capable',
483
		'content' => 'yes',
484
	];
485 4
	$params['metas']['apple-mobile-web-app-capable'] = [
486
		'name' => 'apple-mobile-web-app-capable',
487
		'content' => 'yes',
488
	];
489
	
490
	// RSS feed link
491 4
	if (_elgg_has_rss_link()) {
492
		$url = current_page_url();
493
		if (substr_count($url, '?')) {
494
			$url .= "&view=rss";
495
		} else {
496
			$url .= "?view=rss";
497
		}
498
		$params['links']['rss'] = [
499
			'rel' => 'alternative',
500
			'type' => 'application/rss+xml',
501
			'title' => 'RSS',
502
			'href' => $url,
503
		];
504
	}
505
	
506 4
	return $params;
507
}
508
509
510
/**
511
 * Add favicon link tags to HTML head
512
 *
513
 * @param string $hook        "head"
514
 * @param string $type        "page"
515
 * @param array  $head_params Head params
516
 *                            <code>
517
 *                               [
518
 *                                  'title' => '',
519
 *                                  'metas' => [],
520
 *                                  'links' => [],
521
 *                               ]
522
 *                            </code>
523
 * @param array  $params      Hook params
524
 * @return array
525
 */
526
function _elgg_views_prepare_favicon_links($hook, $type, $head_params, $params) {
527
528
	$head_params['links']['apple-touch-icon'] = [
529
		'rel' => 'apple-touch-icon',
530
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
531
	];
532
533
	// favicons
534
	$head_params['links']['icon-ico'] = [
535
		'rel' => 'icon',
536
		'href' => elgg_get_simplecache_url('graphics/favicon.ico'),
537
	];
538
	$head_params['links']['icon-vector'] = [
539
		'rel' => 'icon',
540
		'sizes' => '16x16 32x32 48x48 64x64 128x128',
541
		'type' => 'image/svg+xml',
542
		'href' => elgg_get_simplecache_url('graphics/favicon.svg'),
543
	];
544
	$head_params['links']['icon-16'] = [
545
		'rel' => 'icon',
546
		'sizes' => '16x16',
547
		'type' => 'image/png',
548
		'href' => elgg_get_simplecache_url('graphics/favicon-16.png'),
549
	];
550
	$head_params['links']['icon-32'] = [
551
		'rel' => 'icon',
552
		'sizes' => '32x32',
553
		'type' => 'image/png',
554
		'href' => elgg_get_simplecache_url('graphics/favicon-32.png'),
555
	];
556
	$head_params['links']['icon-64'] = [
557
		'rel' => 'icon',
558
		'sizes' => '64x64',
559
		'type' => 'image/png',
560
		'href' => elgg_get_simplecache_url('graphics/favicon-64.png'),
561
	];
562
	$head_params['links']['icon-128'] = [
563
		'rel' => 'icon',
564
		'sizes' => '128x128',
565
		'type' => 'image/png',
566
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
567
	];
568
569
	return $head_params;
570
}
571
572
/**
573
 * Displays a layout with optional parameters.
574
 *
575
 * Layouts are templates provide consistency by organizing blocks of content on the page.
576
 *
577
 * Plugins should use one of the core layouts:
578
 *  - default     Primary template with one, two or no sidebars
579
 *  - admin       Admin page template
580
 *  - error       Error page template
581
 *  - widgets     Widgets canvas
582
 *
583
 * Plugins can create and use custom layouts by placing a layout view
584
 * in "page/layouts/<layout_name>" and calling elgg_view_layout(<layout_name>).
585
 *
586
 * For a full list of parameters supported by each of these layouts see
587
 * corresponding layout views.
588
 *
589
 * @param string $layout_name Layout name
590
 *                            Corresponds to a view in "page/layouts/<layout_name>".
591
 * @param array  $vars        Layout parameters
592
 *                            An associative array of parameters to pass to
593
 *                            the layout hooks and views.
594
 *                            Route 'identifier' and 'segments' of the page being
595
 *                            rendered will be added to this array automatially,
596
 *                            allowing plugins to alter layout views and subviews
597
 *                            based on the current route.
598
 * @return string
599
 */
600
function elgg_view_layout($layout_name, $vars = []) {
601
	$timer = _elgg_services()->timer;
602
	if (!$timer->hasEnded(['build page'])) {
603
		$timer->end(['build page']);
604
	}
605
	$timer->begin([__FUNCTION__]);
606
607
	// Help plugins transition without breaking them
608
	switch ($layout_name) {
609
		case 'content' :
610
			$layout_name = 'default';
611
			$vars = _elgg_normalize_content_layout_vars($vars);
612
			break;
613
614 View Code Duplication
		case 'one_sidebar' :
615
			$layout_name = 'default';
616
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
617
			$vars['sidebar_alt'] = false;
618
			break;
619
620
		case 'one_column' :
621
			$layout_name = 'default';
622
			$vars['sidebar'] = false;
623
			$vars['sidebar_alt'] = false;
624
			break;
625
626 View Code Duplication
		case 'two_sidebar' :
627
			$layout_name = 'default';
628
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
629
			$vars['sidebar_alt'] = elgg_extract('sidebar_alt', $vars, '', false);
630
			break;
631
	}
632
633
	if (isset($vars['nav'])) {
634
		// Temporary helper until all core views are updated
635
		$vars['breadcrumbs'] = $vars['nav'];
636
		unset($vars['nav']);
637
	}
638
639
	$vars['identifier'] = _elgg_services()->request->getFirstUrlSegment();
640
	$vars['segments'] = _elgg_services()->request->getUrlSegments();
641
	array_shift($vars['segments']);
642
643
	$layout_name = elgg_trigger_plugin_hook('layout', 'page', $vars, $layout_name);
644
645
	$vars['layout'] = $layout_name;
646
647
	$layout_views = [
648
		"page/layouts/$layout_name",
649
		"page/layouts/default",
650
	];
651
652
	$output = '';
653
	foreach ($layout_views as $layout_view) {
654
		if (elgg_view_exists($layout_view)) {
655
			$output = elgg_view($layout_view, $vars);
656
			break;
657
		}
658
	}
659
660
	$timer->end([__FUNCTION__]);
661
	return $output;
662
}
663
664
/**
665
 * Normalizes deprecated content layout $vars for use in default layout
666
 * Helper function to assist plugins transitioning to 3.0
667
 *
668
 * @param array $vars Vars
669
 * @return array
670
 * @access private
671
 */
672
function _elgg_normalize_content_layout_vars(array $vars = []) {
673
674
	$context = elgg_extract('context', $vars, elgg_get_context());
675
676
	$vars['title'] = elgg_extract('title', $vars, '');
677
	if (!$vars['title'] && $vars['title'] !== false) {
678
		$vars['title'] = elgg_echo($context);
679
	}
680
681
	// 1.8 supported 'filter_override'
682
	if (isset($vars['filter_override'])) {
683
		$vars['filter'] = $vars['filter_override'];
684
	}
685
686
	// register the default content filters
687
	if (!isset($vars['filter']) && $context) {
688
		$selected = elgg_extract('filter_context', $vars);
689
		$vars['filter'] = elgg_get_filter_tabs($context, $selected, null, $vars);
690
		$vars['filter_id'] = $context;
691
		$vars['filter_value'] = $selected;
692
	}
693
	
694
	return $vars;
695
}
696
697
/**
698
 * Render a menu
699
 *
700
 * @see elgg_register_menu_item() for documentation on adding menu items and
701
 * navigation.php for information on the different menus available.
702
 *
703
 * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables
704
 * plugins to add menu items just before a menu is rendered. This is used by
705
 * dynamic menus (menus that change based on some input such as the user hover
706
 * menu). Using elgg_register_menu_item() in response to the hook can cause
707
 * incorrect links to show up. See the blog plugin's blog_owner_block_menu()
708
 * for an example of using this plugin hook.
709
 *
710
 * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins
711
 * to modify the structure of the menu (sort it, remove items, set variables on
712
 * the menu items).
713
 *
714
 * Preset (unprepared) menu items passed to the this function with the $vars
715
 * argument, will be merged with the registered items (registered with
716
 * elgg_register_menu_item()). The combined set of menu items will be passed
717
 * to 'register', 'menu:<menu_name>' hook.
718
 *
719
 * Plugins that pass preset menu items to this function and do not wish to be
720
 * affected by plugin hooks (e.g. if you are displaying multiple menus with
721
 * the same name on the page) should instead choose a unqie menu name
722
 * and define a menu_view argument to render menus consistently.
723
 * For example, if you have multiple 'filter' menus on the page:
724
 * <code>
725
 *    elgg_view_menu("filter:$uid", [
726
 *        'items' => $items,
727
 *        'menu_view' => 'navigation/menu/filter',
728
 *    ]);
729
 * </code>
730
 *
731
 * elgg_view_menu() uses views in navigation/menu
732
 *
733
 * @param string|Menu|UnpreparedMenu $menu Menu name (or object)
734
 * @param array                      $vars An associative array of display options for the menu.
735
 *
736
 *                          Options include:
737
 *                              items => an array of unprepared menu items
738
 *                                       as ElggMenuItem or menu item factory options
739
 *                              sort_by => string or php callback
740
 *                                  string options: 'name', 'priority', 'title' (default),
741
 *                                  'register' (registration order) or a
742
 *                                  php callback (a compare function for usort)
743
 *                              handler: string the page handler to build action URLs
744
 *                              entity: \ElggEntity to use to build action URLs
745
 *                              class: string the class for the entire menu.
746
 *                              menu_view: name of the view to be used to render the menu
747
 *                              show_section_headers: bool show headers before menu sections.
748
 *
749
 * @return string
750
 * @since 1.8.0
751
 */
752
function elgg_view_menu($menu, array $vars = []) {
753
754
	$menu_view = elgg_extract('menu_view', $vars);
755
	unset($vars['menu_view']);
756
757
	if (is_string($menu)) {
758
		$menu = _elgg_services()->menus->getMenu($menu, $vars);
759
	} elseif ($menu instanceof UnpreparedMenu) {
760
		$menu = _elgg_services()->menus->prepareMenu($menu);
761
	}
762
763
	if (!$menu instanceof Menu) {
764
		throw new \InvalidArgumentException('$menu must be a menu name, a Menu, or UnpreparedMenu');
765
	}
766
767
	$name = $menu->getName();
768
	$params = $menu->getParams();
769
770
	$views = [
771
		$menu_view,
772
		"navigation/menu/$name",
773
		'navigation/menu/default',
774
	];
775
776
	foreach ($views as $view) {
777
		if (elgg_view_exists($view)) {
778
			return elgg_view($view, $params);
779
		}
780
	}
781
}
782
783
/**
784
 * Render a menu item (usually as a link)
785
 *
786
 * @param \ElggMenuItem $item The menu item
787
 * @param array         $vars Options to pass to output/url if a link
788
 * @return string
789
 * @since 1.9.0
790
 */
791
function elgg_view_menu_item(\ElggMenuItem $item, array $vars = []) {
792
793
	$vars = array_merge($item->getValues(), $vars);
794
	$vars['class'] = elgg_extract_class($vars, ['elgg-menu-content']);
795
	
796
	if ($item->getLinkClass()) {
797
		$vars['class'][] = $item->getLinkClass();
798
	}
799
800
	if ($item->getHref() === false || $item->getHref() === null) {
801
		$vars['class'][] = 'elgg-non-link';
802
	}
803
804
	if (!isset($vars['rel']) && !isset($vars['is_trusted'])) {
805
		$vars['is_trusted'] = true;
806
	}
807
808
	if ($item->getConfirmText()) {
809
		$vars['confirm'] = $item->getConfirmText();
810
	}
811
812
	return elgg_view('output/url', $vars);
813
}
814
815
/**
816
 * Returns a string of a rendered entity.
817
 *
818
 * Entity views are either determined by setting the view property on the entity
819
 * or by having a view named after the entity $type/$subtype.  Entities that have
820
 * neither a view property nor a defined $type/$subtype view will fall back to
821
 * using the $type/default view.
822
 *
823
 * The entity view is called with the following in $vars:
824
 *  - \ElggEntity 'entity' The entity being viewed
825
 *
826
 * @tip This function can automatically appends annotations to entities if in full
827
 * view and a handler is registered for the entity:annotate.  See https://github.com/Elgg/Elgg/issues/964 and
828
 * {@link elgg_view_entity_annotations()}.
829
 *
830
 * @param \ElggEntity $entity The entity to display
831
 * @param array       $vars   Array of variables to pass to the entity view.
832
 *      'full_view'        Whether to show a full or condensed view. (Default: true)
833
 *      'item_view'        Alternative view used to render this entity
834
 *
835
 * @return string HTML to display or false
836
 * @todo The annotation hook might be better as a generic plugin hook to append content.
837
 */
838
function elgg_view_entity(\ElggEntity $entity, array $vars = []) {
839
840
	// No point continuing if entity is null
841
	if (!$entity || !($entity instanceof \ElggEntity)) {
842
		return false;
843
	}
844
845
	elgg_register_rss_link();
846
847
	$defaults = [
848
		'full_view' => true,
849
	];
850
851
	$vars = array_merge($defaults, $vars);
852
853
	$vars['entity'] = $entity;
854
855
	$entity_type = $entity->getType();
856
	$entity_subtype = $entity->getSubtype();
857
	if (empty($entity_subtype)) {
858
		$entity_subtype = 'default';
859
	}
860
861
	$entity_views = [
862
		elgg_extract('item_view', $vars, ''),
863
		"$entity_type/$entity_subtype",
864
		"$entity_type/default",
865
	];
866
867
	$contents = '';
868
	foreach ($entity_views as $view) {
869
		if (elgg_view_exists($view)) {
870
			$contents = elgg_view($view, $vars);
871
			break;
872
		}
873
	}
874
875
	// Marcus Povey 20090616 : Speculative and low impact approach for fixing #964
876
	if ($vars['full_view']) {
877
		$annotations = elgg_view_entity_annotations($entity, $vars['full_view']);
878
879
		if ($annotations) {
880
			$contents .= $annotations;
881
		}
882
	}
883
	return $contents;
884
}
885
886
/**
887
 * View the icon of an entity
888
 *
889
 * Entity views are determined by having a view named after the entity $type/$subtype.
890
 * Entities that do not have a defined icon/$type/$subtype view will fall back to using
891
 * the icon/$type/default view.
892
 *
893
 * @param \ElggEntity $entity The entity to display
894
 * @param string      $size   The size: tiny, small, medium, large
895
 * @param array       $vars   An array of variables to pass to the view. Some possible
896
 *                            variables are img_class and link_class. See the
897
 *                            specific icon view for more parameters.
898
 *
899
 * @return string HTML to display or false
900
 */
901
function elgg_view_entity_icon(\ElggEntity $entity, $size = 'medium', $vars = []) {
902
903
	// No point continuing if entity is null
904
	if (!$entity || !($entity instanceof \ElggEntity)) {
905
		return false;
906
	}
907
908
	$vars['entity'] = $entity;
909
	$vars['size'] = $size;
910
911
	$entity_type = $entity->getType();
912
913
	$subtype = $entity->getSubtype();
914
	if (empty($subtype)) {
915
		$subtype = 'default';
916
	}
917
918
	$contents = '';
919
	if (elgg_view_exists("icon/$entity_type/$subtype")) {
920
		$contents = elgg_view("icon/$entity_type/$subtype", $vars);
921
	}
922
	if (empty($contents)) {
923
		$contents = elgg_view("icon/$entity_type/default", $vars);
924
	}
925
	if (empty($contents)) {
926
		$contents = elgg_view("icon/default", $vars);
927
	}
928
929
	return $contents;
930
}
931
932
/**
933
 * Returns a string of a rendered annotation.
934
 *
935
 * Annotation views are expected to be in annotation/$annotation_name.
936
 * If a view is not found for $annotation_name, the default annotation/default
937
 * will be used.
938
 *
939
 * @warning annotation/default is not currently defined in core.
940
 *
941
 * The annotation view is called with the following in $vars:
942
 *  - \ElggEntity 'annotation' The annotation being viewed.
943
 *
944
 * @param \ElggAnnotation $annotation The annotation to display
945
 * @param array           $vars       Variable array for view.
946
 *      'item_view'  Alternative view used to render an annotation
947
 *
948
 * @return string/false Rendered annotation
949
 */
950
function elgg_view_annotation(\ElggAnnotation $annotation, array $vars = []) {
951
	elgg_register_rss_link();
952
953
	$defaults = [
954
		'full_view' => true,
955
	];
956
957
	$vars = array_merge($defaults, $vars);
958
	$vars['annotation'] = $annotation;
959
960
	$name = $annotation->name;
961
	if (empty($name)) {
962
		return false;
963
	}
964
965
	$annotation_views = [
966
		elgg_extract('item_view', $vars, ''),
967
		"annotation/$name",
968
		"annotation/default",
969
	];
970
971
	$contents = '';
972
	foreach ($annotation_views as $view) {
973
		if (elgg_view_exists($view)) {
974
			$contents = elgg_view($view, $vars);
975
			break;
976
		}
977
	}
978
979
	return $contents;
980
}
981
982
/**
983
 * Returns a rendered list of entities with pagination. This function should be
984
 * called by wrapper functions.
985
 *
986
 * @see elgg_list_entities()
987
 * @see list_user_friends_objects()
988
 * @see elgg_list_entities_from_metadata()
989
 * @see elgg_list_entities_from_relationships()
990
 * @see elgg_list_entities_from_annotations()
991
 *
992
 * @param array $entities Array of entities
993
 * @param array $vars     Display variables
994
 *      'count'            The total number of entities across all pages
995
 *      'offset'           The current indexing offset
996
 *      'limit'            The number of entities to display per page (default from settings)
997
 *      'full_view'        Display the full view of the entities?
998
 *      'list_class'       CSS class applied to the list
999
 *      'item_class'       CSS class applied to the list items
1000
 *      'item_view'        Alternative view to render list items
1001
 *      'pagination'       Display pagination?
1002
 *      'base_url'         Base URL of list (optional)
1003
 *      'url_fragment'     URL fragment to add to links if not present in base_url (optional)
1004
 *      'position'         Position of the pagination: before, after, or both
1005
 *      'list_type'        List type: 'list' (default), 'gallery'
1006
 *      'list_type_toggle' Display the list type toggle?
1007
 *      'no_results'       Message to display if no results (string|Closure)
1008
 *
1009
 * @return string The rendered list of entities
1010
 */
1011
function elgg_view_entity_list($entities, array $vars = []) {
1012
	$offset = (int) get_input('offset', 0);
1013
1014
	// list type can be passed as request parameter
1015
	$list_type = get_input('list_type', 'list');
1016
1017
	$defaults = [
1018
		'items' => $entities,
1019
		'list_class' => 'elgg-list-entity',
1020
		'full_view' => true,
1021
		'pagination' => true,
1022
		'list_type' => $list_type,
1023
		'list_type_toggle' => false,
1024
		'offset' => $offset,
1025
		'limit' => null,
1026
	];
1027
1028
	$vars = array_merge($defaults, $vars);
1029
1030 View Code Duplication
	if (!$vars["limit"] && !$vars["offset"]) {
1031
		// no need for pagination if listing is unlimited
1032
		$vars["pagination"] = false;
1033
	}
1034
1035
	if ($vars['list_type'] == 'table') {
1036
		return elgg_view('page/components/table', $vars);
1037
	} elseif ($vars['list_type'] == 'list') {
1038
		return elgg_view('page/components/list', $vars);
1039
	} else {
1040
		return elgg_view('page/components/gallery', $vars);
1041
	}
1042
}
1043
1044
/**
1045
 * Returns a rendered list of annotations, plus pagination. This function
1046
 * should be called by wrapper functions.
1047
 *
1048
 * @param array $annotations Array of annotations
1049
 * @param array $vars        Display variables
1050
 *      'count'      The total number of annotations across all pages
1051
 *      'offset'     The current indexing offset
1052
 *      'limit'      The number of annotations to display per page
1053
 *      'full_view'  Display the full view of the annotation?
1054
 *      'list_class' CSS Class applied to the list
1055
 *      'item_view'  Alternative view to render list items
1056
 *      'offset_key' The url parameter key used for offset
1057
 *      'no_results' Message to display if no results (string|Closure)
1058
 *
1059
 * @return string The list of annotations
1060
 * @access private
1061
 */
1062
function elgg_view_annotation_list($annotations, array $vars = []) {
1063
	$defaults = [
1064
		'items' => $annotations,
1065
		'offset' => null,
1066
		'limit' => null,
1067
		'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9
1068
		'full_view' => true,
1069
		'offset_key' => 'annoff',
1070
	];
1071
1072
	$vars = array_merge($defaults, $vars);
1073
1074 View Code Duplication
	if (!$vars["limit"] && !$vars["offset"]) {
1075
		// no need for pagination if listing is unlimited
1076
		$vars["pagination"] = false;
1077
	}
1078
1079
	return elgg_view('page/components/list', $vars);
1080
}
1081
1082
/**
1083
 * Display a plugin-specified rendered list of annotations for an entity.
1084
 *
1085
 * This displays the output of functions registered to the entity:annotation,
1086
 * $entity_type plugin hook.
1087
 *
1088
 * This is called automatically by the framework from {@link elgg_view_entity()}
1089
 *
1090
 * @param \ElggEntity $entity    Entity
1091
 * @param bool        $full_view Display full view?
1092
 *
1093
 * @return mixed string or false on failure
1094
 * @todo Change the hook name.
1095
 */
1096
function elgg_view_entity_annotations(\ElggEntity $entity, $full_view = true) {
1097
	if (!($entity instanceof \ElggEntity)) {
1098
		return false;
1099
	}
1100
1101
	$entity_type = $entity->getType();
1102
1103
	$annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type,
1104
		[
1105
			'entity' => $entity,
1106
			'full_view' => $full_view,
1107
		]
1108
	);
1109
1110
	return $annotations;
1111
}
1112
1113
/**
1114
 * Renders a title.
1115
 *
1116
 * This is a shortcut for {@elgg_view page/elements/title}.
1117
 *
1118
 * @param string $title The page title
1119
 * @param array  $vars  View variables (was submenu be displayed? (deprecated))
1120
 *
1121
 * @return string The HTML (etc)
1122
 */
1123
function elgg_view_title($title, array $vars = []) {
1124
	$vars['title'] = $title;
1125
1126
	return elgg_view('page/elements/title', $vars);
1127
}
1128
1129
/**
1130
 * Displays a UNIX timestamp in a friendly way
1131
 *
1132
 * @see elgg_get_friendly_time()
1133
 *
1134
 * @param int $time A UNIX epoch timestamp
1135
 *
1136
 * @return string The friendly time HTML
1137
 * @since 1.7.2
1138
 */
1139 View Code Duplication
function elgg_view_friendly_time($time) {
1140
	$view = 'output/friendlytime';
1141
	$vars = ['time' => $time];
1142
	$viewtype = elgg_view_exists($view) ? '' : 'default';
1143
1144
	return _elgg_view_under_viewtype($view, $vars, $viewtype);
1145
}
1146
1147
/**
1148
 * Returns rendered comments and a comment form for an entity.
1149
 *
1150
 * @tip Plugins can override the output by registering a handler
1151
 * for the comments, $entity_type hook.  The handler is responsible
1152
 * for formatting the comments and the add comment form.
1153
 *
1154
 * @param \ElggEntity $entity      The entity to view comments of
1155
 * @param bool        $add_comment Include a form to add comments?
1156
 * @param array       $vars        Variables to pass to comment view
1157
 *
1158
 * @return string|false Rendered comments or false on failure
1159
 */
1160
function elgg_view_comments($entity, $add_comment = true, array $vars = []) {
1161
	if (!($entity instanceof \ElggEntity)) {
1162
		return false;
1163
	}
1164
1165
	$vars['entity'] = $entity;
1166
	$vars['show_add_form'] = $add_comment;
1167
	$vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments");
1168
1169
	$output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false);
1170
	if ($output !== false) {
1171
		return $output;
1172
	} else {
1173
		return elgg_view('page/elements/comments', $vars);
1174
	}
1175
}
1176
1177
/**
1178
 * Wrapper function for the image block display pattern.
1179
 *
1180
 * Fixed width media on the side (image, icon, flash, etc.).
1181
 * Descriptive content filling the rest of the column.
1182
 *
1183
 * @note Use the $vars "image_alt" key to set an image on the right. If you do, you may pass
1184
 *       in an empty string for $image to have only the right image.
1185
 *
1186
 * This is a shortcut for {@elgg_view page/components/image_block}.
1187
 *
1188
 * @param string $image The icon and other information
1189
 * @param string $body  Description content
1190
 * @param array  $vars  Additional parameters for the view
1191
 *
1192
 * @return string
1193
 * @since 1.8.0
1194
 */
1195
function elgg_view_image_block($image, $body, $vars = []) {
1196
	$vars['image'] = $image;
1197
	$vars['body'] = $body;
1198
	return elgg_view('page/components/image_block', $vars);
1199
}
1200
1201
/**
1202
 * Wrapper function for the module display pattern.
1203
 *
1204
 * Box with header, body, footer
1205
 *
1206
 * This is a shortcut for {@elgg_view page/components/module}.
1207
 *
1208
 * @param string $type  The type of module (main, info, popup, aside, etc.)
1209
 * @param string $title A title to put in the header
1210
 * @param string $body  Content of the module
1211
 * @param array  $vars  Additional parameters for the module
1212
 *
1213
 * @return string
1214
 * @since 1.8.0
1215
 */
1216
function elgg_view_module($type, $title, $body, array $vars = []) {
1217
	$vars['type'] = $type;
1218
	$vars['title'] = $title;
1219
	$vars['body'] = $body;
1220
	return elgg_view('page/components/module', $vars);
1221
}
1222
1223
/**
1224
 * Renders a human-readable representation of a river item
1225
 *
1226
 * @param \ElggRiverItem $item A river item object
1227
 * @param array          $vars An array of variables for the view
1228
 *      'item_view'  Alternative view to render the item
1229
 * @return string returns empty string if could not be rendered
1230
 */
1231
function elgg_view_river_item($item, array $vars = []) {
1232
	if (!($item instanceof \ElggRiverItem)) {
1233
		return '';
1234
	}
1235
	// checking default viewtype since some viewtypes do not have unique views per item (rss)
1236
	$view = $item->getView();
1237
	if (!$view || !elgg_view_exists($view, 'default')) {
1238
		return '';
1239
	}
1240
1241
	$subject = $item->getSubjectEntity();
1242
	$object = $item->getObjectEntity();
1243
	if (!$subject || !$object) {
1244
		// subject is disabled or subject/object deleted
1245
		return '';
1246
	}
1247
1248
	// @todo this needs to be cleaned up
1249
	// Don't hide objects in closed groups that a user can see.
1250
	// see https://github.com/elgg/elgg/issues/4789
1251
	//	else {
1252
	//		// hide based on object's container
1253
	//		$visibility = \Elgg\GroupItemVisibility::factory($object->container_guid);
1254
	//		if ($visibility->shouldHideItems) {
1255
	//			return '';
1256
	//		}
1257
	//	}
1258
1259
	$vars['item'] = $item;
1260
1261
	$river_views = [
1262
		elgg_extract('item_view', $vars, ''),
1263
		"river/item", // important for other viewtypes, e.g. "rss"
1264
		$view,
1265
	];
1266
1267
	$contents = '';
1268
	foreach ($river_views as $view) {
1269
		if (elgg_view_exists($view)) {
1270
			$contents = elgg_view($view, $vars);
1271
			break;
1272
		}
1273
	}
1274
1275
	return $contents;
1276
}
1277
1278
/**
1279
 * Convenience function for generating a form from a view in a standard location.
1280
 *
1281
 * This function assumes that the body of the form is located at "forms/$action" and
1282
 * sets the action by default to "action/$action".  Automatically wraps the forms/$action
1283
 * view with a <form> tag and inserts the anti-csrf security tokens.
1284
 *
1285
 * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any
1286
 * slashes with dashes (blog/save becomes elgg-form-blog-save)
1287
 *
1288
 * @example
1289
 * <code>echo elgg_view_form('login');</code>
1290
 *
1291
 * This would assume a "login" form body to be at "forms/login" and would set the action
1292
 * of the form to "http://yoursite.com/action/login".
1293
 *
1294
 * If elgg_view('forms/login') is:
1295
 * <input type="text" name="username" />
1296
 * <input type="password" name="password" />
1297
 *
1298
 * Then elgg_view_form('login') generates:
1299
 * <form action="http://yoursite.com/action/login" method="post">
1300
 *     ...security tokens...
1301
 *     <input type="text" name="username" />
1302
 *     <input type="password" name="password" />
1303
 * </form>
1304
 *
1305
 * @param string $action    The name of the action. An action name does not include
1306
 *                          the leading "action/". For example, "login" is an action name.
1307
 * @param array  $form_vars $vars environment passed to the "input/form" view
1308
 * @param array  $body_vars $vars environment passed to the "forms/$action" view
1309
 *
1310
 * @return string The complete form
1311
 */
1312
function elgg_view_form($action, $form_vars = [], $body_vars = []) {
1313 3
	return _elgg_services()->forms->render($action, $form_vars, $body_vars);
1314
}
1315
1316
/**
1317
 * Sets form footer and defers its rendering until the form view and extensions have been rendered.
1318
 * Deferring footer rendering allows plugins to extend the form view while maintaining
1319
 * logical DOM structure.
1320
 * Footer will be rendered using 'elements/forms/footer' view after form body has finished rendering
1321
 *
1322
 * @param string $footer Footer
1323
 * @return bool
1324
 */
1325
function elgg_set_form_footer($footer = '') {
1326 1
	return _elgg_services()->forms->setFooter($footer);
1327
}
1328
1329
/**
1330
 * Returns currently set footer, or false if not in the form rendering stack
1331
 * @return string|false
1332
 */
1333
function elgg_get_form_footer() {
1334
	return _elgg_services()->forms->getFooter();
1335
}
1336
1337
/**
1338
 * Renders a form field
1339
 *
1340
 * @param string $input_type Input type, used to generate an input view ("input/$input_type")
1341
 * @param array  $vars       Fields and input vars.
1342
 *                           Field vars contain both field and input params. 'label', 'help',
1343
 *                           and 'field_class' params will not be passed on to the input view.
1344
 *                           Others, including 'required' and 'id', will be available to the
1345
 *                           input view. Both 'label' and 'help' params accept HTML, and
1346
 *                           will be printed unescaped within their wrapper element.
1347
 * @return string
1348
 *
1349
 * @since 2.1
1350
 * @deprecated 2.3 Use elgg_view_field()
1351
 */
1352
function elgg_view_input($input_type, array $vars = []) {
1353
1354
	elgg_deprecated_notice(__FUNCTION__ . '() is deprecated. Use elgg_view_field()', '2.3');
1355
1356
	$vars['#type'] = $input_type;
1357
1358
	if (isset($vars['label']) && $input_type !== 'checkbox') {
1359
		$vars['#label'] = $vars['label'];
1360
		unset($vars['label']);
1361
	}
1362 View Code Duplication
	if (isset($vars['help'])) {
1363
		$vars['#help'] = $vars['help'];
1364
		unset($vars['help']);
1365
	}
1366
	if (isset($vars['field_class'])) {
1367
		$vars['#class'] = $vars['field_class'];
1368
		unset($vars['field_class']);
1369
	}
1370
1371
	return elgg_view_field($vars);
1372
}
1373
1374
/**
1375
 * Renders a form field, usually with a wrapper element, a label, help text, etc.
1376
 *
1377
 * @param array $params Field parameters and variables for the input view.
1378
 *                      Keys not prefixed with hash (#) are passed to the input view as $vars.
1379
 *                      Keys prefixed with a hash specify the field wrapper (.elgg-view-field) output.
1380
 *                       - #type: specifies input view. E.g. "text" uses the view "input/text".
1381
 *                       - #label: field label HTML
1382
 *                       - #help: field help HTML
1383
 *                       - #class: field class name
1384
 *                      Note: Both #label and #help are printed unescaped within their wrapper element.
1385
 *                      Note: Some fields (like input/checkbox) need special attention because #label and label serve different purposes
1386
 *                      "#label" will be used as a label in the field wrapper but "label" will be used in the input view
1387
 *
1388
 * @return string
1389
 * @since 2.3
1390
 */
1391
function elgg_view_field(array $params = []) {
1392
1393
	if (empty($params['#type'])) {
1394
		_elgg_services()->logger->error(__FUNCTION__ . '(): $params["#type"] is required.');
1395
		return '';
1396
	}
1397
1398
	$input_type = $params['#type'];
1399
	if (!elgg_view_exists("input/$input_type")) {
1400
		return '';
1401
	}
1402
1403
	$hidden_types = ['hidden', 'securitytoken'];
1404
	if (in_array($input_type, $hidden_types)) {
1405
		unset($params['#type']);
1406
		unset($params['#label']);
1407
		unset($params['#help']);
1408
		unset($params['#class']);
1409
		return elgg_view("input/$input_type", $params);
1410
	}
1411
1412
	$id = elgg_extract('id', $params);
1413
	if (!$id) {
1414
		$id = "elgg-field-" . base_convert(mt_rand(), 10, 36);
1415
		$params['id'] = $id;
1416
	}
1417
1418
	// $vars passed to label, help and field wrapper views
1419
	$element_vars = [];
1420
1421
	// $vars passed to input/$input_name
1422
	$input_vars = [];
1423
	
1424
	$make_special_checkbox_label = false;
1425
	if ($input_type == 'checkbox' && (isset($params['label']) || isset($params['#label']))) {
1426
		if (isset($params['#label']) && isset($params['label'])) {
1427
			$params['label_tag'] = 'div';
1428
		} else {
1429
			$label = elgg_extract('label', $params);
1430
			$label = elgg_extract('#label', $params, $label);
1431
			
1432
			$params['#label'] = $label;
1433
			unset($params['label']);
1434
1435
			// Single checkbox input view gets special treatment
1436
			// We don't want the field label to appear a checkbox without a label
1437
			$make_special_checkbox_label = true;
1438
		}
1439
	}
1440
1441
	// first pass non-hash keys into both
1442
	foreach ($params as $key => $value) {
1443
		if ($key[0] !== '#') {
1444
			$element_vars[$key] = $value;
1445
			$input_vars[$key] = $value;
1446
		}
1447
	}
1448
1449
	// field input view needs this
1450
	$input_vars['input_type'] = $input_type;
1451
1452
	// field views get more data
1453
	$element_vars['input_type'] = $input_type;
1454
	
1455
	unset($element_vars['class']);
1456
	if (isset($params['#class'])) {
1457
		$element_vars['class'] = $params['#class'];
1458
	}
1459
	unset($element_vars['help']);
1460
	if (isset($params['#help'])) {
1461
		$element_vars['help'] = $params['#help'];
1462
	}
1463
	unset($element_vars['label']);
1464
	if (isset($params['#label'])) {
1465
		$element_vars['label'] = $params['#label'];
1466
	}
1467
	
1468
	// wrap if present
1469
	$element_vars['label'] = elgg_view('elements/forms/label', $element_vars);
1470
	$element_vars['help'] = elgg_view('elements/forms/help', $element_vars);
1471
1472
	if ($make_special_checkbox_label) {
1473
		$input_vars['label'] = $element_vars['label'];
1474
		$input_vars['label_tag'] = 'div';
1475
		unset($element_vars['label']);
1476
	}
1477
	$element_vars['input'] = elgg_view("elements/forms/input", $input_vars);
1478
1479
	return elgg_view('elements/forms/field', $element_vars);
1480
}
1481
1482
/**
1483
 * Create a tagcloud for viewing
1484
 *
1485
 * @see elgg_get_tags
1486
 *
1487
 * @param array $options Any elgg_get_tags() options except:
1488
 *
1489
 * 	type => must be single entity type
1490
 *
1491
 * 	subtype => must be single entity subtype
1492
 *
1493
 * @return string
1494
 * @since 1.7.1
1495
 */
1496
function elgg_view_tagcloud(array $options = []) {
1497
1498
	$type = $subtype = '';
1499
	if (isset($options['type'])) {
1500
		$type = $options['type'];
1501
	}
1502
	if (isset($options['subtype'])) {
1503
		$subtype = $options['subtype'];
1504
	}
1505
1506
	$tag_data = elgg_get_tags($options);
1507
	return elgg_view("output/tagcloud", [
1508
		'value' => $tag_data,
1509
		'type' => $type,
1510
		'subtype' => $subtype,
1511
	]);
1512
}
1513
1514
/**
1515
 * View an item in a list
1516
 *
1517
 * @param mixed $item Entity, annotation, river item, or other data
1518
 * @param array $vars Additional parameters for the rendering
1519
 *                    'item_view' - Alternative view used to render list items
1520
 *                                  This parameter is required if rendering
1521
 *                                  list items that are not entity, annotation or river
1522
 * @return string
1523
 * @since 1.8.0
1524
 * @access private
1525
 */
1526
function elgg_view_list_item($item, array $vars = []) {
1527
1528
	if ($item instanceof \ElggEntity) {
1529
		return elgg_view_entity($item, $vars);
1530
	} else if ($item instanceof \ElggAnnotation) {
1531
		return elgg_view_annotation($item, $vars);
1532
	} else if ($item instanceof \ElggRiverItem) {
1533
		return elgg_view_river_item($item, $vars);
1534
	}
1535
1536
	$view = elgg_extract('item_view', $vars);
1537
	if ($view && elgg_view_exists($view)) {
1538
		$vars['item'] = $item;
1539
		return elgg_view($view, $vars);
1540
	}
1541
1542
	return '';
1543
}
1544
1545
/**
1546
 * View one of the icons
1547
 *
1548
 * Shorthand for <span class="elgg-icon elgg-icon-$name"></span>
1549
 *
1550
 * @param string $name The specific icon to display
1551
 * @param mixed  $vars The additional classname as a string ('float', 'float-alt' or a custom class)
1552
 *                     or an array of variables (array('class' => 'float')) to pass to the icon view.
1553
 *
1554
 * @return string The html for displaying an icon
1555
 * @throws InvalidArgumentException
1556
 */
1557
function elgg_view_icon($name, $vars = []) {
1558
	if (empty($vars)) {
1559
		$vars = [];
1560
	}
1561
1562
	if (is_string($vars)) {
1563
		$vars = ['class' => $vars];
1564
	}
1565
1566
	if (!is_array($vars)) {
1567
		throw new \InvalidArgumentException('$vars needs to be a string or an array');
1568
	}
1569
1570
	if (!array_key_exists('class', $vars)) {
1571
		$vars['class'] = [];
1572
	}
1573
1574
	if (!is_array($vars['class'])) {
1575
		$vars['class'] = [$vars['class']];
1576
	}
1577
1578
	$vars['class'][] = "elgg-icon-$name";
1579
1580
	return elgg_view("output/icon", $vars);
1581
}
1582
1583
/**
1584
 * Include the RSS icon link and link element in the head
1585
 *
1586
 * @return void
1587
 */
1588
function elgg_register_rss_link() {
1589
	_elgg_config()->_elgg_autofeed = true;
1590
}
1591
1592
/**
1593
 * Remove the RSS icon link and link element from the head
1594
 *
1595
 * @return void
1596
 */
1597
function elgg_unregister_rss_link() {
1598
	_elgg_config()->_elgg_autofeed = false;
1599
}
1600
1601
/**
1602
 * Should the RSS view of this URL be linked to?
1603
 *
1604
 * @return bool
1605
 * @access private
1606
 */
1607
function _elgg_has_rss_link() {
1608 4
	if (isset($GLOBALS['autofeed']) && is_bool($GLOBALS['autofeed'])) {
1609
		elgg_deprecated_notice('Do not set the global $autofeed. Use elgg_register_rss_link()', '2.1');
1610
		return $GLOBALS['autofeed'];
1611
	}
1612 4
	return (bool) _elgg_services()->config->getVolatile('_elgg_autofeed');
1613
}
1614
1615
/**
1616
 * Auto-registers views from a location.
1617
 *
1618
 * @note Views in plugin/views/ are automatically registered for active plugins.
1619
 * Plugin authors would only need to call this if optionally including
1620
 * an entire views structure.
1621
 *
1622
 * @param string $view_base Optional The base of the view name without the view type.
1623
 * @param string $folder    Required The folder to begin looking in
1624
 * @param string $ignored   This argument is ignored
1625
 * @param string $viewtype  The type of view we're looking at (default, rss, etc)
1626
 *
1627
 * @return bool returns false if folder can't be read
1628
 * @since 1.7.0
1629
 * @see elgg_set_view_location()
1630
 * @access private
1631
 */
1632
function autoregister_views($view_base, $folder, $ignored, $viewtype) {
1633
	return _elgg_services()->views->autoregisterViews($view_base, $folder, $viewtype);
1634
}
1635
1636
/**
1637
 * Minifies simplecache CSS and JS views by handling the "simplecache:generate" hook
1638
 *
1639
 * @param string $hook    The name of the hook
1640
 * @param string $type    View type (css, js, or unknown)
1641
 * @param string $content Content of the view
1642
 * @param array  $params  Array of parameters
1643
 *
1644
 * @return string|null View content minified (if css/js type)
1645
 * @access private
1646
 */
1647
function _elgg_views_minify($hook, $type, $content, $params) {
1648
	if (preg_match('~[\.-]min\.~', $params['view'])) {
1649
		// bypass minification
1650
		return;
1651
	}
1652
1653
	if ($type == 'js') {
1654
		if (_elgg_config()->simplecache_minify_js) {
1655
			return JSMin::minify($content);
1656
		}
1657
	} elseif ($type == 'css') {
1658
		if (_elgg_config()->simplecache_minify_css) {
1659
			$cssmin = new CSSmin();
1660
			return $cssmin->run($content);
1661
		}
1662
	}
1663
}
1664
1665
/**
1666
 * Preprocesses CSS views sent by /cache URLs
1667
 *
1668
 * @param string $hook    The name of the hook "simplecache:generate" or "cache:generate"
1669
 * @param string $type    "css"
1670
 * @param string $content Content of the view
1671
 * @param array  $params  Array of parameters
1672
 *
1673
 * @return string|null View content
1674
 * @access private
1675
 */
1676
function _elgg_views_preprocess_css($hook, $type, $content, $params) {
1677
	$options = [
1678
		'minify' => false, // minify handled by _elgg_views_minify
1679
		'formatter' => 'single-line', // shows lowest byte size
1680
		'versioning' => false, // versioning done by Elgg
1681
		'rewrite_import_urls' => false,
1682
	];
1683
	
1684
	return csscrush_string($content, $options);
1685
}
1686
1687
/**
1688
 * Inserts module names into anonymous modules by handling the "simplecache:generate" hook.
1689
 *
1690
 * @param string $hook    The name of the hook
1691
 * @param string $type    View type (css, js, or unknown)
1692
 * @param string $content Content of the view
1693
 * @param array  $params  Array of parameters
1694
 *
1695
 * @return string|null View content minified (if css/js type)
1696
 * @access private
1697
 */
1698
function _elgg_views_amd($hook, $type, $content, $params) {
1699
	$filter = new \Elgg\Amd\ViewFilter();
1700
	return $filter->filter($params['view'], $content);
1701
}
1702
1703
/**
1704
 * Sends X-Frame-Options header on page requests
1705
 *
1706
 * @access private
1707
 */
1708
function _elgg_views_send_header_x_frame_options() {
1709
	header('X-Frame-Options: SAMEORIGIN');
1710
}
1711
1712
/**
1713
 * Is there a chance a plugin is altering this view?
1714
 *
1715
 * @note Must be called after the [init, system] event, ideally as late as possible.
1716
 *
1717
 * @note Always returns true if the view's location is set in /engine/views.php. Elgg does not keep
1718
 *       track of the defaults for those locations.
1719
 *
1720
 * <code>
1721
 * // check a view in core
1722
 * if (_elgg_view_may_be_altered('foo/bar', 'foo/bar.php')) {
1723
 *     // use the view for BC
1724
 * }
1725
 *
1726
 * // check a view in a bundled plugin
1727
 * $dir = __DIR__ . "/views/" . elgg_get_viewtype();
1728
 * if (_elgg_view_may_be_altered('foo.css', "$dir/foo.css.php")) {
1729
 *     // use the view for BC
1730
 * }
1731
 * </code>
1732
 *
1733
 * @param string $view     View name. E.g. "elgg/init.js"
1734
 * @param string $path     Absolute file path, or path relative to the viewtype directory. E.g. "elgg/init.js.php"
1735
 *
1736
 * @return bool
1737
 * @access private
1738
 */
1739
function _elgg_view_may_be_altered($view, $path) {
1740
	$views = _elgg_services()->views;
1741
1742
	if ($views->viewIsExtended($view) || $views->viewHasHookHandlers($view)) {
1743
		return true;
1744
	}
1745
1746
	$viewtype = elgg_get_viewtype();
1747
1748
	// check location
1749
	if (0 === strpos($path, '/') || preg_match('~^([A-Za-z]\:)?\\\\~', $path)) {
1750
		// absolute path
1751
		$expected_path = $path;
1752
	} else {
1753
		// relative path
1754
		$root = dirname(dirname(__DIR__));
1755
		$expected_path = "$root/views/$viewtype/" . ltrim($path, '/\\');
1756
	}
1757
1758
	$view_path = $views->findViewFile($view, $viewtype);
1759
	
1760
	return realpath($view_path) !== realpath($expected_path);
1761
}
1762
1763
/**
1764
 * Initialize viewtypes on system boot event
1765
 * This ensures simplecache is cleared during upgrades. See #2252
1766
 *
1767
 * @return void
1768
 * @access private
1769
 * @elgg_event_handler boot system
1770
 */
1771
function elgg_views_boot() {
1772
	if (!elgg_get_config('system_cache_loaded')) {
1773
		// Core view files in /views
1774
		_elgg_services()->views->registerPluginViews(realpath(__DIR__ . '/../../'));
1775
1776
		// Core view definitions in /engine/views.php
1777
		$file = dirname(__DIR__) . '/views.php';
1778 View Code Duplication
		if (is_file($file)) {
1 ignored issue
show
This code seems to be duplicated across your project.

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

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

Loading history...
1779
			$spec = Includer::includeFile($file);
1780
			if (is_array($spec)) {
1781
				_elgg_services()->views->mergeViewsSpec($spec);
1782
			}
1783
		}
1784
	}
1785
1786
	// on every page
1787
1788
	// jQuery and UI must come before require. See #9024
1789
	elgg_register_js('jquery', elgg_get_simplecache_url('jquery.js'), 'head');
1790
	elgg_load_js('jquery');
1791
1792
	elgg_register_js('jquery-ui', elgg_get_simplecache_url('jquery-ui.js'), 'head');
1793
	elgg_load_js('jquery-ui');
1794
1795
	elgg_register_js('elgg.require_config', elgg_get_simplecache_url('elgg/require_config.js'), 'head');
1796
	elgg_load_js('elgg.require_config');
1797
1798
	elgg_register_js('require', elgg_get_simplecache_url('require.js'), 'head');
1799
	elgg_load_js('require');
1800
1801
	elgg_register_js('elgg', elgg_get_simplecache_url('elgg.js'), 'head');
1802
	elgg_load_js('elgg');
1803
	
1804
	elgg_register_css('font-awesome', elgg_get_simplecache_url('font-awesome/css/font-awesome.css'));
1805
	elgg_load_css('font-awesome');
1806
1807
	elgg_register_css('elgg', elgg_get_simplecache_url('elgg.css'));
1808
	elgg_load_css('elgg');
1809
1810
	elgg_register_simplecache_view('elgg/init.js');
1811
1812
	elgg_extend_view('elgg.css', 'lightbox/elgg-colorbox-theme/colorbox.css');
1813
1814
	elgg_define_js('jquery.ui.autocomplete.html', [
1815
		'deps' => ['jquery-ui'],
1816
	]);
1817
1818
	elgg_register_js('elgg.avatar_cropper', elgg_get_simplecache_url('elgg/ui.avatar_cropper.js'));
1819
1820
	// @deprecated 2.2
1821
	elgg_register_js('elgg.ui.river', elgg_get_simplecache_url('elgg/ui.river.js'));
1822
1823
	elgg_register_js('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.js'));
1824
	elgg_register_css('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.css'));
1825
1826
	elgg_register_css('jquery.treeview', elgg_get_simplecache_url('jquery-treeview/jquery.treeview.css'));
1827
	elgg_define_js('jquery.treeview', [
1828
		'src' => elgg_get_simplecache_url('jquery-treeview/jquery.treeview.js'),
1829
		'exports' => 'jQuery.fn.treeview',
1830
		'deps' => ['jquery'],
1831
	]);
1832
1833
	elgg_register_ajax_view('languages.js');
1834
1835
	// pre-process CSS regardless of simplecache
1836
	elgg_register_plugin_hook_handler('cache:generate', 'css', '_elgg_views_preprocess_css');
1837
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_preprocess_css');
1838
1839
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_amd');
1840
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_minify');
1841
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_minify');
1842
1843
	elgg_register_plugin_hook_handler('output:before', 'page', '_elgg_views_send_header_x_frame_options');
1844
1845
	// registered with high priority for BC
1846
	// prior to 2.2 registration used to take place in _elgg_views_prepare_head() before the hook was triggered
1847
	elgg_register_plugin_hook_handler('head', 'page', '_elgg_views_prepare_favicon_links', 1);
1848
1849
	// set default icon sizes - can be overridden in settings.php or with plugin
1850
	if (!_elgg_services()->config->getVolatile('icon_sizes')) {
1851
		$icon_sizes = [
1852
			'topbar' => ['w' => 16, 'h' => 16, 'square' => true, 'upscale' => true],
1853
			'tiny' => ['w' => 25, 'h' => 25, 'square' => true, 'upscale' => true],
1854
			'small' => ['w' => 40, 'h' => 40, 'square' => true, 'upscale' => true],
1855
			'medium' => ['w' => 100, 'h' => 100, 'square' => true, 'upscale' => true],
1856
			'large' => ['w' => 200, 'h' => 200, 'square' => false, 'upscale' => false],
1857
			'master' => ['w' => 550, 'h' => 550, 'square' => false, 'upscale' => false],
1858
		];
1859
		elgg_set_config('icon_sizes', $icon_sizes);
1860
	}
1861
1862
	// Configure lightbox
1863
	elgg_register_plugin_hook_handler('elgg.data', 'site', '_elgg_set_lightbox_config');
1864
}
1865
1866
/**
1867
 * Get the site data to be merged into "elgg" in elgg.js.
1868
 *
1869
 * Unlike _elgg_get_js_page_data(), the keys returned are literal expressions.
1870
 *
1871
 * @return array
1872
 * @access private
1873
 */
1874
function _elgg_get_js_site_data() {
1875
	$language = _elgg_config()->language;
1876
	if (!$language) {
1877
		$language = 'en';
1878
	}
1879
1880
	return [
1881
		'elgg.data' => (object) elgg_trigger_plugin_hook('elgg.data', 'site', null, []),
1882
		'elgg.version' => elgg_get_version(),
1883
		'elgg.release' => elgg_get_version(true),
1884
		'elgg.config.wwwroot' => elgg_get_site_url(),
1885
1886
		// refresh token 3 times during its lifetime (in microseconds 1000 * 1/3)
1887
		'elgg.security.interval' => (int) _elgg_services()->actions->getActionTokenTimeout() * 333,
1888
		'elgg.config.language' => $language,
1889
	];
1890
}
1891
1892
/**
1893
 * Get the initial contents of "elgg" client side. Will be extended by elgg.js.
1894
 *
1895
 * @return array
1896
 * @access private
1897
 */
1898
function _elgg_get_js_page_data() {
1899
	$data = elgg_trigger_plugin_hook('elgg.data', 'page', null, []);
1900
	if (!is_array($data)) {
1901
		elgg_log('"elgg.data" plugin hook handlers must return an array. Returned ' . gettype($data) . '.', 'ERROR');
1902
		$data = [];
1903
	}
1904
1905
	$elgg = [
1906
		'config' => [
1907
			'lastcache' => (int) _elgg_config()->lastcache,
1908
			'viewtype' => elgg_get_viewtype(),
1909
			'simplecache_enabled' => (int) elgg_is_simplecache_enabled(),
1910
			'current_language' => get_current_language(),
1911
		],
1912
		'security' => [
1913
			'token' => [
1914
				'__elgg_ts' => $ts = time(),
1915
				'__elgg_token' => generate_action_token($ts),
1916
			],
1917
		],
1918
		'session' => [
1919
			'user' => null,
1920
			'token' => _elgg_services()->session->get('__elgg_session'),
1921
		],
1922
		'_data' => (object) $data,
1923
	];
1924
1925
	if (_elgg_config()->elgg_load_sync_code) {
1926
		$elgg['config']['load_sync_code'] = true;
1927
	}
1928
1929
	$page_owner = elgg_get_page_owner_entity();
1930
	if ($page_owner instanceof ElggEntity) {
1931
		$elgg['page_owner'] = $page_owner->toObject();
1932
	}
1933
1934
	$user = elgg_get_logged_in_user_entity();
1935
	if ($user instanceof ElggUser) {
1936
		$user_object = $user->toObject();
1937
		$user_object->admin = $user->isAdmin();
1938
		$elgg['session']['user'] = $user_object;
1939
	}
1940
1941
	return $elgg;
1942
}
1943
1944
/**
1945
 * Render a view while the global viewtype is temporarily changed. This makes sure that
1946
 * nested views use the same viewtype.
1947
 *
1948
 * @param string  $view     View name
1949
 * @param array   $vars     View vars
1950
 * @param string  $viewtype Temporary viewtype ('' to leave current)
1951
 *
1952
 * @return mixed
1953
 * @access private
1954
 */
1955
function _elgg_view_under_viewtype($view, $vars, $viewtype) {
1956
	if ($viewtype) {
1957
		$old = elgg_get_viewtype();
1958
		elgg_set_viewtype($viewtype);
1959
	}
1960
1961
	$ret = elgg_view($view, $vars);
1962
1963
	if ($viewtype) {
1964
		elgg_set_viewtype($old);
1965
	}
1966
1967
	return $ret;
1968
}
1969
1970
/**
1971
 * Set lightbox config
1972
 *
1973
 * @param string $hook   "elgg.data"
1974
 * @param string $type   "site"
1975
 * @param array  $return Data
1976
 * @param array  $params Hook params
1977
 * @return array
1978
 * @access private
1979
 */
1980
function _elgg_set_lightbox_config($hook, $type, $return, $params) {
1981
1982
	$return['lightbox'] = [
1983
		'current' => elgg_echo('js:lightbox:current', ['{current}', '{total}']),
1984
		'previous' => elgg_view_icon('caret-left'),
1985
		'next' => elgg_view_icon('caret-right'),
1986
		'close' => elgg_view_icon('times'),
1987
		'opacity' => 0.5,
1988
		'maxWidth' => '990px',
1989
		'maxHeight' => '990px',
1990
		'initialWidth' => '300px',
1991
		'initialHeight' => '300px',
1992
	];
1993
1994
	return $return;
1995
}
1996