Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

elgg_set_view_location()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 0
cts 1
cp 0
crap 2
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 19
	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 181
	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
	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 40
	elgg_register_external_view($view, false);
135 40
}
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 46
	_elgg_services()->ajax->registerView($view);
159
160 46
	if ($cacheable) {
161 37
		_elgg_services()->views->registerCacheableView($view);
162
	}
163 46
}
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 362
	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 280
	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 280
	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
 * @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 32
	_elgg_services()->views->extendView($view, $view_extension, $priority);
296 32
}
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 31
	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
	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 12
	$timer = _elgg_services()->timer;
363 12
	if (!$timer->hasEnded(['build page'])) {
364 9
		$timer->end(['build page']);
365
	}
366 12
	$timer->begin([__FUNCTION__]);
367
368 12
	$params = [];
369 12
	$params['identifier'] = _elgg_services()->request->getFirstUrlSegment();
370 12
	$params['segments'] = _elgg_services()->request->getUrlSegments();
371 12
	array_shift($params['segments']);
372 12
	$page_shell = elgg_trigger_plugin_hook('shell', 'page', $params, $page_shell);
373
374
375 12
	$system_messages = _elgg_services()->systemMessages;
376
377 12
	$messages = null;
378 12
	if ($system_messages->count()) {
379 2
		$messages = $system_messages->dumpRegister();
380
381 2
		if (isset($messages['error'])) {
382
			// always make sure error is the first type
383
			$errors = [
384 2
				'error' => $messages['error']
385
			];
386
387 2
			unset($messages['error']);
388 2
			$messages = array_merge($errors, $messages);
389
		}
390
	}
391
392 12
	$vars['title'] = $title;
393 12
	$vars['body'] = $body;
394 12
	$vars['sysmessages'] = $messages;
395 12
	$vars['page_shell'] = $page_shell;
396
397
	// head has keys 'title', 'metas', 'links'
398 12
	$head_params = _elgg_views_prepare_head($title);
399
400 12
	$vars['head'] = elgg_trigger_plugin_hook('head', 'page', $vars, $head_params);
401
402 12
	$vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars);
403
404 12
	$output = elgg_view("page/$page_shell", $vars);
405
406
407
	// Allow plugins to modify the output
408 12
	$output = elgg_trigger_plugin_hook('output', 'page', $vars, $output);
409
410 12
	$timer->end([__FUNCTION__]);
411 12
	return $output;
412
}
413
414
/**
415
 * Render a resource view. Use this in your page handler to hand off page rendering to
416
 * a view in "resources/". If not found in the current viewtype, we try the "default" viewtype.
417
 *
418
 * @param string $name The view name without the leading "resources/"
419
 * @param array  $vars Arguments passed to the view
420
 *
421
 * @return string
422
 * @throws \Elgg\PageNotFoundException
423
 */
424
function elgg_view_resource($name, array $vars = []) {
425 6
	$view = "resources/$name";
426
427 6
	if (elgg_view_exists($view)) {
428 6
		return _elgg_services()->views->renderView($view, $vars);
429
	}
430
431
	if (elgg_get_viewtype() !== 'default' && elgg_view_exists($view, 'default')) {
432
		return _elgg_services()->views->renderView($view, $vars, 'default');
433
	}
434
435
	_elgg_services()->logger->error("The view $view is missing.");
436
437
	// only works for default viewtype
438
	throw new \Elgg\PageNotFoundException();
439
}
440
441
/**
442
 * Prepare the variables for the html head
443
 *
444
 * @param string $title Page title for <head>
445
 * @return array
446
 * @access private
447
 */
448
function _elgg_views_prepare_head($title) {
449
	$params = [
450 12
		'links' => [],
451
		'metas' => [],
452
	];
453
454 12
	if (empty($title)) {
455
		$params['title'] = _elgg_config()->sitename;
456
	} else {
457 12
		$params['title'] = $title . ' : ' . _elgg_config()->sitename;
458
	}
459
460 12
	$params['metas']['content-type'] = [
461
		'http-equiv' => 'Content-Type',
462
		'content' => 'text/html; charset=utf-8',
463
	];
464
465 12
	$params['metas']['description'] = [
466 12
		'name' => 'description',
467 12
		'content' => _elgg_config()->sitedescription
468
	];
469
470
	// https://developer.chrome.com/multidevice/android/installtohomescreen
471 12
	$params['metas']['viewport'] = [
472
		'name' => 'viewport',
473
		'content' => 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0',
474
	];
475 12
	$params['metas']['mobile-web-app-capable'] = [
476
		'name' => 'mobile-web-app-capable',
477
		'content' => 'yes',
478
	];
479 12
	$params['metas']['apple-mobile-web-app-capable'] = [
480
		'name' => 'apple-mobile-web-app-capable',
481
		'content' => 'yes',
482
	];
483
	
484
	// RSS feed link
485 12
	if (_elgg_has_rss_link()) {
486
		$url = current_page_url();
487
		if (substr_count($url, '?')) {
488
			$url .= "&view=rss";
489
		} else {
490
			$url .= "?view=rss";
491
		}
492
		$params['links']['rss'] = [
493
			'rel' => 'alternative',
494
			'type' => 'application/rss+xml',
495
			'title' => 'RSS',
496
			'href' => $url,
497
		];
498
	}
499
	
500 12
	return $params;
501
}
502
503
504
/**
505
 * Add favicon link tags to HTML head
506
 *
507
 * @param string $hook        "head"
508
 * @param string $type        "page"
509
 * @param array  $head_params Head params
510
 *                            <code>
511
 *                               [
512
 *                                  'title' => '',
513
 *                                  'metas' => [],
514
 *                                  'links' => [],
515
 *                               ]
516
 *                            </code>
517
 * @param array  $params      Hook params
518
 * @return array
519
 */
520
function _elgg_views_prepare_favicon_links($hook, $type, $head_params, $params) {
521
522 2
	$head_params['links']['apple-touch-icon'] = [
523 2
		'rel' => 'apple-touch-icon',
524 2
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
525
	];
526
527
	// favicons
528 2
	$head_params['links']['icon-ico'] = [
529 2
		'rel' => 'icon',
530 2
		'href' => elgg_get_simplecache_url('graphics/favicon.ico'),
531
	];
532 2
	$head_params['links']['icon-vector'] = [
533 2
		'rel' => 'icon',
534 2
		'sizes' => '16x16 32x32 48x48 64x64 128x128',
535 2
		'type' => 'image/svg+xml',
536 2
		'href' => elgg_get_simplecache_url('graphics/favicon.svg'),
537
	];
538 2
	$head_params['links']['icon-16'] = [
539 2
		'rel' => 'icon',
540 2
		'sizes' => '16x16',
541 2
		'type' => 'image/png',
542 2
		'href' => elgg_get_simplecache_url('graphics/favicon-16.png'),
543
	];
544 2
	$head_params['links']['icon-32'] = [
545 2
		'rel' => 'icon',
546 2
		'sizes' => '32x32',
547 2
		'type' => 'image/png',
548 2
		'href' => elgg_get_simplecache_url('graphics/favicon-32.png'),
549
	];
550 2
	$head_params['links']['icon-64'] = [
551 2
		'rel' => 'icon',
552 2
		'sizes' => '64x64',
553 2
		'type' => 'image/png',
554 2
		'href' => elgg_get_simplecache_url('graphics/favicon-64.png'),
555
	];
556 2
	$head_params['links']['icon-128'] = [
557 2
		'rel' => 'icon',
558 2
		'sizes' => '128x128',
559 2
		'type' => 'image/png',
560 2
		'href' => elgg_get_simplecache_url('graphics/favicon-128.png'),
561
	];
562
563 2
	return $head_params;
564
}
565
566
/**
567
 * Displays a layout with optional parameters.
568
 *
569
 * Layouts are templates provide consistency by organizing blocks of content on the page.
570
 *
571
 * Plugins should use one of the core layouts:
572
 *  - default     Primary template with one, two or no sidebars
573
 *  - admin       Admin page template
574
 *  - error       Error page template
575
 *  - widgets     Widgets canvas
576
 *
577
 * Plugins can create and use custom layouts by placing a layout view
578
 * in "page/layouts/<layout_name>" and calling elgg_view_layout(<layout_name>).
579
 *
580
 * For a full list of parameters supported by each of these layouts see
581
 * corresponding layout views.
582
 *
583
 * @param string $layout_name Layout name
584
 *                            Corresponds to a view in "page/layouts/<layout_name>".
585
 * @param array  $vars        Layout parameters
586
 *                            An associative array of parameters to pass to
587
 *                            the layout hooks and views.
588
 *                            Route 'identifier' and 'segments' of the page being
589
 *                            rendered will be added to this array automatially,
590
 *                            allowing plugins to alter layout views and subviews
591
 *                            based on the current route.
592
 * @return string
593
 */
594
function elgg_view_layout($layout_name, $vars = []) {
595 3
	$timer = _elgg_services()->timer;
596 3
	if (!$timer->hasEnded(['build page'])) {
597 2
		$timer->end(['build page']);
598
	}
599 3
	$timer->begin([__FUNCTION__]);
600
601
	// Help plugins transition without breaking them
602 3
	switch ($layout_name) {
603
		case 'content' :
604 1
			$layout_name = 'default';
605 1
			$vars = _elgg_normalize_content_layout_vars($vars);
606 1
			break;
607
608
		case 'one_sidebar' :
609
			$layout_name = 'default';
610
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
611
			$vars['sidebar_alt'] = false;
612
			break;
613
614
		case 'one_column' :
615
			$layout_name = 'default';
616
			$vars['sidebar'] = false;
617
			$vars['sidebar_alt'] = false;
618
			break;
619
620
		case 'two_sidebar' :
621
			$layout_name = 'default';
622
			$vars['sidebar'] = elgg_extract('sidebar', $vars, '', false);
623
			$vars['sidebar_alt'] = elgg_extract('sidebar_alt', $vars, '', false);
624
			break;
625
	}
626
627 3
	if (isset($vars['nav'])) {
628
		// Temporary helper until all core views are updated
629
		$vars['breadcrumbs'] = $vars['nav'];
630
		unset($vars['nav']);
631
	}
632
633 3
	$vars['identifier'] = _elgg_services()->request->getFirstUrlSegment();
634 3
	$vars['segments'] = _elgg_services()->request->getUrlSegments();
635 3
	array_shift($vars['segments']);
636
637 3
	$layout_name = elgg_trigger_plugin_hook('layout', 'page', $vars, $layout_name);
638
639 3
	$vars['layout'] = $layout_name;
640
641
	$layout_views = [
642 3
		"page/layouts/$layout_name",
643 3
		"page/layouts/default",
644
	];
645
646 3
	$output = '';
647 3
	foreach ($layout_views as $layout_view) {
648 3
		if (elgg_view_exists($layout_view)) {
649 2
			$output = elgg_view($layout_view, $vars);
650 3
			break;
651
		}
652
	}
653
654 3
	$timer->end([__FUNCTION__]);
655 3
	return $output;
656
}
657
658
/**
659
 * Normalizes deprecated content layout $vars for use in default layout
660
 * Helper function to assist plugins transitioning to 3.0
661
 *
662
 * @param array $vars Vars
663
 * @return array
664
 * @access private
665
 */
666
function _elgg_normalize_content_layout_vars(array $vars = []) {
667
668 1
	$context = elgg_extract('context', $vars, elgg_get_context());
669
670 1
	$vars['title'] = elgg_extract('title', $vars, '');
671 1
	if (!$vars['title'] && $vars['title'] !== false) {
672
		$vars['title'] = elgg_echo($context);
673
	}
674
675
	// 1.8 supported 'filter_override'
676 1
	if (isset($vars['filter_override'])) {
677
		$vars['filter'] = $vars['filter_override'];
678
	}
679
680
	// register the default content filters
681 1
	if (!isset($vars['filter']) && $context) {
682
		$selected = elgg_extract('filter_context', $vars);
683
		$vars['filter'] = elgg_get_filter_tabs($context, $selected, null, $vars);
684
		$vars['filter_id'] = $context;
685
		$vars['filter_value'] = $selected;
686
	}
687
	
688 1
	return $vars;
689
}
690
691
/**
692
 * Render a menu
693
 *
694
 * @see elgg_register_menu_item() for documentation on adding menu items and
695
 * navigation.php for information on the different menus available.
696
 *
697
 * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables
698
 * plugins to add menu items just before a menu is rendered. This is used by
699
 * dynamic menus (menus that change based on some input such as the user hover
700
 * menu). Using elgg_register_menu_item() in response to the hook can cause
701
 * incorrect links to show up. See the blog plugin's blog_owner_block_menu()
702
 * for an example of using this plugin hook.
703
 *
704
 * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins
705
 * to modify the structure of the menu (sort it, remove items, set variables on
706
 * the menu items).
707
 *
708
 * Preset (unprepared) menu items passed to the this function with the $vars
709
 * argument, will be merged with the registered items (registered with
710
 * elgg_register_menu_item()). The combined set of menu items will be passed
711
 * to 'register', 'menu:<menu_name>' hook.
712
 *
713
 * Plugins that pass preset menu items to this function and do not wish to be
714
 * affected by plugin hooks (e.g. if you are displaying multiple menus with
715
 * the same name on the page) should instead choose a unqie menu name
716
 * and define a menu_view argument to render menus consistently.
717
 * For example, if you have multiple 'filter' menus on the page:
718
 * <code>
719
 *    elgg_view_menu("filter:$uid", [
720
 *        'items' => $items,
721
 *        'menu_view' => 'navigation/menu/filter',
722
 *    ]);
723
 * </code>
724
 *
725
 * elgg_view_menu() uses views in navigation/menu
726
 *
727
 * @param string|Menu|UnpreparedMenu $menu Menu name (or object)
728
 * @param array                      $vars An associative array of display options for the menu.
729
 *
730
 *                          Options include:
731
 *                              items => an array of unprepared menu items
732
 *                                       as ElggMenuItem or menu item factory options
733
 *                              sort_by => string or php callback
734
 *                                  string options: 'name', 'priority', 'title' (default),
735
 *                                  'register' (registration order) or a
736
 *                                  php callback (a compare function for usort)
737
 *                              handler: string the page handler to build action URLs
738
 *                              entity: \ElggEntity to use to build action URLs
739
 *                              class: string the class for the entire menu.
740
 *                              menu_view: name of the view to be used to render the menu
741
 *                              show_section_headers: bool show headers before menu sections.
742
 *
743
 * @return string
744
 * @since 1.8.0
745
 */
746
function elgg_view_menu($menu, array $vars = []) {
747
748 33
	$menu_view = elgg_extract('menu_view', $vars);
749 33
	unset($vars['menu_view']);
750
751 33
	if (is_string($menu)) {
752 33
		$menu = _elgg_services()->menus->getMenu($menu, $vars);
753
	} elseif ($menu instanceof UnpreparedMenu) {
754
		$menu = _elgg_services()->menus->prepareMenu($menu);
755
	}
756
757 33
	if (!$menu instanceof Menu) {
758
		throw new \InvalidArgumentException('$menu must be a menu name, a Menu, or UnpreparedMenu');
759
	}
760
761 33
	$name = $menu->getName();
762 33
	$params = $menu->getParams();
763
764
	$views = [
765 33
		$menu_view,
766 33
		"navigation/menu/$name",
767 33
		'navigation/menu/default',
768
	];
769
770 33
	foreach ($views as $view) {
771 33
		if (elgg_view_exists($view)) {
772 33
			return elgg_view($view, $params);
773
		}
774
	}
775 18
}
776
777
/**
778
 * Render a menu item (usually as a link)
779
 *
780
 * @param \ElggMenuItem $item The menu item
781
 * @param array         $vars Options to pass to output/url if a link
782
 * @return string
783
 * @since 1.9.0
784
 */
785
function elgg_view_menu_item(\ElggMenuItem $item, array $vars = []) {
786
787 13
	$vars = array_merge($item->getValues(), $vars);
788 13
	$vars['class'] = elgg_extract_class($vars, ['elgg-menu-content']);
789
	
790 13
	if ($item->getLinkClass()) {
791 4
		$vars['class'][] = $item->getLinkClass();
792
	}
793
794 13
	if ($item->getHref() === false || $item->getHref() === null) {
795 2
		$vars['class'][] = 'elgg-non-link';
796
	}
797
798 13
	if (!isset($vars['rel']) && !isset($vars['is_trusted'])) {
799 13
		$vars['is_trusted'] = true;
800
	}
801
802 13
	if ($item->getConfirmText()) {
803
		$vars['confirm'] = $item->getConfirmText();
804
	}
805
806 13
	return elgg_view('output/url', $vars);
807
}
808
809
/**
810
 * Returns a string of a rendered entity.
811
 *
812
 * Entity views are either determined by setting the view property on the entity
813
 * or by having a view named after the entity $type/$subtype.  Entities that have
814
 * neither a view property nor a defined $type/$subtype view will fall back to
815
 * using the $type/default view.
816
 *
817
 * The entity view is called with the following in $vars:
818
 *  - \ElggEntity 'entity' The entity being viewed
819
 *
820
 * @tip This function can automatically appends annotations to entities if in full
821
 * view and a handler is registered for the entity:annotate.  See https://github.com/Elgg/Elgg/issues/964 and
822
 * {@link elgg_view_entity_annotations()}.
823
 *
824
 * @param \ElggEntity $entity The entity to display
825
 * @param array       $vars   Array of variables to pass to the entity view.
826
 *      'full_view'        Whether to show a full or condensed view. (Default: true)
827
 *      'item_view'        Alternative view used to render this entity
828
 *
829
 * @return string HTML to display or false
830
 * @todo The annotation hook might be better as a generic plugin hook to append content.
831
 */
832
function elgg_view_entity(\ElggEntity $entity, array $vars = []) {
833
834
	// No point continuing if entity is null
835 9
	if (!$entity || !($entity instanceof \ElggEntity)) {
836
		return false;
837
	}
838
839 9
	elgg_register_rss_link();
840
841
	$defaults = [
842 9
		'full_view' => true,
843
	];
844
845 9
	$vars = array_merge($defaults, $vars);
846
847 9
	$vars['entity'] = $entity;
848
849 9
	$entity_type = $entity->getType();
850 9
	$entity_subtype = $entity->getSubtype();
851
852
	$entity_views = [
853 9
		elgg_extract('item_view', $vars, ''),
854 9
		"$entity_type/$entity_subtype",
855 9
		"$entity_type/default",
856
	];
857
858 9
	$contents = '';
859 9
	foreach ($entity_views as $view) {
860 9
		if (elgg_view_exists($view)) {
861 9
			$contents = elgg_view($view, $vars);
862 9
			break;
863
		}
864
	}
865
866
	// Marcus Povey 20090616 : Speculative and low impact approach for fixing #964
867 9
	if ($vars['full_view']) {
868 7
		$annotations = elgg_view_entity_annotations($entity, $vars['full_view']);
869
870 7
		if ($annotations) {
871
			$contents .= $annotations;
872
		}
873
	}
874 9
	return $contents;
875
}
876
877
/**
878
 * View the icon of an entity
879
 *
880
 * Entity views are determined by having a view named after the entity $type/$subtype.
881
 * Entities that do not have a defined icon/$type/$subtype view will fall back to using
882
 * the icon/$type/default view.
883
 *
884
 * @param \ElggEntity $entity The entity to display
885
 * @param string      $size   The size: tiny, small, medium, large
886
 * @param array       $vars   An array of variables to pass to the view. Some possible
887
 *                            variables are img_class and link_class. See the
888
 *                            specific icon view for more parameters.
889
 *
890
 * @return string HTML to display or false
891
 */
892
function elgg_view_entity_icon(\ElggEntity $entity, $size = 'medium', $vars = []) {
893
894
	// No point continuing if entity is null
895 14
	if (!$entity || !($entity instanceof \ElggEntity)) {
896
		return false;
897
	}
898
899 14
	$vars['entity'] = $entity;
900 14
	$vars['size'] = $size;
901
902 14
	$entity_type = $entity->getType();
903
904 14
	$subtype = $entity->getSubtype();
905
906 14
	$contents = '';
907 14
	if (elgg_view_exists("icon/$entity_type/$subtype")) {
908
		$contents = elgg_view("icon/$entity_type/$subtype", $vars);
909
	}
910 14
	if (empty($contents)) {
911 14
		$contents = elgg_view("icon/$entity_type/default", $vars);
912
	}
913 14
	if (empty($contents)) {
914 13
		$contents = elgg_view("icon/default", $vars);
915
	}
916
917 14
	return $contents;
918
}
919
920
/**
921
 * Returns a string of a rendered annotation.
922
 *
923
 * Annotation views are expected to be in annotation/$annotation_name.
924
 * If a view is not found for $annotation_name, the default annotation/default
925
 * will be used.
926
 *
927
 * @warning annotation/default is not currently defined in core.
928
 *
929
 * The annotation view is called with the following in $vars:
930
 *  - \ElggEntity 'annotation' The annotation being viewed.
931
 *
932
 * @param \ElggAnnotation $annotation The annotation to display
933
 * @param array           $vars       Variable array for view.
934
 *      'item_view'  Alternative view used to render an annotation
935
 *
936
 * @return string/false Rendered annotation
937
 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/false at position 0 could not be parsed: Unknown type name 'string/false' at position 0 in string/false.
Loading history...
938
function elgg_view_annotation(\ElggAnnotation $annotation, array $vars = []) {
939
	elgg_register_rss_link();
940
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
985
 *      'pagination'       Display pagination?
986
 *      'base_url'         Base URL of list (optional)
987
 *      'url_fragment'     URL fragment to add to links if not present in base_url (optional)
988
 *      'position'         Position of the pagination: before, after, or both
989
 *      'list_type'        List type: 'list' (default), 'gallery'
990
 *      'list_type_toggle' Display the list type toggle?
991
 *      'no_results'       Message to display if no results (string|Closure)
992
 *
993
 * @return string The rendered list of entities
994
 */
995
function elgg_view_entity_list($entities, array $vars = []) {
996 3
	$offset = (int) get_input('offset', 0);
997
998
	// list type can be passed as request parameter
999 3
	$list_type = get_input('list_type', 'list');
1000
1001
	$defaults = [
1002 3
		'items' => $entities,
1003 3
		'list_class' => 'elgg-list-entity',
1004
		'full_view' => true,
1005
		'pagination' => true,
1006 3
		'list_type' => $list_type,
1007
		'list_type_toggle' => false,
1008 3
		'offset' => $offset,
1009
		'limit' => null,
1010
	];
1011
1012 3
	$vars = array_merge($defaults, $vars);
1013
1014 3
	if (!$vars["limit"] && !$vars["offset"]) {
1015
		// no need for pagination if listing is unlimited
1016
		$vars["pagination"] = false;
1017
	}
1018
1019 3
	if ($vars['list_type'] == 'table') {
1020
		return elgg_view('page/components/table', $vars);
1021 3
	} elseif ($vars['list_type'] == 'list') {
1022 3
		return elgg_view('page/components/list', $vars);
1023
	} else {
1024
		return elgg_view('page/components/gallery', $vars);
1025
	}
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|Closure)
1042
 *
1043
 * @return string The list of annotations
1044
 * @access private
1045
 */
1046
function elgg_view_annotation_list($annotations, array $vars = []) {
1047
	$defaults = [
1048 1
		'items' => $annotations,
1049
		'offset' => null,
1050
		'limit' => null,
1051 1
		'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9
1052
		'full_view' => true,
1053 1
		'offset_key' => 'annoff',
1054
	];
1055
1056 1
	$vars = array_merge($defaults, $vars);
1057
1058 1
	if (!$vars["limit"] && !$vars["offset"]) {
1059
		// no need for pagination if listing is unlimited
1060
		$vars["pagination"] = false;
1061
	}
1062
1063 1
	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 7
	if (!($entity instanceof \ElggEntity)) {
1082
		return false;
1083
	}
1084
1085 7
	$entity_type = $entity->getType();
1086
1087 7
	$annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type,
1088
		[
1089 7
			'entity' => $entity,
1090 7
			'full_view' => $full_view,
1091
		]
1092
	);
1093
1094 7
	return $annotations;
1095
}
1096
1097
/**
1098
 * Renders a title.
1099
 *
1100
 * This is a shortcut for {@elgg_view page/elements/title}.
1101
 *
1102
 * @param string $title The page title
1103
 * @param array  $vars  View variables (was submenu be displayed? (deprecated))
1104
 *
1105
 * @return string The HTML (etc)
1106
 */
1107
function elgg_view_title($title, array $vars = []) {
1108
	$vars['title'] = $title;
1109
1110
	return elgg_view('page/elements/title', $vars);
1111
}
1112
1113
/**
1114
 * Displays a UNIX timestamp in a friendly way
1115
 *
1116
 * @see elgg_get_friendly_time()
1117
 *
1118
 * @param int $time A UNIX epoch timestamp
1119
 *
1120
 * @return string The friendly time HTML
1121
 * @since 1.7.2
1122
 */
1123
function elgg_view_friendly_time($time) {
1124 9
	$view = 'output/friendlytime';
1125 9
	$vars = ['time' => $time];
1126 9
	$viewtype = elgg_view_exists($view) ? '' : 'default';
1127
1128 9
	return _elgg_view_under_viewtype($view, $vars, $viewtype);
1129
}
1130
1131
/**
1132
 * Returns rendered comments and a comment form for an entity.
1133
 *
1134
 * @tip Plugins can override the output by registering a handler
1135
 * for the comments, $entity_type hook.  The handler is responsible
1136
 * for formatting the comments and the add comment form.
1137
 *
1138
 * @param \ElggEntity $entity      The entity to view comments of
1139
 * @param bool        $add_comment Include a form to add comments?
1140
 * @param array       $vars        Variables to pass to comment view
1141
 *
1142
 * @return string|false Rendered comments or false on failure
1143
 */
1144
function elgg_view_comments($entity, $add_comment = true, array $vars = []) {
1145 1
	if (!($entity instanceof \ElggEntity)) {
1146
		return false;
1147
	}
1148
1149 1
	$vars['entity'] = $entity;
1150 1
	$vars['show_add_form'] = $add_comment;
1151 1
	$vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments");
1152
1153 1
	$output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false);
1154 1
	if ($output !== false) {
1155
		return $output;
1156
	} else {
1157 1
		return elgg_view('page/elements/comments', $vars);
1158
	}
1159
}
1160
1161
/**
1162
 * Wrapper function for the image block display pattern.
1163
 *
1164
 * Fixed width media on the side (image, icon, flash, etc.).
1165
 * Descriptive content filling the rest of the column.
1166
 *
1167
 * @note Use the $vars "image_alt" key to set an image on the right. If you do, you may pass
1168
 *       in an empty string for $image to have only the right image.
1169
 *
1170
 * This is a shortcut for {@elgg_view page/components/image_block}.
1171
 *
1172
 * @param string $image The icon and other information
1173
 * @param string $body  Description content
1174
 * @param array  $vars  Additional parameters for the view
1175
 *
1176
 * @return string
1177
 * @since 1.8.0
1178
 */
1179
function elgg_view_image_block($image, $body, $vars = []) {
1180 13
	$vars['image'] = $image;
1181 13
	$vars['body'] = $body;
1182 13
	return elgg_view('page/components/image_block', $vars);
1183
}
1184
1185
/**
1186
 * Wrapper function for the module display pattern.
1187
 *
1188
 * Box with header, body, footer
1189
 *
1190
 * This is a shortcut for {@elgg_view page/components/module}.
1191
 *
1192
 * @param string $type  The type of module (main, info, popup, aside, etc.)
1193
 * @param string $title A title to put in the header
1194
 * @param string $body  Content of the module
1195
 * @param array  $vars  Additional parameters for the module
1196
 *
1197
 * @return string
1198
 * @since 1.8.0
1199
 */
1200
function elgg_view_module($type, $title, $body, array $vars = []) {
1201 4
	$vars['type'] = $type;
1202 4
	$vars['title'] = $title;
1203 4
	$vars['body'] = $body;
1204 4
	return elgg_view('page/components/module', $vars);
1205
}
1206
1207
/**
1208
 * Wrapper function for the message display pattern.
1209
 *
1210
 * Box with header, body
1211
 *
1212
 * This is a shortcut for {@elgg_view page/components/message}.
1213
 *
1214
 * @param string $type The type of message (error, success, warning, help, notice)
1215
 * @param string $body Content of the message
1216
 * @param array  $vars Additional parameters for the message
1217
 *
1218
 * @return string
1219
 * @since 3.0.0
1220
 */
1221
function elgg_view_message($type, $body, array $vars = []) {
1222 9
	$vars['type'] = $type;
1223 9
	$vars['body'] = $body;
1224 9
	return elgg_view('page/components/message', $vars);
1225
}
1226
1227
/**
1228
 * Renders a human-readable representation of a river item
1229
 *
1230
 * @param \ElggRiverItem $item A river item object
1231
 * @param array          $vars An array of variables for the view
1232
 *      'item_view'  Alternative view to render the item
1233
 * @return string returns empty string if could not be rendered
1234
 */
1235
function elgg_view_river_item($item, array $vars = []) {
1236
	
1237
	if (!($item instanceof \ElggRiverItem)) {
1238
		return '';
1239
	}
1240
	
1241
	// checking default viewtype since some viewtypes do not have unique views per item (rss)
1242
	$view = $item->getView();
1243
	
1244
	$subject = $item->getSubjectEntity();
1245
	$object = $item->getObjectEntity();
1246
	if (!$subject || !$object) {
1247
		// subject is disabled or subject/object deleted
1248
		return '';
1249
	}
1250
	
1251
	$vars['item'] = $item;
1252
1253
	// create river view logic
1254
	$type = $object->getType();
1255
	$subtype = $object->getSubtype();
1256
	$action = $item->action_type;
1257
	
1258
	$river_views = [
1259
		elgg_extract('item_view', $vars, ''),
1260
		'river/item', // important for other viewtypes, e.g. "rss"
1261
		$view,
1262
		"river/{$type}/{$subtype}/{$action}",
1263
		"river/{$type}/{$subtype}/default",
1264
		"river/{$type}/{$action}",
1265
		"river/{$type}/default",
1266
		'river/elements/layout',
1267
	];
1268
1269
	$contents = '';
1270
	foreach ($river_views as $view) {
1271
		if (elgg_view_exists($view)) {
1272
			$contents = elgg_view($view, $vars);
1273
			break;
1274
		}
1275
	}
1276
1277
	return $contents;
1278
}
1279
1280
/**
1281
 * Convenience function for generating a form from a view in a standard location.
1282
 *
1283
 * This function assumes that the body of the form is located at "forms/$action" and
1284
 * sets the action by default to "action/$action".  Automatically wraps the forms/$action
1285
 * view with a <form> tag and inserts the anti-csrf security tokens.
1286
 *
1287
 * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any
1288
 * slashes with dashes (blog/save becomes elgg-form-blog-save)
1289
 *
1290
 * @example
1291
 * <code>echo elgg_view_form('login');</code>
1292
 *
1293
 * This would assume a "login" form body to be at "forms/login" and would set the action
1294
 * of the form to "http://yoursite.com/action/login".
1295
 *
1296
 * If elgg_view('forms/login') is:
1297
 * <input type="text" name="username" />
1298
 * <input type="password" name="password" />
1299
 *
1300
 * Then elgg_view_form('login') generates:
1301
 * <form action="http://yoursite.com/action/login" method="post">
1302
 *     ...security tokens...
1303
 *     <input type="text" name="username" />
1304
 *     <input type="password" name="password" />
1305
 * </form>
1306
 *
1307
 * @param string $action    The name of the action. An action name does not include
1308
 *                          the leading "action/". For example, "login" is an action name.
1309
 * @param array  $form_vars $vars environment passed to the "input/form" view
1310
 * @param array  $body_vars $vars environment passed to the "forms/$action" view
1311
 *
1312
 * @return string The complete form
1313
 */
1314
function elgg_view_form($action, $form_vars = [], $body_vars = []) {
1315 15
	return _elgg_services()->forms->render($action, $form_vars, $body_vars);
1316
}
1317
1318
/**
1319
 * Sets form footer and defers its rendering until the form view and extensions have been rendered.
1320
 * Deferring footer rendering allows plugins to extend the form view while maintaining
1321
 * logical DOM structure.
1322
 * Footer will be rendered using 'elements/forms/footer' view after form body has finished rendering
1323
 *
1324
 * @param string $footer Footer
1325
 * @return bool
1326
 */
1327
function elgg_set_form_footer($footer = '') {
1328 7
	return _elgg_services()->forms->setFooter($footer);
1329
}
1330
1331
/**
1332
 * Returns currently set footer, or false if not in the form rendering stack
1333
 * @return string|false
1334
 */
1335
function elgg_get_form_footer() {
1336
	return _elgg_services()->forms->getFooter();
1337
}
1338
1339
/**
1340
 * Renders a form field, usually with a wrapper element, a label, help text, etc.
1341
 *
1342
 * @param array $params Field parameters and variables for the input view.
1343
 *                      Keys not prefixed with hash (#) are passed to the input view as $vars.
1344
 *                      Keys prefixed with a hash specify the field wrapper (.elgg-view-field) output.
1345
 *                       - #type: specifies input view. E.g. "text" uses the view "input/text".
1346
 *                       - #label: field label HTML
1347
 *                       - #help: field help HTML
1348
 *                       - #class: field class name
1349
 *                      Note: Both #label and #help are printed unescaped within their wrapper element.
1350
 *                      Note: Some fields (like input/checkbox) need special attention because #label and label serve different purposes
1351
 *                      "#label" will be used as a label in the field wrapper but "label" will be used in the input view
1352
 *
1353
 * @return string
1354
 * @since 2.3
1355
 */
1356
function elgg_view_field(array $params = []) {
1357
1358 11
	if (empty($params['#type'])) {
1359
		_elgg_services()->logger->error(__FUNCTION__ . '(): $params["#type"] is required.');
1360
		return '';
1361
	}
1362
1363 11
	$input_type = $params['#type'];
1364 11
	if (!elgg_view_exists("input/$input_type")) {
1365 10
		return '';
1366
	}
1367
1368 1
	$hidden_types = ['hidden', 'securitytoken'];
1369 1
	if (in_array($input_type, $hidden_types)) {
1370 1
		unset($params['#type']);
1371 1
		unset($params['#label']);
1372 1
		unset($params['#help']);
1373 1
		unset($params['#class']);
1374 1
		return elgg_view("input/$input_type", $params);
1375
	}
1376
1377 1
	$id = elgg_extract('id', $params);
1378 1
	if (!$id) {
1379 1
		$id = "elgg-field-" . base_convert(mt_rand(), 10, 36);
1380 1
		$params['id'] = $id;
1381
	}
1382
1383
	// $vars passed to label, help and field wrapper views
1384 1
	$element_vars = [];
1385
1386
	// $vars passed to input/$input_name
1387 1
	$input_vars = [];
1388
	
1389 1
	$make_special_checkbox_label = false;
1390 1
	if ($input_type == 'checkbox' && (isset($params['label']) || isset($params['#label']))) {
1391
		if (isset($params['#label']) && isset($params['label'])) {
1392
			$params['label_tag'] = 'div';
1393
		} else {
1394
			$label = elgg_extract('label', $params);
1395
			$label = elgg_extract('#label', $params, $label);
1396
			
1397
			$params['#label'] = $label;
1398
			unset($params['label']);
1399
1400
			// Single checkbox input view gets special treatment
1401
			// We don't want the field label to appear a checkbox without a label
1402
			$make_special_checkbox_label = true;
1403
		}
1404
	}
1405
1406
	// first pass non-hash keys into both
1407 1
	foreach ($params as $key => $value) {
1408 1
		if ($key[0] !== '#') {
1409 1
			$element_vars[$key] = $value;
1410 1
			$input_vars[$key] = $value;
1411
		}
1412
	}
1413
1414
	// field input view needs this
1415 1
	$input_vars['input_type'] = $input_type;
1416
1417
	// field views get more data
1418 1
	$element_vars['input_type'] = $input_type;
1419
	
1420 1
	unset($element_vars['class']);
1421 1
	if (isset($params['#class'])) {
1422
		$element_vars['class'] = $params['#class'];
1423
	}
1424 1
	unset($element_vars['help']);
1425 1
	if (isset($params['#help'])) {
1426
		$element_vars['help'] = $params['#help'];
1427
	}
1428 1
	unset($element_vars['label']);
1429 1
	if (isset($params['#label'])) {
1430 1
		$element_vars['label'] = $params['#label'];
1431
	}
1432
	
1433
	// wrap if present
1434 1
	$element_vars['label'] = elgg_view('elements/forms/label', $element_vars);
1435 1
	$element_vars['help'] = elgg_view('elements/forms/help', $element_vars);
1436
1437 1
	if ($make_special_checkbox_label) {
1438
		$input_vars['label'] = $element_vars['label'];
1439
		$input_vars['label_tag'] = 'div';
1440
		unset($element_vars['label']);
1441
	}
1442 1
	$element_vars['input'] = elgg_view("elements/forms/input", $input_vars);
1443
1444 1
	return elgg_view('elements/forms/field', $element_vars);
1445
}
1446
1447
/**
1448
 * Create a tagcloud for viewing
1449
 *
1450
 *
1451
 * @param array $options Any elgg_get_tags() options except:
1452
 *
1453
 * 	type => must be single entity type
1454
 *
1455
 * 	subtype => must be single entity subtype
1456
 *
1457
 * @return string
1458
 *
1459
 * @see elgg_get_tags()
1460
 * @since 1.7.1
1461
 */
1462
function elgg_view_tagcloud(array $options = []) {
1463
1464
	$type = $subtype = '';
1465
	if (isset($options['type'])) {
1466
		$type = $options['type'];
1467
	}
1468
	if (isset($options['subtype'])) {
1469
		$subtype = $options['subtype'];
1470
	}
1471
1472
	$tag_data = elgg_get_tags($options);
1473
	return elgg_view("output/tagcloud", [
1474
		'value' => $tag_data,
1475
		'type' => $type,
1476
		'subtype' => $subtype,
1477
	]);
1478
}
1479
1480
/**
1481
 * View an item in a list
1482
 *
1483
 * @param mixed $item Entity, annotation, river item, or other data
1484
 * @param array $vars Additional parameters for the rendering
1485
 *                    'item_view' - Alternative view used to render list items
1486
 *                                  This parameter is required if rendering
1487
 *                                  list items that are not entity, annotation or river
1488
 * @return string
1489
 * @since 1.8.0
1490
 * @access private
1491
 */
1492
function elgg_view_list_item($item, array $vars = []) {
1493
1494 6
	if ($item instanceof \ElggEntity) {
1495 6
		return elgg_view_entity($item, $vars);
1496
	} else if ($item instanceof \ElggAnnotation) {
1497
		return elgg_view_annotation($item, $vars);
1498
	} else if ($item instanceof \ElggRiverItem) {
1499
		return elgg_view_river_item($item, $vars);
1500
	}
1501
1502
	$view = elgg_extract('item_view', $vars);
1503
	if ($view && elgg_view_exists($view)) {
1504
		$vars['item'] = $item;
1505
		return elgg_view($view, $vars);
1506
	}
1507
1508
	return '';
1509
}
1510
1511
/**
1512
 * View one of the icons
1513
 *
1514
 * Shorthand for <span class="elgg-icon elgg-icon-$name"></span>
1515
 *
1516
 * @param string $name The specific icon to display
1517
 * @param mixed  $vars The additional classname as a string ('float', 'float-alt' or a custom class)
1518
 *                     or an array of variables (array('class' => 'float')) to pass to the icon view.
1519
 *
1520
 * @return string The html for displaying an icon
1521
 * @throws InvalidArgumentException
1522
 */
1523
function elgg_view_icon($name, $vars = []) {
1524 7
	if (empty($vars)) {
1525 4
		$vars = [];
1526
	}
1527
1528 7
	if (is_string($vars)) {
1529
		$vars = ['class' => $vars];
1530
	}
1531
1532 7
	if (!is_array($vars)) {
1533
		throw new \InvalidArgumentException('$vars needs to be a string or an array');
1534
	}
1535
1536 7
	$vars['class'] = elgg_extract_class($vars, "elgg-icon-$name");
1537
1538 7
	return elgg_view("output/icon", $vars);
1539
}
1540
1541
/**
1542
 * Include the RSS icon link and link element in the head
1543
 *
1544
 * @return void
1545
 */
1546
function elgg_register_rss_link() {
1547 14
	_elgg_config()->_elgg_autofeed = true;
1548 14
}
1549
1550
/**
1551
 * Remove the RSS icon link and link element from the head
1552
 *
1553
 * @return void
1554
 */
1555
function elgg_unregister_rss_link() {
1556
	_elgg_config()->_elgg_autofeed = false;
1557
}
1558
1559
/**
1560
 * Should the RSS view of this URL be linked to?
1561
 *
1562
 * @return bool
1563
 * @access private
1564
 */
1565
function _elgg_has_rss_link() {
1566 12
	if (!_elgg_config()->disable_rss) {
1567 12
		return false;
1568
	}
1569
1570
	if (isset($GLOBALS['autofeed']) && is_bool($GLOBALS['autofeed'])) {
1571
		elgg_deprecated_notice('Do not set the global $autofeed. Use elgg_register_rss_link()', '2.1');
1572
		return $GLOBALS['autofeed'];
1573
	}
1574
	return (bool) _elgg_config()->_elgg_autofeed;
1575
}
1576
1577
/**
1578
 * Auto-registers views from a location.
1579
 *
1580
 * @note Views in plugin/views/ are automatically registered for active plugins.
1581
 * Plugin authors would only need to call this if optionally including
1582
 * an entire views structure.
1583
 *
1584
 * @param string $view_base Optional The base of the view name without the view type.
1585
 * @param string $folder    Required The folder to begin looking in
1586
 * @param string $ignored   This argument is ignored
1587
 * @param string $viewtype  The type of view we're looking at (default, rss, etc)
1588
 *
1589
 * @return bool returns false if folder can't be read
1590
 * @since 1.7.0
1591
 * @see elgg_set_view_location()
1592
 * @access private
1593
 */
1594
function autoregister_views($view_base, $folder, $ignored, $viewtype) {
1595
	return _elgg_services()->views->autoregisterViews($view_base, $folder, $viewtype);
1596
}
1597
1598
/**
1599
 * Minifies simplecache CSS and JS views by handling the "simplecache:generate" hook
1600
 *
1601
 * @param string $hook    The name of the hook
1602
 * @param string $type    View type (css, js, or unknown)
1603
 * @param string $content Content of the view
1604
 * @param array  $params  Array of parameters
1605
 *
1606
 * @return string|null View content minified (if css/js type)
1607
 * @access private
1608
 */
1609
function _elgg_views_minify($hook, $type, $content, $params) {
1610
	if (preg_match('~[\.-]min\.~', $params['view'])) {
1611
		// bypass minification
1612
		return;
1613
	}
1614
1615
	if ($type == 'js') {
1616
		if (_elgg_config()->simplecache_minify_js) {
1617
			return JSMin::minify($content);
1618
		}
1619
	} elseif ($type == 'css') {
1620
		if (_elgg_config()->simplecache_minify_css) {
1621
			$cssmin = new CSSmin();
1622
			return $cssmin->run($content);
1623
		}
1624
	}
1625
}
1626
1627
/**
1628
 * Preprocesses CSS views sent by /cache URLs
1629
 *
1630
 * @param string $hook    The name of the hook "simplecache:generate" or "cache:generate"
1631
 * @param string $type    "css"
1632
 * @param string $content Content of the view
1633
 * @param array  $params  Array of parameters
1634
 *
1635
 * @return string|null View content
1636
 * @access private
1637
 */
1638
function _elgg_views_preprocess_css($hook, $type, $content, $params) {
1639 5
	$options = elgg_extract('compiler_options', $params, []);
1640 5
	return _elgg_services()->cssCompiler->compile($content, $options);
1641
}
1642
1643
/**
1644
 * Inserts module names into anonymous modules by handling the "simplecache:generate" hook.
1645
 *
1646
 * @param string $hook    The name of the hook
1647
 * @param string $type    View type (css, js, or unknown)
1648
 * @param string $content Content of the view
1649
 * @param array  $params  Array of parameters
1650
 *
1651
 * @return string|null View content minified (if css/js type)
1652
 * @access private
1653
 */
1654
function _elgg_views_amd($hook, $type, $content, $params) {
1655
	$filter = new \Elgg\Amd\ViewFilter();
1656
	return $filter->filter($params['view'], $content);
1657
}
1658
1659
/**
1660
 * Sends X-Frame-Options header on page requests
1661
 *
1662
 * @return void
1663
 *
1664
 * @access private
1665
 */
1666
function _elgg_views_send_header_x_frame_options() {
1667 2
	elgg_set_http_header('X-Frame-Options: SAMEORIGIN');
1668 2
}
1669
1670
/**
1671
 * Is there a chance a plugin is altering this view?
1672
 *
1673
 * @note Must be called after the [init, system] event, ideally as late as possible.
1674
 *
1675
 * @note Always returns true if the view's location is set in /engine/views.php. Elgg does not keep
1676
 *       track of the defaults for those locations.
1677
 *
1678
 * <code>
1679
 * // check a view in core
1680
 * if (_elgg_view_may_be_altered('foo/bar', 'foo/bar.php')) {
1681
 *     // use the view for BC
1682
 * }
1683
 *
1684
 * // check a view in a bundled plugin
1685
 * $dir = __DIR__ . "/views/" . elgg_get_viewtype();
1686
 * if (_elgg_view_may_be_altered('foo.css', "$dir/foo.css.php")) {
1687
 *     // use the view for BC
1688
 * }
1689
 * </code>
1690
 *
1691
 * @param string $view View name. E.g. "elgg/init.js"
1692
 * @param string $path Absolute file path, or path relative to the viewtype directory. E.g. "elgg/init.js.php"
1693
 *
1694
 * @return bool
1695
 * @access private
1696
 */
1697
function _elgg_view_may_be_altered($view, $path) {
1698
	$views = _elgg_services()->views;
1699
1700
	if ($views->viewIsExtended($view) || $views->viewHasHookHandlers($view)) {
1701
		return true;
1702
	}
1703
1704
	$viewtype = elgg_get_viewtype();
1705
1706
	// check location
1707
	if (0 === strpos($path, '/') || preg_match('~^([A-Za-z]\:)?\\\\~', $path)) {
1708
		// absolute path
1709
		$expected_path = $path;
1710
	} else {
1711
		// relative path
1712
		$expected_path = Paths::elgg() . "views/$viewtype/" . ltrim($path, '/\\');
1713
	}
1714
1715
	$view_path = $views->findViewFile($view, $viewtype);
1716
	
1717
	return realpath($view_path) !== realpath($expected_path);
1718
}
1719
1720
/**
1721
 * Initialize viewtypes on system boot event
1722
 * This ensures simplecache is cleared during upgrades. See #2252
1723
 *
1724
 * @return void
1725
 * @access private
1726
 * @elgg_event_handler boot system
1727
 */
1728
function elgg_views_boot() {
1729 18
	_elgg_services()->viewCacher->registerCoreViews();
1730
1731
	// jQuery and UI must come before require. See #9024
1732 18
	elgg_register_js('jquery', elgg_get_simplecache_url('jquery.js'), 'head');
1733 18
	elgg_load_js('jquery');
1734
1735 18
	elgg_register_js('jquery-ui', elgg_get_simplecache_url('jquery-ui.js'), 'head');
1736 18
	elgg_load_js('jquery-ui');
1737
1738 18
	elgg_register_js('elgg.require_config', elgg_get_simplecache_url('elgg/require_config.js'), 'head');
1739 18
	elgg_load_js('elgg.require_config');
1740
1741 18
	elgg_register_js('require', elgg_get_simplecache_url('require.js'), 'head');
1742 18
	elgg_load_js('require');
1743
1744 18
	elgg_register_js('elgg', elgg_get_simplecache_url('elgg.js'), 'head');
1745 18
	elgg_load_js('elgg');
1746
	
1747 18
	elgg_register_css('font-awesome', elgg_get_simplecache_url('font-awesome/css/font-awesome.css'));
1748 18
	elgg_load_css('font-awesome');
1749
1750 18
	elgg_register_css('elgg', elgg_get_simplecache_url('elgg.css'));
1751 18
	elgg_load_css('elgg');
1752
1753 18
	elgg_register_simplecache_view('elgg/init.js');
1754
1755 18
	elgg_extend_view('elgg.css', 'lightbox/elgg-colorbox-theme/colorbox.css');
1756
1757 18
	elgg_define_js('jquery.ui.autocomplete.html', [
1758 18
		'deps' => ['jquery-ui'],
1759
	]);
1760
1761 18
	elgg_register_js('elgg.avatar_cropper', elgg_get_simplecache_url('elgg/ui.avatar_cropper.js'));
1762
1763
	// @deprecated 2.2
1764 18
	elgg_register_js('elgg.ui.river', elgg_get_simplecache_url('elgg/ui.river.js'));
1765
1766 18
	elgg_register_js('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.js'));
1767 18
	elgg_register_css('jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.css'));
1768
1769 18
	elgg_register_css('jquery.treeview', elgg_get_simplecache_url('jquery-treeview/jquery.treeview.css'));
1770 18
	elgg_define_js('jquery.treeview', [
1771 18
		'src' => elgg_get_simplecache_url('jquery-treeview/jquery.treeview.js'),
1772 18
		'exports' => 'jQuery.fn.treeview',
1773
		'deps' => ['jquery'],
1774
	]);
1775
1776 18
	elgg_register_ajax_view('languages.js');
1777
1778
	// pre-process CSS regardless of simplecache
1779 18
	elgg_register_plugin_hook_handler('cache:generate', 'css', '_elgg_views_preprocess_css');
1780 18
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_preprocess_css');
1781
1782 18
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_amd');
1783 18
	elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_minify');
1784 18
	elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_minify');
1785
1786 18
	elgg_register_plugin_hook_handler('output:before', 'page', '_elgg_views_send_header_x_frame_options');
1787
	
1788 18
	elgg_register_plugin_hook_handler('view_vars', 'elements/forms/help', '_elgg_views_file_help_upload_limit');
1789
1790
	// registered with high priority for BC
1791
	// prior to 2.2 registration used to take place in _elgg_views_prepare_head() before the hook was triggered
1792 18
	elgg_register_plugin_hook_handler('head', 'page', '_elgg_views_prepare_favicon_links', 1);
1793
1794
	// set default icon sizes - can be overridden with plugin
1795 18
	if (!_elgg_config()->icon_sizes) {
1796
		$icon_sizes = [
1797 5
			'topbar' => ['w' => 16, 'h' => 16, 'square' => true, 'upscale' => true],
1798
			'tiny' => ['w' => 25, 'h' => 25, 'square' => true, 'upscale' => true],
1799
			'small' => ['w' => 40, 'h' => 40, 'square' => true, 'upscale' => true],
1800
			'medium' => ['w' => 100, 'h' => 100, 'square' => true, 'upscale' => true],
1801
			'large' => ['w' => 200, 'h' => 200, 'square' => true, 'upscale' => true],
1802
			'master' => ['w' => 2048, 'h' => 2048, 'square' => false, 'upscale' => false],
1803
		];
1804 5
		elgg_set_config('icon_sizes', $icon_sizes);
1805
	}
1806
1807
	// Configure lightbox
1808 18
	elgg_register_plugin_hook_handler('elgg.data', 'site', '_elgg_set_lightbox_config');
1809 18
}
1810
1811
/**
1812
 * Get the site data to be merged into "elgg" in elgg.js.
1813
 *
1814
 * Unlike _elgg_get_js_page_data(), the keys returned are literal expressions.
1815
 *
1816
 * @return array
1817
 * @access private
1818
 */
1819
function _elgg_get_js_site_data() {
1820
	$language = _elgg_config()->language;
1821
	if (!$language) {
1822
		$language = 'en';
1823
	}
1824
1825
	return [
1826
		'elgg.data' => (object) elgg_trigger_plugin_hook('elgg.data', 'site', null, []),
1827
		'elgg.version' => elgg_get_version(),
1828
		'elgg.release' => elgg_get_version(true),
1829
		'elgg.config.wwwroot' => elgg_get_site_url(),
1830
1831
		// refresh token 3 times during its lifetime (in microseconds 1000 * 1/3)
1832
		'elgg.security.interval' => (int) _elgg_services()->actions->getActionTokenTimeout() * 333,
1833
		'elgg.config.language' => $language,
1834
	];
1835
}
1836
1837
/**
1838
 * Get the initial contents of "elgg" client side. Will be extended by elgg.js.
1839
 *
1840
 * @return array
1841
 * @access private
1842
 */
1843
function _elgg_get_js_page_data() {
1844
	$data = elgg_trigger_plugin_hook('elgg.data', 'page', null, []);
1845
	if (!is_array($data)) {
1846
		elgg_log('"elgg.data" plugin hook handlers must return an array. Returned ' . gettype($data) . '.', 'ERROR');
1847
		$data = [];
1848
	}
1849
1850
	$elgg = [
1851
		'config' => [
1852
			'lastcache' => (int) _elgg_config()->lastcache,
1853
			'viewtype' => elgg_get_viewtype(),
1854
			'simplecache_enabled' => (int) elgg_is_simplecache_enabled(),
1855
			'current_language' => get_current_language(),
1856
		],
1857
		'security' => [
1858
			'token' => [
1859
				'__elgg_ts' => $ts = time(),
1860
				'__elgg_token' => generate_action_token($ts),
1861
			],
1862
		],
1863
		'session' => [
1864
			'user' => null,
1865
			'token' => _elgg_services()->session->get('__elgg_session'),
1866
		],
1867
		'_data' => (object) $data,
1868
	];
1869
1870
	if (_elgg_config()->elgg_load_sync_code) {
1871
		$elgg['config']['load_sync_code'] = true;
1872
	}
1873
1874
	$page_owner = elgg_get_page_owner_entity();
1875
	if ($page_owner instanceof ElggEntity) {
1876
		$elgg['page_owner'] = $page_owner->toObject();
1877
	}
1878
1879
	$user = elgg_get_logged_in_user_entity();
1880
	if ($user instanceof ElggUser) {
1881
		$user_object = $user->toObject();
1882
		$user_object->admin = $user->isAdmin();
1883
		$elgg['session']['user'] = $user_object;
1884
	}
1885
1886
	return $elgg;
1887
}
1888
1889
/**
1890
 * Render a view while the global viewtype is temporarily changed. This makes sure that
1891
 * nested views use the same viewtype.
1892
 *
1893
 * @param string $view     View name
1894
 * @param array  $vars     View vars
1895
 * @param string $viewtype Temporary viewtype ('' to leave current)
1896
 *
1897
 * @return mixed
1898
 * @access private
1899
 */
1900
function _elgg_view_under_viewtype($view, $vars, $viewtype) {
1901 20
	if ($viewtype) {
1902 13
		$old = elgg_get_viewtype();
1903 13
		elgg_set_viewtype($viewtype);
1904
	}
1905
1906 20
	$ret = elgg_view($view, $vars);
1907
1908 20
	if ($viewtype) {
1909 13
		elgg_set_viewtype($old);
1910
	}
1911
1912 20
	return $ret;
1913
}
1914
1915
/**
1916
 * Set lightbox config
1917
 *
1918
 * @param string $hook   "elgg.data"
1919
 * @param string $type   "site"
1920
 * @param array  $return Data
1921
 * @param array  $params Hook params
1922
 * @return array
1923
 * @access private
1924
 */
1925
function _elgg_set_lightbox_config($hook, $type, $return, $params) {
1926
1927
	$return['lightbox'] = [
1928
		'current' => elgg_echo('js:lightbox:current', ['{current}', '{total}']),
1929
		'previous' => elgg_view_icon('caret-left'),
1930
		'next' => elgg_view_icon('caret-right'),
1931
		'close' => elgg_view_icon('times'),
1932
		'opacity' => 0.5,
1933
		'maxWidth' => '990px',
1934
		'maxHeight' => '990px',
1935
		'initialWidth' => '300px',
1936
		'initialHeight' => '300px',
1937
	];
1938
1939
	return $return;
1940
}
1941
1942
/**
1943
 * Add a help text to input/file about upload limit
1944
 *
1945
 * In order to not show the help text supply 'show_upload_limit' => false to elgg_view_field()
1946
 *
1947
 * @param \Elgg\Hook $hook 'view_vars' 'elements/forms/help'
1948
 *
1949
 * @return void|array
1950
 * @access private
1951
 */
1952
function _elgg_views_file_help_upload_limit(\Elgg\Hook $hook) {
1953
	
1954
	$return = $hook->getValue();
1955
	if (elgg_extract('input_type', $return) !== 'file') {
1956
		return;
1957
	}
1958
	
1959
	if (!elgg_extract('show_upload_limit', $return, true)) {
1960
		return;
1961
	}
1962
	
1963
	$help = elgg_extract('help', $return, '');
1964
	
1965
	// Get post_max_size and upload_max_filesize
1966
	$post_max_size = elgg_get_ini_setting_in_bytes('post_max_size');
1967
	$upload_max_filesize = elgg_get_ini_setting_in_bytes('upload_max_filesize');
1968
	
1969
	// Determine the correct value
1970
	$max_upload = $upload_max_filesize > $post_max_size ? $post_max_size : $upload_max_filesize;
1971
	
1972
	$help .= ' ' . elgg_echo('input:file:upload_limit', [elgg_format_bytes($max_upload)]);
1973
	
1974
	$return['help'] = trim($help);
1975
	
1976
	return $return;
1977
}
1978