Test Failed
Push — master ( c2873c...a077d1 )
by Jeroen
01:35
created
engine/classes/Elgg/Database/AccessCollections.php 2 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -711,7 +711,7 @@
 block discarded – undo
711 711
 	 * @see get_members_of_access_collection()
712 712
 	 *
713 713
 	 * @param int $collection_id The collection ID
714
-	 * @return \ElggAccessCollection|false
714
+	 * @return string
715 715
 	 */
716 716
 	public function get($collection_id) {
717 717
 
Please login to merge, or discard this patch.
Indentation   +823 added lines, -823 removed lines patch added patch discarded remove patch
@@ -23,171 +23,171 @@  discard block
 block discarded – undo
23 23
  */
24 24
 class AccessCollections {
25 25
 
26
-	/**
27
-	 * @var Conf
28
-	 */
29
-	protected $config;
30
-
31
-	/**
32
-	 * @var Database
33
-	 */
34
-	protected $db;
35
-
36
-	/**
37
-	 * @vars \ElggStateVariableCache
38
-	 */
39
-	protected $access_cache;
40
-
41
-	/**
42
-	 * @var PluginHooksService
43
-	 */
44
-	protected $hooks;
45
-
46
-	/**
47
-	 * @var ElggSession
48
-	 */
49
-	protected $session;
50
-
51
-	/**
52
-	 * @var EntityTable
53
-	 */
54
-	protected $entities;
55
-
56
-	/**
57
-	 * @var Translator
58
-	 */
59
-	protected $translator;
60
-
61
-	/**
62
-	 * @var string
63
-	 */
64
-	protected $table;
65
-
66
-	/**
67
-	 * @var string
68
-	 */
69
-	protected $membership_table;
70
-
71
-	/**
72
-	 * @var bool
73
-	 */
74
-	protected $init_complete = false;
75
-
76
-	/**
77
-	 * Constructor
78
-	 *
79
-	 * @param Conf                    $config     Config
80
-	 * @param Database                $db         Database
81
-	 * @param EntityTable             $entities   Entity table
82
-	 * @param ElggStaticVariableCache $cache      Access cache
83
-	 * @param PluginHooksService      $hooks      Hooks
84
-	 * @param ElggSession             $session    Session
85
-	 * @param Translator              $translator Translator
86
-	 */
87
-	public function __construct(
88
-			Conf $config,
89
-			Database $db,
90
-			EntityTable $entities,
91
-			ElggStaticVariableCache $cache,
92
-			PluginHooksService $hooks,
93
-			ElggSession $session,
94
-			Translator $translator) {
95
-		$this->config = $config;
96
-		$this->db = $db;
97
-		$this->entities = $entities;
98
-		$this->access_cache = $cache;
99
-		$this->hooks = $hooks;
100
-		$this->session = $session;
101
-		$this->translator = $translator;
102
-
103
-		$this->table = "{$this->db->prefix}access_collections";
104
-		$this->membership_table = "{$this->db->prefix}access_collection_membership";
105
-	}
106
-
107
-	/**
108
-	 * Mark the access system as initialized
109
-	 *
110
-	 * @return void
111
-	 */
112
-	public function markInitComplete() {
113
-		$this->init_complete = true;
114
-	}
115
-
116
-	/**
117
-	 * Returns a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
118
-	 *
119
-	 * @see get_access_array()
120
-	 *
121
-	 * @param int  $user_guid User ID; defaults to currently logged in user
122
-	 * @param bool $flush     If set to true, will refresh the access list from the
123
-	 *                        database rather than using this function's cache.
124
-	 *
125
-	 * @return string A concatenated string of access collections suitable for using in an SQL IN clause
126
-	 * @access private
127
-	 */
128
-	public function getAccessList($user_guid = 0, $flush = false) {
129
-		$access_array = $this->getAccessArray($user_guid, $flush);
130
-		$access_ids = implode(',', $access_array);
131
-		$list = "($access_ids)";
132
-
133
-		// for BC, populate the cache
134
-		$hash = $user_guid . 'get_access_list';
135
-		$this->access_cache->add($hash, $list);
136
-
137
-		return $list;
138
-	}
139
-
140
-	/**
141
-	 * Returns an array of access IDs a user is permitted to see.
142
-	 *
143
-	 * Can be overridden with the 'access:collections:read', 'user' plugin hook.
144
-	 * @warning A callback for that plugin hook needs to either not retrieve data
145
-	 * from the database that would use the access system (triggering the plugin again)
146
-	 * or ignore the second call. Otherwise, an infinite loop will be created.
147
-	 *
148
-	 * This returns a list of all the collection ids a user owns or belongs
149
-	 * to plus public and logged in access levels. If the user is an admin, it includes
150
-	 * the private access level.
151
-	 *
152
-	 * @internal this is only used in core for creating the SQL where clause when
153
-	 * retrieving content from the database. The friends access level is handled by
154
-	 * _elgg_get_access_where_sql().
155
-	 *
156
-	 * @see get_write_access_array() for the access levels that a user can write to.
157
-	 *
158
-	 * @param int  $user_guid User ID; defaults to currently logged in user
159
-	 * @param bool $flush     If set to true, will refresh the access ids from the
160
-	 *                        database rather than using this function's cache.
161
-	 *
162
-	 * @return array An array of access collections ids
163
-	 */
164
-	public function getAccessArray($user_guid = 0, $flush = false) {
165
-		$cache = $this->access_cache;
166
-
167
-		if ($flush) {
168
-			$cache->clear();
169
-		}
170
-
171
-		if ($user_guid == 0) {
172
-			$user_guid = $this->session->getLoggedInUserGuid();
173
-		}
174
-
175
-		$user_guid = (int) $user_guid;
176
-
177
-		$hash = $user_guid . 'get_access_array';
178
-
179
-		if ($cache[$hash]) {
180
-			$access_array = $cache[$hash];
181
-		} else {
182
-			// Public access is always visible
183
-			$access_array = [ACCESS_PUBLIC];
184
-
185
-			// The following can only return sensible data for a known user.
186
-			if ($user_guid) {
187
-				$access_array[] = ACCESS_LOGGED_IN;
188
-
189
-				// Get ACLs that user owns or is a member of
190
-				$query = "
26
+    /**
27
+     * @var Conf
28
+     */
29
+    protected $config;
30
+
31
+    /**
32
+     * @var Database
33
+     */
34
+    protected $db;
35
+
36
+    /**
37
+     * @vars \ElggStateVariableCache
38
+     */
39
+    protected $access_cache;
40
+
41
+    /**
42
+     * @var PluginHooksService
43
+     */
44
+    protected $hooks;
45
+
46
+    /**
47
+     * @var ElggSession
48
+     */
49
+    protected $session;
50
+
51
+    /**
52
+     * @var EntityTable
53
+     */
54
+    protected $entities;
55
+
56
+    /**
57
+     * @var Translator
58
+     */
59
+    protected $translator;
60
+
61
+    /**
62
+     * @var string
63
+     */
64
+    protected $table;
65
+
66
+    /**
67
+     * @var string
68
+     */
69
+    protected $membership_table;
70
+
71
+    /**
72
+     * @var bool
73
+     */
74
+    protected $init_complete = false;
75
+
76
+    /**
77
+     * Constructor
78
+     *
79
+     * @param Conf                    $config     Config
80
+     * @param Database                $db         Database
81
+     * @param EntityTable             $entities   Entity table
82
+     * @param ElggStaticVariableCache $cache      Access cache
83
+     * @param PluginHooksService      $hooks      Hooks
84
+     * @param ElggSession             $session    Session
85
+     * @param Translator              $translator Translator
86
+     */
87
+    public function __construct(
88
+            Conf $config,
89
+            Database $db,
90
+            EntityTable $entities,
91
+            ElggStaticVariableCache $cache,
92
+            PluginHooksService $hooks,
93
+            ElggSession $session,
94
+            Translator $translator) {
95
+        $this->config = $config;
96
+        $this->db = $db;
97
+        $this->entities = $entities;
98
+        $this->access_cache = $cache;
99
+        $this->hooks = $hooks;
100
+        $this->session = $session;
101
+        $this->translator = $translator;
102
+
103
+        $this->table = "{$this->db->prefix}access_collections";
104
+        $this->membership_table = "{$this->db->prefix}access_collection_membership";
105
+    }
106
+
107
+    /**
108
+     * Mark the access system as initialized
109
+     *
110
+     * @return void
111
+     */
112
+    public function markInitComplete() {
113
+        $this->init_complete = true;
114
+    }
115
+
116
+    /**
117
+     * Returns a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
118
+     *
119
+     * @see get_access_array()
120
+     *
121
+     * @param int  $user_guid User ID; defaults to currently logged in user
122
+     * @param bool $flush     If set to true, will refresh the access list from the
123
+     *                        database rather than using this function's cache.
124
+     *
125
+     * @return string A concatenated string of access collections suitable for using in an SQL IN clause
126
+     * @access private
127
+     */
128
+    public function getAccessList($user_guid = 0, $flush = false) {
129
+        $access_array = $this->getAccessArray($user_guid, $flush);
130
+        $access_ids = implode(',', $access_array);
131
+        $list = "($access_ids)";
132
+
133
+        // for BC, populate the cache
134
+        $hash = $user_guid . 'get_access_list';
135
+        $this->access_cache->add($hash, $list);
136
+
137
+        return $list;
138
+    }
139
+
140
+    /**
141
+     * Returns an array of access IDs a user is permitted to see.
142
+     *
143
+     * Can be overridden with the 'access:collections:read', 'user' plugin hook.
144
+     * @warning A callback for that plugin hook needs to either not retrieve data
145
+     * from the database that would use the access system (triggering the plugin again)
146
+     * or ignore the second call. Otherwise, an infinite loop will be created.
147
+     *
148
+     * This returns a list of all the collection ids a user owns or belongs
149
+     * to plus public and logged in access levels. If the user is an admin, it includes
150
+     * the private access level.
151
+     *
152
+     * @internal this is only used in core for creating the SQL where clause when
153
+     * retrieving content from the database. The friends access level is handled by
154
+     * _elgg_get_access_where_sql().
155
+     *
156
+     * @see get_write_access_array() for the access levels that a user can write to.
157
+     *
158
+     * @param int  $user_guid User ID; defaults to currently logged in user
159
+     * @param bool $flush     If set to true, will refresh the access ids from the
160
+     *                        database rather than using this function's cache.
161
+     *
162
+     * @return array An array of access collections ids
163
+     */
164
+    public function getAccessArray($user_guid = 0, $flush = false) {
165
+        $cache = $this->access_cache;
166
+
167
+        if ($flush) {
168
+            $cache->clear();
169
+        }
170
+
171
+        if ($user_guid == 0) {
172
+            $user_guid = $this->session->getLoggedInUserGuid();
173
+        }
174
+
175
+        $user_guid = (int) $user_guid;
176
+
177
+        $hash = $user_guid . 'get_access_array';
178
+
179
+        if ($cache[$hash]) {
180
+            $access_array = $cache[$hash];
181
+        } else {
182
+            // Public access is always visible
183
+            $access_array = [ACCESS_PUBLIC];
184
+
185
+            // The following can only return sensible data for a known user.
186
+            if ($user_guid) {
187
+                $access_array[] = ACCESS_LOGGED_IN;
188
+
189
+                // Get ACLs that user owns or is a member of
190
+                $query = "
191 191
 					SELECT ac.id
192 192
 					FROM {$this->table} ac
193 193
 					WHERE ac.owner_guid = :user_guid
@@ -197,686 +197,686 @@  discard block
 block discarded – undo
197 197
 							   AND user_guid = :user_guid)
198 198
 				";
199 199
 
200
-				$collections = $this->db->getData($query, null, [
201
-					':user_guid' => $user_guid,
202
-				]);
203
-
204
-				if ($collections) {
205
-					foreach ($collections as $collection) {
206
-						$access_array[] = (int) $collection->id;
207
-					}
208
-				}
209
-
210
-				$ignore_access = elgg_check_access_overrides($user_guid);
211
-
212
-				if ($ignore_access == true) {
213
-					$access_array[] = ACCESS_PRIVATE;
214
-				}
215
-			}
216
-
217
-			if ($this->init_complete) {
218
-				$cache[$hash] = $access_array;
219
-			}
220
-		}
221
-
222
-		$options = [
223
-			'user_id' => $user_guid,
224
-		];
225
-
226
-		// see the warning in the docs for this function about infinite loop potential
227
-		return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
228
-	}
229
-
230
-	/**
231
-	 * Returns the SQL where clause for enforcing read access to data.
232
-	 *
233
-	 * Note that if this code is executed in privileged mode it will return (1=1).
234
-	 *
235
-	 * Otherwise it returns a where clause to retrieve the data that a user has
236
-	 * permission to read.
237
-	 *
238
-	 * Plugin authors can hook into the 'get_sql', 'access' plugin hook to modify,
239
-	 * remove, or add to the where clauses. The plugin hook will pass an array with the current
240
-	 * ors and ands to the function in the form:
241
-	 *  array(
242
-	 *      'ors' => array(),
243
-	 *      'ands' => array()
244
-	 *  )
245
-	 *
246
-	 * The results will be combined into an SQL where clause in the form:
247
-	 *  ((or1 OR or2 OR orN) AND (and1 AND and2 AND andN))
248
-	 *
249
-	 * @param array $options Array in format:
250
-	 *
251
-	 * 	table_alias => STR Optional table alias. This is based on the select and join clauses.
252
-	 *                     Default is 'e'.
253
-	 *
254
-	 *  user_guid => INT Optional GUID for the user that we are retrieving data for.
255
-	 *                   Defaults to the logged in user if null.
256
-	 *                   Passing 0 will build a query for a logged out user (even if there is a logged in user)
257
-	 *
258
-	 *  use_enabled_clause => BOOL Optional. Should we append the enabled clause? The default
259
-	 *                             is set by access_show_hidden_entities().
260
-	 *
261
-	 *  access_column => STR Optional access column name. Default is 'access_id'.
262
-	 *
263
-	 *  owner_guid_column => STR Optional owner_guid column. Default is 'owner_guid'.
264
-	 *
265
-	 *  guid_column => STR Optional guid_column. Default is 'guid'.
266
-	 *
267
-	 * @return string
268
-	 * @access private
269
-	 */
270
-	public function getWhereSql(array $options = []) {
271
-
272
-		$defaults = [
273
-			'table_alias' => 'e',
274
-			'user_guid' => $this->session->getLoggedInUserGuid(),
275
-			'use_enabled_clause' => !access_get_show_hidden_status(),
276
-			'access_column' => 'access_id',
277
-			'owner_guid_column' => 'owner_guid',
278
-			'guid_column' => 'guid',
279
-		];
280
-
281
-		foreach ($options as $key => $value) {
282
-			if (is_null($value)) {
283
-				// remove null values so we don't loose defaults in array_merge
284
-				unset($options[$key]);
285
-			}
286
-		}
287
-
288
-		$options = array_merge($defaults, $options);
289
-
290
-		// just in case someone passes a . at the end
291
-		$options['table_alias'] = rtrim($options['table_alias'], '.');
292
-
293
-		foreach (['table_alias', 'access_column', 'owner_guid_column', 'guid_column'] as $key) {
294
-			$options[$key] = sanitize_string($options[$key]);
295
-		}
296
-		$options['user_guid'] = sanitize_int($options['user_guid'], false);
297
-
298
-		// only add dot if we have an alias or table name
299
-		$table_alias = $options['table_alias'] ? $options['table_alias'] . '.' : '';
300
-
301
-		if (!isset($options['ignore_access'])) {
302
-			$options['ignore_access'] = elgg_check_access_overrides($options['user_guid']);
303
-		}
304
-
305
-		$clauses = [
306
-			'ors' => [],
307
-			'ands' => []
308
-		];
309
-
310
-		$prefix = $this->db->prefix;
311
-
312
-		if ($options['ignore_access']) {
313
-			$clauses['ors']['ignore_access'] = '1 = 1';
314
-		} else if ($options['user_guid']) {
315
-			// include content of user's friends
316
-			$clauses['ors']['friends_access'] = "$table_alias{$options['access_column']} = " . ACCESS_FRIENDS . "
200
+                $collections = $this->db->getData($query, null, [
201
+                    ':user_guid' => $user_guid,
202
+                ]);
203
+
204
+                if ($collections) {
205
+                    foreach ($collections as $collection) {
206
+                        $access_array[] = (int) $collection->id;
207
+                    }
208
+                }
209
+
210
+                $ignore_access = elgg_check_access_overrides($user_guid);
211
+
212
+                if ($ignore_access == true) {
213
+                    $access_array[] = ACCESS_PRIVATE;
214
+                }
215
+            }
216
+
217
+            if ($this->init_complete) {
218
+                $cache[$hash] = $access_array;
219
+            }
220
+        }
221
+
222
+        $options = [
223
+            'user_id' => $user_guid,
224
+        ];
225
+
226
+        // see the warning in the docs for this function about infinite loop potential
227
+        return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
228
+    }
229
+
230
+    /**
231
+     * Returns the SQL where clause for enforcing read access to data.
232
+     *
233
+     * Note that if this code is executed in privileged mode it will return (1=1).
234
+     *
235
+     * Otherwise it returns a where clause to retrieve the data that a user has
236
+     * permission to read.
237
+     *
238
+     * Plugin authors can hook into the 'get_sql', 'access' plugin hook to modify,
239
+     * remove, or add to the where clauses. The plugin hook will pass an array with the current
240
+     * ors and ands to the function in the form:
241
+     *  array(
242
+     *      'ors' => array(),
243
+     *      'ands' => array()
244
+     *  )
245
+     *
246
+     * The results will be combined into an SQL where clause in the form:
247
+     *  ((or1 OR or2 OR orN) AND (and1 AND and2 AND andN))
248
+     *
249
+     * @param array $options Array in format:
250
+     *
251
+     * 	table_alias => STR Optional table alias. This is based on the select and join clauses.
252
+     *                     Default is 'e'.
253
+     *
254
+     *  user_guid => INT Optional GUID for the user that we are retrieving data for.
255
+     *                   Defaults to the logged in user if null.
256
+     *                   Passing 0 will build a query for a logged out user (even if there is a logged in user)
257
+     *
258
+     *  use_enabled_clause => BOOL Optional. Should we append the enabled clause? The default
259
+     *                             is set by access_show_hidden_entities().
260
+     *
261
+     *  access_column => STR Optional access column name. Default is 'access_id'.
262
+     *
263
+     *  owner_guid_column => STR Optional owner_guid column. Default is 'owner_guid'.
264
+     *
265
+     *  guid_column => STR Optional guid_column. Default is 'guid'.
266
+     *
267
+     * @return string
268
+     * @access private
269
+     */
270
+    public function getWhereSql(array $options = []) {
271
+
272
+        $defaults = [
273
+            'table_alias' => 'e',
274
+            'user_guid' => $this->session->getLoggedInUserGuid(),
275
+            'use_enabled_clause' => !access_get_show_hidden_status(),
276
+            'access_column' => 'access_id',
277
+            'owner_guid_column' => 'owner_guid',
278
+            'guid_column' => 'guid',
279
+        ];
280
+
281
+        foreach ($options as $key => $value) {
282
+            if (is_null($value)) {
283
+                // remove null values so we don't loose defaults in array_merge
284
+                unset($options[$key]);
285
+            }
286
+        }
287
+
288
+        $options = array_merge($defaults, $options);
289
+
290
+        // just in case someone passes a . at the end
291
+        $options['table_alias'] = rtrim($options['table_alias'], '.');
292
+
293
+        foreach (['table_alias', 'access_column', 'owner_guid_column', 'guid_column'] as $key) {
294
+            $options[$key] = sanitize_string($options[$key]);
295
+        }
296
+        $options['user_guid'] = sanitize_int($options['user_guid'], false);
297
+
298
+        // only add dot if we have an alias or table name
299
+        $table_alias = $options['table_alias'] ? $options['table_alias'] . '.' : '';
300
+
301
+        if (!isset($options['ignore_access'])) {
302
+            $options['ignore_access'] = elgg_check_access_overrides($options['user_guid']);
303
+        }
304
+
305
+        $clauses = [
306
+            'ors' => [],
307
+            'ands' => []
308
+        ];
309
+
310
+        $prefix = $this->db->prefix;
311
+
312
+        if ($options['ignore_access']) {
313
+            $clauses['ors']['ignore_access'] = '1 = 1';
314
+        } else if ($options['user_guid']) {
315
+            // include content of user's friends
316
+            $clauses['ors']['friends_access'] = "$table_alias{$options['access_column']} = " . ACCESS_FRIENDS . "
317 317
 				AND $table_alias{$options['owner_guid_column']} IN (
318 318
 					SELECT guid_one FROM {$prefix}entity_relationships
319 319
 					WHERE relationship = 'friend' AND guid_two = {$options['user_guid']}
320 320
 				)";
321 321
 
322
-			// include user's content
323
-			$clauses['ors']['owner_access'] = "$table_alias{$options['owner_guid_column']} = {$options['user_guid']}";
324
-		}
325
-
326
-		// include standard accesses (public, logged in, access collections)
327
-		if (!$options['ignore_access']) {
328
-			$access_list = $this->getAccessList($options['user_guid']);
329
-			$clauses['ors']['acl_access'] = "$table_alias{$options['access_column']} IN {$access_list}";
330
-		}
331
-
332
-		if ($options['use_enabled_clause']) {
333
-			$clauses['ands']['use_enabled'] = "{$table_alias}enabled = 'yes'";
334
-		}
335
-
336
-		$clauses = $this->hooks->trigger('get_sql', 'access', $options, $clauses);
337
-
338
-		$clauses_str = '';
339
-		if (is_array($clauses['ors']) && $clauses['ors']) {
340
-			$clauses_str = '(' . implode(' OR ', $clauses['ors']) . ')';
341
-		}
342
-
343
-		if (is_array($clauses['ands']) && $clauses['ands']) {
344
-			if ($clauses_str) {
345
-				$clauses_str .= ' AND ';
346
-			}
347
-			$clauses_str .= '(' . implode(' AND ', $clauses['ands']) . ')';
348
-		}
349
-
350
-		return "($clauses_str)";
351
-	}
352
-
353
-	/**
354
-	 * Can a user access an entity.
355
-	 *
356
-	 * @warning If a logged in user doesn't have access to an entity, the
357
-	 * core engine will not load that entity.
358
-	 *
359
-	 * @tip This is mostly useful for checking if a user other than the logged in
360
-	 * user has access to an entity that is currently loaded.
361
-	 *
362
-	 * @todo This function would be much more useful if we could pass the guid of the
363
-	 * entity to test access for. We need to be able to tell whether the entity exists
364
-	 * and whether the user has access to the entity.
365
-	 *
366
-	 * @param ElggEntity $entity The entity to check access for.
367
-	 * @param ElggUser   $user   Optionally user to check access for. Defaults to
368
-	 *                           logged in user (which is a useless default).
369
-	 *
370
-	 * @return bool
371
-	 */
372
-	public function hasAccessToEntity($entity, $user = null) {
373
-		if (!$entity instanceof \ElggEntity) {
374
-			return false;
375
-		}
376
-
377
-		if ($entity->access_id == ACCESS_PUBLIC) {
378
-			// Public entities are always accessible
379
-			return true;
380
-		}
381
-
382
-		$user_guid = isset($user) ? (int) $user->guid : elgg_get_logged_in_user_guid();
383
-
384
-		if ($user_guid && $user_guid == $entity->owner_guid) {
385
-			// Owners have access to their own content
386
-			return true;
387
-		}
388
-
389
-		if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
390
-			// Existing users have access to entities with logged in access
391
-			return true;
392
-		}
393
-
394
-		// See #7159. Must not allow ignore access to affect query
395
-		$ia = elgg_set_ignore_access(false);
322
+            // include user's content
323
+            $clauses['ors']['owner_access'] = "$table_alias{$options['owner_guid_column']} = {$options['user_guid']}";
324
+        }
325
+
326
+        // include standard accesses (public, logged in, access collections)
327
+        if (!$options['ignore_access']) {
328
+            $access_list = $this->getAccessList($options['user_guid']);
329
+            $clauses['ors']['acl_access'] = "$table_alias{$options['access_column']} IN {$access_list}";
330
+        }
331
+
332
+        if ($options['use_enabled_clause']) {
333
+            $clauses['ands']['use_enabled'] = "{$table_alias}enabled = 'yes'";
334
+        }
335
+
336
+        $clauses = $this->hooks->trigger('get_sql', 'access', $options, $clauses);
337
+
338
+        $clauses_str = '';
339
+        if (is_array($clauses['ors']) && $clauses['ors']) {
340
+            $clauses_str = '(' . implode(' OR ', $clauses['ors']) . ')';
341
+        }
342
+
343
+        if (is_array($clauses['ands']) && $clauses['ands']) {
344
+            if ($clauses_str) {
345
+                $clauses_str .= ' AND ';
346
+            }
347
+            $clauses_str .= '(' . implode(' AND ', $clauses['ands']) . ')';
348
+        }
349
+
350
+        return "($clauses_str)";
351
+    }
352
+
353
+    /**
354
+     * Can a user access an entity.
355
+     *
356
+     * @warning If a logged in user doesn't have access to an entity, the
357
+     * core engine will not load that entity.
358
+     *
359
+     * @tip This is mostly useful for checking if a user other than the logged in
360
+     * user has access to an entity that is currently loaded.
361
+     *
362
+     * @todo This function would be much more useful if we could pass the guid of the
363
+     * entity to test access for. We need to be able to tell whether the entity exists
364
+     * and whether the user has access to the entity.
365
+     *
366
+     * @param ElggEntity $entity The entity to check access for.
367
+     * @param ElggUser   $user   Optionally user to check access for. Defaults to
368
+     *                           logged in user (which is a useless default).
369
+     *
370
+     * @return bool
371
+     */
372
+    public function hasAccessToEntity($entity, $user = null) {
373
+        if (!$entity instanceof \ElggEntity) {
374
+            return false;
375
+        }
376
+
377
+        if ($entity->access_id == ACCESS_PUBLIC) {
378
+            // Public entities are always accessible
379
+            return true;
380
+        }
381
+
382
+        $user_guid = isset($user) ? (int) $user->guid : elgg_get_logged_in_user_guid();
383
+
384
+        if ($user_guid && $user_guid == $entity->owner_guid) {
385
+            // Owners have access to their own content
386
+            return true;
387
+        }
388
+
389
+        if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
390
+            // Existing users have access to entities with logged in access
391
+            return true;
392
+        }
393
+
394
+        // See #7159. Must not allow ignore access to affect query
395
+        $ia = elgg_set_ignore_access(false);
396 396
 		
397
-		$row = $this->entities->getRow($entity->guid, $user_guid);
398
-
399
-		elgg_set_ignore_access($ia);
400
-
401
-		return !empty($row);
402
-	}
403
-
404
-	/**
405
-	 * Returns an array of access permissions that the user is allowed to save content with.
406
-	 * Permissions returned are of the form (id => 'name').
407
-	 *
408
-	 * Example return value in English:
409
-	 * array(
410
-	 *     0 => 'Private',
411
-	 *    -2 => 'Friends',
412
-	 *     1 => 'Logged in users',
413
-	 *     2 => 'Public',
414
-	 *    34 => 'My favorite friends',
415
-	 * );
416
-	 *
417
-	 * Plugin hook of 'access:collections:write', 'user'
418
-	 *
419
-	 * @warning this only returns access collections that the user owns plus the
420
-	 * standard access levels. It does not return access collections that the user
421
-	 * belongs to such as the access collection for a group.
422
-	 *
423
-	 * @param int   $user_guid    The user's GUID.
424
-	 * @param bool  $flush        If this is set to true, this will ignore a cached access array
425
-	 * @param array $input_params Some parameters passed into an input/access view
426
-	 *
427
-	 * @return array List of access permissions
428
-	 */
429
-	public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
430
-		$cache = $this->access_cache;
431
-
432
-		if ($flush) {
433
-			$cache->clear();
434
-		}
435
-
436
-		if ($user_guid == 0) {
437
-			$user_guid = $this->session->getLoggedInUserGuid();
438
-		}
439
-
440
-		$user_guid = (int) $user_guid;
441
-
442
-		$hash = $user_guid . 'get_write_access_array';
443
-
444
-		if ($cache[$hash]) {
445
-			$access_array = $cache[$hash];
446
-		} else {
447
-			// @todo is there such a thing as public write access?
448
-			$access_array = [
449
-				ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
450
-				ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
451
-				ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
452
-			];
453
-
454
-			$collections = $this->getEntityCollections($user_guid);
455
-			if ($collections) {
456
-				foreach ($collections as $collection) {
457
-					$access_array[$collection->id] = $collection->name;
458
-				}
459
-			}
460
-
461
-			if ($this->init_complete) {
462
-				$cache[$hash] = $access_array;
463
-			}
464
-		}
465
-
466
-		$options = [
467
-			'user_id' => $user_guid,
468
-			'input_params' => $input_params,
469
-		];
470
-		return $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);
471
-	}
472
-
473
-	/**
474
-	 * Can the user change this access collection?
475
-	 *
476
-	 * Use the plugin hook of 'access:collections:write', 'user' to change this.
477
-	 * @see get_write_access_array() for details on the hook.
478
-	 *
479
-	 * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
480
-	 *
481
-	 * @see get_write_access_array()
482
-	 *
483
-	 * @param int   $collection_id The collection id
484
-	 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
485
-	 * @return bool
486
-	 */
487
-	public function canEdit($collection_id, $user_guid = null) {
488
-		try {
489
-			$user = $this->entities->getUserForPermissionsCheck($user_guid);
490
-		} catch (UserFetchFailureException $e) {
491
-			return false;
492
-		}
493
-
494
-		$collection = $this->get($collection_id);
495
-
496
-		if (!$user || !$collection) {
497
-			return false;
498
-		}
499
-
500
-		if (elgg_check_access_overrides($user->guid)) {
501
-			return true;
502
-		}
503
-
504
-		$write_access = $this->getWriteAccessArray($user->guid, true);
505
-		return array_key_exists($collection_id, $write_access);
506
-	}
507
-
508
-	/**
509
-	 * Creates a new access collection.
510
-	 *
511
-	 * Access colletions allow plugins and users to create granular access
512
-	 * for entities.
513
-	 *
514
-	 * Triggers plugin hook 'access:collections:addcollection', 'collection'
515
-	 *
516
-	 * @internal Access collections are stored in the access_collections table.
517
-	 * Memberships to collections are in access_collections_membership.
518
-	 *
519
-	 * @param string $name       The name of the collection.
520
-	 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
521
-	 *
522
-	 * @return int|false The collection ID if successful and false on failure.
523
-	 */
524
-	public function create($name, $owner_guid = 0) {
525
-		$name = trim($name);
526
-		if (empty($name)) {
527
-			return false;
528
-		}
529
-
530
-		if ($owner_guid == 0) {
531
-			$owner_guid = $this->session->getLoggedInUserGuid();
532
-		}
533
-
534
-		$query = "
397
+        $row = $this->entities->getRow($entity->guid, $user_guid);
398
+
399
+        elgg_set_ignore_access($ia);
400
+
401
+        return !empty($row);
402
+    }
403
+
404
+    /**
405
+     * Returns an array of access permissions that the user is allowed to save content with.
406
+     * Permissions returned are of the form (id => 'name').
407
+     *
408
+     * Example return value in English:
409
+     * array(
410
+     *     0 => 'Private',
411
+     *    -2 => 'Friends',
412
+     *     1 => 'Logged in users',
413
+     *     2 => 'Public',
414
+     *    34 => 'My favorite friends',
415
+     * );
416
+     *
417
+     * Plugin hook of 'access:collections:write', 'user'
418
+     *
419
+     * @warning this only returns access collections that the user owns plus the
420
+     * standard access levels. It does not return access collections that the user
421
+     * belongs to such as the access collection for a group.
422
+     *
423
+     * @param int   $user_guid    The user's GUID.
424
+     * @param bool  $flush        If this is set to true, this will ignore a cached access array
425
+     * @param array $input_params Some parameters passed into an input/access view
426
+     *
427
+     * @return array List of access permissions
428
+     */
429
+    public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
430
+        $cache = $this->access_cache;
431
+
432
+        if ($flush) {
433
+            $cache->clear();
434
+        }
435
+
436
+        if ($user_guid == 0) {
437
+            $user_guid = $this->session->getLoggedInUserGuid();
438
+        }
439
+
440
+        $user_guid = (int) $user_guid;
441
+
442
+        $hash = $user_guid . 'get_write_access_array';
443
+
444
+        if ($cache[$hash]) {
445
+            $access_array = $cache[$hash];
446
+        } else {
447
+            // @todo is there such a thing as public write access?
448
+            $access_array = [
449
+                ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
450
+                ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
451
+                ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
452
+            ];
453
+
454
+            $collections = $this->getEntityCollections($user_guid);
455
+            if ($collections) {
456
+                foreach ($collections as $collection) {
457
+                    $access_array[$collection->id] = $collection->name;
458
+                }
459
+            }
460
+
461
+            if ($this->init_complete) {
462
+                $cache[$hash] = $access_array;
463
+            }
464
+        }
465
+
466
+        $options = [
467
+            'user_id' => $user_guid,
468
+            'input_params' => $input_params,
469
+        ];
470
+        return $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);
471
+    }
472
+
473
+    /**
474
+     * Can the user change this access collection?
475
+     *
476
+     * Use the plugin hook of 'access:collections:write', 'user' to change this.
477
+     * @see get_write_access_array() for details on the hook.
478
+     *
479
+     * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
480
+     *
481
+     * @see get_write_access_array()
482
+     *
483
+     * @param int   $collection_id The collection id
484
+     * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
485
+     * @return bool
486
+     */
487
+    public function canEdit($collection_id, $user_guid = null) {
488
+        try {
489
+            $user = $this->entities->getUserForPermissionsCheck($user_guid);
490
+        } catch (UserFetchFailureException $e) {
491
+            return false;
492
+        }
493
+
494
+        $collection = $this->get($collection_id);
495
+
496
+        if (!$user || !$collection) {
497
+            return false;
498
+        }
499
+
500
+        if (elgg_check_access_overrides($user->guid)) {
501
+            return true;
502
+        }
503
+
504
+        $write_access = $this->getWriteAccessArray($user->guid, true);
505
+        return array_key_exists($collection_id, $write_access);
506
+    }
507
+
508
+    /**
509
+     * Creates a new access collection.
510
+     *
511
+     * Access colletions allow plugins and users to create granular access
512
+     * for entities.
513
+     *
514
+     * Triggers plugin hook 'access:collections:addcollection', 'collection'
515
+     *
516
+     * @internal Access collections are stored in the access_collections table.
517
+     * Memberships to collections are in access_collections_membership.
518
+     *
519
+     * @param string $name       The name of the collection.
520
+     * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
521
+     *
522
+     * @return int|false The collection ID if successful and false on failure.
523
+     */
524
+    public function create($name, $owner_guid = 0) {
525
+        $name = trim($name);
526
+        if (empty($name)) {
527
+            return false;
528
+        }
529
+
530
+        if ($owner_guid == 0) {
531
+            $owner_guid = $this->session->getLoggedInUserGuid();
532
+        }
533
+
534
+        $query = "
535 535
 			INSERT INTO {$this->table}
536 536
 			SET name = :name,
537 537
 				owner_guid = :owner_guid
538 538
 		";
539 539
 
540
-		$params = [
541
-			':name' => $name,
542
-			':owner_guid' => (int) $owner_guid,
543
-		];
544
-
545
-		$id = $this->db->insertData($query, $params);
546
-		if (!$id) {
547
-			return false;
548
-		}
549
-
550
-		$this->access_cache->clear();
551
-
552
-		$hook_params = [
553
-			'collection_id' => $id,
554
-			'name' => $name,
555
-			'owner_guid' => $owner_guid,
556
-		];
557
-
558
-		if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
559
-			$this->delete($id);
560
-			return false;
561
-		}
562
-
563
-		return $id;
564
-	}
565
-
566
-	/**
567
-	 * Renames an access collection
568
-	 *
569
-	 * @param int    $collection_id ID of the collection
570
-	 * @param string $name          The name of the collection
571
-	 * @return bool
572
-	 */
573
-	public function rename($collection_id, $name) {
574
-
575
-		$query = "
540
+        $params = [
541
+            ':name' => $name,
542
+            ':owner_guid' => (int) $owner_guid,
543
+        ];
544
+
545
+        $id = $this->db->insertData($query, $params);
546
+        if (!$id) {
547
+            return false;
548
+        }
549
+
550
+        $this->access_cache->clear();
551
+
552
+        $hook_params = [
553
+            'collection_id' => $id,
554
+            'name' => $name,
555
+            'owner_guid' => $owner_guid,
556
+        ];
557
+
558
+        if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
559
+            $this->delete($id);
560
+            return false;
561
+        }
562
+
563
+        return $id;
564
+    }
565
+
566
+    /**
567
+     * Renames an access collection
568
+     *
569
+     * @param int    $collection_id ID of the collection
570
+     * @param string $name          The name of the collection
571
+     * @return bool
572
+     */
573
+    public function rename($collection_id, $name) {
574
+
575
+        $query = "
576 576
 			UPDATE {$this->table}
577 577
 			SET name = :name
578 578
 			WHERE id = :id
579 579
 		";
580 580
 
581
-		$params = [
582
-			':name' => $name,
583
-			':id' => (int) $collection_id,
584
-		];
585
-
586
-		if ($this->db->insertData($query, $params)) {
587
-			$this->access_cache->clear();
588
-			return (int) $collection_id;
589
-		}
590
-
591
-		return false;
592
-	}
593
-
594
-
595
-	/**
596
-	 * Updates the membership in an access collection.
597
-	 *
598
-	 * @warning Expects a full list of all members that should
599
-	 * be part of the access collection
600
-	 *
601
-	 * @note This will run all hooks associated with adding or removing
602
-	 * members to access collections.
603
-	 *
604
-	 * @param int   $collection_id ID of the collection.
605
-	 * @param array $new_members   Array of member entities or GUIDs
606
-	 * @return bool
607
-	 */
608
-	public function update($collection_id, array $new_members = []) {
609
-		$acl = $this->get($collection_id);
610
-
611
-		if (!$acl) {
612
-			return false;
613
-		}
581
+        $params = [
582
+            ':name' => $name,
583
+            ':id' => (int) $collection_id,
584
+        ];
585
+
586
+        if ($this->db->insertData($query, $params)) {
587
+            $this->access_cache->clear();
588
+            return (int) $collection_id;
589
+        }
590
+
591
+        return false;
592
+    }
593
+
594
+
595
+    /**
596
+     * Updates the membership in an access collection.
597
+     *
598
+     * @warning Expects a full list of all members that should
599
+     * be part of the access collection
600
+     *
601
+     * @note This will run all hooks associated with adding or removing
602
+     * members to access collections.
603
+     *
604
+     * @param int   $collection_id ID of the collection.
605
+     * @param array $new_members   Array of member entities or GUIDs
606
+     * @return bool
607
+     */
608
+    public function update($collection_id, array $new_members = []) {
609
+        $acl = $this->get($collection_id);
610
+
611
+        if (!$acl) {
612
+            return false;
613
+        }
614 614
 		
615
-		$to_guid = function($elem) {
616
-			if (empty($elem)) {
617
-				return 0;
618
-			}
619
-			if (is_object($elem)) {
620
-				return (int) $elem->guid;
621
-			}
622
-			return (int) $elem;
623
-		};
615
+        $to_guid = function($elem) {
616
+            if (empty($elem)) {
617
+                return 0;
618
+            }
619
+            if (is_object($elem)) {
620
+                return (int) $elem->guid;
621
+            }
622
+            return (int) $elem;
623
+        };
624 624
 		
625
-		$current_members = [];
626
-		$new_members = array_map($to_guid, $new_members);
625
+        $current_members = [];
626
+        $new_members = array_map($to_guid, $new_members);
627 627
 
628
-		$current_members_batch = $this->getMembers($collection_id, [
629
-			'batch' => true,
630
-			'limit' => 0,
631
-			'callback' => false,
632
-		]);
628
+        $current_members_batch = $this->getMembers($collection_id, [
629
+            'batch' => true,
630
+            'limit' => 0,
631
+            'callback' => false,
632
+        ]);
633 633
 
634
-		foreach ($current_members_batch as $row) {
635
-			$current_members[] = $to_guid($row);
636
-		}
634
+        foreach ($current_members_batch as $row) {
635
+            $current_members[] = $to_guid($row);
636
+        }
637 637
 
638
-		$remove_members = array_diff($current_members, $new_members);
639
-		$add_members = array_diff($new_members, $current_members);
638
+        $remove_members = array_diff($current_members, $new_members);
639
+        $add_members = array_diff($new_members, $current_members);
640 640
 
641
-		$result = true;
641
+        $result = true;
642 642
 
643
-		foreach ($add_members as $guid) {
644
-			$result = $result && $this->addUser($guid, $collection_id);
645
-		}
643
+        foreach ($add_members as $guid) {
644
+            $result = $result && $this->addUser($guid, $collection_id);
645
+        }
646 646
 
647
-		foreach ($remove_members as $guid) {
648
-			$result = $result && $this->removeUser($guid, $collection_id);
649
-		}
647
+        foreach ($remove_members as $guid) {
648
+            $result = $result && $this->removeUser($guid, $collection_id);
649
+        }
650 650
 
651
-		$this->access_cache->clear();
651
+        $this->access_cache->clear();
652 652
 
653
-		return $result;
654
-	}
653
+        return $result;
654
+    }
655 655
 
656
-	/**
657
-	 * Deletes a collection and its membership information
658
-	 *
659
-	 * @param int $collection_id ID of the collection
660
-	 * @return bool
661
-	 */
662
-	public function delete($collection_id) {
663
-		$collection_id = (int) $collection_id;
656
+    /**
657
+     * Deletes a collection and its membership information
658
+     *
659
+     * @param int $collection_id ID of the collection
660
+     * @return bool
661
+     */
662
+    public function delete($collection_id) {
663
+        $collection_id = (int) $collection_id;
664 664
 
665
-		$params = [
666
-			'collection_id' => $collection_id,
667
-		];
665
+        $params = [
666
+            'collection_id' => $collection_id,
667
+        ];
668 668
 
669
-		if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) {
670
-			return false;
671
-		}
669
+        if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) {
670
+            return false;
671
+        }
672 672
 
673
-		// Deleting membership doesn't affect result of deleting ACL.
674
-		$query = "
673
+        // Deleting membership doesn't affect result of deleting ACL.
674
+        $query = "
675 675
 			DELETE FROM {$this->membership_table}
676 676
 			WHERE access_collection_id = :access_collection_id
677 677
 		";
678
-		$this->db->deleteData($query, [
679
-			':access_collection_id' => $collection_id,
680
-		]);
678
+        $this->db->deleteData($query, [
679
+            ':access_collection_id' => $collection_id,
680
+        ]);
681 681
 
682
-		$query = "
682
+        $query = "
683 683
 			DELETE FROM {$this->table}
684 684
 			WHERE id = :id
685 685
 		";
686
-		$result = $this->db->deleteData($query, [
687
-			':id' => $collection_id,
688
-		]);
686
+        $result = $this->db->deleteData($query, [
687
+            ':id' => $collection_id,
688
+        ]);
689 689
 
690
-		$this->access_cache->clear();
690
+        $this->access_cache->clear();
691 691
 		
692
-		return (bool) $result;
693
-	}
694
-
695
-	/**
696
-	 * Transforms a database row to an instance of ElggAccessCollection
697
-	 *
698
-	 * @param \stdClass $row Database row
699
-	 * @return \ElggAccessCollection
700
-	 */
701
-	public function rowToElggAccessCollection(\stdClass $row) {
702
-		return new \ElggAccessCollection($row);
703
-	}
704
-
705
-	/**
706
-	 * Get a specified access collection
707
-	 *
708
-	 * @note This doesn't return the members of an access collection,
709
-	 * just the database row of the actual collection.
710
-	 *
711
-	 * @see get_members_of_access_collection()
712
-	 *
713
-	 * @param int $collection_id The collection ID
714
-	 * @return \ElggAccessCollection|false
715
-	 */
716
-	public function get($collection_id) {
717
-
718
-		$callback = [$this, 'rowToElggAccessCollection'];
719
-
720
-		$query = "
692
+        return (bool) $result;
693
+    }
694
+
695
+    /**
696
+     * Transforms a database row to an instance of ElggAccessCollection
697
+     *
698
+     * @param \stdClass $row Database row
699
+     * @return \ElggAccessCollection
700
+     */
701
+    public function rowToElggAccessCollection(\stdClass $row) {
702
+        return new \ElggAccessCollection($row);
703
+    }
704
+
705
+    /**
706
+     * Get a specified access collection
707
+     *
708
+     * @note This doesn't return the members of an access collection,
709
+     * just the database row of the actual collection.
710
+     *
711
+     * @see get_members_of_access_collection()
712
+     *
713
+     * @param int $collection_id The collection ID
714
+     * @return \ElggAccessCollection|false
715
+     */
716
+    public function get($collection_id) {
717
+
718
+        $callback = [$this, 'rowToElggAccessCollection'];
719
+
720
+        $query = "
721 721
 			SELECT * FROM {$this->table}
722 722
 			WHERE id = :id
723 723
 		";
724 724
 
725
-		return $this->db->getDataRow($query, $callback, [
726
-			':id' => (int) $collection_id,
727
-		]);
728
-	}
729
-
730
-	/**
731
-	 * Check if user is already in the collection
732
-	 *
733
-	 * @param int $user_guid     GUID of the user
734
-	 * @param int $collection_id ID of the collection
735
-	 * @return bool
736
-	 */
737
-	public function hasUser($user_guid, $collection_id) {
738
-		$options = [
739
-			'guids' => (int) $user_guid,
740
-			'count' => true,
741
-		];
742
-		return (bool) $this->getMembers($collection_id, $options);
743
-	}
744
-
745
-	/**
746
-	 * Adds a user to an access collection.
747
-	 *
748
-	 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
749
-	 *
750
-	 * @param int $user_guid     GUID of the user to add
751
-	 * @param int $collection_id ID of the collection to add them to
752
-	 * @return bool
753
-	 */
754
-	public function addUser($user_guid, $collection_id) {
755
-
756
-		$collection = $this->get($collection_id);
757
-
758
-		if (!$collection) {
759
-			return false;
760
-		}
761
-
762
-		if (!$this->entities->exists($user_guid)) {
763
-			return false;
764
-		}
765
-
766
-		$hook_params = [
767
-			'collection_id' => $collection->id,
768
-			'user_guid' => (int) $user_guid
769
-		];
770
-
771
-		$result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
772
-		if ($result == false) {
773
-			return false;
774
-		}
775
-
776
-		// if someone tries to insert the same data twice, we do a no-op on duplicate key
777
-		$query = "
725
+        return $this->db->getDataRow($query, $callback, [
726
+            ':id' => (int) $collection_id,
727
+        ]);
728
+    }
729
+
730
+    /**
731
+     * Check if user is already in the collection
732
+     *
733
+     * @param int $user_guid     GUID of the user
734
+     * @param int $collection_id ID of the collection
735
+     * @return bool
736
+     */
737
+    public function hasUser($user_guid, $collection_id) {
738
+        $options = [
739
+            'guids' => (int) $user_guid,
740
+            'count' => true,
741
+        ];
742
+        return (bool) $this->getMembers($collection_id, $options);
743
+    }
744
+
745
+    /**
746
+     * Adds a user to an access collection.
747
+     *
748
+     * Triggers the 'access:collections:add_user', 'collection' plugin hook.
749
+     *
750
+     * @param int $user_guid     GUID of the user to add
751
+     * @param int $collection_id ID of the collection to add them to
752
+     * @return bool
753
+     */
754
+    public function addUser($user_guid, $collection_id) {
755
+
756
+        $collection = $this->get($collection_id);
757
+
758
+        if (!$collection) {
759
+            return false;
760
+        }
761
+
762
+        if (!$this->entities->exists($user_guid)) {
763
+            return false;
764
+        }
765
+
766
+        $hook_params = [
767
+            'collection_id' => $collection->id,
768
+            'user_guid' => (int) $user_guid
769
+        ];
770
+
771
+        $result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
772
+        if ($result == false) {
773
+            return false;
774
+        }
775
+
776
+        // if someone tries to insert the same data twice, we do a no-op on duplicate key
777
+        $query = "
778 778
 			INSERT INTO {$this->membership_table}
779 779
 				SET access_collection_id = :access_collection_id,
780 780
 				    user_guid = :user_guid
781 781
 				ON DUPLICATE KEY UPDATE user_guid = user_guid
782 782
 		";
783 783
 
784
-		$result = $this->db->insertData($query, [
785
-			':access_collection_id' => (int) $collection->id,
786
-			':user_guid' => (int) $user_guid,
787
-		]);
784
+        $result = $this->db->insertData($query, [
785
+            ':access_collection_id' => (int) $collection->id,
786
+            ':user_guid' => (int) $user_guid,
787
+        ]);
788 788
 
789
-		$this->access_cache->clear();
789
+        $this->access_cache->clear();
790 790
 		
791
-		return $result !== false;
792
-	}
793
-
794
-	/**
795
-	 * Removes a user from an access collection.
796
-	 *
797
-	 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
798
-	 *
799
-	 * @param int $user_guid     GUID of the user
800
-	 * @param int $collection_id ID of the collection
801
-	 * @return bool
802
-	 */
803
-	public function removeUser($user_guid, $collection_id) {
804
-
805
-		$params = [
806
-			'collection_id' => (int) $collection_id,
807
-			'user_guid' => (int) $user_guid,
808
-		];
809
-
810
-		if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
811
-			return false;
812
-		}
813
-
814
-		$query = "
791
+        return $result !== false;
792
+    }
793
+
794
+    /**
795
+     * Removes a user from an access collection.
796
+     *
797
+     * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
798
+     *
799
+     * @param int $user_guid     GUID of the user
800
+     * @param int $collection_id ID of the collection
801
+     * @return bool
802
+     */
803
+    public function removeUser($user_guid, $collection_id) {
804
+
805
+        $params = [
806
+            'collection_id' => (int) $collection_id,
807
+            'user_guid' => (int) $user_guid,
808
+        ];
809
+
810
+        if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
811
+            return false;
812
+        }
813
+
814
+        $query = "
815 815
 			DELETE FROM {$this->membership_table}
816 816
 			WHERE access_collection_id = :access_collection_id
817 817
 				AND user_guid = :user_guid
818 818
 		";
819 819
 
820
-		$this->access_cache->clear();
820
+        $this->access_cache->clear();
821 821
 
822
-		return (bool) $this->db->deleteData($query, [
823
-			':access_collection_id' => (int) $collection_id,
824
-			':user_guid' => (int) $user_guid,
825
-		]);
826
-	}
822
+        return (bool) $this->db->deleteData($query, [
823
+            ':access_collection_id' => (int) $collection_id,
824
+            ':user_guid' => (int) $user_guid,
825
+        ]);
826
+    }
827 827
 
828
-	/**
829
-	 * Returns access collections owned by the user
830
-	 *
831
-	 * @param int $owner_guid GUID of the owner
832
-	 * @return ElggAccessCollection[]|false
833
-	 */
834
-	public function getEntityCollections($owner_guid) {
828
+    /**
829
+     * Returns access collections owned by the user
830
+     *
831
+     * @param int $owner_guid GUID of the owner
832
+     * @return ElggAccessCollection[]|false
833
+     */
834
+    public function getEntityCollections($owner_guid) {
835 835
 
836
-		$callback = [$this, 'rowToElggAccessCollection'];
836
+        $callback = [$this, 'rowToElggAccessCollection'];
837 837
 
838
-		$query = "
838
+        $query = "
839 839
 			SELECT * FROM {$this->table}
840 840
 				WHERE owner_guid = :owner_guid
841 841
 				ORDER BY name ASC
842 842
 		";
843 843
 
844
-		$params = [
845
-			':owner_guid' => (int) $owner_guid,
846
-		];
844
+        $params = [
845
+            ':owner_guid' => (int) $owner_guid,
846
+        ];
847 847
 
848
-		return $this->db->getData($query, $callback, $params);
849
-	}
848
+        return $this->db->getData($query, $callback, $params);
849
+    }
850 850
 
851
-	/**
852
-	 * Get members of an access collection
853
-	 *
854
-	 * @param int   $collection_id The collection's ID
855
-	 * @param array $options       Ege* options
856
-	 * @return ElggEntity[]|false
857
-	 */
858
-	public function getMembers($collection_id, array $options = []) {
851
+    /**
852
+     * Get members of an access collection
853
+     *
854
+     * @param int   $collection_id The collection's ID
855
+     * @param array $options       Ege* options
856
+     * @return ElggEntity[]|false
857
+     */
858
+    public function getMembers($collection_id, array $options = []) {
859 859
 
860
-		$options['joins'][] = "JOIN {$this->membership_table} acm";
860
+        $options['joins'][] = "JOIN {$this->membership_table} acm";
861 861
 
862
-		$collection_id = (int) $collection_id;
863
-		$options['wheres'][] = "e.guid = acm.user_guid AND acm.access_collection_id = {$collection_id}";
862
+        $collection_id = (int) $collection_id;
863
+        $options['wheres'][] = "e.guid = acm.user_guid AND acm.access_collection_id = {$collection_id}";
864 864
 
865
-		return $this->entities->getEntities($options);
866
-	}
865
+        return $this->entities->getEntities($options);
866
+    }
867 867
 
868
-	/**
869
-	 * Return an array of collections that the entity is member of
870
-	 *
871
-	 * @param int $member_guid GUID of th member
872
-	 *
873
-	 * @return ElggAccessCollection[]|false
874
-	 */
875
-	public function getCollectionsByMember($member_guid) {
868
+    /**
869
+     * Return an array of collections that the entity is member of
870
+     *
871
+     * @param int $member_guid GUID of th member
872
+     *
873
+     * @return ElggAccessCollection[]|false
874
+     */
875
+    public function getCollectionsByMember($member_guid) {
876 876
 
877
-		$callback = [$this, 'rowToElggAccessCollection'];
877
+        $callback = [$this, 'rowToElggAccessCollection'];
878 878
 
879
-		$query = "
879
+        $query = "
880 880
 			SELECT ac.* FROM {$this->table} ac
881 881
 				JOIN {$this->membership_table} acm
882 882
 					ON ac.id = acm.access_collection_id
@@ -884,58 +884,58 @@  discard block
 block discarded – undo
884 884
 				ORDER BY name ASC
885 885
 		";
886 886
 
887
-		return $this->db->getData($query, $callback, [
888
-			':member_guid' => (int) $member_guid,
889
-		]);
890
-	}
891
-
892
-	/**
893
-	 * Return the name of an ACCESS_* constant or an access collection,
894
-	 * but only if the logged in user owns the access collection or is an admin.
895
-	 * Ownership requirement prevents us from exposing names of access collections
896
-	 * that current user has been added to by other members and may contain
897
-	 * sensitive classification of the current user (e.g. close friends vs acquaintances).
898
-	 *
899
-	 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
900
-	 * or a name of the owned access collection, e.g. 'My work colleagues';
901
-	 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
902
-	 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
903
-	 *
904
-	 * @param int $entity_access_id The entity's access id
905
-	 *
906
-	 * @return string
907
-	 * @since 1.11
908
-	 */
909
-	public function getReadableAccessLevel($entity_access_id) {
910
-		$access = (int) $entity_access_id;
911
-
912
-		$translator = $this->translator;
913
-
914
-		// Check if entity access id is a defined global constant
915
-		$access_array = [
916
-			ACCESS_PRIVATE => $translator->translate("PRIVATE"),
917
-			ACCESS_FRIENDS => $translator->translate("access:friends:label"),
918
-			ACCESS_LOGGED_IN => $translator->translate("LOGGED_IN"),
919
-			ACCESS_PUBLIC => $translator->translate("PUBLIC"),
920
-		];
921
-
922
-		if (array_key_exists($access, $access_array)) {
923
-			return $access_array[$access];
924
-		}
925
-
926
-		// Entity access id is probably a custom access collection
927
-		// Check if the user has write access to it and can see it's label
928
-		// Admins should always be able to see the readable version
929
-		$collection = $this->get($access);
930
-
931
-		$user_guid = $this->session->getLoggedInUserGuid();
887
+        return $this->db->getData($query, $callback, [
888
+            ':member_guid' => (int) $member_guid,
889
+        ]);
890
+    }
891
+
892
+    /**
893
+     * Return the name of an ACCESS_* constant or an access collection,
894
+     * but only if the logged in user owns the access collection or is an admin.
895
+     * Ownership requirement prevents us from exposing names of access collections
896
+     * that current user has been added to by other members and may contain
897
+     * sensitive classification of the current user (e.g. close friends vs acquaintances).
898
+     *
899
+     * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
900
+     * or a name of the owned access collection, e.g. 'My work colleagues';
901
+     * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
902
+     * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
903
+     *
904
+     * @param int $entity_access_id The entity's access id
905
+     *
906
+     * @return string
907
+     * @since 1.11
908
+     */
909
+    public function getReadableAccessLevel($entity_access_id) {
910
+        $access = (int) $entity_access_id;
911
+
912
+        $translator = $this->translator;
913
+
914
+        // Check if entity access id is a defined global constant
915
+        $access_array = [
916
+            ACCESS_PRIVATE => $translator->translate("PRIVATE"),
917
+            ACCESS_FRIENDS => $translator->translate("access:friends:label"),
918
+            ACCESS_LOGGED_IN => $translator->translate("LOGGED_IN"),
919
+            ACCESS_PUBLIC => $translator->translate("PUBLIC"),
920
+        ];
921
+
922
+        if (array_key_exists($access, $access_array)) {
923
+            return $access_array[$access];
924
+        }
925
+
926
+        // Entity access id is probably a custom access collection
927
+        // Check if the user has write access to it and can see it's label
928
+        // Admins should always be able to see the readable version
929
+        $collection = $this->get($access);
930
+
931
+        $user_guid = $this->session->getLoggedInUserGuid();
932 932
 		
933
-		if (!$collection || !$user_guid) {
934
-			// return 'Limited' if there is no logged in user or collection can not be loaded
935
-			return $translator->translate('access:limited:label');
936
-		}
933
+        if (!$collection || !$user_guid) {
934
+            // return 'Limited' if there is no logged in user or collection can not be loaded
935
+            return $translator->translate('access:limited:label');
936
+        }
937 937
 
938
-		return $collection->getDisplayName();
939
-	}
938
+        return $collection->getDisplayName();
939
+    }
940 940
 
941 941
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/I18n/Translator.php 2 patches
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 	/**
79 79
 	 * Get a map of all loaded translations
80 80
 	 *
81
-	 * @return array
81
+	 * @return string
82 82
 	 */
83 83
 	public function getLoadedTranslations() {
84 84
 		return $this->translations;
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
 	 *
479 479
 	 * @param string $language Language
480 480
 	 *
481
-	 * @return int
481
+	 * @return double
482 482
 	 */
483 483
 	public function getLanguageCompleteness($language) {
484 484
 
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
 	/**
580 580
 	 * Returns an array of language codes.
581 581
 	 *
582
-	 * @return array
582
+	 * @return string[]
583 583
 	 */
584 584
 	public static function getAllLanguageCodes() {
585 585
 		return [
Please login to merge, or discard this patch.
Indentation   +729 added lines, -729 removed lines patch added patch discarded remove patch
@@ -12,737 +12,737 @@
 block discarded – undo
12 12
  */
13 13
 class Translator {
14 14
 
15
-	/**
16
-	 * @var Config
17
-	 */
18
-	private $config;
19
-
20
-	/**
21
-	 * @var array
22
-	 */
23
-	private $translations = [];
24
-
25
-	/**
26
-	 * @var bool
27
-	 */
28
-	private $is_initialized = false;
29
-
30
-	/**
31
-	 * @var string
32
-	 */
33
-	private $current_language = null;
34
-
35
-	/**
36
-	 * Paths to scan for autoloading languages.
37
-	 *
38
-	 * Languages are automatically loaded for the site or
39
-	 * user's default language.  Plugins can extend or override strings.
40
-	 * language_paths is an array of paths to scan for PHP files matching
41
-	 * the default language.  The order of paths is determined by the plugin load order,
42
-	 * with later entries overriding earlier.  Language files within these paths are
43
-	 * named as the two-letter ISO 639-1 country codes for the language they represent.
44
-	 *
45
-	 * @link http://en.wikipedia.org/wiki/ISO_639-1
46
-	 *
47
-	 * @var array (paths are keys)
48
-	 */
49
-	private $language_paths = [];
50
-
51
-	/**
52
-	 * @var bool
53
-	 */
54
-	private $was_reloaded = false;
55
-
56
-	/**
57
-	 * @var bool
58
-	 */
59
-	private $loaded_from_cache = false;
60
-
61
-	/**
62
-	 * Constructor
63
-	 *
64
-	 * @param Config $config Elgg config
65
-	 */
66
-	public function __construct(Config $config) {
67
-		$this->config = $config;
68
-		$this->defaultPath = dirname(dirname(dirname(dirname(__DIR__)))) . "/languages/";
69
-	}
70
-
71
-	/**
72
-	 * @return bool
73
-	 */
74
-	public function wasLoadedFromCache() {
75
-		return $this->loaded_from_cache;
76
-	}
77
-
78
-	/**
79
-	 * Get a map of all loaded translations
80
-	 *
81
-	 * @return array
82
-	 */
83
-	public function getLoadedTranslations() {
84
-		return $this->translations;
85
-	}
86
-
87
-	/**
88
-	 * Given a message key, returns an appropriately translated full-text string
89
-	 *
90
-	 * @param string $message_key The short message code
91
-	 * @param array  $args        An array of arguments to pass through vsprintf().
92
-	 * @param string $language    Optionally, the standard language code
93
-	 *                            (defaults to site/user default, then English)
94
-	 *
95
-	 * @return string Either the translated string, the English string,
96
-	 * or the original language string.
97
-	 */
98
-	public function translate($message_key, array $args = [], $language = "") {
99
-		if (!is_string($message_key) || strlen($message_key) < 1) {
100
-			_elgg_services()->logger->warn(
101
-				'$message_key needs to be a string in ' . __METHOD__ . '(), ' . gettype($message_key) . ' provided'
102
-			);
103
-			return '';
104
-		}
15
+    /**
16
+     * @var Config
17
+     */
18
+    private $config;
19
+
20
+    /**
21
+     * @var array
22
+     */
23
+    private $translations = [];
24
+
25
+    /**
26
+     * @var bool
27
+     */
28
+    private $is_initialized = false;
29
+
30
+    /**
31
+     * @var string
32
+     */
33
+    private $current_language = null;
34
+
35
+    /**
36
+     * Paths to scan for autoloading languages.
37
+     *
38
+     * Languages are automatically loaded for the site or
39
+     * user's default language.  Plugins can extend or override strings.
40
+     * language_paths is an array of paths to scan for PHP files matching
41
+     * the default language.  The order of paths is determined by the plugin load order,
42
+     * with later entries overriding earlier.  Language files within these paths are
43
+     * named as the two-letter ISO 639-1 country codes for the language they represent.
44
+     *
45
+     * @link http://en.wikipedia.org/wiki/ISO_639-1
46
+     *
47
+     * @var array (paths are keys)
48
+     */
49
+    private $language_paths = [];
50
+
51
+    /**
52
+     * @var bool
53
+     */
54
+    private $was_reloaded = false;
55
+
56
+    /**
57
+     * @var bool
58
+     */
59
+    private $loaded_from_cache = false;
60
+
61
+    /**
62
+     * Constructor
63
+     *
64
+     * @param Config $config Elgg config
65
+     */
66
+    public function __construct(Config $config) {
67
+        $this->config = $config;
68
+        $this->defaultPath = dirname(dirname(dirname(dirname(__DIR__)))) . "/languages/";
69
+    }
70
+
71
+    /**
72
+     * @return bool
73
+     */
74
+    public function wasLoadedFromCache() {
75
+        return $this->loaded_from_cache;
76
+    }
77
+
78
+    /**
79
+     * Get a map of all loaded translations
80
+     *
81
+     * @return array
82
+     */
83
+    public function getLoadedTranslations() {
84
+        return $this->translations;
85
+    }
86
+
87
+    /**
88
+     * Given a message key, returns an appropriately translated full-text string
89
+     *
90
+     * @param string $message_key The short message code
91
+     * @param array  $args        An array of arguments to pass through vsprintf().
92
+     * @param string $language    Optionally, the standard language code
93
+     *                            (defaults to site/user default, then English)
94
+     *
95
+     * @return string Either the translated string, the English string,
96
+     * or the original language string.
97
+     */
98
+    public function translate($message_key, array $args = [], $language = "") {
99
+        if (!is_string($message_key) || strlen($message_key) < 1) {
100
+            _elgg_services()->logger->warn(
101
+                '$message_key needs to be a string in ' . __METHOD__ . '(), ' . gettype($message_key) . ' provided'
102
+            );
103
+            return '';
104
+        }
105 105
 		
106
-		if ($this->current_language === null) {
107
-			$this->current_language = $this->getCurrentLanguage();
108
-		}
109
-		if (!$language) {
110
-			$language = $this->current_language;
111
-		}
112
-
113
-		$this->ensureTranslationsLoaded($language);
114
-
115
-		$notice = '';
116
-		$string = $message_key;
117
-
118
-		// avoid dupes without overhead of array_unique
119
-		$langs[$language] = true;
120
-		$langs['en'] = true;
121
-
122
-		foreach (array_keys($langs) as $try_lang) {
123
-			if (isset($this->translations[$try_lang][$message_key])) {
124
-				$string = $this->translations[$try_lang][$message_key];
125
-
126
-				// only pass through if we have arguments to allow backward compatibility
127
-				// with manual sprintf() calls.
128
-				if ($args) {
129
-					$string = vsprintf($string, $args);
130
-				}
131
-
132
-				break;
133
-			} else {
134
-				$notice = sprintf(
135
-					'Missing %s translation for "%s" language key',
136
-					($try_lang === 'en') ? 'English' : $try_lang,
137
-					$message_key
138
-				);
139
-			}
140
-		}
141
-
142
-		if ($notice) {
143
-			_elgg_services()->logger->notice($notice);
144
-		}
145
-
146
-		return $string;
147
-	}
148
-
149
-	/**
150
-	 * Add a translation.
151
-	 *
152
-	 * Translations are arrays in the Zend Translation array format, eg:
153
-	 *
154
-	 *	$english = array('message1' => 'message1', 'message2' => 'message2');
155
-	 *  $german = array('message1' => 'Nachricht1','message2' => 'Nachricht2');
156
-	 *
157
-	 * @param string $country_code   Standard country code (eg 'en', 'nl', 'es')
158
-	 * @param array  $language_array Formatted array of strings
159
-	 *
160
-	 * @return bool Depending on success
161
-	 */
162
-	public function addTranslation($country_code, $language_array) {
163
-		$country_code = strtolower($country_code);
164
-		$country_code = trim($country_code);
165
-
166
-		if (!is_array($language_array) || $country_code === "") {
167
-			return false;
168
-		}
169
-
170
-		if (count($language_array) > 0) {
171
-			if (!isset($this->translations[$country_code])) {
172
-				$this->translations[$country_code] = $language_array;
173
-			} else {
174
-				$this->translations[$country_code] = $language_array + $this->translations[$country_code];
175
-			}
176
-		}
177
-
178
-		return true;
179
-	}
180
-
181
-	/**
182
-	 * Get the current system/user language or "en".
183
-	 *
184
-	 * @return string The language code for the site/user or "en" if not set
185
-	 */
186
-	public function getCurrentLanguage() {
187
-		$language = $this->detectLanguage();
188
-
189
-		if (!$language) {
190
-			$language = 'en';
191
-		}
192
-
193
-		return $language;
194
-	}
195
-
196
-	/**
197
-	 * Detect the current system/user language or false.
198
-	 *
199
-	 * @return string The language code (eg "en") or false if not set
200
-	 */
201
-	public function detectLanguage() {
202
-		$url_lang = _elgg_services()->input->get('hl');
203
-		if ($url_lang) {
204
-			return $url_lang;
205
-		}
206
-
207
-		$user = _elgg_services()->session->getLoggedInUser();
208
-		$language = false;
209
-
210
-		if (($user) && ($user->language)) {
211
-			$language = $user->language;
212
-		}
213
-
214
-		if (!$language) {
215
-			$site_language = $this->config->language;
216
-			if ($site_language) {
217
-				$language = $site_language;
218
-			}
219
-		}
220
-
221
-		return $language ? $language : false;
222
-	}
223
-
224
-	/**
225
-	 * Load both core and plugin translations
226
-	 *
227
-	 * By default this loads only English and the language of the logged
228
-	 * in user.
229
-	 *
230
-	 * The optional $language argument can be used to load translations
231
-	 * on-demand in case we need to translate something to a language not
232
-	 * loaded by default for the current request.
233
-	 *
234
-	 * @param string $language Language code
235
-	 * @access private
236
-	 */
237
-	public function loadTranslations($language = null) {
238
-		if (elgg_is_system_cache_enabled()) {
239
-			$loaded = true;
240
-
241
-			if ($language) {
242
-				$languages = [$language];
243
-			} else {
244
-				$languages = array_unique(['en', $this->getCurrentLanguage()]);
245
-			}
246
-
247
-			foreach ($languages as $language) {
248
-				$data = elgg_load_system_cache("$language.lang");
249
-				if ($data) {
250
-					$this->addTranslation($language, unserialize($data));
251
-				} else {
252
-					$loaded = false;
253
-				}
254
-			}
255
-
256
-			if ($loaded) {
257
-				$this->loaded_from_cache = true;
258
-				$this->language_paths[$this->defaultPath] = true;
259
-				$this->is_initialized = true;
260
-				return;
261
-			}
262
-		}
263
-
264
-		// load core translations from languages directory
265
-		$this->registerTranslations($this->defaultPath, false, $language);
266
-
267
-		// Plugin translation have already been loaded for the default
268
-		// languages by ElggApplication::bootCore(), so there's no need
269
-		// to continue unless loading a specific language on-demand
270
-		if ($language) {
271
-			$this->loadPluginTranslations($language);
272
-		}
273
-	}
274
-
275
-	/**
276
-	 * Load plugin translations for a language
277
-	 *
278
-	 * This is needed only if the current request uses a language
279
-	 * that is neither English of the same as the language of the
280
-	 * logged in user.
281
-	 *
282
-	 * @param string $language Language code
283
-	 * @return void
284
-	 * @throws \PluginException
285
-	 */
286
-	private function loadPluginTranslations($language) {
287
-		// Get active plugins
288
-		$plugins = _elgg_services()->plugins->find('active');
289
-
290
-		if (!$plugins) {
291
-			// Active plugins were not found, so no need to register plugin translations
292
-			return;
293
-		}
294
-
295
-		foreach ($plugins as $plugin) {
296
-			$languages_path = "{$plugin->getPath()}languages/";
297
-
298
-			if (!is_dir($languages_path)) {
299
-				// This plugin doesn't have anything to translate
300
-				continue;
301
-			}
302
-
303
-			$language_file = "{$languages_path}{$language}.php";
304
-
305
-			if (!file_exists($language_file)) {
306
-				// This plugin doesn't have translations for the requested language
307
-
308
-				$name = $plugin->getDisplayName();
309
-				_elgg_services()->logger->notice("Plugin $name is missing translations for $language language");
310
-
311
-				continue;
312
-			}
313
-
314
-			// Register translations from the plugin languages directory
315
-			if (!$this->registerTranslations($languages_path, false, $language)) {
316
-				throw new \PluginException(sprintf('Cannot register languages for plugin %s (guid: %s) at %s.',
317
-					[$plugin->getID(), $plugin->guid, $languages_path]));
318
-			}
319
-		}
320
-	}
321
-
322
-	/**
323
-	 * Registers translations in a directory assuming the standard plugin layout.
324
-	 *
325
-	 * @param string $path Without the trailing slash.
326
-	 *
327
-	 * @return bool Success
328
-	 */
329
-	public function registerPluginTranslations($path) {
330
-		$languages_path = rtrim($path, "\\/") . "/languages";
331
-
332
-		// don't need to have translations
333
-		if (!is_dir($languages_path)) {
334
-			return true;
335
-		}
336
-
337
-		return $this->registerTranslations($languages_path);
338
-	}
339
-
340
-	/**
341
-	 * When given a full path, finds translation files and loads them
342
-	 *
343
-	 * @param string $path     Full path
344
-	 * @param bool   $load_all If true all languages are loaded, if
345
-	 *                         false only the current language + en are loaded
346
-	 * @param string $language Language code
347
-	 *
348
-	 * @return bool success
349
-	 */
350
-	public function registerTranslations($path, $load_all = false, $language = null) {
351
-		$path = sanitise_filepath($path);
352
-
353
-		// Make a note of this path just in case we need to register this language later
354
-		$this->language_paths[$path] = true;
355
-		$this->is_initialized = true;
356
-
357
-		_elgg_services()->logger->info("Translations loaded from: $path");
358
-
359
-		if ($language) {
360
-			$load_language_files = ["$language.php"];
361
-			$load_all = false;
362
-		} else {
363
-			// Get the current language based on site defaults and user preference
364
-			$current_language = $this->getCurrentLanguage();
365
-
366
-			$load_language_files = [
367
-				'en.php',
368
-				"$current_language.php"
369
-			];
370
-
371
-			$load_language_files = array_unique($load_language_files);
372
-		}
373
-
374
-		$handle = opendir($path);
375
-		if (!$handle) {
376
-			_elgg_services()->logger->error("Could not open language path: $path");
377
-			return false;
378
-		}
379
-
380
-		$return = true;
381
-		while (false !== ($language_file = readdir($handle))) {
382
-			// ignore bad files
383
-			if (substr($language_file, 0, 1) == '.' || substr($language_file, -4) !== '.php') {
384
-				continue;
385
-			}
386
-
387
-			if (in_array($language_file, $load_language_files) || $load_all) {
388
-				$result = (include $path . $language_file);
389
-				if ($result === false) {
390
-					$return = false;
391
-					continue;
392
-				} elseif (is_array($result)) {
393
-					$this->addTranslation(basename($language_file, '.php'), $result);
394
-				}
395
-			}
396
-		}
397
-
398
-		return $return;
399
-	}
400
-
401
-	/**
402
-	 * Reload all translations from all registered paths.
403
-	 *
404
-	 * This is only called by functions which need to know all possible translations.
405
-	 *
406
-	 * @todo Better on demand loading based on language_paths array
407
-	 *
408
-	 * @return void
409
-	 */
410
-	public function reloadAllTranslations() {
411
-		if ($this->was_reloaded) {
412
-			return;
413
-		}
414
-
415
-		if ($this->loaded_from_cache) {
416
-			$cache = elgg_get_system_cache();
417
-			$cache_dir = $cache->getVariable("cache_path");
418
-			$filenames = elgg_get_file_list($cache_dir, [], [], [".lang"]);
419
-			foreach ($filenames as $filename) {
420
-				// Look for files matching for example 'en.lang', 'cmn.lang' or 'pt_br.lang'.
421
-				// Note that this regex is just for the system cache. The original language
422
-				// files are allowed to have uppercase letters (e.g. pt_BR.php).
423
-				if (preg_match('/(([a-z]{2,3})(_[a-z]{2})?)\.lang$/', $filename, $matches)) {
424
-					$language = $matches[1];
425
-					$data = elgg_load_system_cache("$language.lang");
426
-					if ($data) {
427
-						$this->addTranslation($language, unserialize($data));
428
-					}
429
-				}
430
-			}
431
-		} else {
432
-			foreach (array_keys($this->language_paths) as $path) {
433
-				$this->registerTranslations($path, true);
434
-			}
435
-		}
436
-
437
-		$this->was_reloaded = true;
438
-	}
439
-
440
-	/**
441
-	 * Return an array of installed translations as an associative
442
-	 * array "two letter code" => "native language name".
443
-	 *
444
-	 * @return array
445
-	 */
446
-	public function getInstalledTranslations() {
447
-		// Ensure that all possible translations are loaded
448
-		$this->reloadAllTranslations();
449
-
450
-		$installed = [];
451
-
452
-		$admin_logged_in = _elgg_services()->session->isAdminLoggedIn();
453
-
454
-		foreach ($this->translations as $k => $v) {
455
-			if ($this->languageKeyExists($k, $k)) {
456
-				$lang = $this->translate($k, [], $k);
457
-			} else {
458
-				$lang = $this->translate($k);
459
-			}
106
+        if ($this->current_language === null) {
107
+            $this->current_language = $this->getCurrentLanguage();
108
+        }
109
+        if (!$language) {
110
+            $language = $this->current_language;
111
+        }
112
+
113
+        $this->ensureTranslationsLoaded($language);
114
+
115
+        $notice = '';
116
+        $string = $message_key;
117
+
118
+        // avoid dupes without overhead of array_unique
119
+        $langs[$language] = true;
120
+        $langs['en'] = true;
121
+
122
+        foreach (array_keys($langs) as $try_lang) {
123
+            if (isset($this->translations[$try_lang][$message_key])) {
124
+                $string = $this->translations[$try_lang][$message_key];
125
+
126
+                // only pass through if we have arguments to allow backward compatibility
127
+                // with manual sprintf() calls.
128
+                if ($args) {
129
+                    $string = vsprintf($string, $args);
130
+                }
131
+
132
+                break;
133
+            } else {
134
+                $notice = sprintf(
135
+                    'Missing %s translation for "%s" language key',
136
+                    ($try_lang === 'en') ? 'English' : $try_lang,
137
+                    $message_key
138
+                );
139
+            }
140
+        }
141
+
142
+        if ($notice) {
143
+            _elgg_services()->logger->notice($notice);
144
+        }
145
+
146
+        return $string;
147
+    }
148
+
149
+    /**
150
+     * Add a translation.
151
+     *
152
+     * Translations are arrays in the Zend Translation array format, eg:
153
+     *
154
+     *	$english = array('message1' => 'message1', 'message2' => 'message2');
155
+     *  $german = array('message1' => 'Nachricht1','message2' => 'Nachricht2');
156
+     *
157
+     * @param string $country_code   Standard country code (eg 'en', 'nl', 'es')
158
+     * @param array  $language_array Formatted array of strings
159
+     *
160
+     * @return bool Depending on success
161
+     */
162
+    public function addTranslation($country_code, $language_array) {
163
+        $country_code = strtolower($country_code);
164
+        $country_code = trim($country_code);
165
+
166
+        if (!is_array($language_array) || $country_code === "") {
167
+            return false;
168
+        }
169
+
170
+        if (count($language_array) > 0) {
171
+            if (!isset($this->translations[$country_code])) {
172
+                $this->translations[$country_code] = $language_array;
173
+            } else {
174
+                $this->translations[$country_code] = $language_array + $this->translations[$country_code];
175
+            }
176
+        }
177
+
178
+        return true;
179
+    }
180
+
181
+    /**
182
+     * Get the current system/user language or "en".
183
+     *
184
+     * @return string The language code for the site/user or "en" if not set
185
+     */
186
+    public function getCurrentLanguage() {
187
+        $language = $this->detectLanguage();
188
+
189
+        if (!$language) {
190
+            $language = 'en';
191
+        }
192
+
193
+        return $language;
194
+    }
195
+
196
+    /**
197
+     * Detect the current system/user language or false.
198
+     *
199
+     * @return string The language code (eg "en") or false if not set
200
+     */
201
+    public function detectLanguage() {
202
+        $url_lang = _elgg_services()->input->get('hl');
203
+        if ($url_lang) {
204
+            return $url_lang;
205
+        }
206
+
207
+        $user = _elgg_services()->session->getLoggedInUser();
208
+        $language = false;
209
+
210
+        if (($user) && ($user->language)) {
211
+            $language = $user->language;
212
+        }
213
+
214
+        if (!$language) {
215
+            $site_language = $this->config->language;
216
+            if ($site_language) {
217
+                $language = $site_language;
218
+            }
219
+        }
220
+
221
+        return $language ? $language : false;
222
+    }
223
+
224
+    /**
225
+     * Load both core and plugin translations
226
+     *
227
+     * By default this loads only English and the language of the logged
228
+     * in user.
229
+     *
230
+     * The optional $language argument can be used to load translations
231
+     * on-demand in case we need to translate something to a language not
232
+     * loaded by default for the current request.
233
+     *
234
+     * @param string $language Language code
235
+     * @access private
236
+     */
237
+    public function loadTranslations($language = null) {
238
+        if (elgg_is_system_cache_enabled()) {
239
+            $loaded = true;
240
+
241
+            if ($language) {
242
+                $languages = [$language];
243
+            } else {
244
+                $languages = array_unique(['en', $this->getCurrentLanguage()]);
245
+            }
246
+
247
+            foreach ($languages as $language) {
248
+                $data = elgg_load_system_cache("$language.lang");
249
+                if ($data) {
250
+                    $this->addTranslation($language, unserialize($data));
251
+                } else {
252
+                    $loaded = false;
253
+                }
254
+            }
255
+
256
+            if ($loaded) {
257
+                $this->loaded_from_cache = true;
258
+                $this->language_paths[$this->defaultPath] = true;
259
+                $this->is_initialized = true;
260
+                return;
261
+            }
262
+        }
263
+
264
+        // load core translations from languages directory
265
+        $this->registerTranslations($this->defaultPath, false, $language);
266
+
267
+        // Plugin translation have already been loaded for the default
268
+        // languages by ElggApplication::bootCore(), so there's no need
269
+        // to continue unless loading a specific language on-demand
270
+        if ($language) {
271
+            $this->loadPluginTranslations($language);
272
+        }
273
+    }
274
+
275
+    /**
276
+     * Load plugin translations for a language
277
+     *
278
+     * This is needed only if the current request uses a language
279
+     * that is neither English of the same as the language of the
280
+     * logged in user.
281
+     *
282
+     * @param string $language Language code
283
+     * @return void
284
+     * @throws \PluginException
285
+     */
286
+    private function loadPluginTranslations($language) {
287
+        // Get active plugins
288
+        $plugins = _elgg_services()->plugins->find('active');
289
+
290
+        if (!$plugins) {
291
+            // Active plugins were not found, so no need to register plugin translations
292
+            return;
293
+        }
294
+
295
+        foreach ($plugins as $plugin) {
296
+            $languages_path = "{$plugin->getPath()}languages/";
297
+
298
+            if (!is_dir($languages_path)) {
299
+                // This plugin doesn't have anything to translate
300
+                continue;
301
+            }
302
+
303
+            $language_file = "{$languages_path}{$language}.php";
304
+
305
+            if (!file_exists($language_file)) {
306
+                // This plugin doesn't have translations for the requested language
307
+
308
+                $name = $plugin->getDisplayName();
309
+                _elgg_services()->logger->notice("Plugin $name is missing translations for $language language");
310
+
311
+                continue;
312
+            }
313
+
314
+            // Register translations from the plugin languages directory
315
+            if (!$this->registerTranslations($languages_path, false, $language)) {
316
+                throw new \PluginException(sprintf('Cannot register languages for plugin %s (guid: %s) at %s.',
317
+                    [$plugin->getID(), $plugin->guid, $languages_path]));
318
+            }
319
+        }
320
+    }
321
+
322
+    /**
323
+     * Registers translations in a directory assuming the standard plugin layout.
324
+     *
325
+     * @param string $path Without the trailing slash.
326
+     *
327
+     * @return bool Success
328
+     */
329
+    public function registerPluginTranslations($path) {
330
+        $languages_path = rtrim($path, "\\/") . "/languages";
331
+
332
+        // don't need to have translations
333
+        if (!is_dir($languages_path)) {
334
+            return true;
335
+        }
336
+
337
+        return $this->registerTranslations($languages_path);
338
+    }
339
+
340
+    /**
341
+     * When given a full path, finds translation files and loads them
342
+     *
343
+     * @param string $path     Full path
344
+     * @param bool   $load_all If true all languages are loaded, if
345
+     *                         false only the current language + en are loaded
346
+     * @param string $language Language code
347
+     *
348
+     * @return bool success
349
+     */
350
+    public function registerTranslations($path, $load_all = false, $language = null) {
351
+        $path = sanitise_filepath($path);
352
+
353
+        // Make a note of this path just in case we need to register this language later
354
+        $this->language_paths[$path] = true;
355
+        $this->is_initialized = true;
356
+
357
+        _elgg_services()->logger->info("Translations loaded from: $path");
358
+
359
+        if ($language) {
360
+            $load_language_files = ["$language.php"];
361
+            $load_all = false;
362
+        } else {
363
+            // Get the current language based on site defaults and user preference
364
+            $current_language = $this->getCurrentLanguage();
365
+
366
+            $load_language_files = [
367
+                'en.php',
368
+                "$current_language.php"
369
+            ];
370
+
371
+            $load_language_files = array_unique($load_language_files);
372
+        }
373
+
374
+        $handle = opendir($path);
375
+        if (!$handle) {
376
+            _elgg_services()->logger->error("Could not open language path: $path");
377
+            return false;
378
+        }
379
+
380
+        $return = true;
381
+        while (false !== ($language_file = readdir($handle))) {
382
+            // ignore bad files
383
+            if (substr($language_file, 0, 1) == '.' || substr($language_file, -4) !== '.php') {
384
+                continue;
385
+            }
386
+
387
+            if (in_array($language_file, $load_language_files) || $load_all) {
388
+                $result = (include $path . $language_file);
389
+                if ($result === false) {
390
+                    $return = false;
391
+                    continue;
392
+                } elseif (is_array($result)) {
393
+                    $this->addTranslation(basename($language_file, '.php'), $result);
394
+                }
395
+            }
396
+        }
397
+
398
+        return $return;
399
+    }
400
+
401
+    /**
402
+     * Reload all translations from all registered paths.
403
+     *
404
+     * This is only called by functions which need to know all possible translations.
405
+     *
406
+     * @todo Better on demand loading based on language_paths array
407
+     *
408
+     * @return void
409
+     */
410
+    public function reloadAllTranslations() {
411
+        if ($this->was_reloaded) {
412
+            return;
413
+        }
414
+
415
+        if ($this->loaded_from_cache) {
416
+            $cache = elgg_get_system_cache();
417
+            $cache_dir = $cache->getVariable("cache_path");
418
+            $filenames = elgg_get_file_list($cache_dir, [], [], [".lang"]);
419
+            foreach ($filenames as $filename) {
420
+                // Look for files matching for example 'en.lang', 'cmn.lang' or 'pt_br.lang'.
421
+                // Note that this regex is just for the system cache. The original language
422
+                // files are allowed to have uppercase letters (e.g. pt_BR.php).
423
+                if (preg_match('/(([a-z]{2,3})(_[a-z]{2})?)\.lang$/', $filename, $matches)) {
424
+                    $language = $matches[1];
425
+                    $data = elgg_load_system_cache("$language.lang");
426
+                    if ($data) {
427
+                        $this->addTranslation($language, unserialize($data));
428
+                    }
429
+                }
430
+            }
431
+        } else {
432
+            foreach (array_keys($this->language_paths) as $path) {
433
+                $this->registerTranslations($path, true);
434
+            }
435
+        }
436
+
437
+        $this->was_reloaded = true;
438
+    }
439
+
440
+    /**
441
+     * Return an array of installed translations as an associative
442
+     * array "two letter code" => "native language name".
443
+     *
444
+     * @return array
445
+     */
446
+    public function getInstalledTranslations() {
447
+        // Ensure that all possible translations are loaded
448
+        $this->reloadAllTranslations();
449
+
450
+        $installed = [];
451
+
452
+        $admin_logged_in = _elgg_services()->session->isAdminLoggedIn();
453
+
454
+        foreach ($this->translations as $k => $v) {
455
+            if ($this->languageKeyExists($k, $k)) {
456
+                $lang = $this->translate($k, [], $k);
457
+            } else {
458
+                $lang = $this->translate($k);
459
+            }
460 460
 			
461
-			$installed[$k] = $lang;
461
+            $installed[$k] = $lang;
462 462
 			
463
-			if (!$admin_logged_in || ($k === 'en')) {
464
-				continue;
465
-			}
463
+            if (!$admin_logged_in || ($k === 'en')) {
464
+                continue;
465
+            }
466 466
 			
467
-			$completeness = $this->getLanguageCompleteness($k);
468
-			if ($completeness < 100) {
469
-				$installed[$k] .= " (" . $completeness . "% " . $this->translate('complete') . ")";
470
-			}
471
-		}
472
-
473
-		return $installed;
474
-	}
475
-
476
-	/**
477
-	 * Return the level of completeness for a given language code (compared to english)
478
-	 *
479
-	 * @param string $language Language
480
-	 *
481
-	 * @return int
482
-	 */
483
-	public function getLanguageCompleteness($language) {
484
-
485
-
486
-		// Ensure that all possible translations are loaded
487
-		$this->reloadAllTranslations();
488
-
489
-		$language = sanitise_string($language);
490
-
491
-		$en = count($this->translations['en']);
492
-
493
-		$missing = $this->getMissingLanguageKeys($language);
494
-		if ($missing) {
495
-			$missing = count($missing);
496
-		} else {
497
-			$missing = 0;
498
-		}
499
-
500
-		$lang = $en - $missing;
501
-
502
-		return round(($lang / $en) * 100, 2);
503
-	}
504
-
505
-	/**
506
-	 * Return the translation keys missing from a given language,
507
-	 * or those that are identical to the english version.
508
-	 *
509
-	 * @param string $language The language
510
-	 *
511
-	 * @return mixed
512
-	 */
513
-	public function getMissingLanguageKeys($language) {
514
-
515
-
516
-		// Ensure that all possible translations are loaded
517
-		$this->reloadAllTranslations();
518
-
519
-		$missing = [];
520
-
521
-		foreach ($this->translations['en'] as $k => $v) {
522
-			if ((!isset($this->translations[$language][$k]))
523
-			|| ($this->translations[$language][$k] == $this->translations['en'][$k])) {
524
-				$missing[] = $k;
525
-			}
526
-		}
527
-
528
-		if (count($missing)) {
529
-			return $missing;
530
-		}
531
-
532
-		return false;
533
-	}
534
-
535
-	/**
536
-	 * Check if a given language key exists
537
-	 *
538
-	 * @param string $key      The translation key
539
-	 * @param string $language The specific language to check
540
-	 *
541
-	 * @return bool
542
-	 * @since 1.11
543
-	 */
544
-	function languageKeyExists($key, $language = 'en') {
545
-		if (empty($key)) {
546
-			return false;
547
-		}
548
-
549
-		$this->ensureTranslationsLoaded($language);
550
-
551
-		if (!array_key_exists($language, $this->translations)) {
552
-			return false;
553
-		}
554
-
555
-		return array_key_exists($key, $this->translations[$language]);
556
-	}
557
-
558
-	/**
559
-	 * Make sure translations are loaded
560
-	 *
561
-	 * @param string $language Language
562
-	 * @return void
563
-	 */
564
-	private function ensureTranslationsLoaded($language) {
565
-		if (!$this->is_initialized) {
566
-			// this means we probably had an exception before translations were initialized
567
-			$this->registerTranslations($this->defaultPath);
568
-		}
569
-
570
-		if (!isset($this->translations[$language])) {
571
-			// The language being requested is not the same as the language of the
572
-			// logged in user, so we will have to load it separately. (Most likely
573
-			// we're sending a notification and the recipient is using a different
574
-			// language than the logged in user.)
575
-			$this->loadTranslations($language);
576
-		}
577
-	}
578
-
579
-	/**
580
-	 * Returns an array of language codes.
581
-	 *
582
-	 * @return array
583
-	 */
584
-	public static function getAllLanguageCodes() {
585
-		return [
586
-			"aa", // "Afar"
587
-			"ab", // "Abkhazian"
588
-			"af", // "Afrikaans"
589
-			"am", // "Amharic"
590
-			"ar", // "Arabic"
591
-			"as", // "Assamese"
592
-			"ay", // "Aymara"
593
-			"az", // "Azerbaijani"
594
-			"ba", // "Bashkir"
595
-			"be", // "Byelorussian"
596
-			"bg", // "Bulgarian"
597
-			"bh", // "Bihari"
598
-			"bi", // "Bislama"
599
-			"bn", // "Bengali; Bangla"
600
-			"bo", // "Tibetan"
601
-			"br", // "Breton"
602
-			"ca", // "Catalan"
603
-			"cmn", // "Mandarin Chinese" // ISO 639-3
604
-			"co", // "Corsican"
605
-			"cs", // "Czech"
606
-			"cy", // "Welsh"
607
-			"da", // "Danish"
608
-			"de", // "German"
609
-			"dz", // "Bhutani"
610
-			"el", // "Greek"
611
-			"en", // "English"
612
-			"eo", // "Esperanto"
613
-			"es", // "Spanish"
614
-			"et", // "Estonian"
615
-			"eu", // "Basque"
616
-			"eu_es", // "Basque (Spain)"
617
-			"fa", // "Persian"
618
-			"fi", // "Finnish"
619
-			"fj", // "Fiji"
620
-			"fo", // "Faeroese"
621
-			"fr", // "French"
622
-			"fy", // "Frisian"
623
-			"ga", // "Irish"
624
-			"gd", // "Scots / Gaelic"
625
-			"gl", // "Galician"
626
-			"gn", // "Guarani"
627
-			"gu", // "Gujarati"
628
-			"he", // "Hebrew"
629
-			"ha", // "Hausa"
630
-			"hi", // "Hindi"
631
-			"hr", // "Croatian"
632
-			"hu", // "Hungarian"
633
-			"hy", // "Armenian"
634
-			"ia", // "Interlingua"
635
-			"id", // "Indonesian"
636
-			"ie", // "Interlingue"
637
-			"ik", // "Inupiak"
638
-			"is", // "Icelandic"
639
-			"it", // "Italian"
640
-			"iu", // "Inuktitut"
641
-			"iw", // "Hebrew (obsolete)"
642
-			"ja", // "Japanese"
643
-			"ji", // "Yiddish (obsolete)"
644
-			"jw", // "Javanese"
645
-			"ka", // "Georgian"
646
-			"kk", // "Kazakh"
647
-			"kl", // "Greenlandic"
648
-			"km", // "Cambodian"
649
-			"kn", // "Kannada"
650
-			"ko", // "Korean"
651
-			"ks", // "Kashmiri"
652
-			"ku", // "Kurdish"
653
-			"ky", // "Kirghiz"
654
-			"la", // "Latin"
655
-			"ln", // "Lingala"
656
-			"lo", // "Laothian"
657
-			"lt", // "Lithuanian"
658
-			"lv", // "Latvian/Lettish"
659
-			"mg", // "Malagasy"
660
-			"mi", // "Maori"
661
-			"mk", // "Macedonian"
662
-			"ml", // "Malayalam"
663
-			"mn", // "Mongolian"
664
-			"mo", // "Moldavian"
665
-			"mr", // "Marathi"
666
-			"ms", // "Malay"
667
-			"mt", // "Maltese"
668
-			"my", // "Burmese"
669
-			"na", // "Nauru"
670
-			"ne", // "Nepali"
671
-			"nl", // "Dutch"
672
-			"no", // "Norwegian"
673
-			"oc", // "Occitan"
674
-			"om", // "(Afan) Oromo"
675
-			"or", // "Oriya"
676
-			"pa", // "Punjabi"
677
-			"pl", // "Polish"
678
-			"ps", // "Pashto / Pushto"
679
-			"pt", // "Portuguese"
680
-			"pt_br", // "Portuguese (Brazil)"
681
-			"qu", // "Quechua"
682
-			"rm", // "Rhaeto-Romance"
683
-			"rn", // "Kirundi"
684
-			"ro", // "Romanian"
685
-			"ro_ro", // "Romanian (Romania)"
686
-			"ru", // "Russian"
687
-			"rw", // "Kinyarwanda"
688
-			"sa", // "Sanskrit"
689
-			"sd", // "Sindhi"
690
-			"sg", // "Sangro"
691
-			"sh", // "Serbo-Croatian"
692
-			"si", // "Singhalese"
693
-			"sk", // "Slovak"
694
-			"sl", // "Slovenian"
695
-			"sm", // "Samoan"
696
-			"sn", // "Shona"
697
-			"so", // "Somali"
698
-			"sq", // "Albanian"
699
-			"sr", // "Serbian"
700
-			"sr_latin", // "Serbian (Latin)"
701
-			"ss", // "Siswati"
702
-			"st", // "Sesotho"
703
-			"su", // "Sundanese"
704
-			"sv", // "Swedish"
705
-			"sw", // "Swahili"
706
-			"ta", // "Tamil"
707
-			"te", // "Tegulu"
708
-			"tg", // "Tajik"
709
-			"th", // "Thai"
710
-			"ti", // "Tigrinya"
711
-			"tk", // "Turkmen"
712
-			"tl", // "Tagalog"
713
-			"tn", // "Setswana"
714
-			"to", // "Tonga"
715
-			"tr", // "Turkish"
716
-			"ts", // "Tsonga"
717
-			"tt", // "Tatar"
718
-			"tw", // "Twi"
719
-			"ug", // "Uigur"
720
-			"uk", // "Ukrainian"
721
-			"ur", // "Urdu"
722
-			"uz", // "Uzbek"
723
-			"vi", // "Vietnamese"
724
-			"vo", // "Volapuk"
725
-			"wo", // "Wolof"
726
-			"xh", // "Xhosa"
727
-			"yi", // "Yiddish"
728
-			"yo", // "Yoruba"
729
-			"za", // "Zuang"
730
-			"zh", // "Chinese"
731
-			"zh_hans", // "Chinese Simplified"
732
-			"zu", // "Zulu"
733
-		];
734
-	}
735
-
736
-	/**
737
-	 * Normalize a language code (e.g. from Transifex)
738
-	 *
739
-	 * @param string $code Language code
740
-	 *
741
-	 * @return string
742
-	 */
743
-	public static function normalizeLanguageCode($code) {
744
-		$code = strtolower($code);
745
-		$code = preg_replace('~[^a-z0-9]~', '_', $code);
746
-		return $code;
747
-	}
467
+            $completeness = $this->getLanguageCompleteness($k);
468
+            if ($completeness < 100) {
469
+                $installed[$k] .= " (" . $completeness . "% " . $this->translate('complete') . ")";
470
+            }
471
+        }
472
+
473
+        return $installed;
474
+    }
475
+
476
+    /**
477
+     * Return the level of completeness for a given language code (compared to english)
478
+     *
479
+     * @param string $language Language
480
+     *
481
+     * @return int
482
+     */
483
+    public function getLanguageCompleteness($language) {
484
+
485
+
486
+        // Ensure that all possible translations are loaded
487
+        $this->reloadAllTranslations();
488
+
489
+        $language = sanitise_string($language);
490
+
491
+        $en = count($this->translations['en']);
492
+
493
+        $missing = $this->getMissingLanguageKeys($language);
494
+        if ($missing) {
495
+            $missing = count($missing);
496
+        } else {
497
+            $missing = 0;
498
+        }
499
+
500
+        $lang = $en - $missing;
501
+
502
+        return round(($lang / $en) * 100, 2);
503
+    }
504
+
505
+    /**
506
+     * Return the translation keys missing from a given language,
507
+     * or those that are identical to the english version.
508
+     *
509
+     * @param string $language The language
510
+     *
511
+     * @return mixed
512
+     */
513
+    public function getMissingLanguageKeys($language) {
514
+
515
+
516
+        // Ensure that all possible translations are loaded
517
+        $this->reloadAllTranslations();
518
+
519
+        $missing = [];
520
+
521
+        foreach ($this->translations['en'] as $k => $v) {
522
+            if ((!isset($this->translations[$language][$k]))
523
+            || ($this->translations[$language][$k] == $this->translations['en'][$k])) {
524
+                $missing[] = $k;
525
+            }
526
+        }
527
+
528
+        if (count($missing)) {
529
+            return $missing;
530
+        }
531
+
532
+        return false;
533
+    }
534
+
535
+    /**
536
+     * Check if a given language key exists
537
+     *
538
+     * @param string $key      The translation key
539
+     * @param string $language The specific language to check
540
+     *
541
+     * @return bool
542
+     * @since 1.11
543
+     */
544
+    function languageKeyExists($key, $language = 'en') {
545
+        if (empty($key)) {
546
+            return false;
547
+        }
548
+
549
+        $this->ensureTranslationsLoaded($language);
550
+
551
+        if (!array_key_exists($language, $this->translations)) {
552
+            return false;
553
+        }
554
+
555
+        return array_key_exists($key, $this->translations[$language]);
556
+    }
557
+
558
+    /**
559
+     * Make sure translations are loaded
560
+     *
561
+     * @param string $language Language
562
+     * @return void
563
+     */
564
+    private function ensureTranslationsLoaded($language) {
565
+        if (!$this->is_initialized) {
566
+            // this means we probably had an exception before translations were initialized
567
+            $this->registerTranslations($this->defaultPath);
568
+        }
569
+
570
+        if (!isset($this->translations[$language])) {
571
+            // The language being requested is not the same as the language of the
572
+            // logged in user, so we will have to load it separately. (Most likely
573
+            // we're sending a notification and the recipient is using a different
574
+            // language than the logged in user.)
575
+            $this->loadTranslations($language);
576
+        }
577
+    }
578
+
579
+    /**
580
+     * Returns an array of language codes.
581
+     *
582
+     * @return array
583
+     */
584
+    public static function getAllLanguageCodes() {
585
+        return [
586
+            "aa", // "Afar"
587
+            "ab", // "Abkhazian"
588
+            "af", // "Afrikaans"
589
+            "am", // "Amharic"
590
+            "ar", // "Arabic"
591
+            "as", // "Assamese"
592
+            "ay", // "Aymara"
593
+            "az", // "Azerbaijani"
594
+            "ba", // "Bashkir"
595
+            "be", // "Byelorussian"
596
+            "bg", // "Bulgarian"
597
+            "bh", // "Bihari"
598
+            "bi", // "Bislama"
599
+            "bn", // "Bengali; Bangla"
600
+            "bo", // "Tibetan"
601
+            "br", // "Breton"
602
+            "ca", // "Catalan"
603
+            "cmn", // "Mandarin Chinese" // ISO 639-3
604
+            "co", // "Corsican"
605
+            "cs", // "Czech"
606
+            "cy", // "Welsh"
607
+            "da", // "Danish"
608
+            "de", // "German"
609
+            "dz", // "Bhutani"
610
+            "el", // "Greek"
611
+            "en", // "English"
612
+            "eo", // "Esperanto"
613
+            "es", // "Spanish"
614
+            "et", // "Estonian"
615
+            "eu", // "Basque"
616
+            "eu_es", // "Basque (Spain)"
617
+            "fa", // "Persian"
618
+            "fi", // "Finnish"
619
+            "fj", // "Fiji"
620
+            "fo", // "Faeroese"
621
+            "fr", // "French"
622
+            "fy", // "Frisian"
623
+            "ga", // "Irish"
624
+            "gd", // "Scots / Gaelic"
625
+            "gl", // "Galician"
626
+            "gn", // "Guarani"
627
+            "gu", // "Gujarati"
628
+            "he", // "Hebrew"
629
+            "ha", // "Hausa"
630
+            "hi", // "Hindi"
631
+            "hr", // "Croatian"
632
+            "hu", // "Hungarian"
633
+            "hy", // "Armenian"
634
+            "ia", // "Interlingua"
635
+            "id", // "Indonesian"
636
+            "ie", // "Interlingue"
637
+            "ik", // "Inupiak"
638
+            "is", // "Icelandic"
639
+            "it", // "Italian"
640
+            "iu", // "Inuktitut"
641
+            "iw", // "Hebrew (obsolete)"
642
+            "ja", // "Japanese"
643
+            "ji", // "Yiddish (obsolete)"
644
+            "jw", // "Javanese"
645
+            "ka", // "Georgian"
646
+            "kk", // "Kazakh"
647
+            "kl", // "Greenlandic"
648
+            "km", // "Cambodian"
649
+            "kn", // "Kannada"
650
+            "ko", // "Korean"
651
+            "ks", // "Kashmiri"
652
+            "ku", // "Kurdish"
653
+            "ky", // "Kirghiz"
654
+            "la", // "Latin"
655
+            "ln", // "Lingala"
656
+            "lo", // "Laothian"
657
+            "lt", // "Lithuanian"
658
+            "lv", // "Latvian/Lettish"
659
+            "mg", // "Malagasy"
660
+            "mi", // "Maori"
661
+            "mk", // "Macedonian"
662
+            "ml", // "Malayalam"
663
+            "mn", // "Mongolian"
664
+            "mo", // "Moldavian"
665
+            "mr", // "Marathi"
666
+            "ms", // "Malay"
667
+            "mt", // "Maltese"
668
+            "my", // "Burmese"
669
+            "na", // "Nauru"
670
+            "ne", // "Nepali"
671
+            "nl", // "Dutch"
672
+            "no", // "Norwegian"
673
+            "oc", // "Occitan"
674
+            "om", // "(Afan) Oromo"
675
+            "or", // "Oriya"
676
+            "pa", // "Punjabi"
677
+            "pl", // "Polish"
678
+            "ps", // "Pashto / Pushto"
679
+            "pt", // "Portuguese"
680
+            "pt_br", // "Portuguese (Brazil)"
681
+            "qu", // "Quechua"
682
+            "rm", // "Rhaeto-Romance"
683
+            "rn", // "Kirundi"
684
+            "ro", // "Romanian"
685
+            "ro_ro", // "Romanian (Romania)"
686
+            "ru", // "Russian"
687
+            "rw", // "Kinyarwanda"
688
+            "sa", // "Sanskrit"
689
+            "sd", // "Sindhi"
690
+            "sg", // "Sangro"
691
+            "sh", // "Serbo-Croatian"
692
+            "si", // "Singhalese"
693
+            "sk", // "Slovak"
694
+            "sl", // "Slovenian"
695
+            "sm", // "Samoan"
696
+            "sn", // "Shona"
697
+            "so", // "Somali"
698
+            "sq", // "Albanian"
699
+            "sr", // "Serbian"
700
+            "sr_latin", // "Serbian (Latin)"
701
+            "ss", // "Siswati"
702
+            "st", // "Sesotho"
703
+            "su", // "Sundanese"
704
+            "sv", // "Swedish"
705
+            "sw", // "Swahili"
706
+            "ta", // "Tamil"
707
+            "te", // "Tegulu"
708
+            "tg", // "Tajik"
709
+            "th", // "Thai"
710
+            "ti", // "Tigrinya"
711
+            "tk", // "Turkmen"
712
+            "tl", // "Tagalog"
713
+            "tn", // "Setswana"
714
+            "to", // "Tonga"
715
+            "tr", // "Turkish"
716
+            "ts", // "Tsonga"
717
+            "tt", // "Tatar"
718
+            "tw", // "Twi"
719
+            "ug", // "Uigur"
720
+            "uk", // "Ukrainian"
721
+            "ur", // "Urdu"
722
+            "uz", // "Uzbek"
723
+            "vi", // "Vietnamese"
724
+            "vo", // "Volapuk"
725
+            "wo", // "Wolof"
726
+            "xh", // "Xhosa"
727
+            "yi", // "Yiddish"
728
+            "yo", // "Yoruba"
729
+            "za", // "Zuang"
730
+            "zh", // "Chinese"
731
+            "zh_hans", // "Chinese Simplified"
732
+            "zu", // "Zulu"
733
+        ];
734
+    }
735
+
736
+    /**
737
+     * Normalize a language code (e.g. from Transifex)
738
+     *
739
+     * @param string $code Language code
740
+     *
741
+     * @return string
742
+     */
743
+    public static function normalizeLanguageCode($code) {
744
+        $code = strtolower($code);
745
+        $code = preg_replace('~[^a-z0-9]~', '_', $code);
746
+        return $code;
747
+    }
748 748
 }
Please login to merge, or discard this patch.
engine/lib/pam.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -37,22 +37,22 @@  discard block
 block discarded – undo
37 37
  * @return bool
38 38
  */
39 39
 function register_pam_handler($handler, $importance = "sufficient", $policy = "user") {
40
-	// setup array for this type of pam if not already set
41
-	if (!isset(\ElggPAM::$_handlers[$policy])) {
42
-		\ElggPAM::$_handlers[$policy] = [];
43
-	}
40
+    // setup array for this type of pam if not already set
41
+    if (!isset(\ElggPAM::$_handlers[$policy])) {
42
+        \ElggPAM::$_handlers[$policy] = [];
43
+    }
44 44
 
45
-	// @todo remove requirement that $handle be a global function
46
-	if (is_string($handler) && is_callable($handler, true)) {
47
-		\ElggPAM::$_handlers[$policy][$handler] = new \stdClass;
45
+    // @todo remove requirement that $handle be a global function
46
+    if (is_string($handler) && is_callable($handler, true)) {
47
+        \ElggPAM::$_handlers[$policy][$handler] = new \stdClass;
48 48
 
49
-		\ElggPAM::$_handlers[$policy][$handler]->handler = $handler;
50
-		\ElggPAM::$_handlers[$policy][$handler]->importance = strtolower($importance);
49
+        \ElggPAM::$_handlers[$policy][$handler]->handler = $handler;
50
+        \ElggPAM::$_handlers[$policy][$handler]->importance = strtolower($importance);
51 51
 
52
-		return true;
53
-	}
52
+        return true;
53
+    }
54 54
 
55
-	return false;
55
+    return false;
56 56
 }
57 57
 
58 58
 /**
@@ -65,5 +65,5 @@  discard block
 block discarded – undo
65 65
  * @since 1.7.0
66 66
  */
67 67
 function unregister_pam_handler($handler, $policy = "user") {
68
-	unset(\ElggPAM::$_handlers[$policy][$handler]);
68
+    unset(\ElggPAM::$_handlers[$policy][$handler]);
69 69
 }
Please login to merge, or discard this patch.
engine/classes/ElggPAM.php 1 patch
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -5,105 +5,105 @@
 block discarded – undo
5 5
  */
6 6
 class ElggPAM {
7 7
 
8
-	/**
9
-	 * @var array
10
-	 * @access private
11
-	 * @todo move state into a PAM service
12
-	 */
13
-	public static $_handlers = [];
8
+    /**
9
+     * @var array
10
+     * @access private
11
+     * @todo move state into a PAM service
12
+     */
13
+    public static $_handlers = [];
14 14
 
15
-	/**
16
-	 * @var string PAM policy type: user, api or plugin-defined policies
17
-	 */
18
-	protected $policy;
15
+    /**
16
+     * @var string PAM policy type: user, api or plugin-defined policies
17
+     */
18
+    protected $policy;
19 19
 
20
-	/**
21
-	 * @var array Failure mesages
22
-	 */
23
-	protected $messages;
20
+    /**
21
+     * @var array Failure mesages
22
+     */
23
+    protected $messages;
24 24
 
25
-	/**
26
-	 * \ElggPAM constructor
27
-	 *
28
-	 * @param string $policy PAM policy type: user, api, or plugin-defined policies
29
-	 */
30
-	public function __construct($policy) {
31
-		$this->policy = $policy;
32
-		$this->messages = ['sufficient' => [], 'required' => []];
33
-	}
25
+    /**
26
+     * \ElggPAM constructor
27
+     *
28
+     * @param string $policy PAM policy type: user, api, or plugin-defined policies
29
+     */
30
+    public function __construct($policy) {
31
+        $this->policy = $policy;
32
+        $this->messages = ['sufficient' => [], 'required' => []];
33
+    }
34 34
 
35
-	/**
36
-	 * Authenticate a set of credentials against a policy
37
-	 * This function will process all registered PAM handlers or stop when the first
38
-	 * handler fails. A handler fails by either returning false or throwing an
39
-	 * exception. The advantage of throwing an exception is that it returns a message
40
-	 * that can be passed to the user. The processing order of the handlers is
41
-	 * determined by the order that they were registered.
42
-	 *
43
-	 * If $credentials are provided, the PAM handler should authenticate using the
44
-	 * provided credentials. If not, then credentials should be prompted for or
45
-	 * otherwise retrieved (eg from the HTTP header or $_SESSION).
46
-	 *
47
-	 * @param array $credentials Credentials array dependant on policy type
48
-	 * @return bool
49
-	 */
50
-	public function authenticate($credentials = []) {
51
-		if (!isset(self::$_handlers[$this->policy]) ||
52
-			!is_array(self::$_handlers[$this->policy])) {
53
-			return false;
54
-		}
35
+    /**
36
+     * Authenticate a set of credentials against a policy
37
+     * This function will process all registered PAM handlers or stop when the first
38
+     * handler fails. A handler fails by either returning false or throwing an
39
+     * exception. The advantage of throwing an exception is that it returns a message
40
+     * that can be passed to the user. The processing order of the handlers is
41
+     * determined by the order that they were registered.
42
+     *
43
+     * If $credentials are provided, the PAM handler should authenticate using the
44
+     * provided credentials. If not, then credentials should be prompted for or
45
+     * otherwise retrieved (eg from the HTTP header or $_SESSION).
46
+     *
47
+     * @param array $credentials Credentials array dependant on policy type
48
+     * @return bool
49
+     */
50
+    public function authenticate($credentials = []) {
51
+        if (!isset(self::$_handlers[$this->policy]) ||
52
+            !is_array(self::$_handlers[$this->policy])) {
53
+            return false;
54
+        }
55 55
 
56
-		$authenticated = false;
56
+        $authenticated = false;
57 57
 
58
-		foreach (self::$_handlers[$this->policy] as $v) {
59
-			$handler = $v->handler;
60
-			if (!is_callable($handler)) {
61
-				continue;
62
-			}
63
-			/* @var callable $handler */
58
+        foreach (self::$_handlers[$this->policy] as $v) {
59
+            $handler = $v->handler;
60
+            if (!is_callable($handler)) {
61
+                continue;
62
+            }
63
+            /* @var callable $handler */
64 64
 
65
-			$importance = $v->importance;
65
+            $importance = $v->importance;
66 66
 
67
-			try {
68
-				// Execute the handler
69
-				// @todo don't assume $handler is a global function
70
-				$result = call_user_func($handler, $credentials);
71
-				if ($result) {
72
-					$authenticated = true;
73
-				} elseif ($result === false) {
74
-					if ($importance == 'required') {
75
-						$this->messages['required'][] = "$handler:failed";
76
-						return false;
77
-					} else {
78
-						$this->messages['sufficient'][] = "$handler:failed";
79
-					}
80
-				}
81
-			} catch (Exception $e) {
82
-				if ($importance == 'required') {
83
-					$this->messages['required'][] = $e->getMessage();
84
-					return false;
85
-				} else {
86
-					$this->messages['sufficient'][] = $e->getMessage();
87
-				}
88
-			}
89
-		}
67
+            try {
68
+                // Execute the handler
69
+                // @todo don't assume $handler is a global function
70
+                $result = call_user_func($handler, $credentials);
71
+                if ($result) {
72
+                    $authenticated = true;
73
+                } elseif ($result === false) {
74
+                    if ($importance == 'required') {
75
+                        $this->messages['required'][] = "$handler:failed";
76
+                        return false;
77
+                    } else {
78
+                        $this->messages['sufficient'][] = "$handler:failed";
79
+                    }
80
+                }
81
+            } catch (Exception $e) {
82
+                if ($importance == 'required') {
83
+                    $this->messages['required'][] = $e->getMessage();
84
+                    return false;
85
+                } else {
86
+                    $this->messages['sufficient'][] = $e->getMessage();
87
+                }
88
+            }
89
+        }
90 90
 
91
-		return $authenticated;
92
-	}
91
+        return $authenticated;
92
+    }
93 93
 
94
-	/**
95
-	 * Get a failure message to display to user
96
-	 *
97
-	 * @return string
98
-	 */
99
-	public function getFailureMessage() {
100
-		$message = _elgg_services()->translator->translate('auth:nopams');
101
-		if (!empty($this->messages['required'])) {
102
-			$message = $this->messages['required'][0];
103
-		} elseif (!empty($this->messages['sufficient'])) {
104
-			$message = $this->messages['sufficient'][0];
105
-		}
94
+    /**
95
+     * Get a failure message to display to user
96
+     *
97
+     * @return string
98
+     */
99
+    public function getFailureMessage() {
100
+        $message = _elgg_services()->translator->translate('auth:nopams');
101
+        if (!empty($this->messages['required'])) {
102
+            $message = $this->messages['required'][0];
103
+        } elseif (!empty($this->messages['sufficient'])) {
104
+            $message = $this->messages['sufficient'][0];
105
+        }
106 106
 
107
-		return _elgg_services()->hooks->trigger('fail', 'auth', $this->messages, $message);
108
-	}
107
+        return _elgg_services()->hooks->trigger('fail', 'auth', $this->messages, $message);
108
+    }
109 109
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/Assets/ExternalFiles.php 1 patch
Indentation   +227 added lines, -227 removed lines patch added patch discarded remove patch
@@ -11,231 +11,231 @@
 block discarded – undo
11 11
  */
12 12
 class ExternalFiles {
13 13
 
14
-	/**
15
-	 * @var ElggPriorityList[]
16
-	 */
17
-	protected $externals = [];
18
-
19
-	/**
20
-	 * @var array
21
-	 */
22
-	protected $externals_map = [];
23
-
24
-	/**
25
-	 * Core registration function for external files
26
-	 *
27
-	 * @param string $type     Type of external resource (js or css)
28
-	 * @param string $name     Identifier used as key
29
-	 * @param string $url      URL
30
-	 * @param string $location Location in the page to include the file
31
-	 * @param int    $priority Loading priority of the file
32
-	 *
33
-	 * @return bool
34
-	 */
35
-	public function register($type, $name, $url, $location, $priority = 500) {
36
-		if (empty($name) || empty($url)) {
37
-			return false;
38
-		}
39
-	
40
-		$url = elgg_normalize_url($url);
41
-
42
-		$this->setupType($type);
43
-	
44
-		$name = trim(strtolower($name));
45
-	
46
-		// normalize bogus priorities, but allow empty, null, and false to be defaults.
47
-		if (!is_numeric($priority)) {
48
-			$priority = 500;
49
-		}
50
-	
51
-		// no negative priorities right now.
52
-		$priority = max((int) $priority, 0);
53
-	
54
-		$item = elgg_extract($name, $this->externals_map[$type]);
55
-	
56
-		if ($item) {
57
-			// updating a registered item
58
-			// don't update loaded because it could already be set
59
-			$item->url = $url;
60
-			$item->location = $location;
61
-	
62
-			// if loaded before registered, that means it hasn't been added to the list yet
63
-			if ($this->externals[$type]->contains($item)) {
64
-				$priority = $this->externals[$type]->move($item, $priority);
65
-			} else {
66
-				$priority = $this->externals[$type]->add($item, $priority);
67
-			}
68
-		} else {
69
-			$item = (object) [
70
-				'loaded' => false,
71
-				'url' => $url,
72
-				'location' => $location,
73
-			];
74
-			$priority = $this->externals[$type]->add($item, $priority);
75
-		}
76
-
77
-		$this->externals_map[$type][$name] = $item;
78
-	
79
-		return $priority !== false;
80
-	}
81
-	
82
-	/**
83
-	 * Unregister an external file
84
-	 *
85
-	 * @param string $type Type of file: js or css
86
-	 * @param string $name The identifier of the file
87
-	 *
88
-	 * @return bool
89
-	 */
90
-	public function unregister($type, $name) {
91
-		$this->setupType($type);
92
-	
93
-		$name = trim(strtolower($name));
94
-		$item = elgg_extract($name, $this->externals_map[$type]);
95
-	
96
-		if ($item) {
97
-			unset($this->externals_map[$type][$name]);
98
-			return $this->externals[$type]->remove($item);
99
-		}
100
-	
101
-		return false;
102
-	}
103
-
104
-	/**
105
-	 * Get metadata for a registered file
106
-	 *
107
-	 * @param string $type
108
-	 * @param string $name
109
-	 *
110
-	 * @return \stdClass|null
111
-	 */
112
-	public function getFile($type, $name) {
113
-		$this->setupType($type);
114
-
115
-		$name = trim(strtolower($name));
116
-		if (!isset($this->externals_map[$type][$name])) {
117
-			return null;
118
-		}
119
-
120
-		$item = $this->externals_map[$type][$name];
121
-		$priority = $this->externals[$type]->getPriority($item);
122
-
123
-		// don't allow internal properties to be altered
124
-		$clone = clone $item;
125
-		$clone->priority = $priority;
126
-
127
-		return $clone;
128
-	}
129
-	
130
-	/**
131
-	 * Load an external resource for use on this page
132
-	 *
133
-	 * @param string $type Type of file: js or css
134
-	 * @param string $name The identifier for the file
135
-	 *
136
-	 * @return void
137
-	 */
138
-	public function load($type, $name) {
139
-		$this->setupType($type);
140
-	
141
-		$name = trim(strtolower($name));
142
-	
143
-		$item = elgg_extract($name, $this->externals_map[$type]);
144
-	
145
-		if ($item) {
146
-			// update a registered item
147
-			$item->loaded = true;
148
-		} else {
149
-			$item = (object) [
150
-				'loaded' => true,
151
-				'url' => '',
152
-				'location' => '',
153
-			];
154
-			if (elgg_view_exists($name)) {
155
-				$item->url = elgg_get_simplecache_url($name);
156
-				$item->location = ($type == 'js') ? 'foot' : 'head';
157
-			}
158
-
159
-			$this->externals[$type]->add($item);
160
-			$this->externals_map[$type][$name] = $item;
161
-		}
162
-	}
163
-	
164
-	/**
165
-	 * Get external resource descriptors
166
-	 *
167
-	 * @param string $type     Type of file: js or css
168
-	 * @param string $location Page location
169
-	 *
170
-	 * @return string[] URLs of files to load
171
-	 */
172
-	public function getLoadedFiles($type, $location) {
173
-		if (!isset($this->externals[$type])) {
174
-			return [];
175
-		}
176
-
177
-		$items = $this->externals[$type]->getElements();
178
-
179
-		$items = array_filter($items, function($v) use ($location) {
180
-			return $v->loaded == true && $v->location == $location;
181
-		});
182
-		if ($items) {
183
-			array_walk($items, function(&$v, $k){
184
-				$v = $v->url;
185
-			});
186
-		}
187
-		return $items;
188
-	}
189
-
190
-	/**
191
-	 * Get registered file objects
192
-	 *
193
-	 * @param string $type     Type of file: js or css
194
-	 * @param string $location Page location
195
-	 *
196
-	 * @return \stdClass[]
197
-	 */
198
-	public function getRegisteredFiles($type, $location) {
199
-		if (!isset($this->externals[$type])) {
200
-			return [];
201
-		}
202
-
203
-		$ret = [];
204
-		$items = $this->externals[$type]->getElements();
205
-		$items = array_filter($items, function($v) use ($location) {
206
-			return ($v->location == $location);
207
-		});
208
-
209
-		foreach ($items as $item) {
210
-			$ret[] = clone $item;
211
-		}
212
-
213
-		return $ret;
214
-	}
215
-
216
-	/**
217
-	 * Unregister all files
218
-	 *
219
-	 * @return void
220
-	 */
221
-	public function reset() {
222
-		$this->externals = [];
223
-		$this->externals_map = [];
224
-	}
225
-	
226
-	/**
227
-	 * Bootstraps the externals data structure
228
-	 *
229
-	 * @param string $type The type of external, js or css.
230
-	 * @return void
231
-	 */
232
-	protected function setupType($type) {
233
-		if (!isset($this->externals[$type])) {
234
-			$this->externals[$type] = new \ElggPriorityList();
235
-		}
236
-	
237
-		if (!isset($this->externals_map[$type])) {
238
-			$this->externals_map[$type] = [];
239
-		}
240
-	}
14
+    /**
15
+     * @var ElggPriorityList[]
16
+     */
17
+    protected $externals = [];
18
+
19
+    /**
20
+     * @var array
21
+     */
22
+    protected $externals_map = [];
23
+
24
+    /**
25
+     * Core registration function for external files
26
+     *
27
+     * @param string $type     Type of external resource (js or css)
28
+     * @param string $name     Identifier used as key
29
+     * @param string $url      URL
30
+     * @param string $location Location in the page to include the file
31
+     * @param int    $priority Loading priority of the file
32
+     *
33
+     * @return bool
34
+     */
35
+    public function register($type, $name, $url, $location, $priority = 500) {
36
+        if (empty($name) || empty($url)) {
37
+            return false;
38
+        }
39
+	
40
+        $url = elgg_normalize_url($url);
41
+
42
+        $this->setupType($type);
43
+	
44
+        $name = trim(strtolower($name));
45
+	
46
+        // normalize bogus priorities, but allow empty, null, and false to be defaults.
47
+        if (!is_numeric($priority)) {
48
+            $priority = 500;
49
+        }
50
+	
51
+        // no negative priorities right now.
52
+        $priority = max((int) $priority, 0);
53
+	
54
+        $item = elgg_extract($name, $this->externals_map[$type]);
55
+	
56
+        if ($item) {
57
+            // updating a registered item
58
+            // don't update loaded because it could already be set
59
+            $item->url = $url;
60
+            $item->location = $location;
61
+	
62
+            // if loaded before registered, that means it hasn't been added to the list yet
63
+            if ($this->externals[$type]->contains($item)) {
64
+                $priority = $this->externals[$type]->move($item, $priority);
65
+            } else {
66
+                $priority = $this->externals[$type]->add($item, $priority);
67
+            }
68
+        } else {
69
+            $item = (object) [
70
+                'loaded' => false,
71
+                'url' => $url,
72
+                'location' => $location,
73
+            ];
74
+            $priority = $this->externals[$type]->add($item, $priority);
75
+        }
76
+
77
+        $this->externals_map[$type][$name] = $item;
78
+	
79
+        return $priority !== false;
80
+    }
81
+	
82
+    /**
83
+     * Unregister an external file
84
+     *
85
+     * @param string $type Type of file: js or css
86
+     * @param string $name The identifier of the file
87
+     *
88
+     * @return bool
89
+     */
90
+    public function unregister($type, $name) {
91
+        $this->setupType($type);
92
+	
93
+        $name = trim(strtolower($name));
94
+        $item = elgg_extract($name, $this->externals_map[$type]);
95
+	
96
+        if ($item) {
97
+            unset($this->externals_map[$type][$name]);
98
+            return $this->externals[$type]->remove($item);
99
+        }
100
+	
101
+        return false;
102
+    }
103
+
104
+    /**
105
+     * Get metadata for a registered file
106
+     *
107
+     * @param string $type
108
+     * @param string $name
109
+     *
110
+     * @return \stdClass|null
111
+     */
112
+    public function getFile($type, $name) {
113
+        $this->setupType($type);
114
+
115
+        $name = trim(strtolower($name));
116
+        if (!isset($this->externals_map[$type][$name])) {
117
+            return null;
118
+        }
119
+
120
+        $item = $this->externals_map[$type][$name];
121
+        $priority = $this->externals[$type]->getPriority($item);
122
+
123
+        // don't allow internal properties to be altered
124
+        $clone = clone $item;
125
+        $clone->priority = $priority;
126
+
127
+        return $clone;
128
+    }
129
+	
130
+    /**
131
+     * Load an external resource for use on this page
132
+     *
133
+     * @param string $type Type of file: js or css
134
+     * @param string $name The identifier for the file
135
+     *
136
+     * @return void
137
+     */
138
+    public function load($type, $name) {
139
+        $this->setupType($type);
140
+	
141
+        $name = trim(strtolower($name));
142
+	
143
+        $item = elgg_extract($name, $this->externals_map[$type]);
144
+	
145
+        if ($item) {
146
+            // update a registered item
147
+            $item->loaded = true;
148
+        } else {
149
+            $item = (object) [
150
+                'loaded' => true,
151
+                'url' => '',
152
+                'location' => '',
153
+            ];
154
+            if (elgg_view_exists($name)) {
155
+                $item->url = elgg_get_simplecache_url($name);
156
+                $item->location = ($type == 'js') ? 'foot' : 'head';
157
+            }
158
+
159
+            $this->externals[$type]->add($item);
160
+            $this->externals_map[$type][$name] = $item;
161
+        }
162
+    }
163
+	
164
+    /**
165
+     * Get external resource descriptors
166
+     *
167
+     * @param string $type     Type of file: js or css
168
+     * @param string $location Page location
169
+     *
170
+     * @return string[] URLs of files to load
171
+     */
172
+    public function getLoadedFiles($type, $location) {
173
+        if (!isset($this->externals[$type])) {
174
+            return [];
175
+        }
176
+
177
+        $items = $this->externals[$type]->getElements();
178
+
179
+        $items = array_filter($items, function($v) use ($location) {
180
+            return $v->loaded == true && $v->location == $location;
181
+        });
182
+        if ($items) {
183
+            array_walk($items, function(&$v, $k){
184
+                $v = $v->url;
185
+            });
186
+        }
187
+        return $items;
188
+    }
189
+
190
+    /**
191
+     * Get registered file objects
192
+     *
193
+     * @param string $type     Type of file: js or css
194
+     * @param string $location Page location
195
+     *
196
+     * @return \stdClass[]
197
+     */
198
+    public function getRegisteredFiles($type, $location) {
199
+        if (!isset($this->externals[$type])) {
200
+            return [];
201
+        }
202
+
203
+        $ret = [];
204
+        $items = $this->externals[$type]->getElements();
205
+        $items = array_filter($items, function($v) use ($location) {
206
+            return ($v->location == $location);
207
+        });
208
+
209
+        foreach ($items as $item) {
210
+            $ret[] = clone $item;
211
+        }
212
+
213
+        return $ret;
214
+    }
215
+
216
+    /**
217
+     * Unregister all files
218
+     *
219
+     * @return void
220
+     */
221
+    public function reset() {
222
+        $this->externals = [];
223
+        $this->externals_map = [];
224
+    }
225
+	
226
+    /**
227
+     * Bootstraps the externals data structure
228
+     *
229
+     * @param string $type The type of external, js or css.
230
+     * @return void
231
+     */
232
+    protected function setupType($type) {
233
+        if (!isset($this->externals[$type])) {
234
+            $this->externals[$type] = new \ElggPriorityList();
235
+        }
236
+	
237
+        if (!isset($this->externals_map[$type])) {
238
+            $this->externals_map[$type] = [];
239
+        }
240
+    }
241 241
 }
Please login to merge, or discard this patch.
views/default/languages.js.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -9,13 +9,13 @@
 block discarded – undo
9 9
 $translations = $all_translations['en'];
10 10
 
11 11
 if ($language != 'en' && !isset($all_translations[$language])) {
12
-	// try to reload missing translations
13
-	reload_all_translations();
14
-	$all_translations = _elgg_services()->translator->getLoadedTranslations();
12
+    // try to reload missing translations
13
+    reload_all_translations();
14
+    $all_translations = _elgg_services()->translator->getLoadedTranslations();
15 15
 }
16 16
 
17 17
 if ($language != 'en' && isset($all_translations[$language])) {
18
-	$translations = array_merge($translations, $all_translations[$language]);
18
+    $translations = array_merge($translations, $all_translations[$language]);
19 19
 }
20 20
 
21 21
 ?>
Please login to merge, or discard this patch.
engine/lib/actions.php 2 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
  *
152 152
  * @param string $action The action being performed
153 153
  *
154
- * @return mixed True if valid or redirects.
154
+ * @return boolean True if valid or redirects.
155 155
  * @access private
156 156
  */
157 157
 function action_gatekeeper($action) {
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 /**
183 183
  * Regenerate a new site key (32 bytes: "z" to indicate format + 186-bit key in Base64 URL).
184 184
  *
185
- * @return mixed The site secret hash
185
+ * @return string The site secret hash
186 186
  * @access private
187 187
  */
188 188
 function init_site_secret() {
Please login to merge, or discard this patch.
Indentation   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -20,7 +20,7 @@  discard block
 block discarded – undo
20 20
  * @access private
21 21
  */
22 22
 function _elgg_action_handler(array $segments) {
23
-	return _elgg_services()->actions->execute(implode('/', $segments));
23
+    return _elgg_services()->actions->execute(implode('/', $segments));
24 24
 }
25 25
 
26 26
 /**
@@ -52,12 +52,12 @@  discard block
 block discarded – undo
52 52
  * @access private
53 53
  */
54 54
 function action($action, $forwarder = "") {
55
-	$response = _elgg_services()->actions->execute($action, $forwarder);
56
-	if ($response instanceof ResponseBuilder) {
57
-		// in case forward() wasn't called in the action
58
-		_elgg_services()->responseFactory->respond($response);
59
-	}
60
-	_elgg_services()->responseFactory->redirect(REFERRER, 'csrf');
55
+    $response = _elgg_services()->actions->execute($action, $forwarder);
56
+    if ($response instanceof ResponseBuilder) {
57
+        // in case forward() wasn't called in the action
58
+        _elgg_services()->responseFactory->respond($response);
59
+    }
60
+    _elgg_services()->responseFactory->redirect(REFERRER, 'csrf');
61 61
 }
62 62
 
63 63
 /**
@@ -87,7 +87,7 @@  discard block
 block discarded – undo
87 87
  * @return bool
88 88
  */
89 89
 function elgg_register_action($action, $filename = "", $access = 'logged_in') {
90
-	return _elgg_services()->actions->register($action, $filename, $access);
90
+    return _elgg_services()->actions->register($action, $filename, $access);
91 91
 }
92 92
 
93 93
 /**
@@ -98,7 +98,7 @@  discard block
 block discarded – undo
98 98
  * @since 1.8.1
99 99
  */
100 100
 function elgg_unregister_action($action) {
101
-	return _elgg_services()->actions->unregister($action);
101
+    return _elgg_services()->actions->unregister($action);
102 102
 }
103 103
 
104 104
 /**
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
  * @since 1.11
110 110
  */
111 111
 function elgg_build_hmac($data) {
112
-	return _elgg_services()->hmac->getHmac($data);
112
+    return _elgg_services()->hmac->getHmac($data);
113 113
 }
114 114
 
115 115
 /**
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
  * @access private
130 130
  */
131 131
 function validate_action_token($visible_errors = true, $token = null, $ts = null) {
132
-	return _elgg_services()->actions->validateActionToken($visible_errors, $token, $ts);
132
+    return _elgg_services()->actions->validateActionToken($visible_errors, $token, $ts);
133 133
 }
134 134
 
135 135
 /**
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
  * @access private
148 148
  */
149 149
 function action_gatekeeper($action) {
150
-	return _elgg_services()->actions->gatekeeper($action);
150
+    return _elgg_services()->actions->gatekeeper($action);
151 151
 }
152 152
 
153 153
 /**
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
  * @return string|false
169 169
  */
170 170
 function generate_action_token($timestamp) {
171
-	return _elgg_services()->actions->generateActionToken($timestamp);
171
+    return _elgg_services()->actions->generateActionToken($timestamp);
172 172
 }
173 173
 
174 174
 /**
@@ -178,9 +178,9 @@  discard block
 block discarded – undo
178 178
  * @access private
179 179
  */
180 180
 function init_site_secret() {
181
-	$secret = SiteSecret::regenerate(_elgg_services()->crypto, _elgg_services()->configTable);
182
-	_elgg_services()->setValue('siteSecret', $secret);
183
-	return $secret->get();
181
+    $secret = SiteSecret::regenerate(_elgg_services()->crypto, _elgg_services()->configTable);
182
+    _elgg_services()->setValue('siteSecret', $secret);
183
+    return $secret->get();
184 184
 }
185 185
 
186 186
 /**
@@ -190,7 +190,7 @@  discard block
 block discarded – undo
190 190
  * @access private
191 191
  */
192 192
 function _elgg_get_site_secret_strength() {
193
-	return _elgg_services()->siteSecret->getStrength();
193
+    return _elgg_services()->siteSecret->getStrength();
194 194
 }
195 195
 
196 196
 /**
@@ -202,7 +202,7 @@  discard block
 block discarded – undo
202 202
  * @since 1.8.0
203 203
  */
204 204
 function elgg_action_exists($action) {
205
-	return _elgg_services()->actions->exists($action);
205
+    return _elgg_services()->actions->exists($action);
206 206
 }
207 207
 
208 208
 /**
@@ -212,7 +212,7 @@  discard block
 block discarded – undo
212 212
  * @since 1.8.0
213 213
  */
214 214
 function elgg_is_xhr() {
215
-	return _elgg_services()->request->isXmlHttpRequest();
215
+    return _elgg_services()->request->isXmlHttpRequest();
216 216
 }
217 217
 
218 218
 /**
@@ -243,8 +243,8 @@  discard block
 block discarded – undo
243 243
  * @deprecated 2.3
244 244
  */
245 245
 function ajax_forward_hook($hook, $type, $reason, $params) {
246
-	elgg_deprecated_notice(__FUNCTION__ . ' is deprecated and is no longer used as a plugin hook handler', '2.3');
247
-	_elgg_services()->actions->ajaxForwardHook($hook, $type, $reason, $params);
246
+    elgg_deprecated_notice(__FUNCTION__ . ' is deprecated and is no longer used as a plugin hook handler', '2.3');
247
+    _elgg_services()->actions->ajaxForwardHook($hook, $type, $reason, $params);
248 248
 }
249 249
 
250 250
 /**
@@ -254,8 +254,8 @@  discard block
 block discarded – undo
254 254
  * @deprecated 2.3
255 255
  */
256 256
 function ajax_action_hook() {
257
-	elgg_deprecated_notice(__FUNCTION__ . ' is deprecated and is no longer used as a plugin hook handler', '2.3');
258
-	_elgg_services()->actions->ajaxActionHook();
257
+    elgg_deprecated_notice(__FUNCTION__ . ' is deprecated and is no longer used as a plugin hook handler', '2.3');
258
+    _elgg_services()->actions->ajaxActionHook();
259 259
 }
260 260
 
261 261
 /**
@@ -265,7 +265,7 @@  discard block
 block discarded – undo
265 265
  * @access private
266 266
  */
267 267
 function _elgg_csrf_token_refresh() {
268
-	return _elgg_services()->actions->handleTokenRefreshRequest();
268
+    return _elgg_services()->actions->handleTokenRefreshRequest();
269 269
 }
270 270
 
271 271
 /**
@@ -273,13 +273,13 @@  discard block
 block discarded – undo
273 273
  * @access private
274 274
  */
275 275
 function actions_init() {
276
-	elgg_register_page_handler('action', '_elgg_action_handler');
277
-	elgg_register_page_handler('refresh_token', '_elgg_csrf_token_refresh');
276
+    elgg_register_page_handler('action', '_elgg_action_handler');
277
+    elgg_register_page_handler('refresh_token', '_elgg_csrf_token_refresh');
278 278
 }
279 279
 
280 280
 /**
281 281
  * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
282 282
  */
283 283
 return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
284
-	$events->registerHandler('init', 'system', 'actions_init');
284
+    $events->registerHandler('init', 'system', 'actions_init');
285 285
 };
Please login to merge, or discard this patch.
engine/classes/Elgg/Application.php 2 patches
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
 	/**
454 454
 	 * Elgg's front controller. Handles basically all incoming URL requests.
455 455
 	 *
456
-	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
456
+	 * @return boolean|null True if Elgg will handle the request, false if the server should (PHP-CLI server)
457 457
 	 */
458 458
 	public static function index() {
459 459
 		$req = Request::createFromGlobals();
@@ -470,7 +470,7 @@  discard block
 block discarded – undo
470 470
 	/**
471 471
 	 * Routes the request, booting core if not yet booted
472 472
 	 *
473
-	 * @return bool False if Elgg wants the PHP CLI server to handle the request
473
+	 * @return boolean|null False if Elgg wants the PHP CLI server to handle the request
474 474
 	 */
475 475
 	public function run() {
476 476
 		$config = $this->services->config;
@@ -781,7 +781,7 @@  discard block
 block discarded – undo
781 781
 	 * @param int    $linenum  The line number the error was raised at
782 782
 	 * @param array  $vars     An array that points to the active symbol table where error occurred
783 783
 	 *
784
-	 * @return true
784
+	 * @return boolean
785 785
 	 * @throws \Exception
786 786
 	 * @access private
787 787
 	 */
Please login to merge, or discard this patch.
Indentation   +880 added lines, -880 removed lines patch added patch discarded remove patch
@@ -25,886 +25,886 @@
 block discarded – undo
25 25
  */
26 26
 class Application {
27 27
 
28
-	const DEFAULT_LANG = 'en';
29
-	const DEFAULT_LIMIT = 10;
30
-
31
-	/**
32
-	 * @var ServiceProvider
33
-	 */
34
-	private $services;
35
-
36
-	/**
37
-	 * @var bool
38
-	 */
39
-	private static $core_loaded = false;
40
-
41
-	/**
42
-	 * @var bool
43
-	 */
44
-	private static $testing_app;
45
-
46
-	/**
47
-	 * Property names of the service provider to be exposed via __get()
48
-	 *
49
-	 * E.g. the presence of `'foo' => true` in the list would allow _elgg_services()->foo to
50
-	 * be accessed via elgg()->foo.
51
-	 *
52
-	 * @var string[]
53
-	 */
54
-	private static $public_services = [
55
-		//'config' => true,
56
-		'menus' => true,
57
-		'table_columns' => true,
58
-	];
59
-
60
-	/**
61
-	 * Reference to the loaded Application returned by elgg()
62
-	 *
63
-	 * @internal Do not use this. use elgg() to access the application
64
-	 * @access private
65
-	 * @var Application
66
-	 */
67
-	public static $_instance;
68
-
69
-	/**
70
-	 * Constructor
71
-	 *
72
-	 * Upon construction, no actions are taken to load or boot Elgg.
73
-	 *
74
-	 * @param ServiceProvider $services Elgg services provider
75
-	 * @throws ConfigurationException
76
-	 */
77
-	public function __construct(ServiceProvider $services) {
78
-		$this->services = $services;
79
-		$services->setValue('app', $this);
80
-
81
-		$this->initConfig();
82
-	}
83
-
84
-	/**
85
-	 * Validate, normalize, fill in missing values, and lock some
86
-	 *
87
-	 * @return void
88
-	 * @throws ConfigurationException
89
-	 */
90
-	private function initConfig() {
91
-		$config = $this->services->config;
92
-
93
-		if ($config->elgg_config_locks === null) {
94
-			$config->elgg_config_locks = true;
95
-		}
96
-
97
-		if ($config->elgg_config_locks) {
98
-			$lock = function ($name) use ($config) {
99
-				$config->lock($name);
100
-			};
101
-		} else {
102
-			// the installer needs to build an application with defaults then update
103
-			// them after they're validated, so we don't want to lock them.
104
-			$lock = function () {
105
-			};
106
-		}
107
-
108
-		$this->services->timer->begin([]);
109
-
110
-		// Until DB loads, let's log problems
111
-		if ($config->debug === null) {
112
-			$config->debug = 'NOTICE';
113
-		}
114
-
115
-		if ($config->dataroot) {
116
-			$config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
117
-		} else {
118
-			if (!$config->installer_running) {
119
-				throw new ConfigurationException('Config value "dataroot" is required.');
120
-			}
121
-		}
122
-		$lock('dataroot');
123
-
124
-		if ($config->cacheroot) {
125
-			$config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
126
-		} else {
127
-			$config->cacheroot = $config->dataroot;
128
-		}
129
-		$lock('cacheroot');
130
-
131
-		if ($config->wwwroot) {
132
-			$config->wwwroot = rtrim($config->wwwroot, '/') . '/';
133
-		} else {
134
-			$config->wwwroot = $this->services->request->sniffElggUrl();
135
-		}
136
-		$lock('wwwroot');
137
-
138
-		if (!$config->language) {
139
-			$config->language = self::DEFAULT_LANG;
140
-		}
141
-
142
-		if ($config->default_limit) {
143
-			$lock('default_limit');
144
-		} else {
145
-			$config->default_limit = self::DEFAULT_LIMIT;
146
-		}
147
-
148
-		$locked_props = [
149
-			'site_guid' => 1,
150
-			'path' => Paths::project(),
151
-			'plugins_path' => Paths::project() . "mod/",
152
-			'pluginspath' => Paths::project() . "mod/",
153
-			'url' => $config->wwwroot,
154
-		];
155
-		foreach ($locked_props as $name => $value) {
156
-			$config->$name = $value;
157
-			$lock($name);
158
-		}
159
-
160
-		// move sensitive credentials into isolated services
161
-		$this->services->dbConfig;
162
-
163
-		// If the site secret is in the settings file, let's move it to that component
164
-		// right away to keep this value out of config.
165
-		$secret = SiteSecret::fromConfig($config);
166
-		if ($secret) {
167
-			$this->services->setValue('siteSecret', $secret);
168
-			$config->elgg_config_set_secret = true;
169
-		}
170
-
171
-		$config->boot_complete = false;
172
-	}
173
-
174
-	/**
175
-	 * Get the DB credentials.
176
-	 *
177
-	 * We no longer leave DB credentials in the config in case it gets accidentally dumped.
178
-	 *
179
-	 * @return \Elgg\Database\DbConfig
180
-	 */
181
-	public function getDbConfig() {
182
-		return $this->services->dbConfig;
183
-	}
184
-
185
-	/**
186
-	 * Define all Elgg global functions and constants, wire up boot events, but don't boot
187
-	 *
188
-	 * This includes all the .php files in engine/lib (not upgrades). If a script returns a function,
189
-	 * it is queued and executed at the end.
190
-	 *
191
-	 * @return void
192
-	 * @access private
193
-	 * @internal
194
-	 * @throws \InstallationException
195
-	 */
196
-	public function loadCore() {
197
-		if (self::$core_loaded) {
198
-			return;
199
-		}
200
-
201
-		$setups = [];
202
-		$path = Paths::elgg() . 'engine/lib';
203
-
204
-		// include library files, capturing setup functions
205
-		foreach (self::getEngineLibs() as $file) {
206
-			$return = Includer::includeFile("$path/$file");
207
-			if (!$return) {
208
-				throw new \InstallationException("Elgg lib file failed include: engine/lib/$file");
209
-			}
210
-			if ($return instanceof \Closure) {
211
-				$setups[$file] = $return;
212
-			}
213
-		}
214
-
215
-		// store instance to be returned by elgg()
216
-		self::$_instance = $this;
217
-
218
-		// allow global services access. :(
219
-		_elgg_services($this->services);
220
-
221
-		// setup logger and inject into config
222
-		//$this->services->config->setLogger($this->services->logger);
223
-
224
-		$hooks = $this->services->hooks;
225
-		$events = $hooks->getEvents();
226
-
227
-		// run setups
228
-		foreach ($setups as $func) {
229
-			$func($events, $hooks);
230
-		}
231
-
232
-		self::$core_loaded = true;
233
-	}
234
-
235
-	/**
236
-	 * Start and boot the core
237
-	 *
238
-	 * @return self
239
-	 */
240
-	public static function start() {
241
-		$app = self::factory();
242
-		$app->bootCore();
243
-		return $app;
244
-	}
245
-
246
-	/**
247
-	 * Bootstrap the Elgg engine, loads plugins, and calls initial system events
248
-	 *
249
-	 * This method loads the full Elgg engine, checks the installation
250
-	 * state, and triggers a series of events to finish booting Elgg:
251
-	 * 	- {@elgg_event boot system}
252
-	 * 	- {@elgg_event init system}
253
-	 * 	- {@elgg_event ready system}
254
-	 *
255
-	 * If Elgg is not fully installed, the browser will be redirected to an installation page.
256
-	 *
257
-	 * @return void
258
-	 */
259
-	public function bootCore() {
260
-		$config = $this->services->config;
261
-
262
-		if ($this->isTestingApplication()) {
263
-			throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
264
-		}
265
-
266
-		if ($config->boot_complete) {
267
-			return;
268
-		}
269
-
270
-		// in case not loaded already
271
-		$this->loadCore();
272
-
273
-		if (!$this->services->db) {
274
-			// no database boot!
275
-			elgg_views_boot();
276
-			$this->services->session->start();
277
-			$this->services->translator->loadTranslations();
278
-
279
-			actions_init();
280
-			_elgg_init();
281
-			_elgg_input_init();
282
-			_elgg_nav_init();
283
-
284
-			$config->boot_complete = true;
285
-			$config->lock('boot_complete');
286
-			return;
287
-		}
288
-
289
-		// Connect to database, load language files, load configuration, init session
290
-		$this->services->boot->boot($this->services);
291
-
292
-		elgg_views_boot();
293
-
294
-		// Load the plugins that are active
295
-		$this->services->plugins->load();
296
-
297
-		if (Paths::project() != Paths::elgg()) {
298
-			// Elgg is installed as a composer dep, so try to treat the root directory
299
-			// as a custom plugin that is always loaded last and can't be disabled...
300
-			if (!$config->system_cache_loaded) {
301
-				// configure view locations for the custom plugin (not Elgg core)
302
-				$viewsFile = Paths::project() . 'views.php';
303
-				if (is_file($viewsFile)) {
304
-					$viewsSpec = Includer::includeFile($viewsFile);
305
-					if (is_array($viewsSpec)) {
306
-						$this->services->views->mergeViewsSpec($viewsSpec);
307
-					}
308
-				}
309
-
310
-				// find views for the custom plugin (not Elgg core)
311
-				$this->services->views->registerPluginViews(Paths::project());
312
-			}
313
-
314
-			if (!$config->i18n_loaded_from_cache) {
315
-				$this->services->translator->registerPluginTranslations(Paths::project());
316
-			}
317
-
318
-			// This is root directory start.php
319
-			$root_start = Paths::project() . "start.php";
320
-			if (is_file($root_start)) {
321
-				require $root_start;
322
-			}
323
-		}
324
-
325
-		// after plugins are started we know which viewtypes are populated
326
-		$this->services->views->clampViewtypeToPopulatedViews();
327
-
328
-		$this->allowPathRewrite();
329
-
330
-		$events = $this->services->hooks->getEvents();
331
-
332
-		// Allows registering handlers strictly before all init, system handlers
333
-		$events->trigger('plugins_boot', 'system');
334
-
335
-		// Complete the boot process for both engine and plugins
336
-		$events->trigger('init', 'system');
337
-
338
-		$config->boot_complete = true;
339
-		$config->lock('boot_complete');
340
-
341
-		// System loaded and ready
342
-		$events->trigger('ready', 'system');
343
-	}
344
-
345
-	/**
346
-	 * Get a Database wrapper for performing queries without booting Elgg
347
-	 *
348
-	 * If settings has not been loaded, it will be loaded to configure the DB connection.
349
-	 *
350
-	 * @note Before boot, the Database instance will not yet be bound to a Logger.
351
-	 *
352
-	 * @return \Elgg\Application\Database
353
-	 */
354
-	public function getDb() {
355
-		return $this->services->publicDb;
356
-	}
357
-
358
-	/**
359
-	 * Get an undefined property
360
-	 *
361
-	 * @param string $name The property name accessed
362
-	 *
363
-	 * @return mixed
364
-	 */
365
-	public function __get($name) {
366
-		if (isset(self::$public_services[$name])) {
367
-			return $this->services->{$name};
368
-		}
369
-		trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
370
-	}
371
-
372
-	/**
373
-	 * Creates a new, trivial instance of Elgg\Application and set it as the singleton instance.
374
-	 * If the singleton is already set, it's returned.
375
-	 *
376
-	 * @param array $spec Specification for initial call.
377
-	 * @return self
378
-	 * @throws ConfigurationException
379
-	 */
380
-	public static function factory(array $spec = []) {
381
-		if (self::$_instance !== null) {
382
-			return self::$_instance;
383
-		}
384
-
385
-		$defaults = [
386
-			'service_provider' => null,
387
-			'config' => null,
388
-			'settings_path' => null,
389
-			'handle_exceptions' => true,
390
-			'handle_shutdown' => true,
391
-			'overwrite_global_config' => true,
392
-			'set_start_time' => true,
393
-			'request' => null,
394
-		];
395
-		$spec = array_merge($defaults, $spec);
396
-
397
-		if ($spec['set_start_time']) {
398
-			/**
399
-			 * The time with microseconds when the Elgg engine was started.
400
-			 *
401
-			 * @global float
402
-			 */
403
-			if (!isset($GLOBALS['START_MICROTIME'])) {
404
-				$GLOBALS['START_MICROTIME'] = microtime(true);
405
-			}
406
-		}
407
-
408
-		if (!$spec['service_provider']) {
409
-			if (!$spec['config']) {
410
-				$spec['config'] = Config::factory($spec['settings_path']);
411
-			}
412
-			$spec['service_provider'] = new ServiceProvider($spec['config']);
413
-		}
414
-
415
-		if ($spec['request']) {
416
-			if ($spec['request'] instanceof Request) {
417
-				$spec['service_provider']->setValue('request', $spec['request']);
418
-			} else {
419
-				throw new \InvalidArgumentException("Given request is not a " . Request::class);
420
-			}
421
-		}
422
-
423
-		self::$_instance = new self($spec['service_provider']);
424
-
425
-		if ($spec['handle_exceptions']) {
426
-			set_error_handler([self::$_instance, 'handleErrors']);
427
-			set_exception_handler([self::$_instance, 'handleExceptions']);
428
-		}
429
-
430
-		if ($spec['handle_shutdown']) {
431
-			// we need to register for shutdown before Symfony registers the
432
-			// session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
433
-			register_shutdown_function(function () {
434
-				// There are cases where we may exit before this function is defined
435
-				if (function_exists('_elgg_shutdown_hook')) {
436
-					_elgg_shutdown_hook();
437
-				}
438
-			});
439
-		}
440
-
441
-		if ($spec['overwrite_global_config']) {
442
-			global $CONFIG;
443
-
444
-			// this will be buggy be at least PHP will log failures
445
-			$CONFIG = $spec['service_provider']->config;
446
-		}
447
-
448
-		return self::$_instance;
449
-	}
450
-
451
-	/**
452
-	 * Elgg's front controller. Handles basically all incoming URL requests.
453
-	 *
454
-	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
455
-	 */
456
-	public static function index() {
457
-		$req = Request::createFromGlobals();
458
-		/** @var Request $req */
459
-
460
-		if ($req->isRewriteCheck()) {
461
-			echo Request::REWRITE_TEST_OUTPUT;
462
-			return true;
463
-		}
464
-
465
-		return self::factory(['request' => $req])->run();
466
-	}
467
-
468
-	/**
469
-	 * Routes the request, booting core if not yet booted
470
-	 *
471
-	 * @return bool False if Elgg wants the PHP CLI server to handle the request
472
-	 */
473
-	public function run() {
474
-		$config = $this->services->config;
475
-		$request = $this->services->request;
476
-
477
-		if ($request->isCliServer()) {
478
-			if ($request->isCliServable(Paths::project())) {
479
-				return false;
480
-			}
481
-
482
-			// overwrite value from settings
483
-			$www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
484
-			$config->wwwroot = $www_root;
485
-			$config->wwwroot_cli_server = $www_root;
486
-		}
487
-
488
-		if (0 === strpos($request->getElggPath(), '/cache/')) {
489
-			$this->services->cacheHandler->handleRequest($request)->prepare($request)->send();
490
-			return true;
491
-		}
492
-
493
-		if (0 === strpos($request->getElggPath(), '/serve-file/')) {
494
-			$this->services->serveFileHandler->getResponse($request)->send();
495
-			return true;
496
-		}
497
-
498
-		$this->bootCore();
499
-
500
-		// TODO use formal Response object instead
501
-		// This is to set the charset to UTF-8.
502
-		header("Content-Type: text/html;charset=utf-8", true);
503
-
504
-		// re-fetch new request from services in case it was replaced by route:rewrite
505
-		$request = $this->services->request;
506
-
507
-		if (!$this->services->router->route($request)) {
508
-			forward('', '404');
509
-		}
510
-	}
511
-
512
-	/**
513
-	 * Returns a directory that points to the root of Elgg, but not necessarily
514
-	 * the install root. See `self::root()` for that.
515
-	 *
516
-	 * @return Directory
517
-	 */
518
-	public static function elggDir() {
519
-		return Local::elggRoot();
520
-	}
521
-
522
-	/**
523
-	 * Returns a directory that points to the project root, where composer is installed.
524
-	 *
525
-	 * @return Directory
526
-	 */
527
-	public static function projectDir() {
528
-		return Local::projectRoot();
529
-	}
530
-
531
-	/**
532
-	 * Renders a web UI for installing Elgg.
533
-	 *
534
-	 * @return void
535
-	 */
536
-	public static function install() {
537
-		ini_set('display_errors', 1);
538
-		$installer = new \ElggInstaller();
539
-		$installer->run();
540
-	}
541
-
542
-	/**
543
-	 * Elgg upgrade script.
544
-	 *
545
-	 * This script triggers any necessary upgrades. If the site has been upgraded
546
-	 * to the most recent version of the code, no upgrades are run but the caches
547
-	 * are flushed.
548
-	 *
549
-	 * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
550
-	 *
551
-	 * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
552
-	 * to a relative URL.
553
-	 *
554
-	 * @return void
555
-	 */
556
-	public static function upgrade() {
557
-		// we want to know if an error occurs
558
-		ini_set('display_errors', 1);
559
-		$is_cli = (php_sapi_name() === 'cli');
560
-
561
-		$forward = function ($url) use ($is_cli) {
562
-			if ($is_cli) {
563
-				echo "Open $url in your browser to continue.";
564
-				exit;
565
-			}
566
-
567
-			forward($url);
568
-		};
569
-
570
-		define('UPGRADING', 'upgrading');
571
-
572
-		self::start();
28
+    const DEFAULT_LANG = 'en';
29
+    const DEFAULT_LIMIT = 10;
30
+
31
+    /**
32
+     * @var ServiceProvider
33
+     */
34
+    private $services;
35
+
36
+    /**
37
+     * @var bool
38
+     */
39
+    private static $core_loaded = false;
40
+
41
+    /**
42
+     * @var bool
43
+     */
44
+    private static $testing_app;
45
+
46
+    /**
47
+     * Property names of the service provider to be exposed via __get()
48
+     *
49
+     * E.g. the presence of `'foo' => true` in the list would allow _elgg_services()->foo to
50
+     * be accessed via elgg()->foo.
51
+     *
52
+     * @var string[]
53
+     */
54
+    private static $public_services = [
55
+        //'config' => true,
56
+        'menus' => true,
57
+        'table_columns' => true,
58
+    ];
59
+
60
+    /**
61
+     * Reference to the loaded Application returned by elgg()
62
+     *
63
+     * @internal Do not use this. use elgg() to access the application
64
+     * @access private
65
+     * @var Application
66
+     */
67
+    public static $_instance;
68
+
69
+    /**
70
+     * Constructor
71
+     *
72
+     * Upon construction, no actions are taken to load or boot Elgg.
73
+     *
74
+     * @param ServiceProvider $services Elgg services provider
75
+     * @throws ConfigurationException
76
+     */
77
+    public function __construct(ServiceProvider $services) {
78
+        $this->services = $services;
79
+        $services->setValue('app', $this);
80
+
81
+        $this->initConfig();
82
+    }
83
+
84
+    /**
85
+     * Validate, normalize, fill in missing values, and lock some
86
+     *
87
+     * @return void
88
+     * @throws ConfigurationException
89
+     */
90
+    private function initConfig() {
91
+        $config = $this->services->config;
92
+
93
+        if ($config->elgg_config_locks === null) {
94
+            $config->elgg_config_locks = true;
95
+        }
96
+
97
+        if ($config->elgg_config_locks) {
98
+            $lock = function ($name) use ($config) {
99
+                $config->lock($name);
100
+            };
101
+        } else {
102
+            // the installer needs to build an application with defaults then update
103
+            // them after they're validated, so we don't want to lock them.
104
+            $lock = function () {
105
+            };
106
+        }
107
+
108
+        $this->services->timer->begin([]);
109
+
110
+        // Until DB loads, let's log problems
111
+        if ($config->debug === null) {
112
+            $config->debug = 'NOTICE';
113
+        }
114
+
115
+        if ($config->dataroot) {
116
+            $config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
117
+        } else {
118
+            if (!$config->installer_running) {
119
+                throw new ConfigurationException('Config value "dataroot" is required.');
120
+            }
121
+        }
122
+        $lock('dataroot');
123
+
124
+        if ($config->cacheroot) {
125
+            $config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
126
+        } else {
127
+            $config->cacheroot = $config->dataroot;
128
+        }
129
+        $lock('cacheroot');
130
+
131
+        if ($config->wwwroot) {
132
+            $config->wwwroot = rtrim($config->wwwroot, '/') . '/';
133
+        } else {
134
+            $config->wwwroot = $this->services->request->sniffElggUrl();
135
+        }
136
+        $lock('wwwroot');
137
+
138
+        if (!$config->language) {
139
+            $config->language = self::DEFAULT_LANG;
140
+        }
141
+
142
+        if ($config->default_limit) {
143
+            $lock('default_limit');
144
+        } else {
145
+            $config->default_limit = self::DEFAULT_LIMIT;
146
+        }
147
+
148
+        $locked_props = [
149
+            'site_guid' => 1,
150
+            'path' => Paths::project(),
151
+            'plugins_path' => Paths::project() . "mod/",
152
+            'pluginspath' => Paths::project() . "mod/",
153
+            'url' => $config->wwwroot,
154
+        ];
155
+        foreach ($locked_props as $name => $value) {
156
+            $config->$name = $value;
157
+            $lock($name);
158
+        }
159
+
160
+        // move sensitive credentials into isolated services
161
+        $this->services->dbConfig;
162
+
163
+        // If the site secret is in the settings file, let's move it to that component
164
+        // right away to keep this value out of config.
165
+        $secret = SiteSecret::fromConfig($config);
166
+        if ($secret) {
167
+            $this->services->setValue('siteSecret', $secret);
168
+            $config->elgg_config_set_secret = true;
169
+        }
170
+
171
+        $config->boot_complete = false;
172
+    }
173
+
174
+    /**
175
+     * Get the DB credentials.
176
+     *
177
+     * We no longer leave DB credentials in the config in case it gets accidentally dumped.
178
+     *
179
+     * @return \Elgg\Database\DbConfig
180
+     */
181
+    public function getDbConfig() {
182
+        return $this->services->dbConfig;
183
+    }
184
+
185
+    /**
186
+     * Define all Elgg global functions and constants, wire up boot events, but don't boot
187
+     *
188
+     * This includes all the .php files in engine/lib (not upgrades). If a script returns a function,
189
+     * it is queued and executed at the end.
190
+     *
191
+     * @return void
192
+     * @access private
193
+     * @internal
194
+     * @throws \InstallationException
195
+     */
196
+    public function loadCore() {
197
+        if (self::$core_loaded) {
198
+            return;
199
+        }
200
+
201
+        $setups = [];
202
+        $path = Paths::elgg() . 'engine/lib';
203
+
204
+        // include library files, capturing setup functions
205
+        foreach (self::getEngineLibs() as $file) {
206
+            $return = Includer::includeFile("$path/$file");
207
+            if (!$return) {
208
+                throw new \InstallationException("Elgg lib file failed include: engine/lib/$file");
209
+            }
210
+            if ($return instanceof \Closure) {
211
+                $setups[$file] = $return;
212
+            }
213
+        }
214
+
215
+        // store instance to be returned by elgg()
216
+        self::$_instance = $this;
217
+
218
+        // allow global services access. :(
219
+        _elgg_services($this->services);
220
+
221
+        // setup logger and inject into config
222
+        //$this->services->config->setLogger($this->services->logger);
223
+
224
+        $hooks = $this->services->hooks;
225
+        $events = $hooks->getEvents();
226
+
227
+        // run setups
228
+        foreach ($setups as $func) {
229
+            $func($events, $hooks);
230
+        }
231
+
232
+        self::$core_loaded = true;
233
+    }
234
+
235
+    /**
236
+     * Start and boot the core
237
+     *
238
+     * @return self
239
+     */
240
+    public static function start() {
241
+        $app = self::factory();
242
+        $app->bootCore();
243
+        return $app;
244
+    }
245
+
246
+    /**
247
+     * Bootstrap the Elgg engine, loads plugins, and calls initial system events
248
+     *
249
+     * This method loads the full Elgg engine, checks the installation
250
+     * state, and triggers a series of events to finish booting Elgg:
251
+     * 	- {@elgg_event boot system}
252
+     * 	- {@elgg_event init system}
253
+     * 	- {@elgg_event ready system}
254
+     *
255
+     * If Elgg is not fully installed, the browser will be redirected to an installation page.
256
+     *
257
+     * @return void
258
+     */
259
+    public function bootCore() {
260
+        $config = $this->services->config;
261
+
262
+        if ($this->isTestingApplication()) {
263
+            throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
264
+        }
265
+
266
+        if ($config->boot_complete) {
267
+            return;
268
+        }
269
+
270
+        // in case not loaded already
271
+        $this->loadCore();
272
+
273
+        if (!$this->services->db) {
274
+            // no database boot!
275
+            elgg_views_boot();
276
+            $this->services->session->start();
277
+            $this->services->translator->loadTranslations();
278
+
279
+            actions_init();
280
+            _elgg_init();
281
+            _elgg_input_init();
282
+            _elgg_nav_init();
283
+
284
+            $config->boot_complete = true;
285
+            $config->lock('boot_complete');
286
+            return;
287
+        }
288
+
289
+        // Connect to database, load language files, load configuration, init session
290
+        $this->services->boot->boot($this->services);
291
+
292
+        elgg_views_boot();
293
+
294
+        // Load the plugins that are active
295
+        $this->services->plugins->load();
296
+
297
+        if (Paths::project() != Paths::elgg()) {
298
+            // Elgg is installed as a composer dep, so try to treat the root directory
299
+            // as a custom plugin that is always loaded last and can't be disabled...
300
+            if (!$config->system_cache_loaded) {
301
+                // configure view locations for the custom plugin (not Elgg core)
302
+                $viewsFile = Paths::project() . 'views.php';
303
+                if (is_file($viewsFile)) {
304
+                    $viewsSpec = Includer::includeFile($viewsFile);
305
+                    if (is_array($viewsSpec)) {
306
+                        $this->services->views->mergeViewsSpec($viewsSpec);
307
+                    }
308
+                }
309
+
310
+                // find views for the custom plugin (not Elgg core)
311
+                $this->services->views->registerPluginViews(Paths::project());
312
+            }
313
+
314
+            if (!$config->i18n_loaded_from_cache) {
315
+                $this->services->translator->registerPluginTranslations(Paths::project());
316
+            }
317
+
318
+            // This is root directory start.php
319
+            $root_start = Paths::project() . "start.php";
320
+            if (is_file($root_start)) {
321
+                require $root_start;
322
+            }
323
+        }
324
+
325
+        // after plugins are started we know which viewtypes are populated
326
+        $this->services->views->clampViewtypeToPopulatedViews();
327
+
328
+        $this->allowPathRewrite();
329
+
330
+        $events = $this->services->hooks->getEvents();
331
+
332
+        // Allows registering handlers strictly before all init, system handlers
333
+        $events->trigger('plugins_boot', 'system');
334
+
335
+        // Complete the boot process for both engine and plugins
336
+        $events->trigger('init', 'system');
337
+
338
+        $config->boot_complete = true;
339
+        $config->lock('boot_complete');
340
+
341
+        // System loaded and ready
342
+        $events->trigger('ready', 'system');
343
+    }
344
+
345
+    /**
346
+     * Get a Database wrapper for performing queries without booting Elgg
347
+     *
348
+     * If settings has not been loaded, it will be loaded to configure the DB connection.
349
+     *
350
+     * @note Before boot, the Database instance will not yet be bound to a Logger.
351
+     *
352
+     * @return \Elgg\Application\Database
353
+     */
354
+    public function getDb() {
355
+        return $this->services->publicDb;
356
+    }
357
+
358
+    /**
359
+     * Get an undefined property
360
+     *
361
+     * @param string $name The property name accessed
362
+     *
363
+     * @return mixed
364
+     */
365
+    public function __get($name) {
366
+        if (isset(self::$public_services[$name])) {
367
+            return $this->services->{$name};
368
+        }
369
+        trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
370
+    }
371
+
372
+    /**
373
+     * Creates a new, trivial instance of Elgg\Application and set it as the singleton instance.
374
+     * If the singleton is already set, it's returned.
375
+     *
376
+     * @param array $spec Specification for initial call.
377
+     * @return self
378
+     * @throws ConfigurationException
379
+     */
380
+    public static function factory(array $spec = []) {
381
+        if (self::$_instance !== null) {
382
+            return self::$_instance;
383
+        }
384
+
385
+        $defaults = [
386
+            'service_provider' => null,
387
+            'config' => null,
388
+            'settings_path' => null,
389
+            'handle_exceptions' => true,
390
+            'handle_shutdown' => true,
391
+            'overwrite_global_config' => true,
392
+            'set_start_time' => true,
393
+            'request' => null,
394
+        ];
395
+        $spec = array_merge($defaults, $spec);
396
+
397
+        if ($spec['set_start_time']) {
398
+            /**
399
+             * The time with microseconds when the Elgg engine was started.
400
+             *
401
+             * @global float
402
+             */
403
+            if (!isset($GLOBALS['START_MICROTIME'])) {
404
+                $GLOBALS['START_MICROTIME'] = microtime(true);
405
+            }
406
+        }
407
+
408
+        if (!$spec['service_provider']) {
409
+            if (!$spec['config']) {
410
+                $spec['config'] = Config::factory($spec['settings_path']);
411
+            }
412
+            $spec['service_provider'] = new ServiceProvider($spec['config']);
413
+        }
414
+
415
+        if ($spec['request']) {
416
+            if ($spec['request'] instanceof Request) {
417
+                $spec['service_provider']->setValue('request', $spec['request']);
418
+            } else {
419
+                throw new \InvalidArgumentException("Given request is not a " . Request::class);
420
+            }
421
+        }
422
+
423
+        self::$_instance = new self($spec['service_provider']);
424
+
425
+        if ($spec['handle_exceptions']) {
426
+            set_error_handler([self::$_instance, 'handleErrors']);
427
+            set_exception_handler([self::$_instance, 'handleExceptions']);
428
+        }
429
+
430
+        if ($spec['handle_shutdown']) {
431
+            // we need to register for shutdown before Symfony registers the
432
+            // session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
433
+            register_shutdown_function(function () {
434
+                // There are cases where we may exit before this function is defined
435
+                if (function_exists('_elgg_shutdown_hook')) {
436
+                    _elgg_shutdown_hook();
437
+                }
438
+            });
439
+        }
440
+
441
+        if ($spec['overwrite_global_config']) {
442
+            global $CONFIG;
443
+
444
+            // this will be buggy be at least PHP will log failures
445
+            $CONFIG = $spec['service_provider']->config;
446
+        }
447
+
448
+        return self::$_instance;
449
+    }
450
+
451
+    /**
452
+     * Elgg's front controller. Handles basically all incoming URL requests.
453
+     *
454
+     * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
455
+     */
456
+    public static function index() {
457
+        $req = Request::createFromGlobals();
458
+        /** @var Request $req */
459
+
460
+        if ($req->isRewriteCheck()) {
461
+            echo Request::REWRITE_TEST_OUTPUT;
462
+            return true;
463
+        }
464
+
465
+        return self::factory(['request' => $req])->run();
466
+    }
467
+
468
+    /**
469
+     * Routes the request, booting core if not yet booted
470
+     *
471
+     * @return bool False if Elgg wants the PHP CLI server to handle the request
472
+     */
473
+    public function run() {
474
+        $config = $this->services->config;
475
+        $request = $this->services->request;
476
+
477
+        if ($request->isCliServer()) {
478
+            if ($request->isCliServable(Paths::project())) {
479
+                return false;
480
+            }
481
+
482
+            // overwrite value from settings
483
+            $www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
484
+            $config->wwwroot = $www_root;
485
+            $config->wwwroot_cli_server = $www_root;
486
+        }
487
+
488
+        if (0 === strpos($request->getElggPath(), '/cache/')) {
489
+            $this->services->cacheHandler->handleRequest($request)->prepare($request)->send();
490
+            return true;
491
+        }
492
+
493
+        if (0 === strpos($request->getElggPath(), '/serve-file/')) {
494
+            $this->services->serveFileHandler->getResponse($request)->send();
495
+            return true;
496
+        }
497
+
498
+        $this->bootCore();
499
+
500
+        // TODO use formal Response object instead
501
+        // This is to set the charset to UTF-8.
502
+        header("Content-Type: text/html;charset=utf-8", true);
503
+
504
+        // re-fetch new request from services in case it was replaced by route:rewrite
505
+        $request = $this->services->request;
506
+
507
+        if (!$this->services->router->route($request)) {
508
+            forward('', '404');
509
+        }
510
+    }
511
+
512
+    /**
513
+     * Returns a directory that points to the root of Elgg, but not necessarily
514
+     * the install root. See `self::root()` for that.
515
+     *
516
+     * @return Directory
517
+     */
518
+    public static function elggDir() {
519
+        return Local::elggRoot();
520
+    }
521
+
522
+    /**
523
+     * Returns a directory that points to the project root, where composer is installed.
524
+     *
525
+     * @return Directory
526
+     */
527
+    public static function projectDir() {
528
+        return Local::projectRoot();
529
+    }
530
+
531
+    /**
532
+     * Renders a web UI for installing Elgg.
533
+     *
534
+     * @return void
535
+     */
536
+    public static function install() {
537
+        ini_set('display_errors', 1);
538
+        $installer = new \ElggInstaller();
539
+        $installer->run();
540
+    }
541
+
542
+    /**
543
+     * Elgg upgrade script.
544
+     *
545
+     * This script triggers any necessary upgrades. If the site has been upgraded
546
+     * to the most recent version of the code, no upgrades are run but the caches
547
+     * are flushed.
548
+     *
549
+     * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
550
+     *
551
+     * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
552
+     * to a relative URL.
553
+     *
554
+     * @return void
555
+     */
556
+    public static function upgrade() {
557
+        // we want to know if an error occurs
558
+        ini_set('display_errors', 1);
559
+        $is_cli = (php_sapi_name() === 'cli');
560
+
561
+        $forward = function ($url) use ($is_cli) {
562
+            if ($is_cli) {
563
+                echo "Open $url in your browser to continue.";
564
+                exit;
565
+            }
566
+
567
+            forward($url);
568
+        };
569
+
570
+        define('UPGRADING', 'upgrading');
571
+
572
+        self::start();
573 573
 		
574
-		// check security settings
575
-		if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
576
-			// only admin's or users with a valid token can run upgrade.php
577
-			elgg_signed_request_gatekeeper();
578
-		}
574
+        // check security settings
575
+        if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
576
+            // only admin's or users with a valid token can run upgrade.php
577
+            elgg_signed_request_gatekeeper();
578
+        }
579 579
 		
580
-		$site_url = _elgg_config()->url;
581
-		$site_host = parse_url($site_url, PHP_URL_HOST) . '/';
582
-
583
-		// turn any full in-site URLs into absolute paths
584
-		$forward_url = get_input('forward', '/admin', false);
585
-		$forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
586
-
587
-		if (strpos($forward_url, '/') !== 0) {
588
-			$forward_url = '/' . $forward_url;
589
-		}
590
-
591
-		if ($is_cli || (get_input('upgrade') == 'upgrade')) {
592
-			$upgrader = _elgg_services()->upgrades;
593
-			$result = $upgrader->run();
594
-
595
-			if ($result['failure'] == true) {
596
-				register_error($result['reason']);
597
-				$forward($forward_url);
598
-			}
599
-
600
-			// Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
601
-			$core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
602
-			$has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
603
-
604
-			if ($has_pending_upgrades) {
605
-				// Forward to the list of pending upgrades
606
-				$forward_url = '/admin/upgrades';
607
-			}
608
-		} else {
609
-			$rewriteTester = new \ElggRewriteTester();
610
-			$url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
611
-			if (!$rewriteTester->runRewriteTest($url)) {
612
-				// see if there is a problem accessing the site at all
613
-				// due to ip restrictions for example
614
-				if (!$rewriteTester->runLocalhostAccessTest()) {
615
-					// note: translation may not be available until after upgrade
616
-					$msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
617
-					if ($msg === "installation:htaccess:localhost:connectionfailed") {
618
-						$msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
619
-								. "that curl is working and there are no IP restrictions preventing "
620
-								. "localhost connections.";
621
-					}
622
-					echo $msg;
623
-					exit;
624
-				}
625
-
626
-				// note: translation may not be available until after upgrade
627
-				$msg = elgg_echo("installation:htaccess:needs_upgrade");
628
-				if ($msg === "installation:htaccess:needs_upgrade") {
629
-					$msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
630
-				}
631
-				echo $msg;
632
-				exit;
633
-			}
634
-
635
-			$vars = [
636
-				'forward' => $forward_url
637
-			];
638
-
639
-			// reset cache to have latest translations available during upgrade
640
-			elgg_reset_system_cache();
641
-
642
-			echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
643
-			exit;
644
-		}
645
-
646
-		$forward($forward_url);
647
-	}
648
-
649
-	/**
650
-	 * Allow plugins to rewrite the path.
651
-	 *
652
-	 * @return void
653
-	 */
654
-	private function allowPathRewrite() {
655
-		$request = $this->services->request;
656
-		$new = $this->services->router->allowRewrite($request);
657
-		if ($new === $request) {
658
-			return;
659
-		}
660
-
661
-		$this->services->setValue('request', $new);
662
-		$this->services->context->initialize($new);
663
-	}
664
-
665
-	/**
666
-	 * Flag this application as running for testing (PHPUnit)
667
-	 *
668
-	 * @param bool $testing Is testing application
669
-	 * @return void
670
-	 */
671
-	public static function setTestingApplication($testing = true) {
672
-		self::$testing_app = $testing;
673
-	}
674
-
675
-	/**
676
-	 * Checks if the application is running in PHPUnit
677
-	 * @return bool
678
-	 */
679
-	public static function isTestingApplication() {
680
-		return (bool) self::$testing_app;
681
-	}
682
-
683
-	/**
684
-	 * Intercepts, logs, and displays uncaught exceptions.
685
-	 *
686
-	 * To use a viewtype other than failsafe, create the views:
687
-	 *  <viewtype>/messages/exceptions/admin_exception
688
-	 *  <viewtype>/messages/exceptions/exception
689
-	 * See the json viewtype for an example.
690
-	 *
691
-	 * @warning This function should never be called directly.
692
-	 *
693
-	 * @see http://www.php.net/set-exception-handler
694
-	 *
695
-	 * @param \Exception|\Error $exception The exception/error being handled
696
-	 *
697
-	 * @return void
698
-	 * @access private
699
-	 */
700
-	public function handleExceptions($exception) {
701
-		$timestamp = time();
702
-		error_log("Exception at time $timestamp: $exception");
703
-
704
-		// Wipe any existing output buffer
705
-		ob_end_clean();
706
-
707
-		// make sure the error isn't cached
708
-		header("Cache-Control: no-cache, must-revalidate", true);
709
-		header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
710
-
711
-		if ($exception instanceof \InstallationException) {
712
-			forward('/install.php');
713
-		}
714
-
715
-		if (!self::$core_loaded) {
716
-			http_response_code(500);
717
-			echo "Exception loading Elgg core. Check log at time $timestamp";
718
-			return;
719
-		}
720
-
721
-		try {
722
-			// allow custom scripts to trigger on exception
723
-			// value in settings.php should be a system path to a file to include
724
-			$exception_include = $this->services->config->exception_include;
725
-
726
-			if ($exception_include && is_file($exception_include)) {
727
-				ob_start();
728
-
729
-				// don't isolate, these scripts may use the local $exception var.
730
-				include $exception_include;
731
-
732
-				$exception_output = ob_get_clean();
733
-
734
-				// if content is returned from the custom handler we will output
735
-				// that instead of our default failsafe view
736
-				if (!empty($exception_output)) {
737
-					echo $exception_output;
738
-					exit;
739
-				}
740
-			}
741
-
742
-			if (elgg_is_xhr()) {
743
-				elgg_set_viewtype('json');
744
-				$response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
745
-			} else {
746
-				elgg_set_viewtype('failsafe');
747
-				$response = new \Symfony\Component\HttpFoundation\Response('', 500);
748
-			}
749
-
750
-			if (elgg_is_admin_logged_in()) {
751
-				$body = elgg_view("messages/exceptions/admin_exception", [
752
-					'object' => $exception,
753
-					'ts' => $timestamp
754
-				]);
755
-			} else {
756
-				$body = elgg_view("messages/exceptions/exception", [
757
-					'object' => $exception,
758
-					'ts' => $timestamp
759
-				]);
760
-			}
761
-
762
-			$response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
763
-			$response->send();
764
-		} catch (\Exception $e) {
765
-			$timestamp = time();
766
-			$message = $e->getMessage();
767
-			http_response_code(500);
768
-			echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
769
-			error_log("Exception at time $timestamp : fatal error in exception handler : $message");
770
-		}
771
-	}
772
-
773
-	/**
774
-	 * Intercepts catchable PHP errors.
775
-	 *
776
-	 * @warning This function should never be called directly.
777
-	 *
778
-	 * @internal
779
-	 * For catchable fatal errors, throws an Exception with the error.
780
-	 *
781
-	 * For non-fatal errors, depending upon the debug settings, either
782
-	 * log the error or ignore it.
783
-	 *
784
-	 * @see http://www.php.net/set-error-handler
785
-	 *
786
-	 * @param int    $errno    The level of the error raised
787
-	 * @param string $errmsg   The error message
788
-	 * @param string $filename The filename the error was raised in
789
-	 * @param int    $linenum  The line number the error was raised at
790
-	 * @param array  $vars     An array that points to the active symbol table where error occurred
791
-	 *
792
-	 * @return true
793
-	 * @throws \Exception
794
-	 * @access private
795
-	 */
796
-	public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
797
-		$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
798
-
799
-		$log = function ($message, $level) {
800
-			if (self::$core_loaded) {
801
-				return elgg_log($message, $level);
802
-			}
803
-
804
-			return false;
805
-		};
806
-
807
-		switch ($errno) {
808
-			case E_USER_ERROR:
809
-				if (!$log("PHP: $error", 'ERROR')) {
810
-					error_log("PHP ERROR: $error");
811
-				}
812
-				if (self::$core_loaded) {
813
-					register_error("ERROR: $error");
814
-				}
815
-
816
-				// Since this is a fatal error, we want to stop any further execution but do so gracefully.
817
-				throw new \Exception($error);
818
-				break;
819
-
820
-			case E_WARNING :
821
-			case E_USER_WARNING :
822
-			case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
823
-
824
-				// check if the error wasn't suppressed by the error control operator (@)
825
-				if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
826
-					error_log("PHP WARNING: $error");
827
-				}
828
-				break;
829
-
830
-			default:
831
-				if (function_exists('_elgg_config')) {
832
-					$debug = _elgg_config()->debug;
833
-				} else {
834
-					$debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
835
-				}
836
-				if ($debug !== 'NOTICE') {
837
-					return true;
838
-				}
839
-
840
-				if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
841
-					error_log("PHP NOTICE: $error");
842
-				}
843
-		}
844
-
845
-		return true;
846
-	}
847
-
848
-	/**
849
-	 * Does nothing.
850
-	 *
851
-	 * @return void
852
-	 * @deprecated
853
-	 */
854
-	public function loadSettings() {
855
-		trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
856
-	}
857
-
858
-	/**
859
-	 * Get all engine/lib library filenames
860
-	 *
861
-	 * @note We can't just pull in all directory files because some users leave old files in place.
862
-	 *
863
-	 * @return string[]
864
-	 */
865
-	private static function getEngineLibs() {
866
-		return [
867
-			'access.php',
868
-			'actions.php',
869
-			'admin.php',
870
-			'annotations.php',
871
-			'autoloader.php',
872
-			'cache.php',
873
-			'comments.php',
874
-			'configuration.php',
875
-			'constants.php',
876
-			'cron.php',
877
-			'database.php',
878
-			'deprecated-3.0.php',
879
-			'elgglib.php',
880
-			'entities.php',
881
-			'filestore.php',
882
-			'group.php',
883
-			'input.php',
884
-			'languages.php',
885
-			'mb_wrapper.php',
886
-			'memcache.php',
887
-			'metadata.php',
888
-			'metastrings.php',
889
-			'navigation.php',
890
-			'notification.php',
891
-			'output.php',
892
-			'pagehandler.php',
893
-			'pageowner.php',
894
-			'pam.php',
895
-			'plugins.php',
896
-			'private_settings.php',
897
-			'relationships.php',
898
-			'river.php',
899
-			'sessions.php',
900
-			'statistics.php',
901
-			'system_log.php',
902
-			'tags.php',
903
-			'upgrade.php',
904
-			'user_settings.php',
905
-			'users.php',
906
-			'views.php',
907
-			'widgets.php',
908
-		];
909
-	}
580
+        $site_url = _elgg_config()->url;
581
+        $site_host = parse_url($site_url, PHP_URL_HOST) . '/';
582
+
583
+        // turn any full in-site URLs into absolute paths
584
+        $forward_url = get_input('forward', '/admin', false);
585
+        $forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
586
+
587
+        if (strpos($forward_url, '/') !== 0) {
588
+            $forward_url = '/' . $forward_url;
589
+        }
590
+
591
+        if ($is_cli || (get_input('upgrade') == 'upgrade')) {
592
+            $upgrader = _elgg_services()->upgrades;
593
+            $result = $upgrader->run();
594
+
595
+            if ($result['failure'] == true) {
596
+                register_error($result['reason']);
597
+                $forward($forward_url);
598
+            }
599
+
600
+            // Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
601
+            $core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
602
+            $has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
603
+
604
+            if ($has_pending_upgrades) {
605
+                // Forward to the list of pending upgrades
606
+                $forward_url = '/admin/upgrades';
607
+            }
608
+        } else {
609
+            $rewriteTester = new \ElggRewriteTester();
610
+            $url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
611
+            if (!$rewriteTester->runRewriteTest($url)) {
612
+                // see if there is a problem accessing the site at all
613
+                // due to ip restrictions for example
614
+                if (!$rewriteTester->runLocalhostAccessTest()) {
615
+                    // note: translation may not be available until after upgrade
616
+                    $msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
617
+                    if ($msg === "installation:htaccess:localhost:connectionfailed") {
618
+                        $msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
619
+                                . "that curl is working and there are no IP restrictions preventing "
620
+                                . "localhost connections.";
621
+                    }
622
+                    echo $msg;
623
+                    exit;
624
+                }
625
+
626
+                // note: translation may not be available until after upgrade
627
+                $msg = elgg_echo("installation:htaccess:needs_upgrade");
628
+                if ($msg === "installation:htaccess:needs_upgrade") {
629
+                    $msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
630
+                }
631
+                echo $msg;
632
+                exit;
633
+            }
634
+
635
+            $vars = [
636
+                'forward' => $forward_url
637
+            ];
638
+
639
+            // reset cache to have latest translations available during upgrade
640
+            elgg_reset_system_cache();
641
+
642
+            echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
643
+            exit;
644
+        }
645
+
646
+        $forward($forward_url);
647
+    }
648
+
649
+    /**
650
+     * Allow plugins to rewrite the path.
651
+     *
652
+     * @return void
653
+     */
654
+    private function allowPathRewrite() {
655
+        $request = $this->services->request;
656
+        $new = $this->services->router->allowRewrite($request);
657
+        if ($new === $request) {
658
+            return;
659
+        }
660
+
661
+        $this->services->setValue('request', $new);
662
+        $this->services->context->initialize($new);
663
+    }
664
+
665
+    /**
666
+     * Flag this application as running for testing (PHPUnit)
667
+     *
668
+     * @param bool $testing Is testing application
669
+     * @return void
670
+     */
671
+    public static function setTestingApplication($testing = true) {
672
+        self::$testing_app = $testing;
673
+    }
674
+
675
+    /**
676
+     * Checks if the application is running in PHPUnit
677
+     * @return bool
678
+     */
679
+    public static function isTestingApplication() {
680
+        return (bool) self::$testing_app;
681
+    }
682
+
683
+    /**
684
+     * Intercepts, logs, and displays uncaught exceptions.
685
+     *
686
+     * To use a viewtype other than failsafe, create the views:
687
+     *  <viewtype>/messages/exceptions/admin_exception
688
+     *  <viewtype>/messages/exceptions/exception
689
+     * See the json viewtype for an example.
690
+     *
691
+     * @warning This function should never be called directly.
692
+     *
693
+     * @see http://www.php.net/set-exception-handler
694
+     *
695
+     * @param \Exception|\Error $exception The exception/error being handled
696
+     *
697
+     * @return void
698
+     * @access private
699
+     */
700
+    public function handleExceptions($exception) {
701
+        $timestamp = time();
702
+        error_log("Exception at time $timestamp: $exception");
703
+
704
+        // Wipe any existing output buffer
705
+        ob_end_clean();
706
+
707
+        // make sure the error isn't cached
708
+        header("Cache-Control: no-cache, must-revalidate", true);
709
+        header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
710
+
711
+        if ($exception instanceof \InstallationException) {
712
+            forward('/install.php');
713
+        }
714
+
715
+        if (!self::$core_loaded) {
716
+            http_response_code(500);
717
+            echo "Exception loading Elgg core. Check log at time $timestamp";
718
+            return;
719
+        }
720
+
721
+        try {
722
+            // allow custom scripts to trigger on exception
723
+            // value in settings.php should be a system path to a file to include
724
+            $exception_include = $this->services->config->exception_include;
725
+
726
+            if ($exception_include && is_file($exception_include)) {
727
+                ob_start();
728
+
729
+                // don't isolate, these scripts may use the local $exception var.
730
+                include $exception_include;
731
+
732
+                $exception_output = ob_get_clean();
733
+
734
+                // if content is returned from the custom handler we will output
735
+                // that instead of our default failsafe view
736
+                if (!empty($exception_output)) {
737
+                    echo $exception_output;
738
+                    exit;
739
+                }
740
+            }
741
+
742
+            if (elgg_is_xhr()) {
743
+                elgg_set_viewtype('json');
744
+                $response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
745
+            } else {
746
+                elgg_set_viewtype('failsafe');
747
+                $response = new \Symfony\Component\HttpFoundation\Response('', 500);
748
+            }
749
+
750
+            if (elgg_is_admin_logged_in()) {
751
+                $body = elgg_view("messages/exceptions/admin_exception", [
752
+                    'object' => $exception,
753
+                    'ts' => $timestamp
754
+                ]);
755
+            } else {
756
+                $body = elgg_view("messages/exceptions/exception", [
757
+                    'object' => $exception,
758
+                    'ts' => $timestamp
759
+                ]);
760
+            }
761
+
762
+            $response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
763
+            $response->send();
764
+        } catch (\Exception $e) {
765
+            $timestamp = time();
766
+            $message = $e->getMessage();
767
+            http_response_code(500);
768
+            echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
769
+            error_log("Exception at time $timestamp : fatal error in exception handler : $message");
770
+        }
771
+    }
772
+
773
+    /**
774
+     * Intercepts catchable PHP errors.
775
+     *
776
+     * @warning This function should never be called directly.
777
+     *
778
+     * @internal
779
+     * For catchable fatal errors, throws an Exception with the error.
780
+     *
781
+     * For non-fatal errors, depending upon the debug settings, either
782
+     * log the error or ignore it.
783
+     *
784
+     * @see http://www.php.net/set-error-handler
785
+     *
786
+     * @param int    $errno    The level of the error raised
787
+     * @param string $errmsg   The error message
788
+     * @param string $filename The filename the error was raised in
789
+     * @param int    $linenum  The line number the error was raised at
790
+     * @param array  $vars     An array that points to the active symbol table where error occurred
791
+     *
792
+     * @return true
793
+     * @throws \Exception
794
+     * @access private
795
+     */
796
+    public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
797
+        $error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
798
+
799
+        $log = function ($message, $level) {
800
+            if (self::$core_loaded) {
801
+                return elgg_log($message, $level);
802
+            }
803
+
804
+            return false;
805
+        };
806
+
807
+        switch ($errno) {
808
+            case E_USER_ERROR:
809
+                if (!$log("PHP: $error", 'ERROR')) {
810
+                    error_log("PHP ERROR: $error");
811
+                }
812
+                if (self::$core_loaded) {
813
+                    register_error("ERROR: $error");
814
+                }
815
+
816
+                // Since this is a fatal error, we want to stop any further execution but do so gracefully.
817
+                throw new \Exception($error);
818
+                break;
819
+
820
+            case E_WARNING :
821
+            case E_USER_WARNING :
822
+            case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
823
+
824
+                // check if the error wasn't suppressed by the error control operator (@)
825
+                if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
826
+                    error_log("PHP WARNING: $error");
827
+                }
828
+                break;
829
+
830
+            default:
831
+                if (function_exists('_elgg_config')) {
832
+                    $debug = _elgg_config()->debug;
833
+                } else {
834
+                    $debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
835
+                }
836
+                if ($debug !== 'NOTICE') {
837
+                    return true;
838
+                }
839
+
840
+                if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
841
+                    error_log("PHP NOTICE: $error");
842
+                }
843
+        }
844
+
845
+        return true;
846
+    }
847
+
848
+    /**
849
+     * Does nothing.
850
+     *
851
+     * @return void
852
+     * @deprecated
853
+     */
854
+    public function loadSettings() {
855
+        trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
856
+    }
857
+
858
+    /**
859
+     * Get all engine/lib library filenames
860
+     *
861
+     * @note We can't just pull in all directory files because some users leave old files in place.
862
+     *
863
+     * @return string[]
864
+     */
865
+    private static function getEngineLibs() {
866
+        return [
867
+            'access.php',
868
+            'actions.php',
869
+            'admin.php',
870
+            'annotations.php',
871
+            'autoloader.php',
872
+            'cache.php',
873
+            'comments.php',
874
+            'configuration.php',
875
+            'constants.php',
876
+            'cron.php',
877
+            'database.php',
878
+            'deprecated-3.0.php',
879
+            'elgglib.php',
880
+            'entities.php',
881
+            'filestore.php',
882
+            'group.php',
883
+            'input.php',
884
+            'languages.php',
885
+            'mb_wrapper.php',
886
+            'memcache.php',
887
+            'metadata.php',
888
+            'metastrings.php',
889
+            'navigation.php',
890
+            'notification.php',
891
+            'output.php',
892
+            'pagehandler.php',
893
+            'pageowner.php',
894
+            'pam.php',
895
+            'plugins.php',
896
+            'private_settings.php',
897
+            'relationships.php',
898
+            'river.php',
899
+            'sessions.php',
900
+            'statistics.php',
901
+            'system_log.php',
902
+            'tags.php',
903
+            'upgrade.php',
904
+            'user_settings.php',
905
+            'users.php',
906
+            'views.php',
907
+            'widgets.php',
908
+        ];
909
+    }
910 910
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/Application/CacheHandler.php 2 patches
Doc Comments   -1 removed lines patch added patch discarded remove patch
@@ -74,7 +74,6 @@
 block discarded – undo
74 74
 	/**
75 75
 	 * Handle a request for a cached view
76 76
 	 *
77
-	 * @param array $path URL path
78 77
 	 * @return Response (unprepared)
79 78
 	 */
80 79
 	public function handleRequest(Request $request) {
Please login to merge, or discard this patch.
Indentation   +352 added lines, -352 removed lines patch added patch discarded remove patch
@@ -15,362 +15,362 @@
 block discarded – undo
15 15
  */
16 16
 class CacheHandler {
17 17
 	
18
-	public static $extensions = [
19
-		'bmp' => "image/bmp",
20
-		'css' => "text/css",
21
-		'gif' => "image/gif",
22
-		'html' => "text/html",
23
-		'ico' => "image/x-icon",
24
-		'jpeg' => "image/jpeg",
25
-		'jpg' => "image/jpeg",
26
-		'js' => "application/javascript",
27
-		'json' => "application/json",
28
-		'png' => "image/png",
29
-		'svg' => "image/svg+xml",
30
-		'swf' => "application/x-shockwave-flash",
31
-		'tiff' => "image/tiff",
32
-		'webp' => "image/webp",
33
-		'xml' => "text/xml",
34
-		'eot' => "application/vnd.ms-fontobject",
35
-		'ttf' => "application/font-ttf",
36
-		'woff' => "application/font-woff",
37
-		'woff2' => "application/font-woff2",
38
-		'otf' => "application/font-otf",
39
-	];
40
-
41
-	public static $utf8_content_types = [
42
-		"text/css",
43
-		"text/html",
44
-		"application/javascript",
45
-		"application/json",
46
-		"image/svg+xml",
47
-		"text/xml",
48
-	];
49
-
50
-	/** @var Application */
51
-	private $application;
52
-
53
-	/** @var Config */
54
-	private $config;
55
-
56
-	/** @var Request */
57
-	private $request;
58
-
59
-	/**
60
-	 * Constructor
61
-	 *
62
-	 * @param Application $app                 Elgg Application
63
-	 * @param Config      $config              Elgg configuration
64
-	 * @param Request     $request             HTTP request
65
-	 * @param bool        $simplecache_enabled Is the simplecache enabled?
66
-	 */
67
-	public function __construct(Application $app, Config $config, Request $request, $simplecache_enabled) {
68
-		$this->application = $app;
69
-		$this->config = $config;
70
-		$this->request = $request;
71
-		$this->simplecache_enabled = $simplecache_enabled;
72
-	}
73
-
74
-	/**
75
-	 * Handle a request for a cached view
76
-	 *
77
-	 * @param array $path URL path
78
-	 * @return Response (unprepared)
79
-	 */
80
-	public function handleRequest(Request $request) {
81
-		$config = $this->config;
82
-
83
-		$parsed = $this->parsePath($request->getElggPath());
84
-		if (!$parsed) {
85
-			return $this->send403();
86
-		}
18
+    public static $extensions = [
19
+        'bmp' => "image/bmp",
20
+        'css' => "text/css",
21
+        'gif' => "image/gif",
22
+        'html' => "text/html",
23
+        'ico' => "image/x-icon",
24
+        'jpeg' => "image/jpeg",
25
+        'jpg' => "image/jpeg",
26
+        'js' => "application/javascript",
27
+        'json' => "application/json",
28
+        'png' => "image/png",
29
+        'svg' => "image/svg+xml",
30
+        'swf' => "application/x-shockwave-flash",
31
+        'tiff' => "image/tiff",
32
+        'webp' => "image/webp",
33
+        'xml' => "text/xml",
34
+        'eot' => "application/vnd.ms-fontobject",
35
+        'ttf' => "application/font-ttf",
36
+        'woff' => "application/font-woff",
37
+        'woff2' => "application/font-woff2",
38
+        'otf' => "application/font-otf",
39
+    ];
40
+
41
+    public static $utf8_content_types = [
42
+        "text/css",
43
+        "text/html",
44
+        "application/javascript",
45
+        "application/json",
46
+        "image/svg+xml",
47
+        "text/xml",
48
+    ];
49
+
50
+    /** @var Application */
51
+    private $application;
52
+
53
+    /** @var Config */
54
+    private $config;
55
+
56
+    /** @var Request */
57
+    private $request;
58
+
59
+    /**
60
+     * Constructor
61
+     *
62
+     * @param Application $app                 Elgg Application
63
+     * @param Config      $config              Elgg configuration
64
+     * @param Request     $request             HTTP request
65
+     * @param bool        $simplecache_enabled Is the simplecache enabled?
66
+     */
67
+    public function __construct(Application $app, Config $config, Request $request, $simplecache_enabled) {
68
+        $this->application = $app;
69
+        $this->config = $config;
70
+        $this->request = $request;
71
+        $this->simplecache_enabled = $simplecache_enabled;
72
+    }
73
+
74
+    /**
75
+     * Handle a request for a cached view
76
+     *
77
+     * @param array $path URL path
78
+     * @return Response (unprepared)
79
+     */
80
+    public function handleRequest(Request $request) {
81
+        $config = $this->config;
82
+
83
+        $parsed = $this->parsePath($request->getElggPath());
84
+        if (!$parsed) {
85
+            return $this->send403();
86
+        }
87 87
 		
88
-		$ts = $parsed['ts'];
89
-		$view = $parsed['view'];
90
-		$viewtype = $parsed['viewtype'];
91
-
92
-		$content_type = $this->getContentType($view);
93
-		if (empty($content_type)) {
94
-			return $this->send403("Asset must have a valid file extension");
95
-		}
96
-
97
-		$response = Response::create();
98
-		if (in_array($content_type, self::$utf8_content_types)) {
99
-			$response->headers->set('Content-Type', "$content_type;charset=utf-8", true);
100
-		} else {
101
-			$response->headers->set('Content-Type', $content_type, true);
102
-		}
103
-
104
-		if (!$this->simplecache_enabled) {
105
-			$this->application->bootCore();
106
-			header_remove('Cache-Control');
107
-			header_remove('Pragma');
108
-			header_remove('Expires');
109
-
110
-			if (!$this->isCacheableView($view)) {
111
-				return $this->send403("Requested view is not an asset");
112
-			}
113
-
114
-			$content = $this->getProcessedView($view, $viewtype);
115
-			if ($content === false) {
116
-				return $this->send403();
117
-			}
118
-
119
-			$etag = '"' . md5($content) . '"';
120
-			$this->setRevalidateHeaders($etag, $response);
121
-			if ($this->is304($etag)) {
122
-				return Response::create()->setNotModified();
123
-			}
124
-
125
-			return $response->setContent($content);
126
-		}
127
-
128
-		$etag = "\"$ts\"";
129
-		if ($this->is304($etag)) {
130
-			return Response::create()->setNotModified();
131
-		}
132
-
133
-		// trust the client but check for an existing cache file
134
-		$filename = $config->cacheroot . "views_simplecache/$ts/$viewtype/$view";
135
-		if (file_exists($filename)) {
136
-			$this->sendCacheHeaders($etag, $response);
137
-			return BinaryFileResponse::create($filename, 200, $response->headers->all());
138
-		}
139
-
140
-		// the hard way
141
-		$this->application->bootCore();
142
-		header_remove('Cache-Control');
143
-		header_remove('Pragma');
144
-		header_remove('Expires');
145
-
146
-		elgg_set_viewtype($viewtype);
147
-		if (!$this->isCacheableView($view)) {
148
-			return $this->send403("Requested view is not an asset");
149
-		}
150
-
151
-		$lastcache = (int) $config->lastcache;
152
-
153
-		$filename = $config->cacheroot . "views_simplecache/$lastcache/$viewtype/$view";
154
-
155
-		if ($lastcache == $ts) {
156
-			$this->sendCacheHeaders($etag, $response);
157
-
158
-			$content = $this->getProcessedView($view, $viewtype);
159
-
160
-			$dir_name = dirname($filename);
161
-			if (!is_dir($dir_name)) {
162
-				// PHP and the server accessing the cache symlink may be a different user. And here
163
-				// it's safe to make everything readable anyway.
164
-				mkdir($dir_name, 0775, true);
165
-			}
166
-
167
-			file_put_contents($filename, $content);
168
-			chmod($filename, 0664);
169
-		} else {
170
-			// if wrong timestamp, don't send HTTP cache
171
-			$content = $this->getProcessedView($view, $viewtype);
172
-		}
173
-
174
-		return $response->setContent($content);
175
-	}
176
-
177
-	/**
178
-	 * Parse a request
179
-	 *
180
-	 * @param string $path Request URL path
181
-	 * @return array Cache parameters (empty array if failure)
182
-	 */
183
-	public function parsePath($path) {
184
-		// no '..'
185
-		if (false !== strpos($path, '..')) {
186
-			return [];
187
-		}
188
-		// only alphanumeric characters plus /, ., -, and _
189
-		if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $path)) {
190
-			return [];
191
-		}
192
-
193
-		// testing showed regex to be marginally faster than array / string functions over 100000 reps
194
-		// it won't make a difference in real life and regex is easier to read.
195
-		// <ts>/<viewtype>/<name/of/view.and.dots>.<type>
196
-		if (!preg_match('#^/cache/([0-9]+)/([^/]+)/(.+)$#', $path, $matches)) {
197
-			return [];
198
-		}
199
-
200
-		return [
201
-			'ts' => $matches[1],
202
-			'viewtype' => $matches[2],
203
-			'view' => $matches[3],
204
-		];
205
-	}
206
-
207
-	/**
208
-	 * Is the view cacheable. Language views are handled specially.
209
-	 *
210
-	 * @param string $view View name
211
-	 *
212
-	 * @return bool
213
-	 */
214
-	protected function isCacheableView($view) {
215
-		if (preg_match('~^languages/(.*)\.js$~', $view, $m)) {
216
-			return in_array($m[1],  _elgg_services()->translator->getAllLanguageCodes());
217
-		}
218
-		return _elgg_services()->views->isCacheableView($view);
219
-	}
220
-
221
-	/**
222
-	 * Send cache headers
223
-	 *
224
-	 * @param string $etag ETag value
225
-	 * @return void
226
-	 */
227
-	protected function sendCacheHeaders($etag, Response $response) {
228
-		$response->setSharedMaxAge(86400 * 30 * 6);
229
-		$response->setMaxAge(86400 * 30 * 6);
230
-		$response->headers->set('ETag', $etag);
231
-	}
232
-
233
-	/**
234
-	 * Send revalidate cache headers
235
-	 *
236
-	 * @param string $etag ETag value
237
-	 * @return void
238
-	 */
239
-	protected function setRevalidateHeaders($etag, Response $response) {
240
-		$response->headers->set('Cache-Control', "public, max-age=0, must-revalidate", true);
241
-		$response->headers->set('ETag', $etag);
242
-	}
243
-
244
-	/**
245
-	 * Send a 304 and exit() if the ETag matches the request
246
-	 *
247
-	 * @param string $etag ETag value
248
-	 * @return bool
249
-	 */
250
-	protected function is304($etag) {
251
-		$if_none_match = $this->request->headers->get('If-None-Match');
252
-		if ($if_none_match === null) {
253
-			return false;
254
-		}
255
-
256
-		// strip -gzip and leading /W
257
-		$if_none_match = trim($if_none_match);
258
-		if (0 === strpos($if_none_match, 'W/')) {
259
-			$if_none_match = substr($if_none_match, 2);
260
-		}
261
-		$if_none_match = str_replace('-gzip', '', $if_none_match);
262
-
263
-		return ($if_none_match === $etag);
264
-	}
265
-
266
-	/**
267
-	 * Get the content type
268
-	 *
269
-	 * @param string $view The view name
270
-	 *
271
-	 * @return string|null
272
-	 * @access private
273
-	 */
274
-	public function getContentType($view) {
275
-		$extension = $this->getViewFileType($view);
88
+        $ts = $parsed['ts'];
89
+        $view = $parsed['view'];
90
+        $viewtype = $parsed['viewtype'];
91
+
92
+        $content_type = $this->getContentType($view);
93
+        if (empty($content_type)) {
94
+            return $this->send403("Asset must have a valid file extension");
95
+        }
96
+
97
+        $response = Response::create();
98
+        if (in_array($content_type, self::$utf8_content_types)) {
99
+            $response->headers->set('Content-Type', "$content_type;charset=utf-8", true);
100
+        } else {
101
+            $response->headers->set('Content-Type', $content_type, true);
102
+        }
103
+
104
+        if (!$this->simplecache_enabled) {
105
+            $this->application->bootCore();
106
+            header_remove('Cache-Control');
107
+            header_remove('Pragma');
108
+            header_remove('Expires');
109
+
110
+            if (!$this->isCacheableView($view)) {
111
+                return $this->send403("Requested view is not an asset");
112
+            }
113
+
114
+            $content = $this->getProcessedView($view, $viewtype);
115
+            if ($content === false) {
116
+                return $this->send403();
117
+            }
118
+
119
+            $etag = '"' . md5($content) . '"';
120
+            $this->setRevalidateHeaders($etag, $response);
121
+            if ($this->is304($etag)) {
122
+                return Response::create()->setNotModified();
123
+            }
124
+
125
+            return $response->setContent($content);
126
+        }
127
+
128
+        $etag = "\"$ts\"";
129
+        if ($this->is304($etag)) {
130
+            return Response::create()->setNotModified();
131
+        }
132
+
133
+        // trust the client but check for an existing cache file
134
+        $filename = $config->cacheroot . "views_simplecache/$ts/$viewtype/$view";
135
+        if (file_exists($filename)) {
136
+            $this->sendCacheHeaders($etag, $response);
137
+            return BinaryFileResponse::create($filename, 200, $response->headers->all());
138
+        }
139
+
140
+        // the hard way
141
+        $this->application->bootCore();
142
+        header_remove('Cache-Control');
143
+        header_remove('Pragma');
144
+        header_remove('Expires');
145
+
146
+        elgg_set_viewtype($viewtype);
147
+        if (!$this->isCacheableView($view)) {
148
+            return $this->send403("Requested view is not an asset");
149
+        }
150
+
151
+        $lastcache = (int) $config->lastcache;
152
+
153
+        $filename = $config->cacheroot . "views_simplecache/$lastcache/$viewtype/$view";
154
+
155
+        if ($lastcache == $ts) {
156
+            $this->sendCacheHeaders($etag, $response);
157
+
158
+            $content = $this->getProcessedView($view, $viewtype);
159
+
160
+            $dir_name = dirname($filename);
161
+            if (!is_dir($dir_name)) {
162
+                // PHP and the server accessing the cache symlink may be a different user. And here
163
+                // it's safe to make everything readable anyway.
164
+                mkdir($dir_name, 0775, true);
165
+            }
166
+
167
+            file_put_contents($filename, $content);
168
+            chmod($filename, 0664);
169
+        } else {
170
+            // if wrong timestamp, don't send HTTP cache
171
+            $content = $this->getProcessedView($view, $viewtype);
172
+        }
173
+
174
+        return $response->setContent($content);
175
+    }
176
+
177
+    /**
178
+     * Parse a request
179
+     *
180
+     * @param string $path Request URL path
181
+     * @return array Cache parameters (empty array if failure)
182
+     */
183
+    public function parsePath($path) {
184
+        // no '..'
185
+        if (false !== strpos($path, '..')) {
186
+            return [];
187
+        }
188
+        // only alphanumeric characters plus /, ., -, and _
189
+        if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $path)) {
190
+            return [];
191
+        }
192
+
193
+        // testing showed regex to be marginally faster than array / string functions over 100000 reps
194
+        // it won't make a difference in real life and regex is easier to read.
195
+        // <ts>/<viewtype>/<name/of/view.and.dots>.<type>
196
+        if (!preg_match('#^/cache/([0-9]+)/([^/]+)/(.+)$#', $path, $matches)) {
197
+            return [];
198
+        }
199
+
200
+        return [
201
+            'ts' => $matches[1],
202
+            'viewtype' => $matches[2],
203
+            'view' => $matches[3],
204
+        ];
205
+    }
206
+
207
+    /**
208
+     * Is the view cacheable. Language views are handled specially.
209
+     *
210
+     * @param string $view View name
211
+     *
212
+     * @return bool
213
+     */
214
+    protected function isCacheableView($view) {
215
+        if (preg_match('~^languages/(.*)\.js$~', $view, $m)) {
216
+            return in_array($m[1],  _elgg_services()->translator->getAllLanguageCodes());
217
+        }
218
+        return _elgg_services()->views->isCacheableView($view);
219
+    }
220
+
221
+    /**
222
+     * Send cache headers
223
+     *
224
+     * @param string $etag ETag value
225
+     * @return void
226
+     */
227
+    protected function sendCacheHeaders($etag, Response $response) {
228
+        $response->setSharedMaxAge(86400 * 30 * 6);
229
+        $response->setMaxAge(86400 * 30 * 6);
230
+        $response->headers->set('ETag', $etag);
231
+    }
232
+
233
+    /**
234
+     * Send revalidate cache headers
235
+     *
236
+     * @param string $etag ETag value
237
+     * @return void
238
+     */
239
+    protected function setRevalidateHeaders($etag, Response $response) {
240
+        $response->headers->set('Cache-Control', "public, max-age=0, must-revalidate", true);
241
+        $response->headers->set('ETag', $etag);
242
+    }
243
+
244
+    /**
245
+     * Send a 304 and exit() if the ETag matches the request
246
+     *
247
+     * @param string $etag ETag value
248
+     * @return bool
249
+     */
250
+    protected function is304($etag) {
251
+        $if_none_match = $this->request->headers->get('If-None-Match');
252
+        if ($if_none_match === null) {
253
+            return false;
254
+        }
255
+
256
+        // strip -gzip and leading /W
257
+        $if_none_match = trim($if_none_match);
258
+        if (0 === strpos($if_none_match, 'W/')) {
259
+            $if_none_match = substr($if_none_match, 2);
260
+        }
261
+        $if_none_match = str_replace('-gzip', '', $if_none_match);
262
+
263
+        return ($if_none_match === $etag);
264
+    }
265
+
266
+    /**
267
+     * Get the content type
268
+     *
269
+     * @param string $view The view name
270
+     *
271
+     * @return string|null
272
+     * @access private
273
+     */
274
+    public function getContentType($view) {
275
+        $extension = $this->getViewFileType($view);
276 276
 		
277
-		if (isset(self::$extensions[$extension])) {
278
-			return self::$extensions[$extension];
279
-		} else {
280
-			return null;
281
-		}
282
-	}
277
+        if (isset(self::$extensions[$extension])) {
278
+            return self::$extensions[$extension];
279
+        } else {
280
+            return null;
281
+        }
282
+    }
283 283
 	
284
-	/**
285
-	 * Returns the type of output expected from the view.
286
-	 *
287
-	 *  - view/name.extension returns "extension" if "extension" is valid
288
-	 *  - css/view return "css"
289
-	 *  - js/view return "js"
290
-	 *  - Otherwise, returns "unknown"
291
-	 *
292
-	 * @param string $view The view name
293
-	 * @return string
294
-	 * @access private
295
-	 */
296
-	public function getViewFileType($view) {
297
-		$extension = (new \SplFileInfo($view))->getExtension();
298
-		$hasValidExtension = isset(self::$extensions[$extension]);
299
-
300
-		if ($hasValidExtension) {
301
-			return $extension;
302
-		}
284
+    /**
285
+     * Returns the type of output expected from the view.
286
+     *
287
+     *  - view/name.extension returns "extension" if "extension" is valid
288
+     *  - css/view return "css"
289
+     *  - js/view return "js"
290
+     *  - Otherwise, returns "unknown"
291
+     *
292
+     * @param string $view The view name
293
+     * @return string
294
+     * @access private
295
+     */
296
+    public function getViewFileType($view) {
297
+        $extension = (new \SplFileInfo($view))->getExtension();
298
+        $hasValidExtension = isset(self::$extensions[$extension]);
299
+
300
+        if ($hasValidExtension) {
301
+            return $extension;
302
+        }
303 303
 		
304
-		if (preg_match('~(?:^|/)(css|js)(?:$|/)~', $view, $m)) {
305
-			return $m[1];
306
-		}
304
+        if (preg_match('~(?:^|/)(css|js)(?:$|/)~', $view, $m)) {
305
+            return $m[1];
306
+        }
307 307
 		
308
-		return 'unknown';
309
-	}
310
-
311
-	/**
312
-	 * Get the contents of a view for caching
313
-	 *
314
-	 * @param string $view     The view name
315
-	 * @param string $viewtype The viewtype
316
-	 * @return string|false
317
-	 * @see CacheHandler::renderView()
318
-	 */
319
-	protected function getProcessedView($view, $viewtype) {
320
-		$content = $this->renderView($view, $viewtype);
321
-		if ($content === false) {
322
-			return false;
323
-		}
324
-
325
-		if ($this->simplecache_enabled) {
326
-			$hook_name = 'simplecache:generate';
327
-		} else {
328
-			$hook_name = 'cache:generate';
329
-		}
330
-		$hook_type = $this->getViewFileType($view);
331
-		$hook_params = [
332
-			'view' => $view,
333
-			'viewtype' => $viewtype,
334
-			'view_content' => $content,
335
-		];
336
-		return \_elgg_services()->hooks->trigger($hook_name, $hook_type, $hook_params, $content);
337
-	}
338
-
339
-	/**
340
-	 * Render a view for caching. Language views are handled specially.
341
-	 *
342
-	 * @param string $view     The view name
343
-	 * @param string $viewtype The viewtype
344
-	 * @return string|false
345
-	 */
346
-	protected function renderView($view, $viewtype) {
347
-		elgg_set_viewtype($viewtype);
348
-
349
-		if ($viewtype === 'default' && preg_match("#^languages/(.*?)\\.js$#", $view, $matches)) {
350
-			$view = "languages.js";
351
-			$vars = ['language' => $matches[1]];
352
-		} else {
353
-			$vars = [];
354
-		}
355
-
356
-		if (!elgg_view_exists($view)) {
357
-			return false;
358
-		}
359
-
360
-		// disable error reporting so we don't cache problems
361
-		$this->config->debug = null;
362
-
363
-		return elgg_view($view, $vars);
364
-	}
365
-
366
-	/**
367
-	 * Send an error message to requestor
368
-	 *
369
-	 * @param string $msg Optional message text
370
-	 * @return Response
371
-	 */
372
-	protected function send403($msg = 'Cache error: bad request') {
373
-		return Response::create($msg, 403);
374
-	}
308
+        return 'unknown';
309
+    }
310
+
311
+    /**
312
+     * Get the contents of a view for caching
313
+     *
314
+     * @param string $view     The view name
315
+     * @param string $viewtype The viewtype
316
+     * @return string|false
317
+     * @see CacheHandler::renderView()
318
+     */
319
+    protected function getProcessedView($view, $viewtype) {
320
+        $content = $this->renderView($view, $viewtype);
321
+        if ($content === false) {
322
+            return false;
323
+        }
324
+
325
+        if ($this->simplecache_enabled) {
326
+            $hook_name = 'simplecache:generate';
327
+        } else {
328
+            $hook_name = 'cache:generate';
329
+        }
330
+        $hook_type = $this->getViewFileType($view);
331
+        $hook_params = [
332
+            'view' => $view,
333
+            'viewtype' => $viewtype,
334
+            'view_content' => $content,
335
+        ];
336
+        return \_elgg_services()->hooks->trigger($hook_name, $hook_type, $hook_params, $content);
337
+    }
338
+
339
+    /**
340
+     * Render a view for caching. Language views are handled specially.
341
+     *
342
+     * @param string $view     The view name
343
+     * @param string $viewtype The viewtype
344
+     * @return string|false
345
+     */
346
+    protected function renderView($view, $viewtype) {
347
+        elgg_set_viewtype($viewtype);
348
+
349
+        if ($viewtype === 'default' && preg_match("#^languages/(.*?)\\.js$#", $view, $matches)) {
350
+            $view = "languages.js";
351
+            $vars = ['language' => $matches[1]];
352
+        } else {
353
+            $vars = [];
354
+        }
355
+
356
+        if (!elgg_view_exists($view)) {
357
+            return false;
358
+        }
359
+
360
+        // disable error reporting so we don't cache problems
361
+        $this->config->debug = null;
362
+
363
+        return elgg_view($view, $vars);
364
+    }
365
+
366
+    /**
367
+     * Send an error message to requestor
368
+     *
369
+     * @param string $msg Optional message text
370
+     * @return Response
371
+     */
372
+    protected function send403($msg = 'Cache error: bad request') {
373
+        return Response::create($msg, 403);
374
+    }
375 375
 }
376 376
 
Please login to merge, or discard this patch.