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

elgg_get_loaded_js()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 1
1
<?php
2
3
use Elgg\Http\ResponseBuilder;
4
5
/**
6
 * Bootstrapping and helper procedural code available for use in Elgg core and plugins.
7
 *
8
 * @package Elgg.Core
9
 * @todo These functions can't be subpackaged because they cover a wide mix of
10
 * purposes and subsystems.  Many of them should be moved to more relevant files.
11
 */
12
13
/**
14
 * Get a reference to the global Application object
15
 *
16
 * @return Elgg\Application
17
 * @since 2.0.0
18
 */
19
function elgg() {
20 5390
	if (!isset(Elgg\Application::$_instance)) {
21
		throw new \RuntimeException(__FUNCTION__ . ' should not be called before an instanceof ' . \Elgg\Application::class . ' is bootstrapped');
22
	}
23
24 5390
	return Elgg\Application::$_instance;
25
}
26
27
/**
28
 * Register a PHP file as a library.
29
 *
30
 * @see elgg_load_library()
31
 *
32
 * @param string $name     The name of the library
33
 * @param string $location The location of the file
34
 *
35
 * @return void
36
 * @since 1.8.0
37
 */
38
function elgg_register_library($name, $location) {
39 31
	$libraries = _elgg_config()->libraries;
40 31
	if ($libraries === null) {
41 18
		$libraries = [];
42
	}
43 31
	$libraries[$name] = $location;
44 31
	_elgg_config()->libraries = $libraries;
45 31
}
46
47
/**
48
 * Load a PHP library.
49
 *
50
 * @see elgg_register_library()
51
 *
52
 * @param string $name The name of the library
53
 *
54
 * @return void
55
 * @throws InvalidParameterException
56
 * @since 1.8.0
57
 */
58
function elgg_load_library($name) {
59 54
	static $loaded_libraries = [];
60
61 54
	if (in_array($name, $loaded_libraries)) {
62 54
		return;
63
	}
64
65 1
	$libraries = _elgg_config()->libraries;
66
67 1
	if (!isset($libraries[$name])) {
68
		$error = "$name is not a registered library";
69
		throw new \InvalidParameterException($error);
70
	}
71
72 1
	if (!include_once($libraries[$name])) {
73
		$error = "Could not load the $name library from {$libraries[$name]}";
74
		throw new \InvalidParameterException($error);
75
	}
76
77 1
	$loaded_libraries[] = $name;
78 1
}
79
80
/**
81
 * Forward to $location.
82
 *
83
 * Sends a 'Location: $location' header and exits.  If headers have already been sent, throws an exception.
84
 *
85
 * @param string $location URL to forward to browser to. This can be a path
86
 *                         relative to the network's URL.
87
 * @param string $reason   Short explanation for why we're forwarding. Set to
88
 *                         '404' to forward to error page. Default message is
89
 *                         'system'.
90
 *
91
 * @return void
92
 * @throws SecurityException|InvalidParameterException
93
 */
94
function forward($location = "", $reason = 'system') {
95
	if (headers_sent($file, $line)) {
96
		throw new \SecurityException("Redirect could not be issued due to headers already being sent. Halting execution for security. "
97
			. "Output started in file $file at line $line. Search http://learn.elgg.org/ for more information.");
98
	}
99
100
	_elgg_services()->responseFactory->redirect($location, $reason);
101
	exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
102
}
103
104
/**
105
 * Set a response HTTP header
106
 *
107
 * @see header()
108
 *
109
 * @param string $header  Header
110
 * @param bool   $replace Replace existing header
111
 * @return void
112
 * @since 2.3
113
 */
114
function elgg_set_http_header($header, $replace = true) {
115 27
	if (headers_sent($file, $line)) {
116 27
		_elgg_services()->logger->error("Cannot modify header information - headers already sent by
117 27
			(output started at $file:$line)");
118
	} else {
119
		header($header, $replace);
120
	}
121
122 27
	if (!preg_match('~^HTTP/\\d\\.\\d~', $header)) {
123 27
		list($name, $value) = explode(':', $header, 2);
124 27
		_elgg_services()->responseFactory->setHeader($name, ltrim($value), $replace);
125
	}
126 27
}
127
128
/**
129
 * Register a JavaScript file for inclusion
130
 *
131
 * This function handles adding JavaScript to a web page. If multiple
132
 * calls are made to register the same JavaScript file based on the $id
133
 * variable, only the last file is included. This allows a plugin to add
134
 * JavaScript from a view that may be called more than once. It also handles
135
 * more than one plugin adding the same JavaScript.
136
 *
137
 * jQuery plugins often have filenames such as jquery.rating.js. A best practice
138
 * is to base $name on the filename: "jquery.rating". It is recommended to not
139
 * use version numbers in the name.
140
 *
141
 * The JavaScript files can be local to the server or remote (such as
142
 * Google's CDN).
143
 *
144
 * @note Since 2.0, scripts with location "head" will also be output in the footer, but before
145
 *       those with location "footer".
146
 *
147
 * @param string $name     An identifier for the JavaScript library
148
 * @param string $url      URL of the JavaScript file
149
 * @param string $location Page location: head or footer. (default: head)
150
 * @param int    $priority Priority of the JS file (lower numbers load earlier)
151
 *
152
 * @return bool
153
 * @since 1.8.0
154
 */
155
function elgg_register_js($name, $url, $location = 'head', $priority = null) {
156 35
	return elgg_register_external_file('js', $name, $url, $location, $priority);
157
}
158
159
/**
160
 * Defines a JS lib as an AMD module. This is useful for shimming
161
 * traditional JS or for setting the paths of AMD modules.
162
 *
163
 * Calling multiple times for the same name will:
164
 *     * set the preferred path to the last call setting a path
165
 *     * overwrite the shimmed AMD modules with the last call setting a shimmed module
166
 *
167
 * Use elgg_require_js($name) to load on the current page.
168
 *
169
 * Calling this function is not needed if your JS are in views named like `module/name.js`
170
 * Instead, simply call elgg_require_js("module/name").
171
 *
172
 * @note The configuration is cached in simplecache, so logic should not depend on user-
173
 *       specific values like get_language().
174
 *
175
 * @param string $name   The module name
176
 * @param array  $config An array like the following:
177
 *                       array  'deps'    An array of AMD module dependencies
178
 *                       string 'exports' The name of the exported module
179
 *                       string 'src'     The URL to the JS. Can be relative.
180
 *
181
 * @return void
182
 */
183
function elgg_define_js($name, $config) {
184 31
	$src = elgg_extract('src', $config);
185
186 31
	if ($src) {
187 18
		$url = elgg_normalize_url($src);
188 18
		_elgg_services()->amdConfig->addPath($name, $url);
189
	}
190
191
	// shimmed module
192 31
	if (isset($config['deps']) || isset($config['exports'])) {
193 31
		_elgg_services()->amdConfig->addShim($name, $config);
194
	}
195 31
}
196
197
/**
198
 * Unregister a JavaScript file
199
 *
200
 * @param string $name The identifier for the JavaScript library
201
 *
202
 * @return bool
203
 * @since 1.8.0
204
 */
205
function elgg_unregister_js($name) {
206 1
	return elgg_unregister_external_file('js', $name);
207
}
208
209
/**
210
 * Load a JavaScript resource on this page
211
 *
212
 * This must be called before elgg_view_page(). It can be called before the
213
 * script is registered. If you do not want a script loaded, unregister it.
214
 *
215
 * @param string $name Identifier of the JavaScript resource
216
 *
217
 * @return void
218
 * @since 1.8.0
219
 */
220
function elgg_load_js($name) {
221 21
	elgg_load_external_file('js', $name);
222 21
}
223
224
225
/**
226
 * Request that Elgg load an AMD module onto the page.
227
 *
228
 * @param string $name The AMD module name.
229
 * @return void
230
 * @since 1.9.0
231
 */
232
function elgg_require_js($name) {
233 4
	_elgg_services()->amdConfig->addDependency($name);
234 4
}
235
236
/**
237
 * Cancel a request to load an AMD module onto the page.
238
 *
239
 * @note The elgg, jquery, and jquery-ui modules cannot be cancelled.
240
 *
241
 * @param string $name The AMD module name.
242
 * @return void
243
 * @since 2.1.0
244
 */
245
function elgg_unrequire_js($name) {
246
	_elgg_services()->amdConfig->removeDependency($name);
247
}
248
249
/**
250
 * Get the JavaScript URLs that are loaded
251
 *
252
 * @param string $location 'head' or 'footer'
253
 *
254
 * @return array
255
 * @since 1.8.0
256
 */
257
function elgg_get_loaded_js($location = 'head') {
258 4
	return elgg_get_loaded_external_files('js', $location);
259
}
260
261
/**
262
 * Register a CSS file for inclusion in the HTML head
263
 *
264
 * @param string $name     An identifier for the CSS file
265
 * @param string $url      URL of the CSS file
266
 * @param int    $priority Priority of the CSS file (lower numbers load earlier)
267
 *
268
 * @return bool
269
 * @since 1.8.0
270
 */
271
function elgg_register_css($name, $url, $priority = null) {
272 32
	return elgg_register_external_file('css', $name, $url, 'head', $priority);
273
}
274
275
/**
276
 * Unregister a CSS file
277
 *
278
 * @param string $name The identifier for the CSS file
279
 *
280
 * @return bool
281
 * @since 1.8.0
282
 */
283
function elgg_unregister_css($name) {
284
	return elgg_unregister_external_file('css', $name);
285
}
286
287
/**
288
 * Load a CSS file for this page
289
 *
290
 * This must be called before elgg_view_page(). It can be called before the
291
 * CSS file is registered. If you do not want a CSS file loaded, unregister it.
292
 *
293
 * @param string $name Identifier of the CSS file
294
 *
295
 * @return void
296
 * @since 1.8.0
297
 */
298
function elgg_load_css($name) {
299 21
	elgg_load_external_file('css', $name);
300 21
}
301
302
/**
303
 * Get the loaded CSS URLs
304
 *
305
 * @return array
306
 * @since 1.8.0
307
 */
308
function elgg_get_loaded_css() {
309 1
	return elgg_get_loaded_external_files('css', 'head');
310
}
311
312
/**
313
 * Core registration function for external files
314
 *
315
 * @param string $type     Type of external resource (js or css)
316
 * @param string $name     Identifier used as key
317
 * @param string $url      URL
318
 * @param string $location Location in the page to include the file
319
 * @param int    $priority Loading priority of the file
320
 *
321
 * @return bool
322
 * @since 1.8.0
323
 */
324
function elgg_register_external_file($type, $name, $url, $location, $priority = 500) {
325 36
	return _elgg_services()->externalFiles->register($type, $name, $url, $location, $priority);
326
}
327
328
/**
329
 * Unregister an external file
330
 *
331
 * @param string $type Type of file: js or css
332
 * @param string $name The identifier of the file
333
 *
334
 * @return bool
335
 * @since 1.8.0
336
 */
337
function elgg_unregister_external_file($type, $name) {
338 1
	return _elgg_services()->externalFiles->unregister($type, $name);
339
}
340
341
/**
342
 * Load an external resource for use on this page
343
 *
344
 * @param string $type Type of file: js or css
345
 * @param string $name The identifier for the file
346
 *
347
 * @return void
348
 * @since 1.8.0
349
 */
350
function elgg_load_external_file($type, $name) {
351 23
	_elgg_services()->externalFiles->load($type, $name);
352 23
}
353
354
/**
355
 * Get external resource descriptors
356
 *
357
 * @param string $type     Type of file: js or css
358
 * @param string $location Page location
359
 *
360
 * @return array
361
 * @since 1.8.0
362
 */
363
function elgg_get_loaded_external_files($type, $location) {
364 5
	return _elgg_services()->externalFiles->getLoadedFiles($type, $location);
365
}
366
367
/**
368
 * Returns a list of files in $directory.
369
 *
370
 * Only returns files.  Does not recurse into subdirs.
371
 *
372
 * @param string $directory  Directory to look in
373
 * @param array  $exceptions Array of filenames to ignore
374
 * @param array  $list       Array of files to append to
375
 * @param mixed  $extensions Array of extensions to allow, null for all. Use a dot: array('.php').
376
 *
377
 * @return array Filenames in $directory, in the form $directory/filename.
378
 */
379
function elgg_get_file_list($directory, $exceptions = [], $list = [], $extensions = null) {
380
381
	$directory = \Elgg\Project\Paths::sanitize($directory);
382
	if ($handle = opendir($directory)) {
383
		while (($file = readdir($handle)) !== false) {
384
			if (!is_file($directory . $file) || in_array($file, $exceptions)) {
385
				continue;
386
			}
387
388
			if (is_array($extensions)) {
389
				if (in_array(strrchr($file, '.'), $extensions)) {
390
					$list[] = $directory . $file;
391
				}
392
			} else {
393
				$list[] = $directory . $file;
394
			}
395
		}
396
		closedir($handle);
397
	}
398
399
	return $list;
400
}
401
402
/**
403
 * Counts the number of messages, either globally or in a particular register
404
 *
405
 * @param string $register Optionally, the register
406
 *
407
 * @return integer The number of messages
408
 */
409
function count_messages($register = "") {
410
	return _elgg_services()->systemMessages->count($register);
411
}
412
413
/**
414
 * Display a system message on next page load.
415
 *
416
 * @param string|array $message Message or messages to add
417
 *
418
 * @return bool
419
 */
420
function system_message($message) {
421 16
	_elgg_services()->systemMessages->addSuccessMessage($message);
422 16
	return true;
423
}
424
425
/**
426
 * Display an error on next page load.
427
 *
428
 * @param string|array $error Error or errors to add
429
 *
430
 * @return bool
431
 */
432
function register_error($error) {
433 39
	_elgg_services()->systemMessages->addErrorMessage($error);
434 39
	return true;
435
}
436
437
/**
438
 * Get a copy of the current system messages.
439
 *
440
 * @return \Elgg\SystemMessages\RegisterSet
441
 * @since 2.1
442
 */
443
function elgg_get_system_messages() {
444
	return _elgg_services()->systemMessages->loadRegisters();
445
}
446
447
/**
448
 * Set the system messages. This will overwrite the state of all messages and errors!
449
 *
450
 * @param \Elgg\SystemMessages\RegisterSet $set Set of messages
451
 * @return void
452
 * @since 2.1
453
 */
454
function elgg_set_system_messages(\Elgg\SystemMessages\RegisterSet $set) {
455
	_elgg_services()->systemMessages->saveRegisters($set);
456
}
457
458
/**
459
 * Register a callback as an Elgg event handler.
460
 *
461
 * Events are emitted by Elgg when certain actions occur.  Plugins
462
 * can respond to these events or halt them completely by registering a handler
463
 * as a callback to an event.  Multiple handlers can be registered for
464
 * the same event and will be executed in order of $priority.
465
 *
466
 * For most events, any handler returning false will halt the execution chain and
467
 * cause the event to be "cancelled". For After Events, the return values of the
468
 * handlers will be ignored and all handlers will be called.
469
 *
470
 * This function is called with the event name, event type, and handler callback name.
471
 * Setting the optional $priority allows plugin authors to specify when the
472
 * callback should be run.  Priorities for plugins should be 1-1000.
473
 *
474
 * The callback is passed 3 arguments when called: $event, $type, and optional $params.
475
 *
476
 * $event is the name of event being emitted.
477
 * $type is the type of event or object concerned.
478
 * $params is an optional parameter passed that can include a related object.  See
479
 * specific event documentation for details on which events pass what parameteres.
480
 *
481
 * @tip If a priority isn't specified it is determined by the order the handler was
482
 * registered relative to the event and type.  For plugins, this generally means
483
 * the earlier the plugin is in the load order, the earlier the priorities are for
484
 * any event handlers.
485
 *
486
 * @tip $event and $object_type can use the special keyword 'all'.  Handler callbacks registered
487
 * with $event = all will be called for all events of type $object_type.  Similarly,
488
 * callbacks registered with $object_type = all will be called for all events of type
489
 * $event, regardless of $object_type.  If $event and $object_type both are 'all', the
490
 * handler callback will be called for all events.
491
 *
492
 * @tip Event handler callbacks are considered in the follow order:
493
 *  - Specific registration where 'all' isn't used.
494
 *  - Registration where 'all' is used for $event only.
495
 *  - Registration where 'all' is used for $type only.
496
 *  - Registration where 'all' is used for both.
497
 *
498
 * @warning If you use the 'all' keyword, you must have logic in the handler callback to
499
 * test the passed parameters before taking an action.
500
 *
501
 * @tip When referring to events, the preferred syntax is "event, type".
502
 *
503
 * @param string $event       The event type
504
 * @param string $object_type The object type
505
 * @param string $callback    The handler callback
506
 * @param int    $priority    The priority - 0 is default, negative before, positive after
507
 *
508
 * @return bool
509
 * @example documentation/events/basic.php
510
 * @example documentation/events/advanced.php
511
 * @example documentation/events/all.php
512
 */
513
function elgg_register_event_handler($event, $object_type, $callback, $priority = 500) {
514 105
	return _elgg_services()->hooks->getEvents()->registerHandler($event, $object_type, $callback, $priority);
515
}
516
517
/**
518
 * Unregisters a callback for an event.
519
 *
520
 * @param string $event       The event type
521
 * @param string $object_type The object type
522
 * @param string $callback    The callback. Since 1.11, static method callbacks will match dynamic methods
523
 *
524
 * @return bool true if a handler was found and removed
525
 * @since 1.7
526
 */
527
function elgg_unregister_event_handler($event, $object_type, $callback) {
528 12
	return _elgg_services()->hooks->getEvents()->unregisterHandler($event, $object_type, $callback);
529
}
530
531
/**
532
 * Clears all callback registrations for a event.
533
 *
534
 * @param string $event       The name of the event
535
 * @param string $object_type The objecttype of the event
536
 *
537
 * @return void
538
 * @since 2.3
539
 */
540
function elgg_clear_event_handlers($event, $object_type) {
541
	_elgg_services()->hooks->getEvents()->clearHandlers($event, $object_type);
542
}
543
544
/**
545
 * Trigger an Elgg Event and attempt to run all handler callbacks registered to that
546
 * event, type.
547
 *
548
 * This function attempts to run all handlers registered to $event, $object_type or
549
 * the special keyword 'all' for either or both. If a handler returns false, the
550
 * event will be cancelled (no further handlers will be called, and this function
551
 * will return false).
552
 *
553
 * $event is usually a verb: create, update, delete, annotation.
554
 *
555
 * $object_type is usually a noun: object, group, user, annotation, relationship, metadata.
556
 *
557
 * $object is usually an Elgg* object associated with the event.
558
 *
559
 * @warning Elgg events should only be triggered by core.  Plugin authors should use
560
 * {@link trigger_elgg_plugin_hook()} instead.
561
 *
562
 * @tip When referring to events, the preferred syntax is "event, type".
563
 *
564
 * @note Internal: Only rarely should events be changed, added, or removed in core.
565
 * When making changes to events, be sure to first create a ticket on Github.
566
 *
567
 * @note Internal: @tip Think of $object_type as the primary namespace element, and
568
 * $event as the secondary namespace.
569
 *
570
 * @param string $event       The event type
571
 * @param string $object_type The object type
572
 * @param mixed  $object      The object involved in the event
573
 *
574
 * @return bool False if any handler returned false, otherwise true.
575
 * @example documentation/examples/events/trigger.php
576
 */
577
function elgg_trigger_event($event, $object_type, $object = null) {
578 434
	return _elgg_services()->hooks->getEvents()->trigger($event, $object_type, $object);
579
}
580
581
/**
582
 * Trigger a "Before event" indicating a process is about to begin.
583
 *
584
 * Like regular events, a handler returning false will cancel the process and false
585
 * will be returned.
586
 *
587
 * To register for a before event, append ":before" to the event name when registering.
588
 *
589
 * @param string $event       The event type. The fired event type will be appended with ":before".
590
 * @param string $object_type The object type
591
 * @param mixed  $object      The object involved in the event
592
 *
593
 * @return bool False if any handler returned false, otherwise true
594
 *
595
 * @see elgg_trigger_event()
596
 * @see elgg_trigger_after_event()
597
 */
598
function elgg_trigger_before_event($event, $object_type, $object = null) {
599 107
	return _elgg_services()->hooks->getEvents()->triggerBefore($event, $object_type, $object);
600
}
601
602
/**
603
 * Trigger an "After event" indicating a process has finished.
604
 *
605
 * Unlike regular events, all the handlers will be called, their return values ignored.
606
 *
607
 * To register for an after event, append ":after" to the event name when registering.
608
 *
609
 * @param string $event       The event type. The fired event type will be appended with ":after".
610
 * @param string $object_type The object type
611
 * @param string $object      The object involved in the event
612
 *
613
 * @return true
614
 *
615
 * @see elgg_trigger_before_event()
616
 */
617
function elgg_trigger_after_event($event, $object_type, $object = null) {
618 279
	return _elgg_services()->hooks->getEvents()->triggerAfter($event, $object_type, $object);
619
}
620
621
/**
622
 * Trigger an event normally, but send a notice about deprecated use if any handlers are registered.
623
 *
624
 * @param string $event       The event type
625
 * @param string $object_type The object type
626
 * @param string $object      The object involved in the event
627
 * @param string $message     The deprecation message
628
 * @param string $version     Human-readable *release* version: 1.9, 1.10, ...
629
 *
630
 * @return bool
631
 *
632
 * @see elgg_trigger_event()
633
 */
634
function elgg_trigger_deprecated_event($event, $object_type, $object = null, $message = null, $version = null) {
635
	return _elgg_services()->hooks->getEvents()->triggerDeprecated($event, $object_type, $object, $message, $version);
636
}
637
638
/**
639
 * Register a callback as a plugin hook handler.
640
 *
641
 * Plugin hooks allow developers to losely couple plugins and features by
642
 * responding to and emitting {@link elgg_trigger_plugin_hook()} customizable hooks.
643
 * Handler callbacks can respond to the hook, change the details of the hook, or
644
 * ignore it.
645
 *
646
 * Multiple handlers can be registered for a plugin hook, and each callback
647
 * is called in order of priority.  If the return value of a handler is not
648
 * null, that value is passed to the next callback in the call stack.  When all
649
 * callbacks have been run, the final value is passed back to the caller
650
 * via {@link elgg_trigger_plugin_hook()}.
651
 *
652
 * Similar to Elgg Events, plugin hook handler callbacks are registered by passing
653
 * a hook, a type, and a priority.
654
 *
655
 * The callback is passed 4 arguments when called: $hook, $type, $value, and $params.
656
 *
657
 *  - str $hook The name of the hook.
658
 *  - str $type The type of hook.
659
 *  - mixed $value The return value of the last handler or the default
660
 *  value if no other handlers have been called.
661
 *  - mixed $params An optional array of parameters.  Used to provide additional
662
 *  information to plugins.
663
 *
664
 * @tip Plugin hooks are similar to Elgg Events in that Elgg emits
665
 * a plugin hook when certain actions occur, but a plugin hook allows you to alter the
666
 * parameters, as well as halt execution.
667
 *
668
 * @tip If a priority isn't specified it is determined by the order the handler was
669
 * registered relative to the event and type.  For plugins, this generally means
670
 * the earlier the plugin is in the load order, the earlier the priorities are for
671
 * any event handlers.
672
 *
673
 * @tip Like Elgg Events, $hook and $type can use the special keyword 'all'.
674
 * Handler callbacks registered with $hook = all will be called for all hooks
675
 * of type $type.  Similarly, handlers registered with $type = all will be
676
 * called for all hooks of type $event, regardless of $object_type.  If $hook
677
 * and $type both are 'all', the handler will be called for all hooks.
678
 *
679
 * @tip Plugin hooks are sometimes used to gather lists from plugins.  This is
680
 * usually done by pushing elements into an array passed in $params.  Be sure
681
 * to append to and then return $value so you don't overwrite other plugin's
682
 * values.
683
 *
684
 * @warning Unlike Elgg Events, a handler that returns false will NOT halt the
685
 * execution chain.
686
 *
687
 * @param string   $hook     The name of the hook
688
 * @param string   $type     The type of the hook
689
 * @param callable $callback The name of a valid function or an array with object and method
690
 * @param int      $priority The priority - 500 is default, lower numbers called first
691
 *
692
 * @return bool
693
 *
694
 * @example hooks/register/basic.php Registering for a plugin hook and examining the variables.
695
 * @example hooks/register/advanced.php Registering for a plugin hook and changing the params.
696
 * @since 1.8.0
697
 */
698
function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = 500) {
699 327
	return _elgg_services()->hooks->registerHandler($hook, $type, $callback, $priority);
700
}
701
702
/**
703
 * Unregister a callback as a plugin hook.
704
 *
705
 * @param string   $hook        The name of the hook
706
 * @param string   $entity_type The name of the type of entity (eg "user", "object" etc)
707
 * @param callable $callback    The PHP callback to be removed. Since 1.11, static method
708
 *                              callbacks will match dynamic methods
709
 *
710
 * @return void
711
 * @since 1.8.0
712
 */
713
function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) {
714 238
	_elgg_services()->hooks->unregisterHandler($hook, $entity_type, $callback);
715 238
}
716
717
/**
718
 * Clears all callback registrations for a plugin hook.
719
 *
720
 * @param string $hook The name of the hook
721
 * @param string $type The type of the hook
722
 *
723
 * @return void
724
 * @since 2.0
725
 */
726
function elgg_clear_plugin_hook_handlers($hook, $type) {
727
	_elgg_services()->hooks->clearHandlers($hook, $type);
728
}
729
730
/**
731
 * Trigger a Plugin Hook and run all handler callbacks registered to that hook:type.
732
 *
733
 * This function runs all handlers registered to $hook, $type or
734
 * the special keyword 'all' for either or both.
735
 *
736
 * Use $params to send additional information to the handler callbacks.
737
 *
738
 * $returnvalue is the initial value to pass to the handlers, which can
739
 * change it by returning non-null values. It is useful to use $returnvalue
740
 * to set defaults. If no handlers are registered, $returnvalue is immediately
741
 * returned.
742
 *
743
 * Handlers that return null (or with no explicit return or return value) will
744
 * not change the value of $returnvalue.
745
 *
746
 * $hook is usually a verb: import, get_views, output.
747
 *
748
 * $type is usually a noun: user, ecml, page.
749
 *
750
 * @tip Like Elgg Events, $hook and $type can use the special keyword 'all'.
751
 * Handler callbacks registered with $hook = all will be called for all hooks
752
 * of type $type.  Similarly, handlers registered with $type = all will be
753
 * called for all hooks of type $event, regardless of $object_type.  If $hook
754
 * and $type both are 'all', the handler will be called for all hooks.
755
 *
756
 * @tip It's not possible for a plugin hook to change a non-null $returnvalue
757
 * to null.
758
 *
759
 * @note Internal: The checks for $hook and/or $type not being equal to 'all' is to
760
 * prevent a plugin hook being registered with an 'all' being called more than
761
 * once if the trigger occurs with an 'all'. An example in core of this is in
762
 * actions.php:
763
 * elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', ...)
764
 *
765
 * @see elgg_register_plugin_hook_handler()
766
 *
767
 * @param string $hook        The name of the hook to trigger ("all" will
768
 *                            trigger for all $types regardless of $hook value)
769
 * @param string $type        The type of the hook to trigger ("all" will
770
 *                            trigger for all $hooks regardless of $type value)
771
 * @param mixed  $params      Additional parameters to pass to the handlers
772
 * @param mixed  $returnvalue An initial return value
773
 *
774
 * @return mixed|null The return value of the last handler callback called
775
 *
776
 * @example hooks/trigger/basic.php    Trigger a hook that determines if execution
777
 *                                     should continue.
778
 * @example hooks/trigger/advanced.php Trigger a hook with a default value and use
779
 *                                     the results to populate a menu.
780
 * @example hooks/basic.php            Trigger and respond to a basic plugin hook.
781
 *
782
 * @since 1.8.0
783
 */
784
function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) {
785 1332
	return _elgg_services()->hooks->trigger($hook, $type, $params, $returnvalue);
786
}
787
788
/**
789
 * Returns an ordered array of hook handlers registered for $hook and $type.
790
 *
791
 * @param string $hook Hook name
792
 * @param string $type Hook type
793
 *
794
 * @return array
795
 *
796
 * @since 2.0.0
797
 */
798
function elgg_get_ordered_hook_handlers($hook, $type) {
799
	return _elgg_services()->hooks->getOrderedHandlers($hook, $type);
800
}
801
802
/**
803
 * Returns an ordered array of event handlers registered for $event and $type.
804
 *
805
 * @param string $event Event name
806
 * @param string $type  Object type
807
 *
808
 * @return array
809
 *
810
 * @since 2.0.0
811
 */
812
function elgg_get_ordered_event_handlers($event, $type) {
813
	return _elgg_services()->hooks->getEvents()->getOrderedHandlers($event, $type);
814
}
815
816
/**
817
 * Log a message.
818
 *
819
 * If $level is >= to the debug setting in {@link $CONFIG->debug}, the
820
 * message will be sent to {@link elgg_dump()}.  Messages with lower
821
 * priority than {@link $CONFIG->debug} are ignored.
822
 *
823
 * @note Use the developers plugin to display logs
824
 *
825
 * @param string $message User message
826
 * @param string $level   NOTICE | WARNING | ERROR
827
 *
828
 * @return bool
829
 * @since 1.7.0
830
 */
831
function elgg_log($message, $level = 'NOTICE') {
832 1081
	static $levels = [
833
		'INFO' => 200,
834
		'NOTICE' => 250,
835
		'WARNING' => 300,
836
		'ERROR' => 400,
837
	];
838
839 1081
	if (!isset($levels[$level])) {
840
		throw new \InvalidArgumentException("Invalid \$level value");
841
	}
842
843 1081
	$level = $levels[$level];
844 1081
	return _elgg_services()->logger->log($message, $level);
845
}
846
847
/**
848
 * Logs $value to PHP's {@link error_log()}
849
 *
850
 * A {@elgg_plugin_hook debug log} is called.  If a handler returns
851
 * false, it will stop the default logging method.
852
 *
853
 * @note Use the developers plugin to display logs
854
 *
855
 * @param mixed $value The value
856
 * @return void
857
 * @since 1.7.0
858
 */
859
function elgg_dump($value) {
860
	_elgg_services()->logger->dump($value);
861
}
862
863
/**
864
 * Get the current Elgg version information
865
 *
866
 * @param bool $human_readable Whether to return a human readable version (default: false)
867
 *
868
 * @return string|false Depending on success
869
 * @since 1.9
870
 */
871
function elgg_get_version($human_readable = false) {
872 34
	static $version, $release;
873
	
874 34
	if (!isset($version) || !isset($release)) {
875
		$path = \Elgg\Application::elggDir()->getPath('version.php');
876
		if (!is_file($path)) {
877
			return false;
878
		}
879
		include $path;
880
	}
881
	
882 34
	return $human_readable ? $release : $version;
883
}
884
885
/**
886
 * Log a notice about deprecated use of a function, view, etc.
887
 *
888
 * @param string $msg             Message to log
889
 * @param string $dep_version     Human-readable *release* version: 1.7, 1.8, ...
890
 * @param int    $backtrace_level How many levels back to display the backtrace.
891
 *                                Useful if calling from functions that are called
892
 *                                from other places (like elgg_view()). Set to -1
893
 *                                for a full backtrace.
894
 *
895
 * @return bool
896
 * @since 1.7.0
897
 */
898
function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) {
899 656
	$backtrace_level += 1;
900 656
	return _elgg_services()->deprecation->sendNotice($msg, $dep_version, $backtrace_level);
901
}
902
903
/**
904
 * Builds a URL from the a parts array like one returned by {@link parse_url()}.
905
 *
906
 * @note If only partial information is passed, a partial URL will be returned.
907
 *
908
 * @param array $parts       Associative array of URL components like parse_url() returns
909
 *                           'user' and 'pass' parts are ignored because of security reasons
910
 * @param bool  $html_encode HTML Encode the url?
911
 *
912
 * @see https://github.com/Elgg/Elgg/pull/8146#issuecomment-91544585
913
 * @return string Full URL
914
 * @since 1.7.0
915
 */
916
function elgg_http_build_url(array $parts, $html_encode = true) {
917
	// build only what's given to us.
918 37
	$scheme = isset($parts['scheme']) ? "{$parts['scheme']}://" : '';
919 37
	$host = isset($parts['host']) ? "{$parts['host']}" : '';
920 37
	$port = isset($parts['port']) ? ":{$parts['port']}" : '';
921 37
	$path = isset($parts['path']) ? "{$parts['path']}" : '';
922 37
	$query = isset($parts['query']) ? "?{$parts['query']}" : '';
923 37
	$fragment = isset($parts['fragment']) ? "#{$parts['fragment']}" : '';
924
925 37
	$string = $scheme . $host . $port . $path . $query . $fragment;
926
927 37
	if ($html_encode) {
928 22
		return htmlspecialchars($string, ENT_QUOTES, 'UTF-8', false);
929
	} else {
930 15
		return $string;
931
	}
932
}
933
934
/**
935
 * Adds action tokens to URL
936
 *
937
 * As of 1.7.0 action tokens are required on all actions.
938
 * Use this function to append action tokens to a URL's GET parameters.
939
 * This will preserve any existing GET parameters.
940
 *
941
 * @note If you are using {@elgg_view input/form} you don't need to
942
 * add tokens to the action.  The form view automatically handles
943
 * tokens.
944
 *
945
 * @param string $url         Full action URL
946
 * @param bool   $html_encode HTML encode the url? (default: false)
947
 *
948
 * @return string URL with action tokens
949
 * @since 1.7.0
950
 */
951
function elgg_add_action_tokens_to_url($url, $html_encode = false) {
952
	$url = elgg_normalize_url($url);
953
	$components = parse_url($url);
954
955
	if (isset($components['query'])) {
956
		$query = elgg_parse_str($components['query']);
957
	} else {
958
		$query = [];
959
	}
960
961
	if (isset($query['__elgg_ts']) && isset($query['__elgg_token'])) {
962
		return $url;
963
	}
964
965
	// append action tokens to the existing query
966
	$query['__elgg_ts'] = time();
967
	$query['__elgg_token'] = generate_action_token($query['__elgg_ts']);
968
	$components['query'] = http_build_query($query);
969
970
	// rebuild the full url
971
	return elgg_http_build_url($components, $html_encode);
972
}
973
974
/**
975
 * Removes an element from a URL's query string.
976
 *
977
 * @note You can send a partial URL string.
978
 *
979
 * @param string $url     Full URL
980
 * @param string $element The element to remove
981
 *
982
 * @return string The new URL with the query element removed.
983
 * @since 1.7.0
984
 */
985
function elgg_http_remove_url_query_element($url, $element) {
986 7
	return elgg_http_add_url_query_elements($url, [$element => null]);
987
}
988
989
/**
990
 * Sets elements in a URL's query string.
991
 *
992
 * @param string $url      The URL
993
 * @param array  $elements Key/value pairs to set in the URL. If the value is null, the
994
 *                         element is removed from the URL.
995
 *
996
 * @return string The new URL with the query strings added
997
 * @since 1.7.0
998
 */
999
function elgg_http_add_url_query_elements($url, array $elements) {
1000 15
	$url_array = parse_url($url);
1001
1002 15
	if (isset($url_array['query'])) {
1003 5
		$query = elgg_parse_str($url_array['query']);
1004
	} else {
1005 10
		$query = [];
1006
	}
1007
1008 15
	foreach ($elements as $k => $v) {
1009 14
		if ($v === null) {
1010 10
			unset($query[$k]);
1011
		} else {
1012 14
			$query[$k] = $v;
1013
		}
1014
	}
1015
1016
	// why check path? A: if no path, this may be a relative URL like "?foo=1". In this case,
1017
	// the output "" would be interpreted the current URL, so in this case we *must* set
1018
	// a query to make sure elements are removed.
1019 15
	if ($query || empty($url_array['path'])) {
1020 14
		$url_array['query'] = http_build_query($query);
1021
	} else {
1022 1
		unset($url_array['query']);
1023
	}
1024 15
	$string = elgg_http_build_url($url_array, false);
1025
1026
	// Restore relative protocol to url if missing and is provided as part of the initial url (see #9874)
1027 15
	if (!isset($url['scheme']) && (substr($url, 0, 2) == '//')) {
1028
		$string = "//{$string}";
1029
	}
1030
	
1031 15
	return $string;
1032
}
1033
1034
/**
1035
 * Test if two URLs are functionally identical.
1036
 *
1037
 * @tip If $ignore_params is used, neither the name nor its value will be considered when comparing.
1038
 *
1039
 * @tip The order of GET params doesn't matter.
1040
 *
1041
 * @param string $url1          First URL
1042
 * @param string $url2          Second URL
1043
 * @param array  $ignore_params GET params to ignore in the comparison
1044
 *
1045
 * @return bool
1046
 * @since 1.8.0
1047
 */
1048
function elgg_http_url_is_identical($url1, $url2, $ignore_params = ['offset', 'limit']) {
1049 13
	$url1 = elgg_normalize_url($url1);
1050 13
	$url2 = elgg_normalize_url($url2);
1051
1052 13
	if ($url1 == $url2) {
1053
		return true;
1054
	}
1055
1056 13
	$url1_info = parse_url($url1);
1057 13
	$url2_info = parse_url($url2);
1058
1059 13
	if (isset($url1_info['path'])) {
1060
		$url1_info['path'] = trim($url1_info['path'], '/');
1061
	}
1062 13
	if (isset($url2_info['path'])) {
1063 13
		$url2_info['path'] = trim($url2_info['path'], '/');
1064
	}
1065
1066
	// compare basic bits
1067 13
	$parts = ['scheme', 'host', 'path'];
1068
1069 13
	foreach ($parts as $part) {
1070 13
		if ((isset($url1_info[$part]) && isset($url2_info[$part]))
1071 13
		&& $url1_info[$part] != $url2_info[$part]) {
1072 5
			return false;
1073 11
		} elseif (isset($url1_info[$part]) && !isset($url2_info[$part])) {
1074
			return false;
1075 11
		} elseif (!isset($url1_info[$part]) && isset($url2_info[$part])) {
1076 11
			return false;
1077
		}
1078
	}
1079
1080
	// quick compare of get params
1081
	if (isset($url1_info['query']) && isset($url2_info['query'])
1082
	&& $url1_info['query'] == $url2_info['query']) {
1083
		return true;
1084
	}
1085
1086
	// compare get params that might be out of order
1087
	$url1_params = [];
1088
	$url2_params = [];
1089
1090
	if (isset($url1_info['query'])) {
1091
		if ($url1_info['query'] = html_entity_decode($url1_info['query'])) {
1092
			$url1_params = elgg_parse_str($url1_info['query']);
1093
		}
1094
	}
1095
1096
	if (isset($url2_info['query'])) {
1097
		if ($url2_info['query'] = html_entity_decode($url2_info['query'])) {
1098
			$url2_params = elgg_parse_str($url2_info['query']);
1099
		}
1100
	}
1101
1102
	// drop ignored params
1103
	foreach ($ignore_params as $param) {
1104
		if (isset($url1_params[$param])) {
1105
			unset($url1_params[$param]);
1106
		}
1107
		if (isset($url2_params[$param])) {
1108
			unset($url2_params[$param]);
1109
		}
1110
	}
1111
1112
	// array_diff_assoc only returns the items in arr1 that aren't in arrN
1113
	// but not the items that ARE in arrN but NOT in arr1
1114
	// if arr1 is an empty array, this function will return 0 no matter what.
1115
	// since we only care if they're different and not how different,
1116
	// add the results together to get a non-zero (ie, different) result
1117
	$diff_count = count(array_diff_assoc($url1_params, $url2_params));
1118
	$diff_count += count(array_diff_assoc($url2_params, $url1_params));
1119
	if ($diff_count > 0) {
1120
		return false;
1121
	}
1122
1123
	return true;
1124
}
1125
1126
/**
1127
 * Signs provided URL with a SHA256 HMAC key
1128
 *
1129
 * @note Signed URLs do not offer CSRF protection and should not be used instead of action tokens.
1130
 *
1131
 * @param string $url     URL to sign
1132
 * @param string $expires Expiration time
1133
 *                        A string suitable for strtotime()
1134
 *                        Falsey values indicate non-expiring URL
1135
 * @return string
1136
 */
1137
function elgg_http_get_signed_url($url, $expires = false) {
1138
	return _elgg_services()->urlSigner->sign($url, $expires);
0 ignored issues
show
Bug introduced by
It seems like $expires can also be of type false; however, parameter $expires of Elgg\Security\UrlSigner::sign() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1138
	return _elgg_services()->urlSigner->sign($url, /** @scrutinizer ignore-type */ $expires);
Loading history...
1139
}
1140
1141
/**
1142
 * Validates if the HMAC signature of the URL is valid
1143
 *
1144
 * @param string $url URL to validate
1145
 * @return bool
1146
 */
1147
function elgg_http_validate_signed_url($url) {
1148
	return _elgg_services()->urlSigner->isValid($url);
1149
}
1150
1151
/**
1152
 * Validates if the HMAC signature of the current request is valid
1153
 * Issues 403 response if signature is inalid
1154
 * @return void
1155
 */
1156
function elgg_signed_request_gatekeeper() {
1157
1158
	switch (php_sapi_name()) {
1159
		case 'cli' :
1160
		case 'phpdbg' :
1161
			return;
1162
1163
		default :
1164
			if (!elgg_http_validate_signed_url(current_page_url())) {
1165
				register_error(elgg_echo('invalid_request_signature'));
1166
				forward('', '403');
1167
			}
1168
	}
1169
}
1170
1171
/**
1172
 * Checks for $array[$key] and returns its value if it exists, else
1173
 * returns $default.
1174
 *
1175
 * Shorthand for $value = (isset($array['key'])) ? $array['key'] : 'default';
1176
 *
1177
 * @param string $key     Key to check in the source array
1178
 * @param array  $array   Source array
1179
 * @param mixed  $default Value to return if key is not found
1180
 * @param bool   $strict  Return array key if it's set, even if empty. If false,
1181
 *                        return $default if the array key is unset or empty.
1182
 *
1183
 * @return mixed
1184
 * @since 1.8.0
1185
 */
1186
function elgg_extract($key, $array, $default = null, $strict = true) {
1187 4202
	if (!is_array($array)) {
1188 1
		return $default;
1189
	}
1190
1191 4202
	if ($strict) {
1192 4202
		return (isset($array[$key])) ? $array[$key] : $default;
1193
	} else {
1194 270
		return (isset($array[$key]) && !empty($array[$key])) ? $array[$key] : $default;
1195
	}
1196
}
1197
1198
/**
1199
 * Extract class names from an array, optionally merging into a preexisting set.
1200
 *
1201
 * @param array           $array       Source array
1202
 * @param string|string[] $existing    Existing name(s)
1203
 * @param string          $extract_key Key to extract new classes from
1204
 * @return string[]
1205
 *
1206
 * @since 2.3.0
1207
 */
1208
function elgg_extract_class(array $array, $existing = [], $extract_key = 'class') {
1209 60
	$existing = empty($existing) ? [] : (array) $existing;
1210
1211 60
	$merge = (array) elgg_extract($extract_key, $array, []);
1212
1213 60
	array_splice($existing, count($existing), 0, $merge);
1214
1215 60
	return array_values(array_unique($existing));
1216
}
1217
1218
/**
1219
 * Sorts a 3d array by specific element.
1220
 *
1221
 * @warning Will re-index numeric indexes.
1222
 *
1223
 * @note This operates the same as the built-in sort functions.
1224
 * It sorts the array and returns a bool for success.
1225
 *
1226
 * Do this: elgg_sort_3d_array_by_value($my_array);
1227
 * Not this: $my_array = elgg_sort_3d_array_by_value($my_array);
1228
 *
1229
 * @param array  $array      Array to sort
1230
 * @param string $element    Element to sort by
1231
 * @param int    $sort_order PHP sort order {@link http://us2.php.net/array_multisort}
1232
 * @param int    $sort_type  PHP sort type {@link http://us2.php.net/sort}
1233
 *
1234
 * @return bool
1235
 */
1236
function elgg_sort_3d_array_by_value(&$array, $element, $sort_order = SORT_ASC, $sort_type = SORT_LOCALE_STRING) {
1237
1238
	$sort = [];
1239
1240
	foreach ($array as $v) {
1241
		if (isset($v[$element])) {
1242
			$sort[] = strtolower($v[$element]);
1243
		} else {
1244
			$sort[] = null;
1245
		}
1246
	};
1247
1248
	return array_multisort($sort, $sort_order, $sort_type, $array);
1249
}
1250
1251
/**
1252
 * Return the state of a php.ini setting as a bool
1253
 *
1254
 * @warning Using this on ini settings that are not boolean
1255
 * will be inaccurate!
1256
 *
1257
 * @param string $ini_get_arg The INI setting
1258
 *
1259
 * @return bool Depending on whether it's on or off
1260
 */
1261
function ini_get_bool($ini_get_arg) {
1262
	$temp = strtolower(ini_get($ini_get_arg));
1263
1264
	if ($temp == '1' || $temp == 'on' || $temp == 'true') {
1265
		return true;
1266
	}
1267
	return false;
1268
}
1269
1270
/**
1271
 * Returns a PHP INI setting in bytes.
1272
 *
1273
 * @tip Use this for arithmetic when determining if a file can be uploaded.
1274
 *
1275
 * @param string $setting The php.ini setting
1276
 *
1277
 * @return int
1278
 * @since 1.7.0
1279
 * @link http://www.php.net/manual/en/function.ini-get.php
1280
 */
1281
function elgg_get_ini_setting_in_bytes($setting) {
1282
	// retrieve INI setting
1283
	$val = ini_get($setting);
1284
1285
	// convert INI setting when shorthand notation is used
1286
	$last = strtolower($val[strlen($val) - 1]);
1287
	if (in_array($last, ['g', 'm', 'k'])) {
1288
		$val = substr($val, 0, -1);
1289
	}
1290
	$val = (int) $val;
1291
	switch ($last) {
1292
		case 'g':
1293
			$val *= 1024;
1294
			// fallthrough intentional
1295
		case 'm':
1296
			$val *= 1024;
1297
			// fallthrough intentional
1298
		case 'k':
1299
			$val *= 1024;
1300
	}
1301
1302
	// return byte value
1303
	return $val;
1304
}
1305
1306
/**
1307
 * Returns true is string is not empty, false, or null.
1308
 *
1309
 * Function to be used in array_filter which returns true if $string is not null.
1310
 *
1311
 * @param string $string The string to test
1312
 *
1313
 * @return bool
1314
 * @todo This is used once in metadata.php.  Use a lambda function instead.
1315
 */
1316
function is_not_null($string) {
1317 2
	if (($string === '') || ($string === false) || ($string === null)) {
1318
		return false;
1319
	}
1320
1321 2
	return true;
1322
}
1323
1324
/**
1325
 * Get the global service provider
1326
 *
1327
 * @return \Elgg\Di\ServiceProvider
1328
 * @access private
1329
 */
1330
function _elgg_services() {
1331
	// This yields a more shallow stack depth in recursive APIs like views. This aids in debugging and
1332
	// reduces false positives in xdebug's infinite recursion protection.
1333 5390
	return Elgg\Application::$_instance->_services;
1334
}
1335
1336
/**
1337
 * Normalise the singular keys in an options array to plural keys.
1338
 *
1339
 * Used in elgg_get_entities*() functions to support shortcutting plural
1340
 * names by singular names.
1341
 *
1342
 * @param array $options   The options array. $options['keys'] = 'values';
1343
 * @param array $singulars A list of singular words to pluralize by adding 's'.
1344
 *
1345
 * @return array
1346
 * @since 1.7.0
1347
 * @access private
1348
 */
1349
function _elgg_normalize_plural_options_array($options, $singulars) {
1350 1301
	foreach ($singulars as $singular) {
1351 1301
		$plural = $singular . 's';
1352
1353 1301
		if (array_key_exists($singular, $options)) {
1354 957
			if ($options[$singular] === ELGG_ENTITIES_ANY_VALUE) {
1355 2
				$options[$plural] = $options[$singular];
1356
			} else {
1357
				// Test for array refs #2641
1358 957
				if (!is_array($options[$singular])) {
1359 949
					$options[$plural] = [$options[$singular]];
1360
				} else {
1361 18
					$options[$plural] = $options[$singular];
1362
				}
1363
			}
1364
		}
1365
1366 1301
		unset($options[$singular]);
1367
	}
1368
1369 1301
	return $options;
1370
}
1371
1372
/**
1373
 * Emits a shutdown:system event upon PHP shutdown, but before database connections are dropped.
1374
 *
1375
 * @tip Register for the shutdown:system event to perform functions at the end of page loads.
1376
 *
1377
 * @warning Using this event to perform long-running functions is not very
1378
 * useful.  Servers will hold pages until processing is done before sending
1379
 * them out to the browser.
1380
 *
1381
 * @see http://www.php.net/register-shutdown-function
1382
 *
1383
 * @internal This is registered in \Elgg\Application::create()
1384
 *
1385
 * @return void
1386
 * @see register_shutdown_hook()
1387
 * @access private
1388
 */
1389
function _elgg_shutdown_hook() {
1390
	try {
1391
		elgg_trigger_event('shutdown', 'system');
1392
1393
		$time = (float) (microtime(true) - $GLOBALS['START_MICROTIME']);
1394
		$uri = _elgg_services()->request->server->get('REQUEST_URI', 'CLI');
1395
		// demoted to NOTICE from DEBUG so javascript is not corrupted
1396
		elgg_log("Page {$uri} generated in $time seconds", 'INFO');
1397
	} catch (Exception $e) {
1398
		$message = 'Error: ' . get_class($e) . ' thrown within the shutdown handler. ';
1399
		$message .= "Message: '{$e->getMessage()}' in file {$e->getFile()} (line {$e->getLine()})";
1400
		error_log($message);
1401
		error_log("Exception trace stack: {$e->getTraceAsString()}");
1402
	}
1403
1404
	// Prevent an APC session bug: https://bugs.php.net/bug.php?id=60657
1405
	session_write_close();
1406
}
1407
1408
/**
1409
 * Serve individual views for Ajax.
1410
 *
1411
 * /ajax/view/<view_name>?<key/value params>
1412
 * /ajax/form/<action_name>?<key/value params>
1413
 *
1414
 * @param string[] $segments URL segments (not including "ajax")
1415
 * @return ResponseBuilder
1416
 *
1417
 * @see elgg_register_ajax_view()
1418
 * @elgg_pagehandler ajax
1419
 * @access private
1420
 */
1421
function _elgg_ajax_page_handler($segments) {
1422 17
	elgg_ajax_gatekeeper();
1423
1424 17
	if (count($segments) < 2) {
1425
		return elgg_error_response("Ajax pagehandler called with invalid segments", REFERRER, ELGG_HTTP_BAD_REQUEST);
1426
	}
1427
1428 17
	if ($segments[0] === 'view' || $segments[0] === 'form') {
1429 17
		if ($segments[0] === 'view') {
1430 13
			if ($segments[1] === 'admin') {
1431
				// protect admin views similar to all admin pages that are protected automatically in the admin_page_handler
1432
				elgg_admin_gatekeeper();
1433
			}
1434
			// ignore 'view/'
1435 13
			$view = implode('/', array_slice($segments, 1));
1436
		} else {
1437
			// form views start with "forms", not "form"
1438 4
			$view = 'forms/' . implode('/', array_slice($segments, 1));
1439
		}
1440
1441 17
		$ajax_api = _elgg_services()->ajax;
1442 17
		$allowed_views = $ajax_api->getViews();
1443
1444
		// cacheable views are always allowed
1445 17
		if (!in_array($view, $allowed_views) && !_elgg_services()->views->isCacheableView($view)) {
1446 4
			return elgg_error_response("Ajax view '$view' was not registered", REFERRER, ELGG_HTTP_FORBIDDEN);
1447
		}
1448
1449 13
		if (!elgg_view_exists($view)) {
1450
			return elgg_error_response("Ajax view '$view' was not found", REFERRER, ELGG_HTTP_NOT_FOUND);
1451
		}
1452
1453
		// pull out GET parameters through filter
1454 13
		$vars = [];
1455 13
		foreach (_elgg_services()->request->query->keys() as $name) {
1456 7
			$vars[$name] = get_input($name);
1457
		}
1458
1459 13
		if (isset($vars['guid'])) {
1460
			$vars['entity'] = get_entity($vars['guid']);
1461
		}
1462
1463 13
		if (isset($vars['river_id'])) {
1464
			$vars['item'] = elgg_get_river_item_from_id($vars['river_id']);
1465
		}
1466
1467 13
		$content_type = '';
1468 13
		if ($segments[0] === 'view') {
1469 11
			$output = elgg_view($view, $vars);
1470
1471
			// Try to guess the mime-type
1472 11
			switch ($segments[1]) {
1473
				case "js":
1474 2
					$content_type = 'text/javascript;charset=utf-8';
1475 2
					break;
1476
				case "css":
1477 2
					$content_type = 'text/css;charset=utf-8';
1478 2
					break;
1479
				default :
1480 7
					if (_elgg_services()->views->isCacheableView($view)) {
1481 2
						$file = _elgg_services()->views->findViewFile($view, elgg_get_viewtype());
1482 2
						$content_type = (new \Elgg\Filesystem\MimeTypeDetector())->getType($file, 'text/html');
1483
					}
1484 11
					break;
1485
			}
1486
		} else {
1487 2
			$action = implode('/', array_slice($segments, 1));
1488 2
			$output = elgg_view_form($action, [], $vars);
1489
		}
1490
1491 13
		if ($content_type) {
1492 6
			elgg_set_http_header("Content-Type: $content_type");
1493
		}
1494
1495 13
		return elgg_ok_response($output);
1496
	}
1497
1498
	return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type Elgg\Http\ResponseBuilder.
Loading history...
1499
}
1500
1501
/**
1502
 * Handle requests for /favicon.ico
1503
 *
1504
 * @param string[] $segments The URL segments
1505
 * @return bool
1506
 * @access private
1507
 * @since 1.10
1508
 */
1509
function _elgg_favicon_page_handler($segments) {
0 ignored issues
show
Unused Code introduced by
The parameter $segments is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1509
function _elgg_favicon_page_handler(/** @scrutinizer ignore-unused */ $segments) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1510
	header("HTTP/1.1 404 Not Found", true, 404);
1511
1512
	header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+1 week")), true);
1513
	header("Pragma: public", true);
1514
	header("Cache-Control: public", true);
1515
1516
	header('Content-Type: image/x-icon');
1517
	echo elgg_view('graphics/favicon.ico');
1518
1519
	return true;
1520
}
1521
1522
/**
1523
 * Enable objects with an enable() method.
1524
 *
1525
 * Used as a callback for \ElggBatch.
1526
 *
1527
 * @todo why aren't these static methods on \ElggBatch?
1528
 *
1529
 * @param object $object The object to enable
1530
 * @return bool
1531
 * @access private
1532
 */
1533
function elgg_batch_enable_callback($object) {
1534
	// our db functions return the number of rows affected...
1535
	return $object->enable() ? true : false;
1536
}
1537
1538
/**
1539
 * Disable objects with a disable() method.
1540
 *
1541
 * Used as a callback for \ElggBatch.
1542
 *
1543
 * @param object $object The object to disable
1544
 * @return bool
1545
 * @access private
1546
 */
1547
function elgg_batch_disable_callback($object) {
1548
	// our db functions return the number of rows affected...
1549
	return $object->disable() ? true : false;
1550
}
1551
1552
/**
1553
 * Checks if there are some constraints on the options array for
1554
 * potentially dangerous operations.
1555
 *
1556
 * @param array  $options Options array
1557
 * @param string $type    Options type: metadata, annotation or river
1558
 * @return bool
1559
 * @access private
1560
 */
1561
function _elgg_is_valid_options_for_batch_operation($options, $type) {
1562 273
	if (!$options || !is_array($options)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1563 1
		return false;
1564
	}
1565
1566
	// at least one of these is required.
1567
	$required = [
1568
		// generic restraints
1569 273
		'guid', 'guids'
1570
	];
1571
1572 273
	switch ($type) {
1573
		case 'metadata':
1574
			$metadata_required = [
1575 271
				'metadata_name', 'metadata_names',
1576
				'metadata_value', 'metadata_values'
1577
			];
1578
1579 271
			$required = array_merge($required, $metadata_required);
1580 271
			break;
1581
1582
		case 'annotations':
1583
		case 'annotation':
1584
			$annotations_required = [
1585 216
				'annotation_owner_guid', 'annotation_owner_guids',
1586
				'annotation_name', 'annotation_names',
1587
				'annotation_value', 'annotation_values'
1588
			];
1589
1590 216
			$required = array_merge($required, $annotations_required);
1591 216
			break;
1592
1593
		case 'river':
1594
			// overriding generic restraints as guids isn't supported in river
1595
			$required = [
1596 215
				'id', 'ids',
1597
				'subject_guid', 'subject_guids',
1598
				'object_guid', 'object_guids',
1599
				'target_guid', 'target_guids',
1600
				'annotation_id', 'annotation_ids',
1601
				'view', 'views',
1602
			];
1603 215
			break;
1604
		
1605
		default:
1606
			return false;
1607
	}
1608
1609 273
	foreach ($required as $key) {
1610
		// check that it exists and is something.
1611 273
		if (isset($options[$key]) && $options[$key]) {
1612 273
			return true;
1613
		}
1614
	}
1615
1616 1
	return false;
1617
}
1618
1619
/**
1620
 * Checks the status of the Walled Garden and forwards to a login page
1621
 * if required.
1622
 *
1623
 * If the site is in Walled Garden mode, all page except those registered as
1624
 * plugin pages by {@elgg_hook public_pages walled_garden} will redirect to
1625
 * a login page.
1626
 *
1627
 * @since 1.8.0
1628
 * @elgg_event_handler init system
1629
 * @return void
1630
 * @access private
1631
 */
1632
function _elgg_walled_garden_init() {
1633 31
	if (!_elgg_config()->walled_garden) {
1634 31
		return;
1635
	}
1636
1637
	elgg_register_css('elgg.walled_garden', elgg_get_simplecache_url('walled_garden.css'));
1638
1639
	elgg_register_plugin_hook_handler('register', 'menu:walled_garden', '_elgg_walled_garden_menu');
1640
1641
	if (_elgg_config()->default_access == ACCESS_PUBLIC) {
1642
		elgg_set_config('default_access', ACCESS_LOGGED_IN);
1643
	}
1644
1645
	elgg_register_plugin_hook_handler('access:collections:write', 'all', '_elgg_walled_garden_remove_public_access', 9999);
1646
1647
	if (!elgg_is_logged_in()) {
1648
		// override the front page
1649
		elgg_register_route('index', [
1650
			'path' => '/',
1651
			'resource' => 'walled_garden',
1652
		]);
1653
	}
1654
}
1655
1656
/**
1657
 * Adds home link to walled garden menu
1658
 *
1659
 * @param string $hook         'register'
1660
 * @param string $type         'menu:walled_garden'
1661
 * @param array  $return_value Current menu items
1662
 * @param array  $params       Optional menu parameters
1663
 *
1664
 * @return array
1665
 *
1666
 * @access private
1667
 */
1668
function _elgg_walled_garden_menu($hook, $type, $return_value, $params) {
3 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1668
function _elgg_walled_garden_menu($hook, /** @scrutinizer ignore-unused */ $type, $return_value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1668
function _elgg_walled_garden_menu(/** @scrutinizer ignore-unused */ $hook, $type, $return_value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1668
function _elgg_walled_garden_menu($hook, $type, $return_value, /** @scrutinizer ignore-unused */ $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1669
	
1670
	if (current_page_url() === elgg_get_site_url()) {
1671
		return;
1672
	}
1673
	
1674
	$return_value[] = \ElggMenuItem::factory([
1675
		'name' => 'home',
1676
		'href' => '/',
1677
		'text' => elgg_echo('walled_garden:home'),
1678
		'priority' => 10,
1679
	]);
1680
1681
	return $return_value;
1682
}
1683
1684
/**
1685
 * Remove public access for walled gardens
1686
 *
1687
 * @param string $hook     'access:collections:write'
1688
 * @param string $type     'all'
1689
 * @param array  $accesses current return value
1690
 *
1691
 * @return array
1692
 *
1693
 * @access private
1694
 */
1695
function _elgg_walled_garden_remove_public_access($hook, $type, $accesses) {
2 ignored issues
show
Unused Code introduced by
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1695
function _elgg_walled_garden_remove_public_access(/** @scrutinizer ignore-unused */ $hook, $type, $accesses) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1695
function _elgg_walled_garden_remove_public_access($hook, /** @scrutinizer ignore-unused */ $type, $accesses) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1696
	if (isset($accesses[ACCESS_PUBLIC])) {
1697
		unset($accesses[ACCESS_PUBLIC]);
1698
	}
1699
	return $accesses;
1700
}
1701
1702
/**
1703
 * Elgg's main init.
1704
 *
1705
 * Handles core actions, the JS pagehandler, and the shutdown function.
1706
 *
1707
 * @elgg_event_handler init system
1708
 * @return void
1709
 * @access private
1710
 */
1711
function _elgg_init() {
1712 31
	elgg_register_action('entity/delete');
1713
1714
	elgg_register_plugin_hook_handler('head', 'page', function($hook, $type, array $result) {
1715 2
		$result['links']['manifest'] = [
1716 2
			'rel' => 'manifest',
1717 2
			'href' => elgg_normalize_url('/manifest.json'),
1718
		];
1719
1720 2
		return $result;
1721 31
	});
1722
1723 31
	if (_elgg_config()->enable_profiling) {
1724
		/**
1725
		 * @see \Elgg\Profiler::handlePageOutput
1726
		 */
1727
		elgg_register_plugin_hook_handler('output', 'page', [\Elgg\Profiler::class, 'handlePageOutput'], 999);
1728
	}
1729
1730 31
	elgg_register_plugin_hook_handler('commands', 'cli', '_elgg_init_cli_commands');
1731 31
}
1732
1733
/**
1734
 * Initialize Cli commands
1735
 *
1736
 * @elgg_plugin_hook commands cli
1737
 *
1738
 * @param \Elgg\Hook $hook Hook
1739
 *
1740
 * @return \Elgg\Cli\Command[]
1741
 * @access private
1742
 */
1743
function _elgg_init_cli_commands(\Elgg\Hook $hook) {
1744
	$defaults = [
1745
		\Elgg\Cli\SimpletestCommand::class,
1746
		\Elgg\Cli\DatabaseSeedCommand::class,
1747
		\Elgg\Cli\DatabaseUnseedCommand::class,
1748
		\Elgg\Cli\CronCommand::class,
1749
	];
1750
1751
	return array_merge($defaults, (array) $hook->getValue());
1752
}
1753
1754
/**
1755
 * Delete the autoload system cache
1756
 *
1757
 * @return void
1758
 *
1759
 * @access private
1760
 */
1761
function _elgg_delete_autoload_cache() {
1762
	_elgg_services()->autoloadManager->deleteCache();
1763
}
1764
1765
/**
1766
 * Register core routes
1767
 * @return void
1768
 * @internal
1769
 */
1770
function _elgg_register_routes() {
1771 18
	$conf = \Elgg\Project\Paths::elgg() . 'engine/routes.php';
1772 18
	$routes = \Elgg\Includer::includeFile($conf);
1773
1774 18
	foreach ($routes as $name => $def) {
1775 18
		elgg_register_route($name, $def);
1776
	}
1777 18
}
1778
1779
/**
1780
 * Adds unit tests for the general API.
1781
 *
1782
 * @param string $hook   unit_test
1783
 * @param string $type   system
1784
 * @param array  $value  array of test files
1785
 * @param array  $params empty
1786
 *
1787
 * @elgg_plugin_hook unit_tests system
1788
 * @return array
1789
 * @access private
1790
 * @codeCoverageIgnore
1791
 */
1792
function _elgg_api_test($hook, $type, $value, $params) {
3 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1792
function _elgg_api_test($hook, $type, $value, /** @scrutinizer ignore-unused */ $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1792
function _elgg_api_test(/** @scrutinizer ignore-unused */ $hook, $type, $value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1792
function _elgg_api_test($hook, /** @scrutinizer ignore-unused */ $type, $value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1793
	$value[] = ElggTravisInstallTest::class;
1794
	$value[] = ElggCoreHelpersTest::class;
1795
	$value[] = ElggCoreRegressionBugsTest::class;
1796
	$value[] = ElggBatchTest::class;
1797
	return $value;
1798
}
1799
1800
/**
1801
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
1802
 */
1803
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
1804
1805 18
	_elgg_register_routes();
1806
1807 18
	elgg_set_entity_class('user', 'user', \ElggUser::class);
1808 18
	elgg_set_entity_class('group', 'group', \ElggGroup::class);
1809 18
	elgg_set_entity_class('site', 'site', \ElggSite::class);
1810 18
	elgg_set_entity_class('object', 'plugin', \ElggPlugin::class);
1811 18
	elgg_set_entity_class('object', 'file', \ElggFile::class);
1812 18
	elgg_set_entity_class('object', 'widget', \ElggWidget::class);
1813 18
	elgg_set_entity_class('object', 'comment', \ElggComment::class);
1814 18
	elgg_set_entity_class('object', 'elgg_upgrade', \ElggUpgrade::class);
1815
1816
	$events->registerHandler('cache:flush', 'system', function () {
1817 1
		_elgg_services()->boot->invalidateCache();
1818 1
		_elgg_services()->plugins->clear();
1819 1
		_elgg_services()->sessionCache->clear();
1820 1
		_elgg_services()->dataCache->clear();
1821 18
	});
1822
1823 18
	$events->registerHandler('init', 'system', '_elgg_init');
1824 18
	$events->registerHandler('init', 'system', '_elgg_walled_garden_init', 1000);
1825
1826 18
	$hooks->registerHandler('unit_test', 'system', '_elgg_api_test');
1827
1828 18
	$events->registerHandler('upgrade', 'all', '_elgg_delete_autoload_cache', 600);
1829
};
1830