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

engine/lib/river.php (2 issues)

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
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 (default: current logged in user guid)
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_item => BOOL set to true to return the ElggRiverItem created
35
 *
36
 * @return int|ElggRiverItem|bool River ID/item or false on failure
37
 * @since 1.9
38
 */
39
function elgg_create_river_item(array $options = []) {
40
41 85
	$view = elgg_extract('view', $options, '');
42
	// use default viewtype for when called from web services api
43 85
	if (!empty($view) && !elgg_view_exists($view, 'default')) {
44 1
		return false;
45
	}
46
47 84
	$action_type = elgg_extract('action_type', $options);
48 84
	if (empty($action_type)) {
49 1
		return false;
50
	}
51
52 84
	$subject_guid = elgg_extract('subject_guid', $options, elgg_get_logged_in_user_guid());
53 84
	if (!($subject = get_entity($subject_guid))) {
0 ignored issues
show
The assignment to $subject is dead and can be removed.
Loading history...
54 2
		return false;
55
	}
56
57 84
	$object_guid = elgg_extract('object_guid', $options, 0);
58 84
	if (!($object = get_entity($object_guid))) {
59 2
		return false;
60
	}
61
62 83
	$target_guid = elgg_extract('target_guid', $options, 0);
63 83
	if ($target_guid) {
64
		// target_guid is not a required parameter so check
65
		// it only if it is included in the parameters
66 74
		if (!($target = get_entity($target_guid))) {
0 ignored issues
show
The assignment to $target is dead and can be removed.
Loading history...
67 1
			return false;
68
		}
69
	}
70
71 82
	$access_id = elgg_extract('access_id', $options, $object->access_id);
72
73 82
	$posted = elgg_extract('posted', $options, time());
74
75 82
	$annotation_id = elgg_extract('annotation_id', $options, 0);
76 82
	if ($annotation_id) {
77
		if (!elgg_get_annotation_from_id($annotation_id)) {
78
			return false;
79
		}
80
	}
81
82 82
	$return_item = elgg_extract('return_item', $options, false);
83
84
	$values = [
85 82
		'type' => $object->getType(),
86 82
		'subtype' => $object->getSubtype(),
87 82
		'action_type' => $action_type,
88 82
		'access_id' => $access_id,
89 82
		'view' => $view,
90 82
		'subject_guid' => $subject_guid,
91 82
		'object_guid' => $object_guid,
92 82
		'target_guid' => $target_guid,
93 82
		'annotation_id' => $annotation_id,
94 82
		'posted' => $posted,
95
	];
96
	$col_types = [
97 82
		'type' => ELGG_VALUE_STRING,
98
		'subtype' => ELGG_VALUE_STRING,
99
		'action_type' => ELGG_VALUE_STRING,
100
		'access_id' => ELGG_VALUE_INTEGER,
101
		'view' => ELGG_VALUE_STRING,
102
		'subject_guid' => ELGG_VALUE_INTEGER,
103
		'object_guid' => ELGG_VALUE_INTEGER,
104
		'target_guid' => ELGG_VALUE_INTEGER,
105
		'annotation_id' => ELGG_VALUE_INTEGER,
106
		'posted' => ELGG_VALUE_INTEGER,
107
	];
108
109
	// return false to stop insert
110 82
	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values);
111 82
	if ($values == false) {
112
		// inserting did not fail - it was just prevented
113 1
		return true;
114
	}
115
116 81
	$qb = \Elgg\Database\Insert::intoTable('river');
117 81
	foreach ($values as $name => $value) {
118 81
		$query_params[$name] = $qb->param($value, $col_types[$name]);
119
	}
120 81
	$qb->values($query_params);
121
122 81
	$id = _elgg_services()->db->insertData($qb);
123 81
	if (!$id) {
124
		return false;
125
	}
126
127 81
	if (!$return_item) {
128 77
		return $id;
129
	}
130
131 5
	$ia = elgg_set_ignore_access(true);
132 5
	$item = elgg_get_river_item_from_id($id);
133 5
	elgg_set_ignore_access($ia);
134
135 5
	if (!$item) {
136
		return false;
137
	}
138
139 5
	elgg_trigger_event('created', 'river', $item);
140
141 5
	return $item;
142
}
143
144
/**
145
 * Get river items
146
 *
147
 * @note If using types and subtypes in a query, they are joined with an AND.
148
 *
149
 * @param array $options Parameters:
150
 *   ids                  => INT|ARR River item id(s)
151
 *   subject_guids        => INT|ARR Subject guid(s)
152
 *   object_guids         => INT|ARR Object guid(s)
153
 *   target_guids         => INT|ARR Target guid(s)
154
 *   action_types         => STR|ARR The river action type(s) identifier
155
 *   posted_time_lower    => INT     The lower bound on the time posted
156
 *   posted_time_upper    => INT     The upper bound on the time posted
157
 *
158
 *   Additionally accepts all "annotation_*" options supported by {@link elgg_get_entities()}
159
 *   annotation_ids       => INT|ARR The identifier of the annotation(s)
160
 *
161
 *   types                => STR|ARR Entity type string(s)
162
 *   subtypes             => STR|ARR Entity subtype string(s)
163
 *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype
164
 *                                   can be an array of subtype strings
165
 *
166
 *   Additionally accepts all "relationship_*" options supported by {@link elgg_get_entities()}
167
 *   relationship         => STR     Relationship identifier
168
 *   relationship_guid    => INT|ARR Entity guid(s)
169
 *   inverse_relationship => BOOL    Subject or object of the relationship (false)
170
 *   relationship_join_on => STR     subject_guid|object_guid|target_guid (defaults to subject_guid)
171
 *
172
 *   limit                => INT     Number to show per page (20)
173
 *   offset               => INT     Offset in list (0)
174
 *   count                => BOOL    Count the river items? (false)
175
 *   order_by             => STR     Order by clause (rv.posted desc)
176
 *   group_by             => STR     Group by clause
177
 *
178
 *   distinct             => BOOL    If set to false, Elgg will drop the DISTINCT
179
 *                                   clause from the MySQL query, which will improve
180
 *                                   performance in some situations. Avoid setting this
181
 *                                   option without a full understanding of the
182
 *                                   underlying SQL query Elgg creates. (true)
183
 *
184
 *   batch                => BOOL    If set to true, an Elgg\BatchResult object will be returned
185
 *                                   instead of an array. (false) Since 2.3.
186
 *
187
 *   batch_inc_offset     => BOOL    If "batch" is used, this tells the batch to increment the offset
188
 *                                   on each fetch. This must be set to false if you delete the batched
189
 *                                   results. (true)
190
 *
191
 *   batch_size           => INT     If "batch" is used, this is the number of entities/rows to pull
192
 *                                   in before requesting more. (25)
193
 *
194
 * @return \ElggRiverItem[]|\Elgg\BatchResult|array|int
195
 * @since 1.8.0
196
 */
197
function elgg_get_river(array $options = []) {
198 215
	return \Elgg\Database\River::find($options);
199
}
200
201
/**
202
 * Get river item from its ID
203
 *
204
 * @param int $id ID
205
 * @return ElggRiverItem|false
206
 */
207
function elgg_get_river_item_from_id($id) {
208 5
	$items = elgg_get_river([
209 5
		'id' => $id,
210
	]);
211
212 5
	return $items ? $items[0] : false;
213
}
214
215
/**
216
 * Delete river items based on $options.
217
 *
218
 * @warning Unlike elgg_get_river() this will not accept an empty options array!
219
 *          This requires at least one constraint: id(s), annotation_id(s)
220
 *          subject_guid(s), object_guid(s), target_guid(s)
221
 *          or view(s) must be set.
222
 *
223
 * @param array $options An options array. {@link elgg_get_river()}
224
 *
225
 * @return bool|null true on success, false on failure, null if no metadata to delete.
226
 *
227
 * @since   1.8.0
228
 */
229
function elgg_delete_river(array $options = []) {
230
231 215
	if (!_elgg_is_valid_options_for_batch_operation($options, 'river')) {
232
		// requirements not met
233
		return false;
234
	}
235
236 215
	$options['batch'] = true;
237 215
	$options['batch_size'] = 25;
238 215
	$options['batch_inc_offset'] = false;
239
240 215
	$river = elgg_get_river($options);
241 215
	$count = $river->count();
242
243 215
	if (!$count) {
244 215
		return;
245
	}
246
247 7
	$success = 0;
248 7
	foreach ($river as $river_item) {
249 7
		if ($river_item->delete()) {
250 7
			$success++;
251
		}
252
	}
253
254 7
	return $success == $count;
255
}
256
257
/**
258
 * List river items
259
 *
260
 * @param array $options Any options from elgg_get_river() plus:
261
 *   item_view  => STR         Alternative view to render list items
262
 *   pagination => BOOL        Display pagination links (true)
263
 *   no_results => STR|Closure Message to display if no items
264
 *
265
 * @return string
266
 * @since 1.8.0
267
 */
268
function elgg_list_river(array $options = []) {
269
	elgg_register_rss_link();
270
271
	$defaults = [
272
		'offset'     => (int) max(get_input('offset', 0), 0),
273
		'limit'      => (int) max(get_input('limit', max(20, _elgg_config()->default_limit)), 0),
274
		'pagination' => true,
275
		'list_class' => 'elgg-list-river',
276
		'no_results' => '',
277
	];
278
279
	$options = array_merge($defaults, $options);
280
281
	if (!$options["limit"] && !$options["offset"]) {
282
		// no need for pagination if listing is unlimited
283
		$options["pagination"] = false;
284
	}
285
286
	$options['count'] = true;
287
	$count = elgg_get_river($options);
288
289
	if ($count > 0) {
290
		$options['count'] = false;
291
		$items = elgg_get_river($options);
292
	} else {
293
		$items = [];
294
	}
295
296
	$options['count'] = $count;
297
	$options['items'] = $items;
298
299
	return elgg_view('page/components/list', $options);
300
}
301
302
/**
303
 * Sets the access ID on river items for a particular object
304
 *
305
 * @param int $object_guid The GUID of the entity
306
 * @param int $access_id   The access ID
307
 *
308
 * @return bool Depending on success
309
 */
310
function update_river_access_by_object($object_guid, $access_id) {
311
312 5
	$dbprefix = _elgg_config()->dbprefix;
313
	$query = "
314 5
		UPDATE {$dbprefix}river
315
		SET access_id = :access_id
316
		WHERE object_guid = :object_guid
317
	";
318
319
	$params = [
320 5
		':access_id' => (int) $access_id,
321 5
		':object_guid' => (int) $object_guid,
322
	];
323
324 5
	return update_data($query, $params);
325
}
326
327
/**
328
 * Register river unit tests
329
 *
330
 * @param string $hook  'unit_test'
331
 * @param string $type  'system'
332
 * @param array  $value current return value
333
 *
334
 * @return array
335
 * @codeCoverageIgnore
336
 */
337
function _elgg_river_test($hook, $type, $value) {
338
	$value[] = ElggCoreRiverAPITest::class;
339
	return $value;
340
}
341
342
/**
343
 * Disable river entries that reference a disabled entity as subject/object/target
344
 *
345
 * @param string     $event  'disable'
346
 * @param string     $type   'all'
347
 * @param ElggEntity $entity The entity being disabled
348
 *
349
 * @return void
350
 *
351
 * @access private
352
 */
353
function _elgg_river_disable($event, $type, $entity) {
354
355 4
	if (!$entity instanceof ElggEntity) {
356
		return;
357
	}
358
359 4
	$dbprefix = _elgg_config()->dbprefix;
360
	$query = <<<QUERY
361 4
	UPDATE {$dbprefix}river AS rv
362
	SET rv.enabled = 'no'
363 4
	WHERE (rv.subject_guid = {$entity->guid} OR rv.object_guid = {$entity->guid} OR rv.target_guid = {$entity->guid});
364
QUERY;
365
366 4
	update_data($query);
367 4
	return;
368
}
369
370
371
/**
372
 * Enable river entries that reference a re-enabled entity as subject/object/target
373
 *
374
 * @param string     $event  'enable'
375
 * @param string     $type   'all'
376
 * @param ElggEntity $entity The entity being enabled
377
 *
378
 * @return void
379
 *
380
 * @access private
381
 */
382
function _elgg_river_enable($event, $type, $entity) {
383
384 3
	if (!$entity instanceof ElggEntity) {
385
		return;
386
	}
387
388 3
	$dbprefix = _elgg_config()->dbprefix;
389
	$query = <<<QUERY
390 3
	UPDATE {$dbprefix}river AS rv
391 3
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
392 3
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
393 3
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
394
	SET rv.enabled = 'yes'
395
	WHERE (
396
			(se.enabled = 'yes' OR se.guid IS NULL) AND
397
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
398
			(te.enabled = 'yes' OR te.guid IS NULL)
399
		)
400 3
		AND (se.guid = {$entity->guid} OR oe.guid = {$entity->guid} OR te.guid = {$entity->guid});
401
QUERY;
402
403 3
	update_data($query);
404 3
	return;
405
}
406
407
/**
408
 * Add the delete to river actions menu
409
 *
410
 * @param \Elgg\Hook $hook 'register' 'menu:river'
411
 *
412
 * @return void|ElggMenuItem[]
413
 *
414
 * @access private
415
 */
416
function _elgg_river_menu_setup(\Elgg\Hook $hook) {
417 1
	if (!elgg_is_logged_in()) {
418 1
		return;
419
	}
420
421
	$item = $hook->getParam('item');
422
	if (!($item instanceof ElggRiverItem)) {
423
		return;
424
	}
425
426
	if (!$item->canDelete()) {
427
		return;
428
	}
429
430
	$return = $hook->getValue();
431
432
	$return[] = \ElggMenuItem::factory([
433
		'name' => 'delete',
434
		'href' => "action/river/delete?id={$item->id}",
435
		'is_action' => true,
436
		'icon' => 'delete',
437
		'text' => elgg_echo('river:delete'),
438
		'confirm' => elgg_echo('deleteconfirm'),
439
		'priority' => 999,
440
	]);
441
442
	return $return;
443
}
444
445
/**
446
 * Initialize river library
447
 *
448
 * @return void
449
 *
450
 * @access private
451
 */
452
function _elgg_river_init() {
453
454 31
	elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_river_test');
455
456 31
	elgg_register_plugin_hook_handler('register', 'menu:river', '_elgg_river_menu_setup');
457 31
}
458
459
/**
460
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
461
 */
462
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
463 18
	$events->registerHandler('init', 'system', '_elgg_river_init');
464 18
	$events->registerHandler('disable:after', 'all', '_elgg_river_disable', 600);
465 18
	$events->registerHandler('enable:after', 'all', '_elgg_river_enable', 600);
466
};
467