Passed
Branch master (6237eb)
by Jeroen
33:17
created

RelationshipsTable::removeAllWithEvents()   B

Complexity

Conditions 9
Paths 108

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 9.648

Importance

Changes 0
Metric Value
cc 9
eloc 30
nc 108
nop 4
dl 0
loc 53
ccs 4
cts 5
cp 0.8
crap 9.648
rs 7.9888
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Database;
6
use Elgg\Database\Clauses\GroupByClause;
7
use Elgg\Database\Clauses\OrderByClause;
8
use Elgg\Database\Clauses\SelectClause;
9
use Elgg\EventsService;
10
use Elgg\Exceptions\DatabaseException;
11
use Elgg\Exceptions\InvalidArgumentException;
12
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
13
14
/**
15
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
16
 *
17
 * @internal
18
 *
19
 * @since 1.10.0
20
 */
21
class RelationshipsTable {
22
23
	use \Elgg\TimeUsing;
24
	
25
	/**
26
	 * @var Database
27
	 */
28
	protected $db;
29
30
	/**
31
	 * @var EntityTable
32
	 */
33
	protected $entities;
34
35
	/**
36
	 * @var MetadataTable
37
	 */
38
	protected $metadata;
39
40
	/**
41
	 * @var EventsService
42
	 */
43
	protected $events;
44
45
	/**
46
	 * Constructor
47
	 *
48
	 * @param Database      $db       Elgg Database
49
	 * @param EntityTable   $entities Entity table
50
	 * @param MetadataTable $metadata Metadata table
51
	 * @param EventsService $events   Events service
52
	 */
53 5096
	public function __construct(Database $db, EntityTable $entities, MetadataTable $metadata, EventsService $events) {
54 5096
		$this->db = $db;
55 5096
		$this->entities = $entities;
56 5096
		$this->metadata = $metadata;
57 5096
		$this->events = $events;
58 5096
	}
59
60
	/**
61
	 * Get a relationship by its ID
62
	 *
63
	 * @param int $id The relationship ID
64
	 *
65
	 * @return \ElggRelationship|false False if not found
66
	 */
67 236
	public function get($id) {
68 236
		$select = Select::fromTable('entity_relationships');
69 236
		$select->select('*')
70 236
			->where($select->compare('id', '=', $id, ELGG_VALUE_ID));
71
		
72 236
		$relationship = $this->db->getDataRow($select, [$this, 'rowToElggRelationship']);
73 236
		if (!$relationship) {
74 2
			return false;
75
		}
76
77 234
		return $relationship;
78
	}
79
80
	/**
81
	 * Delete a relationship by its ID
82
	 *
83
	 * @param int  $id         Relationship ID
84
	 * @param bool $call_event Call the delete event before deleting
85
	 *
86
	 * @return bool
87
	 */
88 28
	public function delete($id, $call_event = true) {
89 28
		$relationship = $this->get($id);
90 28
		if (!$relationship instanceof \ElggRelationship) {
91 1
			return false;
92
		}
93
94 27
		if ($call_event && !$this->events->trigger('delete', 'relationship', $relationship)) {
95 2
			return false;
96
		}
97
98 25
		$delete = Delete::fromTable('entity_relationships');
99 25
		$delete->where($delete->compare('id', '=', $id, ELGG_VALUE_ID));
100
		
101 25
		return (bool) $this->db->deleteData($delete);
102
	}
103
104
	/**
105
	 * Create a relationship between two entities. E.g. friendship, group membership, site membership.
106
	 *
107
	 * This function lets you make the statement "$guid_one is a $relationship of $guid_two". In the statement,
108
	 * $guid_one is the subject of the relationship, $guid_two is the target, and $relationship is the type.
109
	 *
110
	 * @param int    $guid_one     GUID of the subject entity of the relationship
111
	 * @param string $relationship Type of the relationship
112
	 * @param int    $guid_two     GUID of the target entity of the relationship
113
	 * @param bool   $return_id    Return the ID instead of bool?
114
	 *
115
	 * @return bool|int
116
	 * @throws InvalidArgumentException
117
	 */
118 233
	public function add($guid_one, $relationship, $guid_two, $return_id = false) {
119 233
		if (strlen($relationship) > \ElggRelationship::RELATIONSHIP_LIMIT) {
120 1
			$msg = "relationship name cannot be longer than " . \ElggRelationship::RELATIONSHIP_LIMIT;
121 1
			throw new InvalidArgumentException($msg);
122
		}
123
124
		// Check for duplicates
125
		// note: escape $relationship after this call, we don't want to double-escape
126 232
		if ($this->check($guid_one, $relationship, $guid_two)) {
127
			return false;
128
		}
129
		
130 232
		$insert = Insert::intoTable('entity_relationships');
131 232
		$insert->values([
132 232
			'guid_one' => $insert->param($guid_one, ELGG_VALUE_GUID),
133 232
			'relationship' => $insert->param($relationship, ELGG_VALUE_STRING),
134 232
			'guid_two' => $insert->param($guid_two, ELGG_VALUE_GUID),
135 232
			'time_created' => $insert->param($this->getCurrentTime()->getTimestamp(), ELGG_VALUE_TIMESTAMP),
136
		]);
137
		
138
		try {
139 232
			$id = $this->db->insertData($insert);
140 232
			if (!$id) {
141 232
				return false;
142
			}
143
		} catch (DatabaseException $e) {
144
			$prev = $e->getPrevious();
145
			if ($prev instanceof UniqueConstraintViolationException) {
146
				// duplicate key error see https://github.com/Elgg/Elgg/issues/9179
147
				return false;
148
			}
149
			throw $e;
150
		}
151
152 232
		$obj = $this->get($id);
153
154 232
		$result = $this->events->trigger('create', 'relationship', $obj);
155 232
		if (!$result) {
156 2
			$this->delete($id, false);
157 2
			return false;
158
		}
159
160 230
		return $return_id ? $obj->id : true;
161
	}
162
163
	/**
164
	 * Check if a relationship exists between two entities. If so, the relationship object is returned.
165
	 *
166
	 * This function lets you ask "Is $guid_one a $relationship of $guid_two?"
167
	 *
168
	 * @param int    $guid_one     GUID of the subject entity of the relationship
169
	 * @param string $relationship Type of the relationship
170
	 * @param int    $guid_two     GUID of the target entity of the relationship
171
	 *
172
	 * @return \ElggRelationship|false Depending on success
173
	 */
174 288
	public function check($guid_one, $relationship, $guid_two) {
175 288
		$select = Select::fromTable('entity_relationships');
176 288
		$select->select('*')
177 288
			->where($select->compare('guid_one', '=', $guid_one, ELGG_VALUE_GUID))
178 288
			->andWhere($select->compare('relationship', '=', $relationship, ELGG_VALUE_STRING))
179 288
			->andWhere($select->compare('guid_two', '=', $guid_two, ELGG_VALUE_GUID))
180 288
			->setMaxResults(1);
181
		
182 288
		$row = $this->db->getDataRow($select, [$this, 'rowToElggRelationship']);
183 288
		if ($row instanceof \ElggRelationship) {
184 106
			return $row;
185
		}
186
187 246
		return false;
188
	}
189
190
	/**
191
	 * Delete a relationship between two entities.
192
	 *
193
	 * This function lets you say "$guid_one is no longer a $relationship of $guid_two."
194
	 *
195
	 * @param int    $guid_one     GUID of the subject entity of the relationship
196
	 * @param string $relationship Type of the relationship
197
	 * @param int    $guid_two     GUID of the target entity of the relationship
198
	 *
199
	 * @return bool
200
	 */
201 103
	public function remove($guid_one, $relationship, $guid_two) {
202 103
		$obj = $this->check($guid_one, $relationship, $guid_two);
203 103
		if (!$obj instanceof \ElggRelationship) {
204 89
			return false;
205
		}
206
207 19
		return $this->delete($obj->id);
208
	}
209
210
	/**
211
	 * Removes all relationships originating from a particular entity
212
	 *
213
	 * @param int    $guid                 GUID of the subject or target entity (see $inverse)
214
	 * @param string $relationship         Type of the relationship (optional, default is all relationships)
215
	 * @param bool   $inverse_relationship Is $guid the target of the deleted relationships? By default, $guid is the
216
	 *                                     subject of the relationships.
217
	 * @param string $type                 The type of entity related to $guid (defaults to all)
218
	 * @param bool   $trigger_events       Trigger the delete event for each relationship (default: true)
219
	 *
220
	 * @return true
221 407
	 */
222 407
	public function removeAll($guid, $relationship = '', $inverse_relationship = false, $type = '', bool $trigger_events = true) {
223
		
224 407
		if ($trigger_events) {
225 403
			return $this->removeAllWithEvents($guid, $relationship, $inverse_relationship, $type);
226
		}
227 404
		
228
		return $this->removeAllWithoutEvents($guid, $relationship, $inverse_relationship, $type);
229
	}
230 407
	
231 5
	/**
232
	 * Removes all relationships originating from a particular entity
233
	 *
234 407
	 * This doesn't trigger the delete event for each relationship
235 3
	 *
236 3
	 * @param int    $guid                 GUID of the subject or target entity (see $inverse)
237 3
	 * @param string $relationship         Type of the relationship (optional, default is all relationships)
238
	 * @param bool   $inverse_relationship Is $guid the target of the deleted relationships? By default, $guid is the
239 3
	 *                                     subject of the relationships.
240 2
	 * @param string $type                 The type of entity related to $guid (defaults to all)
241
	 *
242 1
	 * @return true
243
	 */
244
	protected function removeAllWithoutEvents($guid, $relationship = '', $inverse_relationship = false, $type = '') {
245
		$delete = Delete::fromTable('entity_relationships');
246 407
		
247
		if ((bool) $inverse_relationship) {
248 407
			$delete->where($delete->compare('guid_two', '=', $guid, ELGG_VALUE_GUID));
249
		} else {
250
			$delete->where($delete->compare('guid_one', '=', $guid, ELGG_VALUE_GUID));
251
		}
252
		
253
		if (!empty($relationship)) {
254
			$delete->andWhere($delete->compare('relationship', '=', $relationship, ELGG_VALUE_STRING));
255
		}
256
		
257
		if (!empty($type)) {
258
			$entity_sub = $delete->subquery('entities');
259
			$entity_sub->select('guid')
260 2
			->where($delete->compare('type', '=', $type, ELGG_VALUE_STRING));
261 2
			
262 2
			if (!(bool) $inverse_relationship) {
263
				$delete->andWhere($delete->compare('guid_two', 'in', $entity_sub->getSQL()));
264 2
			} else {
265 1
				$delete->andWhere($delete->compare('guid_one', 'in', $entity_sub->getSQL()));
266
			}
267 1
		}
268
		
269
		$this->db->deleteData($delete);
270 2
		
271
		return true;
272
	}
273
	
274
	/**
275
	 * Removes all relationships originating from a particular entity
276
	 *
277
	 * The does trigger the delete event for each relationship
278
	 *
279
	 * @param int    $guid                 GUID of the subject or target entity (see $inverse)
280
	 * @param string $relationship         Type of the relationship (optional, default is all relationships)
281
	 * @param bool   $inverse_relationship Is $guid the target of the deleted relationships? By default, $guid is the
282 1
	 *                                     subject of the relationships.
283 1
	 * @param string $type                 The type of entity related to $guid (defaults to all)
284 1
	 *
285 1
	 * @return true
286
	 */
287 1
	protected function removeAllWithEvents($guid, $relationship = '', $inverse_relationship = false, $type = '') {
288
		$select = Select::fromTable('entity_relationships');
289
		$select->select('*');
290
		
291
		if ((bool) $inverse_relationship) {
292
			$select->where($select->compare('guid_two', '=', $guid, ELGG_VALUE_GUID));
293
		} else {
294
			$select->where($select->compare('guid_one', '=', $guid, ELGG_VALUE_GUID));
295
		}
296
		
297 242
		if (!empty($relationship)) {
298 242
			$select->andWhere($select->compare('relationship', '=', $relationship, ELGG_VALUE_STRING));
299 242
		}
300
		
301
		if (!empty($type)) {
302
			$entity_sub = $select->subquery('entities');
303
			$entity_sub->select('guid')
304
			->where($select->compare('type', '=', $type, ELGG_VALUE_STRING));
305
			
306
			if (!(bool) $inverse_relationship) {
307
				$select->andWhere($select->compare('guid_two', 'in', $entity_sub->getSQL()));
308
			} else {
309
				$select->andWhere($select->compare('guid_one', 'in', $entity_sub->getSQL()));
310
			}
311
		}
312
		
313
		$remove_ids = [];
314
		
315
		$relationships = $this->db->getData($select, [$this, 'rowToElggRelationship']);
316
		
317
		/* @var $rel \ElggRelationship */
318
		foreach ($relationships as $rel) {
319
			if (!$this->events->trigger('delete', 'relationship', $rel)) {
320
				continue;
321
			}
322
			
323
			$remove_ids[] = $rel->id;
324
		}
325
		
326
		// to prevent MySQL query length issues
327
		$chunks = array_chunk($remove_ids, 250);
328
		foreach ($chunks as $chunk) {
329
			if (empty($chunk)) {
330
				continue;
331
			}
332
			
333
			$delete = Delete::fromTable('entity_relationships');
334
			$delete->where($delete->compare('id', 'in', $chunk));
335
			
336
			$this->db->deleteData($delete);
337
		}
338
		
339
		return true;
340
	}
341
342
	/**
343
	 * Get all the relationships for a given GUID.
344
	 *
345
	 * @param int  $guid                 GUID of the subject or target entity (see $inverse)
346
	 * @param bool $inverse_relationship Is $guid the target of the deleted relationships? By default $guid is
347
	 *                                   the subject of the relationships.
348
	 *
349
	 * @return \ElggRelationship[]
350
	 */
351
	public function getAll($guid, $inverse_relationship = false) {
352
		$select = Select::fromTable('entity_relationships');
353
		$select->select('*');
354
		
355
		if ((bool) $inverse_relationship) {
356
			$select->where($select->compare('guid_two', '=', $guid, ELGG_VALUE_GUID));
357
		} else {
358
			$select->where($select->compare('guid_one', '=', $guid, ELGG_VALUE_GUID));
359
		}
360
		
361
		return $this->db->getData($select, [$this, 'rowToElggRelationship']);
362
	}
363
364
	/**
365
	 * Gets the number of entities by a the number of entities related to them in a particular way.
366
	 * This is a good way to get out the users with the most friends, or the groups with the
367
	 * most members.
368
	 *
369
	 * @param array $options An options array compatible with elgg_get_entities()
370
	 *
371
	 * @return \ElggEntity[]|int|boolean If count, int. If not count, array. false on errors.
372
	 */
373
	public function getEntitiesFromCount(array $options = []) {
374
		$options['selects'][] = new SelectClause("COUNT(e.guid) AS total");
375
		$options['group_by'][] = new GroupByClause('r.guid_two');
376
		$options['order_by'][] = new OrderByClause('total', 'desc');
377
378
		return Entities::find($options);
379
	}
380
381
	/**
382
	 * Convert a database row to a new \ElggRelationship
383
	 *
384
	 * @param \stdClass $row Database row from the relationship table
385
	 *
386
	 * @return \ElggRelationship|false
387
	 */
388
	public function rowToElggRelationship($row) {
389
		if ($row instanceof \stdClass) {
390
			return new \ElggRelationship($row);
391
		}
392
393
		return false;
394
	}
395
}
396