river.php ➔ _elgg_river_page_handler()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 22

Duplication

Lines 9
Ratio 40.91 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 9
loc 22
ccs 0
cts 15
cp 0
crap 12
rs 9.568
c 0
b 0
f 0
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