Completed
Push — 3.1 ( d59679...4b8741 )
by Jeroen
62:38 queued 13s
created

engine/lib/river.php (10 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
 * @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 = []) {
0 ignored issues
show
Function name "elgg_create_river_item" is not in camel caps format
Loading history...
32
33 88
	$view = elgg_extract('view', $options, '');
34
	// use default viewtype for when called from web services api
35 88
	if (!empty($view) && !elgg_view_exists($view, 'default')) {
36 1
		return false;
37
	}
38
39 87
	$action_type = elgg_extract('action_type', $options);
40 87
	if (empty($action_type)) {
41 1
		return false;
42
	}
43
44 87
	$subject_guid = elgg_extract('subject_guid', $options, elgg_get_logged_in_user_guid());
45 87
	if (!(get_entity($subject_guid) instanceof ElggEntity)) {
46 2
		return false;
47
	}
48
49 87
	$object_guid = elgg_extract('object_guid', $options, 0);
50 87
	if (!(get_entity($object_guid) instanceof ElggEntity)) {
51 2
		return false;
52
	}
53
54 86
	$target_guid = elgg_extract('target_guid', $options, 0);
55 86
	if ($target_guid) {
56
		// target_guid is not a required parameter so check
57
		// it only if it is included in the parameters
58 77
		if (!(get_entity($target_guid) instanceof ElggEntity)) {
59 1
			return false;
60
		}
61
	}
62
63 85
	$posted = elgg_extract('posted', $options, time());
64
65 85
	$annotation_id = elgg_extract('annotation_id', $options, 0);
66 85
	if ($annotation_id) {
67
		if (!elgg_get_annotation_from_id($annotation_id)) {
68
			return false;
69
		}
70
	}
71
72 85
	$return_item = elgg_extract('return_item', $options, false);
73
74
	$values = [
75 85
		'action_type' => $action_type,
76 85
		'view' => $view,
77 85
		'subject_guid' => $subject_guid,
78 85
		'object_guid' => $object_guid,
79 85
		'target_guid' => $target_guid,
80 85
		'annotation_id' => $annotation_id,
81 85
		'posted' => $posted,
82
	];
83
	$col_types = [
84 85
		'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 85
	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values);
95 85
	if ($values == false) {
96
		// inserting did not fail - it was just prevented
97 1
		return true;
98
	}
99
100 84
	$qb = \Elgg\Database\Insert::intoTable('river');
101 84
	$query_params = [];
102 84
	foreach ($values as $name => $value) {
103 84
		$query_params[$name] = $qb->param($value, $col_types[$name]);
104
	}
105 84
	$qb->values($query_params);
106
107 84
	$id = _elgg_services()->db->insertData($qb);
108 84
	if (!$id) {
109
		return false;
110
	}
111
112 84
	if (!$return_item) {
113 80
		return $id;
114
	}
115
116
	$item = elgg_call(ELGG_IGNORE_ACCESS, function () use ($id) {
117 5
		return elgg_get_river_item_from_id($id);
118 5
	});
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 = []) {
0 ignored issues
show
Function name "elgg_get_river" is not in camel caps format
Loading history...
183 280
	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) {
0 ignored issues
show
Function name "elgg_get_river_item_from_id" is not in camel caps format
Loading history...
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 = []) {
0 ignored issues
show
Function name "elgg_delete_river" is not in camel caps format
Loading history...
219
220 280
	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 280
		$options['batch'] = true;
227 280
		$options['batch_size'] = 25;
228 280
		$options['batch_inc_offset'] = false;
229
	
230 280
		$river = elgg_get_river($options);
231 280
		$count = $river->count();
232
	
233 280
		if (!$count) {
234 280
			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 280
	});
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 = []) {
0 ignored issues
show
Function name "elgg_list_river" is not in camel caps format
Loading history...
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
 * Updates the last action of the object of an river item
307
 *
308
 * @param \Elgg\Event $event 'create', 'river'
309
 *
310
 * @return void
311
 *
312
 * @internal
313
 */
314
function _elgg_river_update_object_last_action(\Elgg\Event $event) {
0 ignored issues
show
Function name "_elgg_river_update_object_last_action" is not in camel caps format
Loading history...
315 5
	$item = $event->getObject();
316 5
	if (!$item instanceof \ElggRiverItem) {
317
		return;
318
	}
319
	
320 5
	$object = $item->getObjectEntity();
321 5
	if (!$object) {
322
		return;
323
	}
324
	
325 5
	$object->updateLastAction($item->getTimePosted());
326 5
}
327
328
/**
329
 * Disable river entries that reference a disabled entity as subject/object/target
330
 *
331
 * @param \Elgg\Event $event 'disable', 'all'
332
 *
333
 * @return void
334
 *
335
 * @internal
336
 */
337
function _elgg_river_disable(\Elgg\Event $event) {
0 ignored issues
show
Function name "_elgg_river_disable" is not in camel caps format
Loading history...
338 4
	$entity = $event->getObject();
339 4
	if (!$entity instanceof ElggEntity) {
340
		return;
341
	}
342
343 4
	$dbprefix = _elgg_config()->dbprefix;
344
	$query = <<<QUERY
345 4
	UPDATE {$dbprefix}river AS rv
346
	SET rv.enabled = 'no'
347 4
	WHERE (rv.subject_guid = {$entity->guid} OR rv.object_guid = {$entity->guid} OR rv.target_guid = {$entity->guid});
348
QUERY;
349
350 4
	elgg()->db->updateData($query);
351 4
	return;
352
}
353
354
355
/**
356
 * Enable river entries that reference a re-enabled entity as subject/object/target
357
 *
358
 * @param \Elgg\Event $event 'enable', 'all'
359
 *
360
 * @return void
361
 *
362
 * @internal
363
 */
364
function _elgg_river_enable(\Elgg\Event $event) {
0 ignored issues
show
Function name "_elgg_river_enable" is not in camel caps format
Loading history...
365 3
	$entity = $event->getObject();
366 3
	if (!$entity instanceof ElggEntity) {
367
		return;
368
	}
369
370 3
	$dbprefix = _elgg_config()->dbprefix;
371
	$query = <<<QUERY
372 3
	UPDATE {$dbprefix}river AS rv
373 3
	LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
374 3
	LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
375 3
	LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
376
	SET rv.enabled = 'yes'
377
	WHERE (
378
			(se.enabled = 'yes' OR se.guid IS NULL) AND
379
			(oe.enabled = 'yes' OR oe.guid IS NULL) AND
380
			(te.enabled = 'yes' OR te.guid IS NULL)
381
		)
382 3
		AND (se.guid = {$entity->guid} OR oe.guid = {$entity->guid} OR te.guid = {$entity->guid});
383
QUERY;
384
385 3
	elgg()->db->updateData($query);
386 3
	return;
387
}
388
389
/**
390
 * Add the delete to river actions menu
391
 *
392
 * @param \Elgg\Hook $hook 'register' 'menu:river'
393
 *
394
 * @return void|ElggMenuItem[]
395
 *
396
 * @internal
397
 */
398
function _elgg_river_menu_setup(\Elgg\Hook $hook) {
0 ignored issues
show
Function name "_elgg_river_menu_setup" is not in camel caps format
Loading history...
399 1
	if (!elgg_is_logged_in()) {
400 1
		return;
401
	}
402
403
	$item = $hook->getParam('item');
404
	if (!($item instanceof ElggRiverItem)) {
405
		return;
406
	}
407
408
	if (!$item->canDelete()) {
409
		return;
410
	}
411
412
	$return = $hook->getValue();
413
414
	$return[] = \ElggMenuItem::factory([
415
		'name' => 'delete',
416
		'href' => "action/river/delete?id={$item->id}",
417
		'is_action' => true,
418
		'icon' => 'delete',
419
		'text' => elgg_echo('river:delete'),
420
		'confirm' => elgg_echo('deleteconfirm'),
421
		'priority' => 999,
422
	]);
423
424
	return $return;
425
}
426
427
/**
428
 * Initialize river library
429
 *
430
 * @return void
431
 *
432
 * @internal
433
 */
434
function _elgg_river_init() {
0 ignored issues
show
Function name "_elgg_river_init" is not in camel caps format
Loading history...
435 118
	elgg_register_plugin_hook_handler('register', 'menu:river', '_elgg_river_menu_setup');
436
	
437 118
	elgg_register_event_handler('created', 'river', '_elgg_river_update_object_last_action');
438 118
}
439
440
/**
441
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
442
 */
443
return function(\Elgg\EventsService $events) {
444 107
	$events->registerHandler('init', 'system', '_elgg_river_init');
445 107
	$events->registerHandler('disable:after', 'all', '_elgg_river_disable', 600);
446 107
	$events->registerHandler('enable:after', 'all', '_elgg_river_enable', 600);
447
};
448