Passed
Push — master ( f13f78...5c1b24 )
by Ismayil
04:22
created

engine/classes/Elgg/Database/Annotations.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Elgg\Database;
3
4
/**
5
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
6
 *
7
 * @access private
8
 *
9
 * @package    Elgg.Core
10
 * @subpackage Database
11
 * @since      1.10.0
12
 */
13
class Annotations {
14
15
	use \Elgg\TimeUsing;
16
	
17
	/**
18
	 * @var \Elgg\Database
19
	 */
20
	protected $db;
21
22
	/**
23
	 * @var \ElggSession
24
	 */
25
	protected $session;
26
27
	/**
28
	 * @var \Elgg\EventsService
29
	 */
30
	protected $events;
31
32
	/**
33
	 * Constructor
34
	 *
35
	 * @param \Elgg\Database      $db      Database
36
	 * @param \ElggSession        $session Session
37
	 * @param \Elgg\EventsService $events  Events
38
	 */
39 197
	public function __construct(\Elgg\Database $db, \ElggSession $session, \Elgg\EventsService $events) {
40 197
		$this->db = $db;
41 197
		$this->session = $session;
42 197
		$this->events = $events;
43 197
	}
44
45
	/**
46
	 * Get a specific annotation by its id.
47
	 * If you want multiple annotation objects, use
48
	 * {@link elgg_get_annotations()}.
49
	 *
50
	 * @param int $id The id of the annotation object being retrieved.
51
	 *
52
	 * @return \ElggAnnotation|false
53
	 */
54
	function get($id) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
55
		return _elgg_get_metastring_based_object_from_id($id, 'annotation');
56
	}
57
	
58
	/**
59
	 * Deletes an annotation using its ID.
60
	 *
61
	 * @param int $id The annotation ID to delete.
62
	 * @return bool
63
	 */
64
	function delete($id) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
65
		$annotation = $this->get($id);
66
		if (!$annotation) {
67
			return false;
68
		}
69
		return $annotation->delete();
70
	}
71
	
72
	/**
73
	 * Create a new annotation.
74
	 *
75
	 * @param int    $entity_guid GUID of entity to be annotated
76
	 * @param string $name        Name of annotation
77
	 * @param string $value       Value of annotation
78
	 * @param string $value_type  Type of value (default is auto detection)
79
	 * @param int    $owner_guid  Owner of annotation (default is logged in user)
80
	 * @param int    $access_id   Access level of annotation
81
	 *
82
	 * @return int|bool id on success or false on failure
83
	 */
84
	function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0, $access_id = ACCESS_PRIVATE) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
85
		
86
		$result = false;
87
	
88
		$entity_guid = (int) $entity_guid;
89
		$value_type = \ElggExtender::detectValueType($value, $value_type);
90
91
		$owner_guid = (int) $owner_guid;
92
		if ($owner_guid == 0) {
93
			$owner_guid = $this->session->getLoggedInUserGuid();
94
		}
95
	
96
		$access_id = (int) $access_id;
97
		
98
		// @todo we don't check that the entity is loaded which means the user may
99
		// not have access to the entity
100
		$entity = get_entity($entity_guid);
101
	
102
		if ($this->events->trigger('annotate', $entity->type, $entity)) {
103
			$sql = "INSERT INTO {$this->db->prefix}annotations
104
				(entity_guid, name, value, value_type, owner_guid, time_created, access_id)
105
				VALUES
106
				(:entity_guid, :name, :value, :value_type, :owner_guid, :time_created, :access_id)";
107
	
108
			$result = $this->db->insertData($sql, [
109
				':entity_guid' => $entity_guid,
110
				':name' => $name,
111
				':value' => $value,
112
				':value_type' => $value_type,
113
				':owner_guid' => $owner_guid,
114
				':time_created' => $this->getCurrentTime()->getTimestamp(),
115
				':access_id' => $access_id,
116
			]);
117
				
118
			if ($result !== false) {
119
				$obj = elgg_get_annotation_from_id($result);
120
				if ($this->events->trigger('create', 'annotation', $obj)) {
121
					return $result;
122
				} else {
123
					// plugin returned false to reject annotation
124
					elgg_delete_annotation_by_id($result);
125
					return false;
126
				}
127
			}
128
		}
129
	
130
		return $result;
131
	}
132
	
133
	/**
134
	 * Update an annotation.
135
	 *
136
	 * @param int    $annotation_id Annotation ID
137
	 * @param string $name          Name of annotation
138
	 * @param string $value         Value of annotation
139
	 * @param string $value_type    Type of value
140
	 * @param int    $owner_guid    Owner of annotation
141
	 * @param int    $access_id     Access level of annotation
142
	 *
143
	 * @return bool
144
	 */
145
	function update($annotation_id, $name, $value, $value_type, $owner_guid, $access_id) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
146
147
		$annotation_id = (int) $annotation_id;
148
	
149
		$annotation = $this->get($annotation_id);
150
		if (!$annotation) {
151
			return false;
152
		}
153
		if (!$annotation->canEdit()) {
154
			return false;
155
		}
156
	
157
		$name = trim($name);
158
		$value_type = \ElggExtender::detectValueType($value, $value_type);
159
	
160
		$owner_guid = (int) $owner_guid;
161
		if ($owner_guid == 0) {
162
			$owner_guid = $this->session->getLoggedInUserGuid();
163
		}
164
	
165
		$access_id = (int) $access_id;
166
				
167
		$sql = "UPDATE {$this->db->prefix}annotations
168
			(name, value, value_type, access_id, owner_guid)
169
			VALUES
170
			(:name, :value, :value_type, :access_id, :owner_guid)
171
			WHERE id = :annotation_id";
172
173
		$result = $this->db->updateData($sql, false, [
174
			':name' => $name,
175
			':value' => $value,
176
			':value_type' => $value_type,
177
			':access_id' => $access_id,
178
			':owner_guid' => $owner_guid,
179
			':annotation_id' => $annotation_id,
180
		]);
181
			
182
		if ($result !== false) {
183
			// @todo add plugin hook that sends old and new annotation information before db access
184
			$obj = $this->get($annotation_id);
185
			$this->events->trigger('update', 'annotation', $obj);
186
		}
187
	
188
		return $result;
189
	}
190
	
191
	/**
192
	 * Returns annotations.  Accepts all elgg_get_entities() options for entity
193
	 * restraints.
194
	 *
195
	 * @see elgg_get_entities
196
	 *
197
	 * @param array $options Array in format:
198
	 *
199
	 * annotation_names              => null|ARR Annotation names
200
	 * annotation_values             => null|ARR Annotation values
201
	 * annotation_ids                => null|ARR annotation ids
202
	 * annotation_case_sensitive     => BOOL Overall Case sensitive
203
	 * annotation_owner_guids        => null|ARR guids for annotation owners
204
	 * annotation_created_time_lower => INT Lower limit for created time.
205
	 * annotation_created_time_upper => INT Upper limit for created time.
206
	 * annotation_calculation        => STR Perform the MySQL function on the annotation values returned.
207
	 *                                   Do not confuse this "annotation_calculation" option with the
208
	 *                                   "calculation" option to elgg_get_entities_from_annotation_calculation().
209
	 *                                   The "annotation_calculation" option causes this function to
210
	 *                                   return the result of performing a mathematical calculation on
211
	 *                                   all annotations that match the query instead of \ElggAnnotation
212
	 *                                   objects.
213
	 *                                   See the docs for elgg_get_entities_from_annotation_calculation()
214
	 *                                   for the proper use of the "calculation" option.
215
	 *
216
	 *
217
	 * @return \ElggAnnotation[]|mixed
218
	 */
219 View Code Duplication
	function find(array $options = []) {
220
221
		// support shortcut of 'count' => true for 'annotation_calculation' => 'count'
222
		if (isset($options['count']) && $options['count']) {
223
			$options['annotation_calculation'] = 'count';
224
			unset($options['count']);
225
		}
226
		
227
		$options['metastring_type'] = 'annotations';
228
		return _elgg_get_metastring_based_objects($options);
229
	}
230
	
231
	/**
232
	 * Deletes annotations based on $options.
233
	 *
234
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
235
	 *          This requires at least one constraint: annotation_owner_guid(s),
236
	 *          annotation_name(s), annotation_value(s), or guid(s) must be set.
237
	 *
238
	 * @param array $options An options array. {@link elgg_get_annotations()}
239
	 * @return bool|null true on success, false on failure, null if no annotations to delete.
240
	 */
241 1 View Code Duplication
	function deleteAll(array $options) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242 1
		if (!_elgg_is_valid_options_for_batch_operation($options, 'annotation')) {
243
			return false;
244
		}
245
	
246 1
		$options['metastring_type'] = 'annotations';
247 1
		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
248
	}
249
	
250
	/**
251
	 * Disables annotations based on $options.
252
	 *
253
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
254
	 *
255
	 * @param array $options An options array. {@link elgg_get_annotations()}
256
	 * @return bool|null true on success, false on failure, null if no annotations disabled.
257
	 */
258 1 View Code Duplication
	function disableAll(array $options) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259 1
		if (!_elgg_is_valid_options_for_batch_operation($options, 'annotation')) {
260
			return false;
261
		}
262
		
263
		// if we can see hidden (disabled) we need to use the offset
264
		// otherwise we risk an infinite loop if there are more than 50
265 1
		$inc_offset = access_get_show_hidden_status();
266
	
267 1
		$options['metastring_type'] = 'annotations';
268 1
		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
269
	}
270
	
271
	/**
272
	 * Enables annotations based on $options.
273
	 *
274
	 * @warning Unlike elgg_get_annotations() this will not accept an empty options array!
275
	 *
276
	 * @warning In order to enable annotations, you must first use
277
	 * {@link access_show_hidden_entities()}.
278
	 *
279
	 * @param array $options An options array. {@link elgg_get_annotations()}
280
	 * @return bool|null true on success, false on failure, null if no metadata enabled.
281
	 */
282
	function enableAll(array $options) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
283
		if (!$options || !is_array($options)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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...
284
			return false;
285
		}
286
	
287
		$options['metastring_type'] = 'annotations';
288
		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
289
	}
290
	
291
	/**
292
	 * Returns entities based upon annotations.  Also accepts all options available
293
	 * to elgg_get_entities() and elgg_get_entities_from_metadata().
294
	 *
295
	 * @see elgg_get_entities
296
	 * @see elgg_get_entities_from_metadata
297
	 *
298
	 * @param array $options Array in format:
299
	 *
300
	 * 	annotation_names => null|ARR annotations names
301
	 *
302
	 * 	annotation_values => null|ARR annotations values
303
	 *
304
	 * 	annotation_name_value_pairs => null|ARR (name = 'name', value => 'value',
305
	 * 	'operator' => '=', 'case_sensitive' => true) entries.
306
	 * 	Currently if multiple values are sent via an array (value => array('value1', 'value2')
307
	 * 	the pair's operator will be forced to "IN".
308
	 *
309
	 * 	annotation_name_value_pairs_operator => null|STR The operator to use for combining
310
	 *  (name = value) OPERATOR (name = value); default AND
311
	 *
312
	 * 	annotation_case_sensitive => BOOL Overall Case sensitive
313
	 *
314
	 *  order_by_annotation => null|ARR (array('name' => 'annotation_text1', 'direction' => ASC|DESC,
315
	 *  'as' => text|integer),
316
	 *
317
	 *  Also supports array('name' => 'annotation_text1')
318
	 *
319
	 *  annotation_owner_guids => null|ARR guids for annotaiton owners
320
	 *
321
	 * @return mixed If count, int. If not count, array. false on errors.
322
	 */
323
	function getEntities(array $options = []) {
324
		$defaults = [
325
			'annotation_names'                      => ELGG_ENTITIES_ANY_VALUE,
326
			'annotation_values'                     => ELGG_ENTITIES_ANY_VALUE,
327
			'annotation_name_value_pairs'           => ELGG_ENTITIES_ANY_VALUE,
328
329
			'annotation_name_value_pairs_operator'  => 'AND',
330
			'annotation_case_sensitive'             => true,
331
			'order_by_annotation'                   => [],
332
333
			'annotation_created_time_lower'         => ELGG_ENTITIES_ANY_VALUE,
334
			'annotation_created_time_upper'         => ELGG_ENTITIES_ANY_VALUE,
335
			'annotation_owner_guids'                => ELGG_ENTITIES_ANY_VALUE,
336
		];
337
	
338
		$options = array_merge($defaults, $options);
339
	
340
		$singulars = ['annotation_name', 'annotation_value', 'annotation_name_value_pair', 'annotation_owner_guid'];
341
	
342
		$options = _elgg_normalize_plural_options_array($options, $singulars);
343
		$options = _elgg_entities_get_metastrings_options('annotation', $options);
344
	
345
		if (!$options) {
346
			return false;
347
		}
348
		
349
		$time_wheres = _elgg_get_entity_time_where_sql('n_table', $options['annotation_created_time_upper'],
350
			$options['annotation_created_time_lower']);
351
352
		if ($time_wheres) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $time_wheres of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
353
			$options['wheres'][] = $time_wheres;
354
		}
355
	
356
		return elgg_get_entities_from_metadata($options);
357
	}
358
	
359
	/**
360
	 * Get entities ordered by a mathematical calculation on annotation values
361
	 *
362
	 * @tip Note that this function uses { @link elgg_get_annotations() } to return a list of entities ordered by a mathematical
363
	 * calculation on annotation values, and { @link elgg_get_entities_from_annotations() } to return a count of entities
364
	 * if $options['count'] is set to a truthy value
365
	 *
366
	 * @param array $options An options array:
367
	 * 	'calculation'            => The calculation to use. Must be a valid MySQL function.
368
	 *                              Defaults to sum.  Result selected as 'annotation_calculation'.
369
	 *                              Don't confuse this "calculation" option with the
370
	 *                              "annotation_calculation" option to elgg_get_annotations().
371
	 *                              This "calculation" option is applied to each entity's set of
372
	 *                              annotations and is selected as annotation_calculation for that row.
373
	 *                              See the docs for elgg_get_annotations() for proper use of the
374
	 *                              "annotation_calculation" option.
375
	 *	'order_by'               => The order for the sorting. Defaults to 'annotation_calculation desc'.
376
	 *	'annotation_names'       => The names of annotations on the entity.
377
	 *	'annotation_values'	     => The values of annotations on the entity.
378
	 *
379
	 * 	'metadata_names'         => The name of metadata on the entity.
380
	 * 	'metadata_values'        => The value of metadata on the entitiy.
381
	 * 	'callback'               => Callback function to pass each row through.
382
	 *                              @tip This function is different from other ege* functions,
383
	 *                              as it uses a metastring-based getter function { @link elgg_get_annotations() },
384
	 *                              therefore the callback function should be a derivative of { @link entity_row_to_elggstar() }
385
	 *                              and not of { @link row_to_annotation() }
386
	 *
387
	 * @return \ElggEntity[]|int An array or a count of entities
388
	 * @see elgg_get_annotations()
389
	 * @see elgg_get_entities_from_annotations()
390
	 */
391
	function getEntitiesFromCalculation($options) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
392
		
393
		if (isset($options['count']) && $options['count']) {
394
			return elgg_get_entities_from_annotations($options);
395
		}
396
		
397
		$db_prefix = $this->db->prefix;
398
		$defaults = [
399
			'calculation' => 'sum',
400
			'order_by' => 'annotation_calculation desc'
401
		];
402
	
403
		$options = array_merge($defaults, $options);
404
	
405
		$function = sanitize_string(elgg_extract('calculation', $options, 'sum', false));
0 ignored issues
show
Deprecated Code introduced by
The function sanitize_string() has been deprecated with message: Use query parameters where possible

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
406
	
407
		// you must cast this as an int or it sorts wrong.
408
		$options['selects'][] = 'e.*';
409
		$options['selects'][] = "$function(CAST(n_table.value AS signed)) AS annotation_calculation";
410
	
411
		// don't need access control because it's taken care of by elgg_get_annotations.
412
		$options['group_by'] = 'n_table.entity_guid';
413
414
		// do not default to a callback function used in elgg_get_annotation()
415
		if (!isset($options['callback'])) {
416
			$options['callback'] = 'entity_row_to_elggstar';
417
		}
418
419
		return elgg_get_annotations($options);
420
	}
421
	
422
	/**
423
	 * Check to see if a user has already created an annotation on an object
424
	 *
425
	 * @param int    $entity_guid     Entity guid
426
	 * @param string $annotation_type Type of annotation
427
	 * @param int    $owner_guid      Defaults to logged in user.
428
	 *
429
	 * @return bool
430
	 */
431
	function exists($entity_guid, $annotation_type, $owner_guid = null) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
432
	
433
		if (!$owner_guid && !($owner_guid = $this->session->getLoggedInUserGuid())) {
434
			return false;
435
		}
436
		
437
		$sql = "SELECT id FROM {$this->db->prefix}annotations
438
				WHERE owner_guid = :owner_guid
439
				AND entity_guid = :entity_guid
440
				AND name = :annotation_type";
441
442
		$result = $this->db->getDataRow($sql, null, [
443
			':owner_guid' => (int) $owner_guid,
444
			':entity_guid' => (int) $entity_guid,
445
			':annotation_type' => $annotation_type,
446
		]);
447
	
448
		return (bool) $result;
449
	}
450
}
451