Issues (2473)

Branch: master

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

engine/lib/river.php (5 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * Elgg river.
4
 * Activity stream functions.
5
 *
6
 * @package    Elgg.Core
7
 * @subpackage River
8
 */
9
10
/**
11
 * Adds an item to the river.
12
 *
13
 * @tip Read the item like "Lisa (subject) posted (action)
14
 * a comment (object) on John's blog (target)".
15
 *
16
 * @param array $options Array in format:
17
 *
18
 * 	view => STR The view that will handle the river item (must exist)
19
 *
20
 * 	action_type => STR An arbitrary string to define the action (eg 'comment', 'create')
21
 *
22
 *  subject_guid => INT The GUID of the entity doing the action
23
 *
24
 *  object_guid => INT The GUID of the entity being acted upon
25
 *
26
 *  target_guid => INT The GUID of the the object entity's container
27
 *
28
 *  access_id => INT The access ID of the river item (default: same as the object)
29
 *
30
 *  posted => INT The UNIX epoch timestamp of the river item (default: now)
31
 *
32
 *  annotation_id INT The annotation ID associated with this river entry
33
 *
34
 * @return int|bool River ID or false on failure
35
 * @since 1.9
36
 */
37
function elgg_create_river_item(array $options = array()) {
38
	$view = elgg_extract('view', $options);
39
	// use default viewtype for when called from web services api
40
	if (empty($view) || !(elgg_view_exists($view, 'default'))) {
41
		return false;
42
	}
43
44
	$action_type = elgg_extract('action_type', $options);
45
	if (empty($action_type)) {
46
		return false;
47
	}
48
49
	$subject_guid = elgg_extract('subject_guid', $options, 0);
50
	if (!($subject = get_entity($subject_guid))) {
51
		return false;
52
	}
53
54
	$object_guid = elgg_extract('object_guid', $options, 0);
55
	if (!($object = get_entity($object_guid))) {
56
		return false;
57
	}
58
59
	$target_guid = elgg_extract('target_guid', $options, 0);
60
	if ($target_guid) {
61
		// target_guid is not a required parameter so check
62
		// it only if it is included in the parameters
63
		if (!($target = get_entity($target_guid))) {
64
			return false;
65
		}
66
	}
67
68
	$access_id = elgg_extract('access_id', $options, $object->access_id);
69
70
	$posted = elgg_extract('posted', $options, time());
71
72
	$annotation_id = elgg_extract('annotation_id', $options, 0);
73
	if ($annotation_id) {
74
		if (!elgg_get_annotation_from_id($annotation_id)) {
75
			return false;
76
		}
77
	}
78
79
	$values = array(
80
		'type' => $object->getType(),
81
		'subtype' => $object->getSubtype(),
82
		'action_type' => $action_type,
83
		'access_id' => $access_id,
84
		'view' => $view,
85
		'subject_guid' => $subject_guid,
86
		'object_guid' => $object_guid,
87
		'target_guid' => $target_guid,
88
		'annotation_id' => $annotation_id,
89
		'posted' => $posted,
90
	);
91
	$col_types = array(
92
		'type' => 'string',
93
		'subtype' => 'string',
94
		'action_type' => 'string',
95
		'access_id' => 'int',
96
		'view' => 'string',
97
		'subject_guid' => 'int',
98
		'object_guid' => 'int',
99
		'target_guid' => 'int',
100
		'annotation_id' => 'int',
101
		'posted' => 'int',
102
	);
103
104
	// return false to stop insert
105
	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values);
106
	if ($values == false) {
107
		// inserting did not fail - it was just prevented
108
		return true;
109
	}
110
111
	$dbprefix = elgg_get_config('dbprefix');
112
113
	// escape values array and build INSERT assignments
114
	$assignments = array();
115
	foreach ($col_types as $name => $type) {
116
		$values[$name] = ($type === 'int') ? (int)$values[$name] : sanitize_string($values[$name]);
117
		$assignments[] = "$name = '{$values[$name]}'";
118
	}
119
120
	$id = insert_data("INSERT INTO {$dbprefix}river SET " . implode(',', $assignments));
121
122
	// update the entities which had the action carried out on it
123
	// @todo shouldn't this be done elsewhere? Like when an annotation is saved?
124
	if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
125
		update_entity_last_action($values['object_guid'], $values['posted']);
126
127
		$river_items = elgg_get_river(array('id' => $id));
128
		if ($river_items) {
129
			elgg_trigger_event('created', 'river', $river_items[0]);
130
		}
131
		return $id;
132
	} else {
133
		return false;
134
	}
135
}
136
137
/**
138
 * Delete river items
139
 *
140
 * @warning not checking access (should we?)
141
 *
142
 * @param array $options Parameters:
143
 *   ids                  => INT|ARR River item id(s)
144
 *   subject_guids        => INT|ARR Subject guid(s)
145
 *   object_guids         => INT|ARR Object guid(s)
146
 *   target_guids         => INT|ARR Target guid(s)
147
 *   annotation_ids       => INT|ARR The identifier of the annotation(s)
148
 *   action_types         => STR|ARR The river action type(s) identifier
149
 *   views                => STR|ARR River view(s)
150
 *
151
 *   types                => STR|ARR Entity type string(s)
152
 *   subtypes             => STR|ARR Entity subtype string(s)
153
 *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype
154
 *                                   can be an array of subtype strings
155
 *
156
 *   posted_time_lower    => INT     The lower bound on the time posted
157
 *   posted_time_upper    => INT     The upper bound on the time posted
158
 *
159
 * @return bool
160
 * @since 1.8.0
161
 */
162
function elgg_delete_river(array $options = array()) {
163
	global $CONFIG;
164
165
	$defaults = array(
166
		'ids'                  => ELGG_ENTITIES_ANY_VALUE,
167
168
		'subject_guids'	       => ELGG_ENTITIES_ANY_VALUE,
169
		'object_guids'         => ELGG_ENTITIES_ANY_VALUE,
170
		'target_guids'         => ELGG_ENTITIES_ANY_VALUE,
171
		'annotation_ids'       => ELGG_ENTITIES_ANY_VALUE,
172
173
		'views'                => ELGG_ENTITIES_ANY_VALUE,
174
		'action_types'         => ELGG_ENTITIES_ANY_VALUE,
175
176
		'types'	               => ELGG_ENTITIES_ANY_VALUE,
177
		'subtypes'             => ELGG_ENTITIES_ANY_VALUE,
178
		'type_subtype_pairs'   => ELGG_ENTITIES_ANY_VALUE,
179
180
		'posted_time_lower'	   => ELGG_ENTITIES_ANY_VALUE,
181
		'posted_time_upper'	   => ELGG_ENTITIES_ANY_VALUE,
182
183
		'wheres'               => array(),
184
		'joins'                => array(),
185
186
	);
187
188
	$options = array_merge($defaults, $options);
189
190
	$singulars = array('id', 'subject_guid', 'object_guid', 'target_guid', 'annotation_id', 'action_type', 'view', 'type', 'subtype');
191
	$options = _elgg_normalize_plural_options_array($options, $singulars);
192
193
	$wheres = $options['wheres'];
194
195
	$wheres[] = _elgg_get_guid_based_where_sql('rv.id', $options['ids']);
196
	$wheres[] = _elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']);
197
	$wheres[] = _elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']);
198
	$wheres[] = _elgg_get_guid_based_where_sql('rv.target_guid', $options['target_guids']);
199
	$wheres[] = _elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']);
200
	$wheres[] = _elgg_river_get_action_where_sql($options['action_types']);
201
	$wheres[] = _elgg_river_get_view_where_sql($options['views']);
202
	$wheres[] = _elgg_get_river_type_subtype_where_sql('rv', $options['types'],
203
		$options['subtypes'], $options['type_subtype_pairs']);
204
205 View Code Duplication
	if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) {
206
		$wheres[] = "rv.posted >= {$options['posted_time_lower']}";
207
	}
208
209 View Code Duplication
	if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) {
210
		$wheres[] = "rv.posted <= {$options['posted_time_upper']}";
211
	}
212
213
	// see if any functions failed
214
	// remove empty strings on successful functions
215 View Code Duplication
	foreach ($wheres as $i => $where) {
216
		if ($where === false) {
217
			return false;
218
		} elseif (empty($where)) {
219
			unset($wheres[$i]);
220
		}
221
	}
222
223
	// remove identical where clauses
224
	$wheres = array_unique($wheres);
225
226
	$query = "DELETE rv.* FROM {$CONFIG->dbprefix}river rv ";
227
228
	// remove identical join clauses
229
	$joins = array_unique($options['joins']);
230
231
	// add joins
232
	foreach ($joins as $j) {
233
		$query .= " $j ";
234
	}
235
236
	// add wheres
237
	$query .= ' WHERE ';
238
239
	foreach ($wheres as $w) {
240
		$query .= " $w AND ";
241
	}
242
	$query .= "1=1";
243
244
	return delete_data($query);
245
}
246
247
/**
248
 * Get river items
249
 *
250
 * @note If using types and subtypes in a query, they are joined with an AND.
251
 *
252
 * @param array $options Parameters:
253
 *   ids                  => INT|ARR River item id(s)
254
 *   subject_guids        => INT|ARR Subject guid(s)
255
 *   object_guids         => INT|ARR Object guid(s)
256
 *   target_guids         => INT|ARR Target guid(s)
257
 *   annotation_ids       => INT|ARR The identifier of the annotation(s)
258
 *   action_types         => STR|ARR The river action type(s) identifier
259
 *   posted_time_lower    => INT     The lower bound on the time posted
260
 *   posted_time_upper    => INT     The upper bound on the time posted
261
 *
262
 *   types                => STR|ARR Entity type string(s)
263
 *   subtypes             => STR|ARR Entity subtype string(s)
264
 *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype
265
 *                                   can be an array of subtype strings
266
 *
267
 *   relationship         => STR     Relationship identifier
268
 *   relationship_guid    => INT|ARR Entity guid(s)
269
 *   inverse_relationship => BOOL    Subject or object of the relationship (false)
270
 *
271
 * 	 limit                => INT     Number to show per page (20)
272
 *   offset               => INT     Offset in list (0)
273
 *   count                => BOOL    Count the river items? (false)
274
 *   order_by             => STR     Order by clause (rv.posted desc)
275
 *   group_by             => STR     Group by clause
276
 *
277
 *   distinct             => BOOL    If set to false, Elgg will drop the DISTINCT
278
 *                                   clause from the MySQL query, which will improve
279
 *                                   performance in some situations. Avoid setting this
280
 *                                   option without a full understanding of the
281
 *                                   underlying SQL query Elgg creates. (true)
282
 *
283
 * @return array|int
284
 * @since 1.8.0
285
 */
286
function elgg_get_river(array $options = array()) {
287
	global $CONFIG;
288
289
	$defaults = array(
290
		'ids'                  => ELGG_ENTITIES_ANY_VALUE,
291
292
		'subject_guids'	       => ELGG_ENTITIES_ANY_VALUE,
293
		'object_guids'         => ELGG_ENTITIES_ANY_VALUE,
294
		'target_guids'         => ELGG_ENTITIES_ANY_VALUE,
295
		'annotation_ids'       => ELGG_ENTITIES_ANY_VALUE,
296
		'action_types'         => ELGG_ENTITIES_ANY_VALUE,
297
298
		'relationship'         => null,
299
		'relationship_guid'    => null,
300
		'inverse_relationship' => false,
301
302
		'types'	               => ELGG_ENTITIES_ANY_VALUE,
303
		'subtypes'             => ELGG_ENTITIES_ANY_VALUE,
304
		'type_subtype_pairs'   => ELGG_ENTITIES_ANY_VALUE,
305
306
		'posted_time_lower'	   => ELGG_ENTITIES_ANY_VALUE,
307
		'posted_time_upper'	   => ELGG_ENTITIES_ANY_VALUE,
308
309
		'limit'                => 20,
310
		'offset'               => 0,
311
		'count'                => false,
312
		'distinct'             => false,
313
314
		'order_by'             => 'rv.posted desc',
315
		'group_by'             => ELGG_ENTITIES_ANY_VALUE,
316
317
		'wheres'               => array(),
318
		'joins'                => array(),
319
	);
320
321
	$options = array_merge($defaults, $options);
322
323
	$singulars = array('id', 'subject_guid', 'object_guid', 'target_guid', 'annotation_id', 'action_type', 'type', 'subtype');
324
	$options = _elgg_normalize_plural_options_array($options, $singulars);
325
326
	$wheres = $options['wheres'];
327
328
	$wheres[] = _elgg_get_guid_based_where_sql('rv.id', $options['ids']);
329
	$wheres[] = _elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']);
330
	$wheres[] = _elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']);
331
	$wheres[] = _elgg_get_guid_based_where_sql('rv.target_guid', $options['target_guids']);
332
	$wheres[] = _elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']);
333
	$wheres[] = _elgg_river_get_action_where_sql($options['action_types']);
334
	$wheres[] = _elgg_get_river_type_subtype_where_sql('rv', $options['types'],
335
		$options['subtypes'], $options['type_subtype_pairs']);
336
337 View Code Duplication
	if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) {
338
		$wheres[] = "rv.posted >= {$options['posted_time_lower']}";
339
	}
340
341 View Code Duplication
	if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) {
342
		$wheres[] = "rv.posted <= {$options['posted_time_upper']}";
343
	}
344
345
	if (!access_get_show_hidden_status()) {
346
		$wheres[] = "rv.enabled = 'yes'";
347
	}
348
349
	$joins = $options['joins'];
350
351
	$dbprefix = elgg_get_config('dbprefix');
352
353
	// joins
354
	$joins = array();
355
	$joins[] = "JOIN {$dbprefix}entities oe ON rv.object_guid = oe.guid";
356
357
	// LEFT JOIN is used because all river items do not necessarily have target
358
	$joins[] = "LEFT JOIN {$dbprefix}entities te ON rv.target_guid = te.guid";
359
360
	if ($options['relationship_guid']) {
361
		$clauses = elgg_get_entity_relationship_where_sql(
362
				'rv.subject_guid',
363
				$options['relationship'],
364
				$options['relationship_guid'],
365
				$options['inverse_relationship']);
366
		if ($clauses) {
367
			$wheres = array_merge($wheres, $clauses['wheres']);
368
			$joins = array_merge($joins, $clauses['joins']);
369
		}
370
	}
371
372
	// add optional joins
373
	$joins = array_merge($joins, $options['joins']);
374
375
	// see if any functions failed
376
	// remove empty strings on successful functions
377 View Code Duplication
	foreach ($wheres as $i => $where) {
378
		if ($where === false) {
379
			return false;
380
		} elseif (empty($where)) {
381
			unset($wheres[$i]);
382
		}
383
	}
384
385
	// remove identical where clauses
386
	$wheres = array_unique($wheres);
387
388 View Code Duplication
	if (!$options['count']) {
389
		$distinct = $options['distinct'] ? "DISTINCT" : "";
390
391
		$query = "SELECT $distinct rv.* FROM {$CONFIG->dbprefix}river rv ";
392
	} else {
393
		// note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than IDs
394
		$count_expr = $options['distinct'] ? "DISTINCT rv.id" : "*";
395
396
		$query = "SELECT COUNT($count_expr) as total FROM {$CONFIG->dbprefix}river rv ";
397
	}
398
399
	// add joins
400
	foreach ($joins as $j) {
401
		$query .= " $j ";
402
	}
403
404
	// add wheres
405
	$query .= ' WHERE ';
406
407
	foreach ($wheres as $w) {
408
		$query .= " $w AND ";
409
	}
410
411
	// Make sure that user has access to all the entities referenced by each river item
412
	$object_access_where = _elgg_get_access_where_sql(array('table_alias' => 'oe'));
413
	$target_access_where = _elgg_get_access_where_sql(array('table_alias' => 'te'));
414
415
	// We use LEFT JOIN with entities table but the WHERE clauses are used
416
	// regardless if a JOIN is successfully made. The "te.guid IS NULL" is
417
	// needed because of this.
418
	$query .= "$object_access_where AND ($target_access_where OR te.guid IS NULL) ";
419
420
	if (!$options['count']) {
421
		$options['group_by'] = sanitise_string($options['group_by']);
422
		if ($options['group_by']) {
423
			$query .= " GROUP BY {$options['group_by']}";
424
		}
425
426
		$options['order_by'] = sanitise_string($options['order_by']);
427
		$query .= " ORDER BY {$options['order_by']}";
428
429 View Code Duplication
		if ($options['limit']) {
430
			$limit = sanitise_int($options['limit']);
431
			$offset = sanitise_int($options['offset'], false);
432
			$query .= " LIMIT $offset, $limit";
433
		}
434
435
		$river_items = get_data($query, '_elgg_row_to_elgg_river_item');
436
		_elgg_prefetch_river_entities($river_items);
437
438
		return $river_items;
439
	} else {
440
		$total = get_data_row($query);
441
		return (int)$total->total;
442
	}
443
}
444
445
/**
446
 * Prefetch entities that will be displayed in the river.
447
 *
448
 * @param \ElggRiverItem[] $river_items
449
 * @access private
450
 */
451
function _elgg_prefetch_river_entities(array $river_items) {
452
	// prefetch objects, subjects and targets
453
	$guids = array();
454
	foreach ($river_items as $item) {
455
		if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) {
456
			$guids[$item->subject_guid] = true;
457
		}
458
		if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) {
459
			$guids[$item->object_guid] = true;
460
		}
461
		if ($item->target_guid && !_elgg_retrieve_cached_entity($item->target_guid)) {
462
			$guids[$item->target_guid] = true;
463
		}
464
	}
465 View Code Duplication
	if ($guids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
466
		// The entity cache only holds 256. We don't want to bump out any plugins.
467
		$guids = array_slice($guids, 0, 200, true);
468
		// return value unneeded, just priming cache
469
		elgg_get_entities(array(
470
			'guids' => array_keys($guids),
471
			'limit' => 0,
472
			'distinct' => false,
473
		));
474
	}
475
476
	// prefetch object containers, in case they were not in the targets
477
	$guids = array();
478
	foreach ($river_items as $item) {
479
		$object = $item->getObjectEntity();
480
		if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) {
481
			$guids[$object->container_guid] = true;
482
		}
483
	}
484 View Code Duplication
	if ($guids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
485
		$guids = array_slice($guids, 0, 200, true);
486
		elgg_get_entities(array(
487
			'guids' => array_keys($guids),
488
			'limit' => 0,
489
			'distinct' => false,
490
491
			// Why specify? user containers are likely already loaded via the owners, and
492
			// specifying groups allows ege() to auto-join the groups_entity table
493
			'type' => 'group',
494
		));
495
	}
496
497
	// Note: We've tried combining the above ege() calls into one (pulling containers at the same time).
498
	// Although it seems like it would reduce queries, it added some. o_O
499
}
500
501
/**
502
 * List river items
503
 *
504
 * @param array $options Any options from elgg_get_river() plus:
505
 *   item_view  => STR         Alternative view to render list items
506
 *   pagination => BOOL        Display pagination links (true)
507
 *   no_results => STR|Closure Message to display if no items
508
 *
509
 * @return string
510
 * @since 1.8.0
511
 */
512
function elgg_list_river(array $options = array()) {
513
	global $autofeed;
514
	$autofeed = true;
515
516
	$defaults = array(
517
		'offset'     => (int) max(get_input('offset', 0), 0),
518
		'limit'      => (int) max(get_input('limit', max(20, elgg_get_config('default_limit'))), 0),
519
		'pagination' => true,
520
		'list_class' => 'elgg-list-river',
521
		'no_results' => '',
522
	);
523
524
	$options = array_merge($defaults, $options);
525
526 View Code Duplication
	if (!$options["limit"] && !$options["offset"]) {
527
		// no need for pagination if listing is unlimited
528
		$options["pagination"] = false;
529
	}
530
531
	$options['count'] = true;
532
	$count = elgg_get_river($options);
533
534
	if ($count > 0) {
535
		$options['count'] = false;
536
		$items = elgg_get_river($options);
537
	} else {
538
		$items = array();
539
	}
540
541
	$options['count'] = $count;
542
	$options['items'] = $items;
543
544
	return elgg_view('page/components/list', $options);
545
}
546
547
/**
548
 * Convert a database row to a new \ElggRiverItem
549
 *
550
 * @param \stdClass $row Database row from the river table
551
 *
552
 * @return \ElggRiverItem
553
 * @since 1.8.0
554
 * @access private
555
 */
556
function _elgg_row_to_elgg_river_item($row) {
557
	if (!($row instanceof \stdClass)) {
558
		return null;
559
	}
560
561
	return new \ElggRiverItem($row);
562
}
563
564
/**
565
 * Returns SQL where clause for type and subtype on river table
566
 *
567
 * @internal This is a simplified version of elgg_get_entity_type_subtype_where_sql()
568
 * which could be used for all queries once the subtypes have been denormalized.
569
 *
570
 * @param string     $table    'rv'
571
 * @param null|array $types    Array of types or null if none.
572
 * @param null|array $subtypes Array of subtypes or null if none
573
 * @param null|array $pairs    Array of pairs of types and subtypes
574
 *
575
 * @return string
576
 * @since 1.8.0
577
 * @access private
578
 */
579
function _elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs) {
580
	// short circuit if nothing is requested
581
	if (!$types && !$subtypes && !$pairs) {
582
		return '';
583
	}
584
585
	$wheres = array();
586
	$types_wheres = array();
587
	$subtypes_wheres = array();
588
589
	// if no pairs, use types and subtypes
590
	if (!is_array($pairs)) {
591
		if ($types) {
592
			if (!is_array($types)) {
593
				$types = array($types);
594
			}
595
			foreach ($types as $type) {
596
				$type = sanitise_string($type);
597
				$types_wheres[] = "({$table}.type = '$type')";
598
			}
599
		}
600
601
		if ($subtypes) {
602
			if (!is_array($subtypes)) {
603
				$subtypes = array($subtypes);
604
			}
605
			foreach ($subtypes as $subtype) {
606
				$subtype = sanitise_string($subtype);
607
				$subtypes_wheres[] = "({$table}.subtype = '$subtype')";
608
			}
609
		}
610
611
		if (is_array($types_wheres) && count($types_wheres)) {
612
			$types_wheres = array(implode(' OR ', $types_wheres));
613
		}
614
615
		if (is_array($subtypes_wheres) && count($subtypes_wheres)) {
616
			$subtypes_wheres = array('(' . implode(' OR ', $subtypes_wheres) . ')');
617
		}
618
619
		$wheres = array(implode(' AND ', array_merge($types_wheres, $subtypes_wheres)));
620
621
	} else {
622
		// using type/subtype pairs
623
		foreach ($pairs as $paired_type => $paired_subtypes) {
624
			$paired_type = sanitise_string($paired_type);
625
			if (is_array($paired_subtypes)) {
626
				$paired_subtypes = array_map('sanitise_string', $paired_subtypes);
627
				$paired_subtype_str = implode("','", $paired_subtypes);
628
				if ($paired_subtype_str) {
629
					$wheres[] = "({$table}.type = '$paired_type'"
630
						. " AND {$table}.subtype IN ('$paired_subtype_str'))";
631
				}
632
			} else {
633
				$paired_subtype = sanitise_string($paired_subtypes);
634
				$wheres[] = "({$table}.type = '$paired_type'"
635
					. " AND {$table}.subtype = '$paired_subtype')";
636
			}
637
		}
638
	}
639
640 View Code Duplication
	if (is_array($wheres) && count($wheres)) {
641
		$where = implode(' OR ', $wheres);
642
		return "($where)";
643
	}
644
645
	return '';
646
}
647
648
/**
649
 * Get the where clause based on river action type strings
650
 *
651
 * @param array $types Array of action type strings
652
 *
653
 * @return string
654
 * @since 1.8.0
655
 * @access private
656
 */
657 View Code Duplication
function _elgg_river_get_action_where_sql($types) {
658
	if (!$types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
659
		return '';
660
	}
661
662
	if (!is_array($types)) {
663
		$types = sanitise_string($types);
664
		return "(rv.action_type = '$types')";
665
	}
666
667
	// sanitize types array
668
	$types_sanitized = array();
669
	foreach ($types as $type) {
670
		$types_sanitized[] = sanitise_string($type);
671
	}
672
673
	$type_str = implode("','", $types_sanitized);
674
	return "(rv.action_type IN ('$type_str'))";
675
}
676
677
/**
678
 * Get the where clause based on river view strings
679
 *
680
 * @param array $views Array of view strings
681
 *
682
 * @return string
683
 * @since 1.8.0
684
 * @access private
685
 */
686 View Code Duplication
function _elgg_river_get_view_where_sql($views) {
687
	if (!$views) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $views of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
688
		return '';
689
	}
690
691
	if (!is_array($views)) {
692
		$views = sanitise_string($views);
693
		return "(rv.view = '$views')";
694
	}
695
696
	// sanitize views array
697
	$views_sanitized = array();
698
	foreach ($views as $view) {
699
		$views_sanitized[] = sanitise_string($view);
700
	}
701
702
	$view_str = implode("','", $views_sanitized);
703
	return "(rv.view IN ('$view_str'))";
704
}
705
706
/**
707
 * Sets the access ID on river items for a particular object
708
 *
709
 * @param int $object_guid The GUID of the entity
710
 * @param int $access_id   The access ID
711
 *
712
 * @return bool Depending on success
713
 */
714
function update_river_access_by_object($object_guid, $access_id) {
715
	// Sanitise
716
	$object_guid = (int) $object_guid;
717
	$access_id = (int) $access_id;
718
719
	// Load config
720
	global $CONFIG;
721
722
	$query = "UPDATE {$CONFIG->dbprefix}river
723
		SET access_id = {$access_id}
724
		WHERE object_guid = {$object_guid}";
725
	return update_data($query);
726
}
727
728
/**
729
 * Page handler for activity
730
 *
731
 * @param array $page
732
 * @return bool
733
 * @access private
734
 */
735
function _elgg_river_page_handler($page) {
736
	global $CONFIG;
737
738
	elgg_set_page_owner_guid(elgg_get_logged_in_user_guid());
739
740
	// make a URL segment available in page handler script
741
	$page_type = elgg_extract(0, $page, 'all');
742
	$page_type = preg_replace('[\W]', '', $page_type);
743 View Code Duplication
	if ($page_type == 'owner') {
744
		elgg_gatekeeper();
745
		$page_username = elgg_extract(1, $page, '');
746
		if ($page_username == elgg_get_logged_in_user_entity()->username) {
747
			$page_type = 'mine';
748
		} else {
749
			set_input('subject_username', $page_username);
750
		}
751
	}
752
	set_input('page_type', $page_type);
753
754
	require_once("{$CONFIG->path}pages/river.php");
755
	return true;
756
}
757
758
/**
759
 * Register river unit tests
760
 * @access private
761
 */
762
function _elgg_river_test($hook, $type, $value) {
763
	global $CONFIG;
764
	$value[] = $CONFIG->path . 'engine/tests/ElggCoreRiverAPITest.php';
765
	return $value;
766
}
767
768
/**
769
 * Disable river entries that reference a disabled entity as subject/object/target
770
 *
771
 * @param string $event The event 'disable'
772
 * @param string $type Type of entity being disabled 'all'
773
 * @param mixed $entity The entity being disabled
774
 * @return boolean
775
 * @access private
776
 */
777
function _elgg_river_disable($event, $type, $entity) {
778
779
	if (!elgg_instanceof($entity)) {
780
		return true;
781
	}
782
783
	$dbprefix = elgg_get_config('dbprefix');
784
	$query = <<<QUERY
785
	UPDATE {$dbprefix}river AS rv
786
	SET rv.enabled = 'no'
787
	WHERE (rv.subject_guid = {$entity->guid} OR rv.object_guid = {$entity->guid} OR rv.target_guid = {$entity->guid});
788
QUERY;
789
790
	update_data($query);
791
	return true;
792
}
793
794
/*
795
 * GC_MODIFICATION
796
 * Description: Split into 3 queries in order to not use ORs in (se.guid = {$entity->guid} OR oe.guid = {$entity->guid} OR te.guid = {$entity->guid}), this allows it to not use indexes and greatly speeds up execution
797
 * Author: Ilia   github.com/Phanoix  [email protected]
798
 * Date: 24/01/2017
799
 * Pull Request #734
800
 */
801
/**
802
 * Enable river entries that reference a re-enabled entity as subject/object/target
803
 *
804
 * @param string $event The event 'enable'
805
 * @param string $type Type of entity being enabled 'all'
806
 * @param mixed $entity The entity being enabled
807
 * @return boolean
808
 * @access private
809
 */
810
function _elgg_river_enable($event, $type, $entity) {
811
812
	if (!elgg_instanceof($entity)) {
813
		return true;
814
	}
815
816
	$dbprefix = elgg_get_config('dbprefix');
817
	$query1 = <<<QUERY
818
	UPDATE {$dbprefix}river AS rv
819
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
820
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
821
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
822
	SET rv.enabled = 'yes'
823
	WHERE (
824
			(se.enabled = 'yes' OR se.guid IS NULL) AND
825
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
826
			(te.enabled = 'yes' OR te.guid IS NULL)
827
		)
828
		AND se.guid = {$entity->guid};
829
QUERY;
830
831
	$query2 = <<<QUERY
832
	UPDATE {$dbprefix}river AS rv
833
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
834
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
835
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
836
	SET rv.enabled = 'yes'
837
	WHERE (
838
			(se.enabled = 'yes' OR se.guid IS NULL) AND 
839
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
840
			(te.enabled = 'yes' OR te.guid IS NULL)		
841
		)
842
		AND oe.guid = {$entity->guid};
843
QUERY;
844
845
	$query3 = <<<QUERY
846
	UPDATE {$dbprefix}river AS rv
847
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
848
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
849
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
850
	SET rv.enabled = 'yes'
851
	WHERE (
852
			(se.enabled = 'yes' OR se.guid IS NULL) AND 
853
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
854
			(te.enabled = 'yes' OR te.guid IS NULL)		
855
		)
856
		AND te.guid = {$entity->guid};
857
QUERY;
858
859
	update_data($query1);
860
	update_data($query2);
861
	update_data($query3);
862
	return true;
863
}
864
865
/**
866
 * Initialize river library
867
 * @access private
868
 */
869
function _elgg_river_init() {
870
	elgg_register_page_handler('activity', '_elgg_river_page_handler');
871
	$item = new \ElggMenuItem('activity', elgg_echo('activity'), 'activity');
872
	elgg_register_menu_item('site', $item);
873
874
	elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description'));
875
876
	elgg_register_action('river/delete', '', 'admin');
877
878
	elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_river_test');
879
}
880
881
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
882
	$events->registerHandler('init', 'system', '_elgg_river_init');
883
	$events->registerHandler('disable:after', 'all', '_elgg_river_disable');
884
	$events->registerHandler('enable:after', 'all', '_elgg_river_enable');
885
};
886