Passed
Push — 4.x ( c09d5f...b7eddf )
by Jeroen
31:11 queued 15s
created

AnnotationsTable::update()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8.216

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 5
nop 1
dl 0
loc 31
ccs 17
cts 20
cp 0.85
crap 8.216
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Database;
6
use Elgg\Database\Clauses\AnnotationWhereClause;
7
use Elgg\EventsService;
8
use Elgg\Traits\TimeUsing;
9
10
/**
11
 * Interfaces with the database to perform CRUD operations on annotations
12
 *
13
 * @internal
14
 */
15
class AnnotationsTable {
16
17
	use TimeUsing;
18
19
	/**
20
	 * @var Database
21
	 */
22
	protected $db;
23
24
	/**
25
	 * @var EventsService
26
	 */
27
	protected $events;
28
29
	/**
30
	 * Constructor
31
	 *
32
	 * @param Database      $db     Database
33
	 * @param EventsService $events Events
34
	 */
35 6136
	public function __construct(Database $db, EventsService $events) {
36 6136
		$this->db = $db;
37 6136
		$this->events = $events;
38
	}
39
40
	/**
41
	 * Get a specific annotation by its id
42
	 *
43
	 * @param int $id The id of the annotation object
44
	 *
45
	 * @return \ElggAnnotation|false
46
	 */
47 29
	public function get(int $id) {
48 29
		$qb = Select::fromTable('annotations');
49 29
		$qb->select('*');
50
51 29
		$where = new AnnotationWhereClause();
52 29
		$where->ids = $id;
53 29
		$qb->addClause($where);
54
55 29
		$row = $this->db->getDataRow($qb);
56 29
		if (!empty($row)) {
57 29
			return new \ElggAnnotation($row);
58
		}
59
60 1
		return false;
61
	}
62
63
	/**
64
	 * Deletes an annotation using its ID
65
	 *
66
	 * @param \ElggAnnotation $annotation Annotation
67
	 *
68
	 * @return bool
69
	 */
70 454
	public function delete(\ElggAnnotation $annotation) {
71 454
		if (!$annotation->canEdit()) {
72
			return false;
73
		}
74
75 454
		if (!$this->events->trigger('delete', 'annotation', $annotation)) {
76
			return false;
77
		}
78
79 454
		$qb = Delete::fromTable('annotations');
80 454
		$qb->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
81 454
		$deleted = $this->db->deleteData($qb);
82
83 454
		if ($deleted) {
84 454
			elgg_delete_river([
85 454
				'annotation_id' => $annotation->id,
86
				'limit' => false,
87
			]);
88
		}
89
90 454
		return $deleted !== false;
91
	}
92
93
	/**
94
	 * Create a new annotation and return its ID
95
	 *
96
	 * @param \ElggAnnotation $annotation Annotation
97
	 * @param \ElggEntity     $entity     Entity being annotated
98
	 *
99
	 * @return int|bool
100
	 */
101 455
	public function create(\ElggAnnotation $annotation, \ElggEntity $entity) {
102 455
		if ($annotation->id) {
103 1
			return $this->update($annotation);
104
		}
105
		
106 455
		if (is_null($annotation->owner_guid) || is_null($annotation->name) || is_null($annotation->value)) {
1 ignored issue
show
introduced by
The condition is_null($annotation->name) is always false.
Loading history...
107 1
			return false;
108
		}
109
110 454
		$annotation->entity_guid = $entity->guid;
111
112
		// @todo It looks like annotations permissions are not being checked anywhere...
113
		// Uncomment once tests have been updated
114
		// See #11418
115
		//if (!$entity->canAnnotate(0, $annotation->name)) {
116
		//	return false;
117
		//}
118
119 454
		if (!$this->events->triggerDeprecated('annotate', $entity->getType(), $entity, "The 'annotate', '{$entity->getType()}' event is deprecated. Use the 'create', 'annotation' event instead.", '4.3')) {
120
			return false;
121
		}
122
123 454
		if (!$this->events->triggerBefore('create', 'annotation', $annotation)) {
124
			return false;
125
		}
126
127 454
		$time_created = $this->getCurrentTime()->getTimestamp();
128
129 454
		$qb = Insert::intoTable('annotations');
130 454
		$qb->values([
131 454
			'entity_guid' => $qb->param($annotation->entity_guid, ELGG_VALUE_INTEGER),
132 454
			'name' => $qb->param($annotation->name, ELGG_VALUE_STRING),
133 454
			'value' => $qb->param($annotation->value, $annotation->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER),
134 454
			'value_type' => $qb->param($annotation->value_type, ELGG_VALUE_STRING),
135 454
			'owner_guid' => $qb->param($annotation->owner_guid, ELGG_VALUE_INTEGER),
136 454
			'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
137 454
			'access_id' => $qb->param($annotation->access_id, ELGG_VALUE_INTEGER),
138
		]);
139
140 454
		$result = $this->db->insertData($qb);
141 454
		if ($result === false) {
142
			return false;
143
		}
144
145 454
		$annotation->id = $result;
146 454
		$annotation->time_created = $time_created;
147
148 454
		if (!$this->events->trigger('create', 'annotation', $annotation)) {
149
			elgg_delete_annotation_by_id($result);
150
151
			return false;
152
		}
153
154 454
		$this->events->triggerAfter('create', 'annotation', $annotation);
155
156 454
		return $result;
157
	}
158
159
	/**
160
	 * Store updated annotation in the database
161
	 *
162
	 * @todo Add canAnnotate check if entity guid changes
163
	 *
164
	 * @param \ElggAnnotation $annotation Annotation to store
165
	 *
166
	 * @return bool
167
	 */
168 3
	public function update(\ElggAnnotation $annotation) {
169 3
		if (!$annotation->canEdit()) {
170
			return false;
171
		}
172
		
173 3
		if (is_null($annotation->owner_guid) || is_null($annotation->name) || is_null($annotation->value)) {
1 ignored issue
show
introduced by
The condition is_null($annotation->name) is always false.
Loading history...
174 1
			return false;
175
		}
176
177 2
		if (!$this->events->triggerBefore('update', 'annotation', $annotation)) {
178
			return false;
179
		}
180
181 2
		$qb = Update::table('annotations');
182 2
		$qb->set('name', $qb->param($annotation->name, ELGG_VALUE_STRING))
183 2
			->set('value', $qb->param($annotation->value, $annotation->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING))
184 2
			->set('value_type', $qb->param($annotation->value_type, ELGG_VALUE_STRING))
185 2
			->set('access_id', $qb->param($annotation->access_id, ELGG_VALUE_INTEGER))
186 2
			->set('owner_guid', $qb->param($annotation->owner_guid, ELGG_VALUE_INTEGER))
187 2
			->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
188
189 2
		$result = $this->db->updateData($qb);
190
191 2
		if ($result === false) {
192
			return false;
193
		}
194
195 2
		$this->events->trigger('update', 'annotation', $annotation);
196 2
		$this->events->triggerAfter('update', 'annotation', $annotation);
197
198 2
		return $result;
199
	}
200
201
	/**
202
	 * Disable the annotation.
203
	 *
204
	 * @param \ElggAnnotation $annotation Annotation
205
	 *
206
	 * @return bool
207
	 * @since 1.8
208
	 */
209 9
	public function disable(\ElggAnnotation $annotation) {
210 9
		if ($annotation->enabled == 'no') {
211
			return true;
212
		}
213
214 9
		if (!$annotation->canEdit()) {
215
			return false;
216
		}
217
218 9
		if (!_elgg_services()->events->trigger('disable', $annotation->getType(), $annotation)) {
219
			return false;
220
		}
221
222 9
		if ($annotation->id) {
223 9
			$qb = Update::table('annotations');
224 9
			$qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
225 9
				->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
226
227 9
			if (!$this->db->updateData($qb)) {
228
				return false;
229
			}
230
		}
231
232 9
		$annotation->enabled = 'no';
233
234 9
		return true;
235
	}
236
237
	/**
238
	 * Enable the annotation
239
	 *
240
	 * @param \ElggAnnotation $annotation Annotation
241
	 *
242
	 * @return bool
243
	 * @since 1.8
244
	 */
245 4
	public function enable(\ElggAnnotation $annotation) {
246 4
		if ($annotation->enabled == 'yes') {
247
			return true;
248
		}
249
250 4
		if (!$annotation->canEdit()) {
251
			return false;
252
		}
253
254 4
		if (!$this->events->trigger('enable', $annotation->getType(), $annotation)) {
255
			return false;
256
		}
257
258 4
		if ($annotation->id) {
259 4
			$qb = Update::table('annotations');
260 4
			$qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
261 4
				->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
262
263 4
			if (!$this->db->updateData($qb)) {
264
				return false;
265
			}
266
		}
267
268 4
		$annotation->enabled = 'yes';
269
270 4
		return true;
271
	}
272
273
	/**
274
	 * Returns annotations.  Accepts all {@link elgg_get_entities()} options
275
	 *
276
	 * @see elgg_get_entities()
277
	 *
278
	 * @param array $options Options
279
	 *
280
	 * @return \ElggAnnotation[]|mixed
281
	 */
282 47
	public function find(array $options = []) {
283 47
		$options['metastring_type'] = 'annotations';
284 47
		$options = QueryOptions::normalizeMetastringOptions($options);
285
286 47
		return Annotations::find($options);
287
	}
288
289
	/**
290
	 * Deletes annotations based on $options.
291
	 *
292
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
293
	 *          This requires at least one constraint: annotation_owner_guid(s),
294
	 *          annotation_name(s), annotation_value(s), or guid(s) must be set.
295
	 *
296
	 * @see     elgg_get_annotations()
297
	 * @see     elgg_get_entities()
298
	 *
299
	 * @param array $options Options
300
	 *
301
	 * @return bool|null true on success, false on failure, null if no annotations to delete.
302
	 */
303 1370
	public function deleteAll(array $options) {
304 1370
		if (!$this->isValidOptionsForBatchOperation($options)) {
305 2
			return false;
306
		}
307
308 1370
		$options['batch'] = true;
309 1370
		$options['batch_size'] = 50;
310 1370
		$options['batch_inc_offset'] = false;
311
312 1370
		$annotations = Annotations::find($options);
313 1370
		$count = $annotations->count();
314
315 1370
		if (!$count) {
316 1370
			return;
317
		}
318
319 448
		$success = 0;
320 448
		foreach ($annotations as $annotation) {
321 448
			if ($annotation->delete()) {
322 448
				$success++;
323
			}
324
		}
325
326 448
		return $success == $count;
327
	}
328
329
	/**
330
	 * Disables annotations based on $options.
331
	 *
332
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
333
	 *
334
	 * @param array $options An options array. {@link elgg_get_annotations()}
335
	 * @return bool|null true on success, false on failure, null if no annotations disabled.
336
	 */
337 14
	public function disableAll(array $options) {
338 14
		if (!$this->isValidOptionsForBatchOperation($options)) {
339
			return false;
340
		}
341
342
		// if we can see hidden (disabled) we need to use the offset
343
		// otherwise we risk an infinite loop if there are more than 50
344 14
		$inc_offset = _elgg_services()->session->getDisabledEntityVisibility();
345
346 14
		$options['batch'] = true;
347 14
		$options['batch_size'] = 50;
348 14
		$options['batch_inc_offset'] = $inc_offset;
349
350 14
		$annotations = Annotations::find($options);
351 14
		$count = $annotations->count();
352
353 14
		if (!$count) {
354 9
			return;
355
		}
356
357 6
		$success = 0;
358 6
		foreach ($annotations as $annotation) {
359 6
			if ($annotation->disable()) {
360 6
				$success++;
361
			}
362
		}
363
364 6
		return $success == $count;
365
	}
366
367
	/**
368
	 * Enables annotations based on $options.
369
	 *
370
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
371
	 *
372
	 * @param array $options An options array. {@link elgg_get_annotations()}
373
	 * @return bool|null true on success, false on failure, null if no metadata enabled.
374
	 */
375 6
	public function enableAll(array $options) {
376 6
		if (!$this->isValidOptionsForBatchOperation($options)) {
377
			return false;
378
		}
379
380 6
		$options['batch'] = true;
381 6
		$options['batch_size'] = 50;
382
383 6
		$annotations = Annotations::find($options);
384 6
		$count = $annotations->count();
385
386 6
		if (!$count) {
387 5
			return;
388
		}
389
390 3
		$success = 0;
391 3
		foreach ($annotations as $annotation) {
392 3
			if ($annotation->enable()) {
393 3
				$success++;
394
			}
395
		}
396
397 3
		return $success == $count;
398
	}
399
	
400
	/**
401
	 * Checks if there are some constraints on the options array for potentially dangerous operations
402
	 *
403
	 * @param array $options options to check
404
	 *
405
	 * @return bool
406
	 */
407 1374
	protected function isValidOptionsForBatchOperation(array $options): bool {
408
		$required = [
409
			'guid', 'guids',
410
			'annotation_owner_guid', 'annotation_owner_guids',
411
			'annotation_name', 'annotation_names',
412
			'annotation_value', 'annotation_values',
413
		];
414
415 1374
		foreach ($required as $key) {
416
			// check that it exists and is something.
417 1374
			if (isset($options[$key]) && !elgg_is_empty($options[$key])) {
418 1374
				return true;
419
			}
420
		}
421
422 2
		return false;
423
	}
424
425
	/**
426
	 * Check to see if a user has already created an annotation on an object
427
	 *
428
	 * @param int    $entity_guid Entity guid
429
	 * @param string $name        Annotation name
430
	 * @param int    $owner_guid  Owner guid
431
	 *
432
	 * @return bool
433
	 */
434 3
	public function exists(int $entity_guid, string $name, int $owner_guid) {
435 3
		if (!$owner_guid) {
436
			return false;
437
		}
438
439 3
		$qb = Select::fromTable('annotations');
440 3
		$qb->select('id');
441 3
		$qb->where($qb->compare('owner_guid', '=', $owner_guid, ELGG_VALUE_INTEGER))
442 3
			->andWhere($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
443 3
			->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
444
445 3
		$result = $this->db->getDataRow($qb);
446
447 3
		return !empty($result) && $result->id;
448
	}
449
}
450