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

engine/classes/Elgg/Database/AnnotationsTable.php (7 issues)

1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Database;
6
use Elgg\Database\Clauses\AnnotationWhereClause;
7
use Elgg\EventsService;
8
use ElggAnnotation;
9
use ElggEntity;
10
11
/**
12
 * Interfaces with the database to perform CRUD operations on annotations
13
 *
14
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
15
 *
16
 * @access private
17
 */
18
class AnnotationsTable {
19
20
	use \Elgg\TimeUsing;
21
22
	/**
23
	 * @var Database
24
	 */
25
	protected $db;
26
27
	/**
28
	 * @var EventsService
29
	 */
30
	protected $events;
31
32
	/**
33
	 * Constructor
34
	 *
35
	 * @param Database      $db     Database
36
	 * @param EventsService $events Events
37
	 */
38 4419
	public function __construct(Database $db, EventsService $events) {
39 4419
		$this->db = $db;
40 4419
		$this->events = $events;
41 4419
	}
42
43
	/**
44
	 * Get a specific annotation by its id
45
	 *
46
	 * @param int $id The id of the annotation object
47
	 *
48
	 * @return ElggAnnotation|false
49
	 */
50 8
	public function get($id) {
51 8
		$qb = Select::fromTable('annotations');
52 8
		$qb->select('*');
53
54 8
		$where = new AnnotationWhereClause();
55 8
		$where->ids = $id;
1 ignored issue
show
Documentation Bug introduced by
It seems like $id of type integer is incompatible with the declared type integer[] of property $ids.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
56 8
		$qb->addClause($where);
57
58 8
		$row = $this->db->getDataRow($qb);
59 8
		if ($row) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
60 8
			return new ElggAnnotation($row);
61
		}
62
63 1
		return false;
64
	}
65
66
	/**
67
	 * Deletes an annotation using its ID
68
	 *
69
	 * @param ElggAnnotation $annotation Annotation
70
	 *
71
	 * @return bool
72
	 */
73 30
	public function delete(ElggAnnotation $annotation) {
74 30
		if (!$annotation->canEdit()) {
75
			return false;
76
		}
77
78 30
		if (!$this->events->trigger('delete', 'annotation', $annotation)) {
79
			return false;
80
		}
81
82 30
		$qb = Delete::fromTable('annotations');
83 30
		$qb->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
84 30
		$deleted = $this->db->deleteData($qb);
85
86 30
		if ($deleted) {
87 30
			elgg_delete_river([
88 30
				'annotation_id' => $annotation->id,
89
				'limit' => false,
90
			]);
91
		}
92
93 30
		return $deleted;
94
	}
95
96
	/**
97
	 * Create a new annotation and return its ID
98
	 *
99
	 * @param ElggAnnotation $annotation Annotation
100
	 * @param ElggEntity     $entity     Entity being annotated
101
	 *
102
	 * @return int|bool
103
	 */
104 118
	public function create(ElggAnnotation $annotation, ElggEntity $entity) {
105 118
		if ($annotation->id) {
106
			return $this->update($annotation);
107
		}
108
109 118
		$annotation->entity_guid = $entity->guid;
110
111
		// @todo It looks like annotations permissions are not being checked anywhere...
112
		// Uncomment once tests have been updated
113
		// See #11418
114
		//if (!$entity->canAnnotate(0, $annotation->name)) {
115
		//	return false;
116
		//}
117
118 118
		if (!$this->events->trigger('annotate', $entity->getType(), $entity)) {
119
			return false;
120
		}
121
122 118
		if (!$this->events->triggerBefore('create', 'annotation', $annotation)) {
0 ignored issues
show
$annotation of type ElggAnnotation is incompatible with the type string expected by parameter $object of Elgg\EventsService::triggerBefore(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
		if (!$this->events->triggerBefore('create', 'annotation', /** @scrutinizer ignore-type */ $annotation)) {
Loading history...
123
			return false;
124
		}
125
126 118
		$time_created = $this->getCurrentTime()->getTimestamp();
127
128 118
		$qb = Insert::intoTable('annotations');
129 118
		$qb->values([
130 118
			'entity_guid' => $qb->param($annotation->entity_guid, ELGG_VALUE_INTEGER),
131 118
			'name' => $qb->param($annotation->name, ELGG_VALUE_STRING),
132 118
			'value' => $qb->param($annotation->value, $annotation->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING),
133 118
			'value_type' => $qb->param($annotation->value_type, ELGG_VALUE_STRING),
134 118
			'owner_guid' => $qb->param($annotation->owner_guid, ELGG_VALUE_INTEGER),
135 118
			'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
136 118
			'access_id' => $qb->param($annotation->access_id, ELGG_VALUE_INTEGER),
137
		]);
138
139 118
		$result = $this->db->insertData($qb);
140 118
		if ($result === false) {
141
			return false;
142
		}
143
144 118
		$annotation->id = $result;
145 118
		$annotation->time_created = $time_created;
146
147 118
		if (!$this->events->trigger('create', 'annotation', $annotation)) {
148
			elgg_delete_annotation_by_id($result);
149
150
			return false;
151
		}
152
153 118
		$entity->updateLastAction($annotation->time_created);
154
155 118
		$this->events->triggerAfter('create', 'annotation', $annotation);
0 ignored issues
show
$annotation of type ElggAnnotation is incompatible with the type string expected by parameter $object of Elgg\EventsService::triggerAfter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
		$this->events->triggerAfter('create', 'annotation', /** @scrutinizer ignore-type */ $annotation);
Loading history...
156
157 118
		return $result;
158
	}
159
160
	/**
161
	 * Store updated annotation in the database
162
	 *
163
	 * @todo Add canAnnotate check if entity guid changes
164
	 *
165
	 * @param ElggAnnotation $annotation Annotation to store
166
	 *
167
	 * @return bool
168
	 */
169
	public function update(ElggAnnotation $annotation) {
170
		if (!$annotation->canEdit()) {
171
			return false;
172
		}
173
174
		if (!$this->events->triggerBefore('update', 'annotation', $annotation)) {
0 ignored issues
show
$annotation of type ElggAnnotation is incompatible with the type string expected by parameter $object of Elgg\EventsService::triggerBefore(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
		if (!$this->events->triggerBefore('update', 'annotation', /** @scrutinizer ignore-type */ $annotation)) {
Loading history...
175
			return false;
176
		}
177
178
		$qb = Update::table('annotations');
179
		$qb->set('name', $qb->param($annotation->name, ELGG_VALUE_STRING))
180
			->set('value', $qb->param($annotation->value, $annotation->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING))
181
			->set('value_type', $qb->param($annotation->value_type, ELGG_VALUE_STRING))
182
			->set('access_id', $qb->param($annotation->access_id, ELGG_VALUE_INTEGER))
183
			->set('owner_guid', $qb->param($annotation->owner_guid, ELGG_VALUE_INTEGER))
184
			->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
185
186
		$result = $this->db->updateData($qb);
187
188
		if ($result === false) {
189
			return false;
190
		}
191
192
		$this->events->trigger('update', 'annotation', $annotation);
193
		$this->events->triggerAfter('update', 'annotation', $annotation);
0 ignored issues
show
$annotation of type ElggAnnotation is incompatible with the type string expected by parameter $object of Elgg\EventsService::triggerAfter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
		$this->events->triggerAfter('update', 'annotation', /** @scrutinizer ignore-type */ $annotation);
Loading history...
194
195
		return $result;
196
	}
197
198
	/**
199
	 * Disable the annotation.
200
	 *
201
	 * @param ElggAnnotation $annotation Annotation
202
	 *
203
	 * @return bool
204
	 * @since 1.8
205
	 */
206 9
	public function disable(ElggAnnotation $annotation) {
207 9
		if ($annotation->enabled == 'no') {
208
			return true;
209
		}
210
211 9
		if (!$annotation->canEdit()) {
212
			return false;
213
		}
214
215 9
		if (!elgg_trigger_event('disable', $annotation->getType(), $annotation)) {
216
			return false;
217
		}
218
219 9
		if ($annotation->id) {
220 9
			$qb = Update::table('annotations');
221 9
			$qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
222 9
				->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
223
224 9
			if (!$this->db->updateData($qb)) {
225
				return false;
226
			}
227
		}
228
229 9
		$annotation->enabled = 'no';
230
231 9
		return true;
232
	}
233
234
	/**
235
	 * Enable the annotation
236
	 *
237
	 * @param ElggAnnotation $annotation Annotation
238
	 *
239
	 * @return bool
240
	 * @since 1.8
241
	 */
242 4
	public function enable(ElggAnnotation $annotation) {
243 4
		if ($annotation->enabled == 'yes') {
244
			return true;
245
		}
246
247 4
		if (!$annotation->canEdit()) {
248
			return false;
249
		}
250
251 4
		if (!$this->events->trigger('enable', $annotation->getType(), $annotation)) {
252
			return false;
253
		}
254
255 4
		if ($annotation->id) {
256 4
			$qb = Update::table('annotations');
257 4
			$qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
258 4
				->where($qb->compare('id', '=', $annotation->id, ELGG_VALUE_INTEGER));
259
260 4
			if (!$this->db->updateData($qb)) {
261
				return false;
262
			}
263
		}
264
265 4
		$annotation->enabled = 'yes';
266
267 4
		return true;
268
	}
269
270
	/**
271
	 * Returns annotations.  Accepts all {@link elgg_get_entities()} options
272
	 *
273
	 * @see elgg_get_entities()
274
	 *
275
	 * @param array $options Options
276
	 *
277
	 * @return ElggAnnotation[]|mixed
278
	 */
279 20
	public function find(array $options = []) {
280 20
		$options['metastring_type'] = 'annotations';
281 20
		$options = _elgg_normalize_metastrings_options($options);
282
283 20
		return Annotations::find($options);
284
	}
285
286
	/**
287
	 * Deletes annotations based on $options.
288
	 *
289
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
290
	 *          This requires at least one constraint: annotation_owner_guid(s),
291
	 *          annotation_name(s), annotation_value(s), or guid(s) must be set.
292
	 *
293
	 * @see     elgg_get_annotations()
294
	 * @see     elgg_get_entities()
295
	 *
296
	 * @param array $options Options
297
	 *
298
	 * @return bool|null true on success, false on failure, null if no annotations to delete.
299
	 */
300 215
	public function deleteAll(array $options) {
301 215
		if (!_elgg_is_valid_options_for_batch_operation($options, 'annotation')) {
302
			return false;
303
		}
304
305 215
		$options['batch'] = true;
306 215
		$options['batch_size'] = 50;
307 215
		$options['batch_inc_offset'] = false;
308
309 215
		$annotations = Annotations::find($options);
310 215
		$count = $annotations->count();
311
312 215
		if (!$count) {
313 214
			return;
314
		}
315
316 25
		$success = 0;
317 25
		foreach ($annotations as $annotation) {
318 25
			if ($annotation->delete()) {
319 25
				$success++;
320
			}
321
		}
322
323 25
		return $success == $count;
324
	}
325
326
	/**
327
	 * Disables annotations based on $options.
328
	 *
329
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
330
	 *
331
	 * @param array $options An options array. {@link elgg_get_annotations()}
332
	 * @return bool|null true on success, false on failure, null if no annotations disabled.
333
	 */
334 9
	public function disableAll(array $options) {
335 9
		if (!_elgg_is_valid_options_for_batch_operation($options, 'annotation')) {
336
			return false;
337
		}
338
339
		// if we can see hidden (disabled) we need to use the offset
340
		// otherwise we risk an infinite loop if there are more than 50
341 9
		$inc_offset = access_get_show_hidden_status();
342
343 9
		$options['batch'] = true;
344 9
		$options['batch_size'] = 50;
345 9
		$options['batch_inc_offset'] = $inc_offset;
346
347 9
		$annotations = Annotations::find($options);
348 9
		$count = $annotations->count();
349
350 9
		if (!$count) {
351 4
			return;
352
		}
353
354 6
		$success = 0;
355 6
		foreach ($annotations as $annotation) {
356 6
			if ($annotation->disable()) {
357 6
				$success++;
358
			}
359
		}
360
361 6
		return $success == $count;
362
	}
363
364
	/**
365
	 * Enables annotations based on $options.
366
	 *
367
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
368
	 *
369
	 * @warning In order to enable annotations, you must first use
370
	 * {@link access_show_hidden_entities()}.
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 4
	public function enableAll(array $options) {
376 4
		if (!_elgg_is_valid_options_for_batch_operation($options, 'annotation')) {
377
			return false;
378
		}
379
380 4
		$options['batch'] = true;
381 4
		$options['batch_size'] = 50;
382
383 4
		$annotations = Annotations::find($options);
384 4
		$count = $annotations->count();
385
386 4
		if (!$count) {
387 3
			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
	 * Check to see if a user has already created an annotation on an object
402
	 *
403
	 * @param int    $entity_guid Entity guid
404
	 * @param string $name        Annotation name
405
	 * @param int    $owner_guid  Owner guid
406
	 *
407
	 * @return bool
408
	 */
409 1
	public function exists($entity_guid, $name, $owner_guid) {
410 1
		if (!$owner_guid) {
411
			return false;
412
		}
413
414 1
		$qb = Select::fromTable('annotations');
415 1
		$qb->select('id');
416 1
		$qb->where($qb->compare('owner_guid', '=', $owner_guid, ELGG_VALUE_INTEGER))
417 1
			->andWhere($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
418 1
			->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
419
420 1
		$result = $this->db->getDataRow($qb);
421
422 1
		return $result && $result->id;
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
423
	}
424
}
425