Completed
Push — 3.0 ( 9dd29c...237018 )
by Jeroen
53:05
created

engine/lib/river.php (1 issue)

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
 * @option string $view The view that will handle the river item
19
 * @option string $action_type   An arbitrary string to define the action (eg 'comment', 'create')
20
 * @option int    $subject_guid  The GUID of the entity doing the action (default: current logged in user guid)
21
 * @option int    $object_guid   The GUID of the entity being acted upon
22
 * @option int    $target_guid   The GUID of the the object entity's container
23
 * @option int    $posted        The UNIX epoch timestamp of the river item (default: now)
24
 * @option int    $annotation_id The annotation ID associated with this river entry
25
 * @option bool   $return_item   set to true to return the ElggRiverItem created
26
 *
27
 * @return int|ElggRiverItem|bool River ID/item or false on failure
28
 * @since  1.9
29
 * @throws DatabaseException
30
 */
31
function elgg_create_river_item(array $options = []) {
32
33 86
	$view = elgg_extract('view', $options, '');
34
	// use default viewtype for when called from web services api
35 86
	if (!empty($view) && !elgg_view_exists($view, 'default')) {
36 1
		return false;
37
	}
38
39 85
	$action_type = elgg_extract('action_type', $options);
40 85
	if (empty($action_type)) {
41 1
		return false;
42
	}
43
44 85
	$subject_guid = elgg_extract('subject_guid', $options, elgg_get_logged_in_user_guid());
45 85
	if (!(get_entity($subject_guid) instanceof ElggEntity)) {
46 2
		return false;
47
	}
48
49 85
	$object_guid = elgg_extract('object_guid', $options, 0);
50 85
	if (!(get_entity($object_guid) instanceof ElggEntity)) {
51 2
		return false;
52
	}
53
54 84
	$target_guid = elgg_extract('target_guid', $options, 0);
55 84
	if ($target_guid) {
56
		// target_guid is not a required parameter so check
57
		// it only if it is included in the parameters
58 75
		if (!(get_entity($target_guid) instanceof ElggEntity)) {
59 1
			return false;
60
		}
61
	}
62
63 83
	$posted = elgg_extract('posted', $options, time());
64
65 83
	$annotation_id = elgg_extract('annotation_id', $options, 0);
66 83
	if ($annotation_id) {
67
		if (!elgg_get_annotation_from_id($annotation_id)) {
68
			return false;
69
		}
70
	}
71
72 83
	$return_item = elgg_extract('return_item', $options, false);
73
74
	$values = [
75 83
		'action_type' => $action_type,
76 83
		'view' => $view,
77 83
		'subject_guid' => $subject_guid,
78 83
		'object_guid' => $object_guid,
79 83
		'target_guid' => $target_guid,
80 83
		'annotation_id' => $annotation_id,
81 83
		'posted' => $posted,
82
	];
83
	$col_types = [
84 83
		'action_type' => ELGG_VALUE_STRING,
85
		'view' => ELGG_VALUE_STRING,
86
		'subject_guid' => ELGG_VALUE_INTEGER,
87
		'object_guid' => ELGG_VALUE_INTEGER,
88
		'target_guid' => ELGG_VALUE_INTEGER,
89
		'annotation_id' => ELGG_VALUE_INTEGER,
90
		'posted' => ELGG_VALUE_INTEGER,
91
	];
92
93
	// return false to stop insert
94 83
	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values);
95 83
	if ($values == false) {
96
		// inserting did not fail - it was just prevented
97 1
		return true;
98
	}
99
100 82
	$qb = \Elgg\Database\Insert::intoTable('river');
101 82
	$query_params = [];
102 82
	foreach ($values as $name => $value) {
103 82
		$query_params[$name] = $qb->param($value, $col_types[$name]);
104
	}
105 82
	$qb->values($query_params);
106
107 82
	$id = _elgg_services()->db->insertData($qb);
108 82
	if (!$id) {
109
		return false;
110
	}
111
112 82
	if (!$return_item) {
113 78
		return $id;
114
	}
115
116 5
	$ia = elgg_set_ignore_access(true);
117 5
	$item = elgg_get_river_item_from_id($id);
118 5
	elgg_set_ignore_access($ia);
119
120 5
	if (!$item) {
121
		return false;
122
	}
123
124 5
	elgg_trigger_event('created', 'river', $item);
125
126 5
	return $item;
127
}
128
129
/**
130
 * Get river items
131
 *
132
 * @note If using types and subtypes in a query, they are joined with an AND.
133
 *
134
 * @param array $options Parameters:
135
 *   ids                  => INT|ARR River item id(s)
136
 *   subject_guids        => INT|ARR Subject guid(s)
137
 *   object_guids         => INT|ARR Object guid(s)
138
 *   target_guids         => INT|ARR Target guid(s)
139
 *   action_types         => STR|ARR The river action type(s) identifier
140
 *   posted_time_lower    => INT     The lower bound on the time posted
141
 *   posted_time_upper    => INT     The upper bound on the time posted
142
 *
143
 *   Additionally accepts all "annotation_*" options supported by {@link elgg_get_entities()}
144
 *   annotation_ids       => INT|ARR The identifier of the annotation(s)
145
 *
146
 *   types                => STR|ARR Entity type string(s)
147
 *   subtypes             => STR|ARR Entity subtype string(s)
148
 *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype
149
 *                                   can be an array of subtype strings
150
 *
151
 *   Additionally accepts all "relationship_*" options supported by {@link elgg_get_entities()}
152
 *   relationship         => STR     Relationship identifier
153
 *   relationship_guid    => INT|ARR Entity guid(s)
154
 *   inverse_relationship => BOOL    Subject or object of the relationship (false)
155
 *   relationship_join_on => STR     subject_guid|object_guid|target_guid (defaults to subject_guid)
156
 *
157
 *   limit                => INT     Number to show per page (20)
158
 *   offset               => INT     Offset in list (0)
159
 *   count                => BOOL    Count the river items? (false)
160
 *   order_by             => STR     Order by clause (rv.posted desc)
161
 *   group_by             => STR     Group by clause
162
 *
163
 *   distinct             => BOOL    If set to false, Elgg will drop the DISTINCT
164
 *                                   clause from the MySQL query, which will improve
165
 *                                   performance in some situations. Avoid setting this
166
 *                                   option without a full understanding of the
167
 *                                   underlying SQL query Elgg creates. (true)
168
 *
169
 *   batch                => BOOL    If set to true, an Elgg\BatchResult object will be returned
170
 *                                   instead of an array. (false) Since 2.3.
171
 *
172
 *   batch_inc_offset     => BOOL    If "batch" is used, this tells the batch to increment the offset
173
 *                                   on each fetch. This must be set to false if you delete the batched
174
 *                                   results. (true)
175
 *
176
 *   batch_size           => INT     If "batch" is used, this is the number of entities/rows to pull
177
 *                                   in before requesting more. (25)
178
 *
179
 * @return \ElggRiverItem[]|\Elgg\BatchResult|array|int
180
 * @since 1.8.0
181
 */
182
function elgg_get_river(array $options = []) {
183 253
	return \Elgg\Database\River::find($options);
184
}
185
186
/**
187
 * Get river item from its ID
188
 *
189
 * @param int $id ID
190
 * @return ElggRiverItem|false
191
 */
192
function elgg_get_river_item_from_id($id) {
193 5
	$items = elgg_get_river([
194 5
		'id' => $id,
195
	]);
196
197 5
	return $items ? $items[0] : false;
198
}
199
200
/**
201
 * Delete river items based on $options.
202
 *
203
 * @warning Unlike elgg_get_river() this will not accept an empty options array!
204
 *          This requires at least one constraint: id(s), annotation_id(s)
205
 *          subject_guid(s), object_guid(s), target_guid(s)
206
 *          or view(s) must be set.
207
 *
208
 *          Access is ignored during the execution of this function.
209
 *          Intended usage of this function is to cleanup river content.
210
 *          For an example see actions/avatar/upload.
211
 *
212
 * @param array $options An options array. {@link elgg_get_river()}
213
 *
214
 * @return bool|null true on success, false on failure, null if no metadata to delete.
215
 *
216
 * @since   1.8.0
217
 */
218
function elgg_delete_river(array $options = []) {
219
220 253
	if (!_elgg_is_valid_options_for_batch_operation($options, 'river')) {
221
		// requirements not met
222
		return false;
223
	}
224
225
	return elgg_call(ELGG_IGNORE_ACCESS, function() use ($options) {
226 253
		$options['batch'] = true;
227 253
		$options['batch_size'] = 25;
228 253
		$options['batch_inc_offset'] = false;
229
	
230 253
		$river = elgg_get_river($options);
231 253
		$count = $river->count();
232
	
233 253
		if (!$count) {
234 253
			return;
235
		}
236
	
237 7
		$success = 0;
238 7
		foreach ($river as $river_item) {
239 7
			if ($river_item->delete()) {
240 7
				$success++;
241
			}
242
		}
243
	
244 7
		return $success == $count;
245 253
	});
246
}
247
248
/**
249
 * List river items
250
 *
251
 * @param array $options Any options from elgg_get_river() plus:
252
 *   item_view  => STR         Alternative view to render list items
253
 *   pagination => BOOL        Display pagination links (true)
254
 *   no_results => STR|true|Closure Message to display if no items
255
 *
256
 * @return string
257
 * @since 1.8.0
258
 */
259
function elgg_list_river(array $options = []) {
260
	$defaults = [
261
		'offset'     => (int) max(get_input('offset', 0), 0),
262
		'limit'      => (int) max(get_input('limit', max(20, _elgg_config()->default_limit)), 0),
263
		'pagination' => true,
264
		'list_class' => 'elgg-list-river',
265
	];
266
267
	$options = array_merge($defaults, $options);
268
	
269
	$options['register_rss_link'] = elgg_extract('register_rss_link', $options, elgg_extract('pagination', $options));
270
	if ($options['register_rss_link']) {
271
		elgg_register_rss_link();
272
	}
273
	
274
	if (!$options["limit"] && !$options["offset"]) {
275
		// no need for pagination if listing is unlimited
276
		$options["pagination"] = false;
277
	}
278
	
279
	$options['count'] = false;
280
	$items = elgg_get_river($options);
281
	$options['count'] = is_array($items) ? count($items) : 0;
282
	
283
	if (!empty($items)) {
284
		$count_needed = true;
285
		if (!$options['pagination']) {
286
			$count_needed = false;
287
		} elseif (!$options['offset'] && !$options['limit']) {
288
			$count_needed = false;
289
		} elseif (($options['count'] < (int) $options['limit']) && !$options['offset']) {
290
			$count_needed = false;
291
		}
292
		
293
		if ($count_needed) {
294
			$options['count'] = true;
295
		
296
			$options['count'] = (int) elgg_get_river($options);
297
		}
298
	}
299
	
300
	$options['items'] = $items;
301
302
	return elgg_view('page/components/list', $options);
303
}
304
305
/**
306
 * Register river unit tests
307
 *
308
 * @param string $hook  'unit_test'
309
 * @param string $type  'system'
310
 * @param array  $value current return value
311
 *
312
 * @return array
313
 * @codeCoverageIgnore
314
 */
315
function _elgg_river_test($hook, $type, $value) {
316
	$value[] = ElggCoreRiverAPITest::class;
0 ignored issues
show
The type ElggCoreRiverAPITest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
317
	return $value;
318
}
319
320
/**
321
 * Updates the last action of the object of an river item
322
 *
323
 * @param string         $event 'create'
324
 * @param string         $type  'river'
325
 * @param \ElggRiverItem $item  The entity being disabled
326
 *
327
 * @return void
328
 *
329
 * @access private
330
 */
331
function _elgg_river_update_object_last_action($event, $type, $item) {
332 5
	if (!$item instanceof \ElggRiverItem) {
333
		return;
334
	}
335
	
336 5
	$object = $item->getObjectEntity();
337 5
	if (!$object) {
338
		return;
339
	}
340
	
341 5
	$object->updateLastAction($item->getTimePosted());
342 5
}
343
344
/**
345
 * Disable river entries that reference a disabled entity as subject/object/target
346
 *
347
 * @param string     $event  'disable'
348
 * @param string     $type   'all'
349
 * @param ElggEntity $entity The entity being disabled
350
 *
351
 * @return void
352
 *
353
 * @access private
354
 */
355
function _elgg_river_disable($event, $type, $entity) {
356
357 4
	if (!$entity instanceof ElggEntity) {
358
		return;
359
	}
360
361 4
	$dbprefix = _elgg_config()->dbprefix;
362
	$query = <<<QUERY
363 4
	UPDATE {$dbprefix}river AS rv
364
	SET rv.enabled = 'no'
365 4
	WHERE (rv.subject_guid = {$entity->guid} OR rv.object_guid = {$entity->guid} OR rv.target_guid = {$entity->guid});
366
QUERY;
367
368 4
	elgg()->db->updateData($query);
369 4
	return;
370
}
371
372
373
/**
374
 * Enable river entries that reference a re-enabled entity as subject/object/target
375
 *
376
 * @param string     $event  'enable'
377
 * @param string     $type   'all'
378
 * @param ElggEntity $entity The entity being enabled
379
 *
380
 * @return void
381
 *
382
 * @access private
383
 */
384
function _elgg_river_enable($event, $type, $entity) {
385
386 3
	if (!$entity instanceof ElggEntity) {
387
		return;
388
	}
389
390 3
	$dbprefix = _elgg_config()->dbprefix;
391
	$query = <<<QUERY
392 3
	UPDATE {$dbprefix}river AS rv
393 3
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
394 3
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
395 3
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
396
	SET rv.enabled = 'yes'
397
	WHERE (
398
			(se.enabled = 'yes' OR se.guid IS NULL) AND
399
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
400
			(te.enabled = 'yes' OR te.guid IS NULL)
401
		)
402 3
		AND (se.guid = {$entity->guid} OR oe.guid = {$entity->guid} OR te.guid = {$entity->guid});
403
QUERY;
404
405 3
	elgg()->db->updateData($query);
406 3
	return;
407
}
408
409
/**
410
 * Add the delete to river actions menu
411
 *
412
 * @param \Elgg\Hook $hook 'register' 'menu:river'
413
 *
414
 * @return void|ElggMenuItem[]
415
 *
416
 * @access private
417
 */
418
function _elgg_river_menu_setup(\Elgg\Hook $hook) {
419 1
	if (!elgg_is_logged_in()) {
420 1
		return;
421
	}
422
423
	$item = $hook->getParam('item');
424
	if (!($item instanceof ElggRiverItem)) {
425
		return;
426
	}
427
428
	if (!$item->canDelete()) {
429
		return;
430
	}
431
432
	$return = $hook->getValue();
433
434
	$return[] = \ElggMenuItem::factory([
435
		'name' => 'delete',
436
		'href' => "action/river/delete?id={$item->id}",
437
		'is_action' => true,
438
		'icon' => 'delete',
439
		'text' => elgg_echo('river:delete'),
440
		'confirm' => elgg_echo('deleteconfirm'),
441
		'priority' => 999,
442
	]);
443
444
	return $return;
445
}
446
447
/**
448
 * Initialize river library
449
 *
450
 * @return void
451
 *
452
 * @access private
453
 */
454
function _elgg_river_init() {
455 75
	elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_river_test');
456
457 75
	elgg_register_plugin_hook_handler('register', 'menu:river', '_elgg_river_menu_setup');
458
	
459 75
	elgg_register_event_handler('created', 'river', '_elgg_river_update_object_last_action');
460 75
}
461
462
/**
463
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
464
 */
465
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
466 64
	$events->registerHandler('init', 'system', '_elgg_river_init');
467 64
	$events->registerHandler('disable:after', 'all', '_elgg_river_disable', 600);
468 64
	$events->registerHandler('enable:after', 'all', '_elgg_river_enable', 600);
469
};
470