Completed
Push — master ( 79ec0c...eef89d )
by Jeroen
23:16
created
engine/classes/Elgg/Database/MetadataTable.php 1 patch
Indentation   +751 added lines, -751 removed lines patch added patch discarded remove patch
@@ -19,203 +19,203 @@  discard block
 block discarded – undo
19 19
  */
20 20
 class MetadataTable {
21 21
 
22
-	use \Elgg\TimeUsing;
22
+    use \Elgg\TimeUsing;
23 23
 
24
-	/** @var array */
25
-	protected $independents = [];
24
+    /** @var array */
25
+    protected $independents = [];
26 26
 	
27
-	/** @var Cache */
28
-	protected $cache;
27
+    /** @var Cache */
28
+    protected $cache;
29 29
 	
30
-	/** @var Database */
31
-	protected $db;
30
+    /** @var Database */
31
+    protected $db;
32 32
 	
33
-	/** @var EntityTable */
34
-	protected $entityTable;
33
+    /** @var EntityTable */
34
+    protected $entityTable;
35 35
 		
36
-	/** @var Events */
37
-	protected $events;
38
-	
39
-	/** @var Session */
40
-	protected $session;
41
-	
42
-	/** @var string */
43
-	protected $table;
44
-	const MYSQL_TEXT_BYTE_LIMIT = 65535;
45
-	/**
46
-	 * Constructor
47
-	 *
48
-	 * @param Cache       $cache       A cache for this table
49
-	 * @param Database    $db          The Elgg database
50
-	 * @param EntityTable $entityTable The entities table
51
-	 * @param Events      $events      The events registry
52
-	 * @param Session     $session     The session
53
-	 */
54
-	public function __construct(
55
-			Cache $cache,
56
-			Database $db,
57
-			EntityTable $entityTable,
58
-			Events $events,
59
-			Session $session) {
60
-		$this->cache = $cache;
61
-		$this->db = $db;
62
-		$this->entityTable = $entityTable;
63
-		$this->events = $events;
64
-		$this->session = $session;
65
-		$this->table = $this->db->prefix . "metadata";
66
-	}
36
+    /** @var Events */
37
+    protected $events;
38
+	
39
+    /** @var Session */
40
+    protected $session;
41
+	
42
+    /** @var string */
43
+    protected $table;
44
+    const MYSQL_TEXT_BYTE_LIMIT = 65535;
45
+    /**
46
+     * Constructor
47
+     *
48
+     * @param Cache       $cache       A cache for this table
49
+     * @param Database    $db          The Elgg database
50
+     * @param EntityTable $entityTable The entities table
51
+     * @param Events      $events      The events registry
52
+     * @param Session     $session     The session
53
+     */
54
+    public function __construct(
55
+            Cache $cache,
56
+            Database $db,
57
+            EntityTable $entityTable,
58
+            Events $events,
59
+            Session $session) {
60
+        $this->cache = $cache;
61
+        $this->db = $db;
62
+        $this->entityTable = $entityTable;
63
+        $this->events = $events;
64
+        $this->session = $session;
65
+        $this->table = $this->db->prefix . "metadata";
66
+    }
67 67
 
68
-	/**
69
-	 * Get a specific metadata object by its id.
70
-	 * If you want multiple metadata objects, use
71
-	 * {@link elgg_get_metadata()}.
72
-	 *
73
-	 * @param int $id The id of the metadata object being retrieved.
74
-	 *
75
-	 * @return \ElggMetadata|false  false if not found
76
-	 */
77
-	function get($id) {
78
-		return _elgg_get_metastring_based_object_from_id($id, 'metadata');
79
-	}
80
-	
81
-	/**
82
-	 * Deletes metadata using its ID.
83
-	 *
84
-	 * @param int $id The metadata ID to delete.
85
-	 * @return bool
86
-	 */
87
-	function delete($id) {
88
-		$metadata = $this->get($id);
68
+    /**
69
+     * Get a specific metadata object by its id.
70
+     * If you want multiple metadata objects, use
71
+     * {@link elgg_get_metadata()}.
72
+     *
73
+     * @param int $id The id of the metadata object being retrieved.
74
+     *
75
+     * @return \ElggMetadata|false  false if not found
76
+     */
77
+    function get($id) {
78
+        return _elgg_get_metastring_based_object_from_id($id, 'metadata');
79
+    }
80
+	
81
+    /**
82
+     * Deletes metadata using its ID.
83
+     *
84
+     * @param int $id The metadata ID to delete.
85
+     * @return bool
86
+     */
87
+    function delete($id) {
88
+        $metadata = $this->get($id);
89 89
 
90
-		return $metadata ? $metadata->delete() : false;
91
-	}
92
-	
93
-	/**
94
-	 * Create a new metadata object, or update an existing one.
95
-	 *
96
-	 * Metadata can be an array by setting allow_multiple to true, but it is an
97
-	 * indexed array with no control over the indexing.
98
-	 *
99
-	 * @param int    $entity_guid    The entity to attach the metadata to
100
-	 * @param string $name           Name of the metadata
101
-	 * @param string $value          Value of the metadata
102
-	 * @param string $value_type     'text', 'integer', or '' for automatic detection
103
-	 * @param int    $owner_guid     GUID of entity that owns the metadata. Default is logged in user.
104
-	 * @param int    $access_id      Default is ACCESS_PRIVATE
105
-	 * @param bool   $allow_multiple Allow multiple values for one key. Default is false
106
-	 *
107
-	 * @return int|false id of metadata or false if failure
108
-	 */
109
-	function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0,
110
-			$access_id = ACCESS_PRIVATE, $allow_multiple = false) {
90
+        return $metadata ? $metadata->delete() : false;
91
+    }
92
+	
93
+    /**
94
+     * Create a new metadata object, or update an existing one.
95
+     *
96
+     * Metadata can be an array by setting allow_multiple to true, but it is an
97
+     * indexed array with no control over the indexing.
98
+     *
99
+     * @param int    $entity_guid    The entity to attach the metadata to
100
+     * @param string $name           Name of the metadata
101
+     * @param string $value          Value of the metadata
102
+     * @param string $value_type     'text', 'integer', or '' for automatic detection
103
+     * @param int    $owner_guid     GUID of entity that owns the metadata. Default is logged in user.
104
+     * @param int    $access_id      Default is ACCESS_PRIVATE
105
+     * @param bool   $allow_multiple Allow multiple values for one key. Default is false
106
+     *
107
+     * @return int|false id of metadata or false if failure
108
+     */
109
+    function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0,
110
+            $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
111 111
 
112
-		$entity_guid = (int) $entity_guid;
113
-		$value_type = \ElggExtender::detectValueType($value, trim($value_type));
114
-		$owner_guid = (int) $owner_guid;
115
-		$allow_multiple = (boolean) $allow_multiple;
112
+        $entity_guid = (int) $entity_guid;
113
+        $value_type = \ElggExtender::detectValueType($value, trim($value_type));
114
+        $owner_guid = (int) $owner_guid;
115
+        $allow_multiple = (boolean) $allow_multiple;
116 116
 
117
-		if (!isset($value)) {
118
-			return false;
119
-		}
120
-	
121
-		if ($owner_guid == 0) {
122
-			$owner_guid = $this->session->getLoggedInUserGuid();
123
-		}
124
-	
125
-		$access_id = (int) $access_id;
126
-		if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) {
127
-			elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
128
-		}
129
-		$query = "SELECT * FROM {$this->table}
117
+        if (!isset($value)) {
118
+            return false;
119
+        }
120
+	
121
+        if ($owner_guid == 0) {
122
+            $owner_guid = $this->session->getLoggedInUserGuid();
123
+        }
124
+	
125
+        $access_id = (int) $access_id;
126
+        if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) {
127
+            elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
128
+        }
129
+        $query = "SELECT * FROM {$this->table}
130 130
 			WHERE entity_guid = :entity_guid and name = :name LIMIT 1";
131 131
 
132
-		$existing = $this->db->getDataRow($query, null, [
133
-			':entity_guid' => $entity_guid,
134
-			':name' => $name,
135
-		]);
136
-		if ($existing && !$allow_multiple) {
137
-			$id = (int) $existing->id;
138
-			$result = $this->update($id, $name, $value, $value_type, $owner_guid, $access_id);
139
-	
140
-			if (!$result) {
141
-				return false;
142
-			}
143
-		} else {
144
-			// Support boolean types
145
-			if (is_bool($value)) {
146
-				$value = (int) $value;
147
-			}
132
+        $existing = $this->db->getDataRow($query, null, [
133
+            ':entity_guid' => $entity_guid,
134
+            ':name' => $name,
135
+        ]);
136
+        if ($existing && !$allow_multiple) {
137
+            $id = (int) $existing->id;
138
+            $result = $this->update($id, $name, $value, $value_type, $owner_guid, $access_id);
139
+	
140
+            if (!$result) {
141
+                return false;
142
+            }
143
+        } else {
144
+            // Support boolean types
145
+            if (is_bool($value)) {
146
+                $value = (int) $value;
147
+            }
148 148
 		
149
-			// If ok then add it
150
-			$query = "INSERT INTO {$this->table}
149
+            // If ok then add it
150
+            $query = "INSERT INTO {$this->table}
151 151
 				(entity_guid, name, value, value_type, owner_guid, time_created, access_id)
152 152
 				VALUES (:entity_guid, :name, :value, :value_type, :owner_guid, :time_created, :access_id)";
153 153
 			
154
-			$id = $this->db->insertData($query, [
155
-				':entity_guid' => $entity_guid,
156
-				':name' => $name,
157
-				':value' => $value,
158
-				':value_type' => $value_type,
159
-				':owner_guid' => (int) $owner_guid,
160
-				':time_created' => $this->getCurrentTime()->getTimestamp(),
161
-				':access_id' => $access_id,
162
-			]);
154
+            $id = $this->db->insertData($query, [
155
+                ':entity_guid' => $entity_guid,
156
+                ':name' => $name,
157
+                ':value' => $value,
158
+                ':value_type' => $value_type,
159
+                ':owner_guid' => (int) $owner_guid,
160
+                ':time_created' => $this->getCurrentTime()->getTimestamp(),
161
+                ':access_id' => $access_id,
162
+            ]);
163 163
 			
164
-			if ($id !== false) {
165
-				$obj = $this->get($id);
166
-				if ($this->events->trigger('create', 'metadata', $obj)) {
167
-					$this->cache->clear($entity_guid);
168
-	
169
-					return $id;
170
-				} else {
171
-					$this->delete($id);
172
-				}
173
-			}
174
-		}
175
-	
176
-		return $id;
177
-	}
178
-	
179
-	/**
180
-	 * Update a specific piece of metadata.
181
-	 *
182
-	 * @param int    $id         ID of the metadata to update
183
-	 * @param string $name       Metadata name
184
-	 * @param string $value      Metadata value
185
-	 * @param string $value_type Value type
186
-	 * @param int    $owner_guid Owner guid
187
-	 * @param int    $access_id  Access ID
188
-	 *
189
-	 * @return bool
190
-	 */
191
-	function update($id, $name, $value, $value_type, $owner_guid, $access_id) {
192
-		$id = (int) $id;
193
-	
194
-		if (!$md = $this->get($id)) {
195
-			return false;
196
-		}
197
-		if (!$md->canEdit()) {
198
-			return false;
199
-		}
200
-	
201
-		$value_type = \ElggExtender::detectValueType($value, trim($value_type));
202
-	
203
-		$owner_guid = (int) $owner_guid;
204
-		if ($owner_guid == 0) {
205
-			$owner_guid = $this->session->getLoggedInUserGuid();
206
-		}
207
-	
208
-		$access_id = (int) $access_id;
209
-	
210
-		// Support boolean types (as integers)
211
-		if (is_bool($value)) {
212
-			$value = (int) $value;
213
-		}
214
-		if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) {
215
-			elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
216
-		}
217
-		// If ok then add it
218
-		$query = "UPDATE {$this->table}
164
+            if ($id !== false) {
165
+                $obj = $this->get($id);
166
+                if ($this->events->trigger('create', 'metadata', $obj)) {
167
+                    $this->cache->clear($entity_guid);
168
+	
169
+                    return $id;
170
+                } else {
171
+                    $this->delete($id);
172
+                }
173
+            }
174
+        }
175
+	
176
+        return $id;
177
+    }
178
+	
179
+    /**
180
+     * Update a specific piece of metadata.
181
+     *
182
+     * @param int    $id         ID of the metadata to update
183
+     * @param string $name       Metadata name
184
+     * @param string $value      Metadata value
185
+     * @param string $value_type Value type
186
+     * @param int    $owner_guid Owner guid
187
+     * @param int    $access_id  Access ID
188
+     *
189
+     * @return bool
190
+     */
191
+    function update($id, $name, $value, $value_type, $owner_guid, $access_id) {
192
+        $id = (int) $id;
193
+	
194
+        if (!$md = $this->get($id)) {
195
+            return false;
196
+        }
197
+        if (!$md->canEdit()) {
198
+            return false;
199
+        }
200
+	
201
+        $value_type = \ElggExtender::detectValueType($value, trim($value_type));
202
+	
203
+        $owner_guid = (int) $owner_guid;
204
+        if ($owner_guid == 0) {
205
+            $owner_guid = $this->session->getLoggedInUserGuid();
206
+        }
207
+	
208
+        $access_id = (int) $access_id;
209
+	
210
+        // Support boolean types (as integers)
211
+        if (is_bool($value)) {
212
+            $value = (int) $value;
213
+        }
214
+        if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) {
215
+            elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
216
+        }
217
+        // If ok then add it
218
+        $query = "UPDATE {$this->table}
219 219
 			SET name = :name,
220 220
 			    value = :value,
221 221
 				value_type = :value_type,
@@ -223,588 +223,588 @@  discard block
 block discarded – undo
223 223
 			    owner_guid = :owner_guid
224 224
 			WHERE id = :id";
225 225
 		
226
-		$result = $this->db->updateData($query, false, [
227
-			':name' => $name,
228
-			':value' => $value,
229
-			':value_type' => $value_type,
230
-			':access_id' => $access_id,
231
-			':owner_guid' => $owner_guid,
232
-			':id' => $id,
233
-		]);
226
+        $result = $this->db->updateData($query, false, [
227
+            ':name' => $name,
228
+            ':value' => $value,
229
+            ':value_type' => $value_type,
230
+            ':access_id' => $access_id,
231
+            ':owner_guid' => $owner_guid,
232
+            ':id' => $id,
233
+        ]);
234 234
 		
235
-		if ($result !== false) {
236
-			$this->cache->clear($md->entity_guid);
237
-	
238
-			// @todo this event tells you the metadata has been updated, but does not
239
-			// let you do anything about it. What is needed is a plugin hook before
240
-			// the update that passes old and new values.
241
-			$obj = $this->get($id);
242
-			$this->events->trigger('update', 'metadata', $obj);
243
-		}
244
-	
245
-		return $result;
246
-	}
247
-	
248
-	/**
249
-	 * This function creates metadata from an associative array of "key => value" pairs.
250
-	 *
251
-	 * To achieve an array for a single key, pass in the same key multiple times with
252
-	 * allow_multiple set to true. This creates an indexed array. It does not support
253
-	 * associative arrays and there is no guarantee on the ordering in the array.
254
-	 *
255
-	 * @param int    $entity_guid     The entity to attach the metadata to
256
-	 * @param array  $name_and_values Associative array - a value can be a string, number, bool
257
-	 * @param string $value_type      'text', 'integer', or '' for automatic detection
258
-	 * @param int    $owner_guid      GUID of entity that owns the metadata
259
-	 * @param int    $access_id       Default is ACCESS_PRIVATE
260
-	 * @param bool   $allow_multiple  Allow multiple values for one key. Default is false
261
-	 *
262
-	 * @return bool
263
-	 */
264
-	function createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid,
265
-			$access_id = ACCESS_PRIVATE, $allow_multiple = false) {
266
-	
267
-		foreach ($name_and_values as $k => $v) {
268
-			$result = $this->create($entity_guid, $k, $v, $value_type, $owner_guid,
269
-				$access_id, $allow_multiple);
270
-			if (!$result) {
271
-				return false;
272
-			}
273
-		}
274
-		return true;
275
-	}
276
-	
277
-	/**
278
-	 * Returns metadata.  Accepts all elgg_get_entities() options for entity
279
-	 * restraints.
280
-	 *
281
-	 * @see elgg_get_entities
282
-	 *
283
-	 * @warning 1.7's find_metadata() didn't support limits and returned all metadata.
284
-	 *          This function defaults to a limit of 25. There is probably not a reason
285
-	 *          for you to return all metadata unless you're exporting an entity,
286
-	 *          have other restraints in place, or are doing something horribly
287
-	 *          wrong in your code.
288
-	 *
289
-	 * @param array $options Array in format:
290
-	 *
291
-	 * metadata_names               => null|ARR metadata names
292
-	 * metadata_values              => null|ARR metadata values
293
-	 * metadata_ids                 => null|ARR metadata ids
294
-	 * metadata_case_sensitive      => BOOL Overall Case sensitive
295
-	 * metadata_owner_guids         => null|ARR guids for metadata owners
296
-	 * metadata_created_time_lower  => INT Lower limit for created time.
297
-	 * metadata_created_time_upper  => INT Upper limit for created time.
298
-	 * metadata_calculation         => STR Perform the MySQL function on the metadata values returned.
299
-	 *                                   The "metadata_calculation" option causes this function to
300
-	 *                                   return the result of performing a mathematical calculation on
301
-	 *                                   all metadata that match the query instead of returning
302
-	 *                                   \ElggMetadata objects.
303
-	 *
304
-	 * @return \ElggMetadata[]|mixed
305
-	 */
306
-	function getAll(array $options = []) {
307
-	
308
-		// @todo remove support for count shortcut - see #4393
309
-		// support shortcut of 'count' => true for 'metadata_calculation' => 'count'
310
-		if (isset($options['count']) && $options['count']) {
311
-			$options['metadata_calculation'] = 'count';
312
-			unset($options['count']);
313
-		}
314
-	
315
-		$options['metastring_type'] = 'metadata';
316
-		return _elgg_get_metastring_based_objects($options);
317
-	}
318
-	
319
-	/**
320
-	 * Deletes metadata based on $options.
321
-	 *
322
-	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
323
-	 *          This requires at least one constraint: metadata_owner_guid(s),
324
-	 *          metadata_name(s), metadata_value(s), or guid(s) must be set.
325
-	 *
326
-	 * @param array $options An options array. {@link elgg_get_metadata()}
327
-	 * @return bool|null true on success, false on failure, null if no metadata to delete.
328
-	 */
329
-	function deleteAll(array $options) {
330
-		if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
331
-			return false;
332
-		}
333
-		$options['metastring_type'] = 'metadata';
334
-		$result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
335
-	
336
-		// This moved last in case an object's constructor sets metadata. Currently the batch
337
-		// delete process has to create the entity to delete its metadata. See #5214
338
-		$this->cache->invalidateByOptions($options);
339
-	
340
-		return $result;
341
-	}
342
-	
343
-	/**
344
-	 * Disables metadata based on $options.
345
-	 *
346
-	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
347
-	 *
348
-	 * @param array $options An options array. {@link elgg_get_metadata()}
349
-	 * @return bool|null true on success, false on failure, null if no metadata disabled.
350
-	 */
351
-	function disableAll(array $options) {
352
-		if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
353
-			return false;
354
-		}
355
-	
356
-		$this->cache->invalidateByOptions($options);
357
-	
358
-		// if we can see hidden (disabled) we need to use the offset
359
-		// otherwise we risk an infinite loop if there are more than 50
360
-		$inc_offset = access_get_show_hidden_status();
361
-	
362
-		$options['metastring_type'] = 'metadata';
363
-		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
364
-	}
365
-	
366
-	/**
367
-	 * Enables metadata based on $options.
368
-	 *
369
-	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
370
-	 *
371
-	 * @warning In order to enable metadata, you must first use
372
-	 * {@link access_show_hidden_entities()}.
373
-	 *
374
-	 * @param array $options An options array. {@link elgg_get_metadata()}
375
-	 * @return bool|null true on success, false on failure, null if no metadata enabled.
376
-	 */
377
-	function enableAll(array $options) {
378
-		if (!$options || !is_array($options)) {
379
-			return false;
380
-		}
381
-	
382
-		$this->cache->invalidateByOptions($options);
383
-	
384
-		$options['metastring_type'] = 'metadata';
385
-		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
386
-	}
387
-	
388
-	/**
389
-	 * Returns entities based upon metadata.  Also accepts all
390
-	 * options available to elgg_get_entities().  Supports
391
-	 * the singular option shortcut.
392
-	 *
393
-	 * @note Using metadata_names and metadata_values results in a
394
-	 * "names IN (...) AND values IN (...)" clause.  This is subtly
395
-	 * differently than default multiple metadata_name_value_pairs, which use
396
-	 * "(name = value) AND (name = value)" clauses.
397
-	 *
398
-	 * When in doubt, use name_value_pairs.
399
-	 *
400
-	 * To ask for entities that do not have a metadata value, use a custom
401
-	 * where clause like this:
402
-	 *
403
-	 * 	$options['wheres'][] = "NOT EXISTS (
404
-	 *			SELECT 1 FROM {$dbprefix}metadata md
405
-	 *			WHERE md.entity_guid = e.guid
406
-	 *				AND md.name = $name
407
-	 *				AND md.value = $value)";
408
-	 *
409
-	 * Note the metadata name and value has been denormalized in the above example.
410
-	 *
411
-	 * @see elgg_get_entities
412
-	 *
413
-	 * @param array $options Array in format:
414
-	 *
415
-	 * 	metadata_names => null|ARR metadata names
416
-	 *
417
-	 * 	metadata_values => null|ARR metadata values
418
-	 *
419
-	 * 	metadata_name_value_pairs => null|ARR (
420
-	 *                                         name => 'name',
421
-	 *                                         value => 'value',
422
-	 *                                         'operand' => '=',
423
-	 *                                         'case_sensitive' => true
424
-	 *                                        )
425
-	 *                               Currently if multiple values are sent via
426
-	 *                               an array (value => array('value1', 'value2')
427
-	 *                               the pair's operand will be forced to "IN".
428
-	 *                               If passing "IN" as the operand and a string as the value,
429
-	 *                               the value must be a properly quoted and escaped string.
430
-	 *
431
-	 * 	metadata_name_value_pairs_operator => null|STR The operator to use for combining
432
-	 *                                        (name = value) OPERATOR (name = value); default AND
433
-	 *
434
-	 * 	metadata_case_sensitive => BOOL Overall Case sensitive
435
-	 *
436
-	 *  order_by_metadata => null|ARR array(
437
-	 *                                      'name' => 'metadata_text1',
438
-	 *                                      'direction' => ASC|DESC,
439
-	 *                                      'as' => text|integer
440
-	 *                                     )
441
-	 *                                Also supports array('name' => 'metadata_text1')
442
-	 *
443
-	 *  metadata_owner_guids => null|ARR guids for metadata owners
444
-	 *
445
-	 * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors.
446
-	 */
447
-	function getEntities(array $options = []) {
448
-		$defaults = [
449
-			'metadata_names'                     => ELGG_ENTITIES_ANY_VALUE,
450
-			'metadata_values'                    => ELGG_ENTITIES_ANY_VALUE,
451
-			'metadata_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE,
452
-	
453
-			'metadata_name_value_pairs_operator' => 'AND',
454
-			'metadata_case_sensitive'            => true,
455
-			'order_by_metadata'                  => [],
456
-	
457
-			'metadata_owner_guids'               => ELGG_ENTITIES_ANY_VALUE,
458
-		];
459
-	
460
-		$options = array_merge($defaults, $options);
461
-	
462
-		$singulars = ['metadata_name', 'metadata_value',
463
-			'metadata_name_value_pair', 'metadata_owner_guid'];
464
-	
465
-		$options = _elgg_normalize_plural_options_array($options, $singulars);
466
-	
467
-		if (!$options = _elgg_entities_get_metastrings_options('metadata', $options)) {
468
-			return false;
469
-		}
470
-	
471
-		return $this->entityTable->getEntities($options);
472
-	}
473
-	
474
-	/**
475
-	 * Returns metadata name and value SQL where for entities.
476
-	 * NB: $names and $values are not paired. Use $pairs for this.
477
-	 * Pairs default to '=' operand.
478
-	 *
479
-	 * This function is reused for annotations because the tables are
480
-	 * exactly the same.
481
-	 *
482
-	 * @param string     $e_table           Entities table name
483
-	 * @param string     $n_table           Normalized metastrings table name (Where entities,
484
-	 *                                    values, and names are joined. annotations / metadata)
485
-	 * @param array|null $names             Array of names
486
-	 * @param array|null $values            Array of values
487
-	 * @param array|null $pairs             Array of names / values / operands
488
-	 * @param string     $pair_operator     ("AND" or "OR") Operator to use to join the where clauses for pairs
489
-	 * @param bool       $case_sensitive    Case sensitive metadata names?
490
-	 * @param array|null $order_by_metadata Array of names / direction
491
-	 * @param array|null $owner_guids       Array of owner GUIDs
492
-	 *
493
-	 * @return false|array False on fail, array('joins', 'wheres')
494
-	 * @access private
495
-	 */
496
-	function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null,
497
-			$pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null,
498
-			$owner_guids = null) {
499
-		// short circuit if nothing requested
500
-		// 0 is a valid (if not ill-conceived) metadata name.
501
-		// 0 is also a valid metadata value for false, null, or 0
502
-		// 0 is also a valid(ish) owner_guid
503
-		if ((!$names && $names !== 0)
504
-			&& (!$values && $values !== 0)
505
-			&& (!$pairs && $pairs !== 0)
506
-			&& (!$owner_guids && $owner_guids !== 0)
507
-			&& !$order_by_metadata) {
508
-			return '';
509
-		}
510
-	
511
-		// join counter for incremental joins.
512
-		$i = 1;
513
-	
514
-		// binary forces byte-to-byte comparision of strings, making
515
-		// it case- and diacritical-mark- sensitive.
516
-		// only supported on values.
517
-		$binary = ($case_sensitive) ? ' BINARY ' : '';
518
-	
519
-		$access = _elgg_get_access_where_sql([
520
-			'table_alias' => 'n_table',
521
-			'guid_column' => 'entity_guid',
522
-		]);
523
-	
524
-		$return =  [
525
-			'joins' =>  [],
526
-			'wheres' => [],
527
-			'orders' => []
528
-		];
529
-	
530
-		$return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table on
235
+        if ($result !== false) {
236
+            $this->cache->clear($md->entity_guid);
237
+	
238
+            // @todo this event tells you the metadata has been updated, but does not
239
+            // let you do anything about it. What is needed is a plugin hook before
240
+            // the update that passes old and new values.
241
+            $obj = $this->get($id);
242
+            $this->events->trigger('update', 'metadata', $obj);
243
+        }
244
+	
245
+        return $result;
246
+    }
247
+	
248
+    /**
249
+     * This function creates metadata from an associative array of "key => value" pairs.
250
+     *
251
+     * To achieve an array for a single key, pass in the same key multiple times with
252
+     * allow_multiple set to true. This creates an indexed array. It does not support
253
+     * associative arrays and there is no guarantee on the ordering in the array.
254
+     *
255
+     * @param int    $entity_guid     The entity to attach the metadata to
256
+     * @param array  $name_and_values Associative array - a value can be a string, number, bool
257
+     * @param string $value_type      'text', 'integer', or '' for automatic detection
258
+     * @param int    $owner_guid      GUID of entity that owns the metadata
259
+     * @param int    $access_id       Default is ACCESS_PRIVATE
260
+     * @param bool   $allow_multiple  Allow multiple values for one key. Default is false
261
+     *
262
+     * @return bool
263
+     */
264
+    function createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid,
265
+            $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
266
+	
267
+        foreach ($name_and_values as $k => $v) {
268
+            $result = $this->create($entity_guid, $k, $v, $value_type, $owner_guid,
269
+                $access_id, $allow_multiple);
270
+            if (!$result) {
271
+                return false;
272
+            }
273
+        }
274
+        return true;
275
+    }
276
+	
277
+    /**
278
+     * Returns metadata.  Accepts all elgg_get_entities() options for entity
279
+     * restraints.
280
+     *
281
+     * @see elgg_get_entities
282
+     *
283
+     * @warning 1.7's find_metadata() didn't support limits and returned all metadata.
284
+     *          This function defaults to a limit of 25. There is probably not a reason
285
+     *          for you to return all metadata unless you're exporting an entity,
286
+     *          have other restraints in place, or are doing something horribly
287
+     *          wrong in your code.
288
+     *
289
+     * @param array $options Array in format:
290
+     *
291
+     * metadata_names               => null|ARR metadata names
292
+     * metadata_values              => null|ARR metadata values
293
+     * metadata_ids                 => null|ARR metadata ids
294
+     * metadata_case_sensitive      => BOOL Overall Case sensitive
295
+     * metadata_owner_guids         => null|ARR guids for metadata owners
296
+     * metadata_created_time_lower  => INT Lower limit for created time.
297
+     * metadata_created_time_upper  => INT Upper limit for created time.
298
+     * metadata_calculation         => STR Perform the MySQL function on the metadata values returned.
299
+     *                                   The "metadata_calculation" option causes this function to
300
+     *                                   return the result of performing a mathematical calculation on
301
+     *                                   all metadata that match the query instead of returning
302
+     *                                   \ElggMetadata objects.
303
+     *
304
+     * @return \ElggMetadata[]|mixed
305
+     */
306
+    function getAll(array $options = []) {
307
+	
308
+        // @todo remove support for count shortcut - see #4393
309
+        // support shortcut of 'count' => true for 'metadata_calculation' => 'count'
310
+        if (isset($options['count']) && $options['count']) {
311
+            $options['metadata_calculation'] = 'count';
312
+            unset($options['count']);
313
+        }
314
+	
315
+        $options['metastring_type'] = 'metadata';
316
+        return _elgg_get_metastring_based_objects($options);
317
+    }
318
+	
319
+    /**
320
+     * Deletes metadata based on $options.
321
+     *
322
+     * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
323
+     *          This requires at least one constraint: metadata_owner_guid(s),
324
+     *          metadata_name(s), metadata_value(s), or guid(s) must be set.
325
+     *
326
+     * @param array $options An options array. {@link elgg_get_metadata()}
327
+     * @return bool|null true on success, false on failure, null if no metadata to delete.
328
+     */
329
+    function deleteAll(array $options) {
330
+        if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
331
+            return false;
332
+        }
333
+        $options['metastring_type'] = 'metadata';
334
+        $result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
335
+	
336
+        // This moved last in case an object's constructor sets metadata. Currently the batch
337
+        // delete process has to create the entity to delete its metadata. See #5214
338
+        $this->cache->invalidateByOptions($options);
339
+	
340
+        return $result;
341
+    }
342
+	
343
+    /**
344
+     * Disables metadata based on $options.
345
+     *
346
+     * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
347
+     *
348
+     * @param array $options An options array. {@link elgg_get_metadata()}
349
+     * @return bool|null true on success, false on failure, null if no metadata disabled.
350
+     */
351
+    function disableAll(array $options) {
352
+        if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
353
+            return false;
354
+        }
355
+	
356
+        $this->cache->invalidateByOptions($options);
357
+	
358
+        // if we can see hidden (disabled) we need to use the offset
359
+        // otherwise we risk an infinite loop if there are more than 50
360
+        $inc_offset = access_get_show_hidden_status();
361
+	
362
+        $options['metastring_type'] = 'metadata';
363
+        return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
364
+    }
365
+	
366
+    /**
367
+     * Enables metadata based on $options.
368
+     *
369
+     * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
370
+     *
371
+     * @warning In order to enable metadata, you must first use
372
+     * {@link access_show_hidden_entities()}.
373
+     *
374
+     * @param array $options An options array. {@link elgg_get_metadata()}
375
+     * @return bool|null true on success, false on failure, null if no metadata enabled.
376
+     */
377
+    function enableAll(array $options) {
378
+        if (!$options || !is_array($options)) {
379
+            return false;
380
+        }
381
+	
382
+        $this->cache->invalidateByOptions($options);
383
+	
384
+        $options['metastring_type'] = 'metadata';
385
+        return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
386
+    }
387
+	
388
+    /**
389
+     * Returns entities based upon metadata.  Also accepts all
390
+     * options available to elgg_get_entities().  Supports
391
+     * the singular option shortcut.
392
+     *
393
+     * @note Using metadata_names and metadata_values results in a
394
+     * "names IN (...) AND values IN (...)" clause.  This is subtly
395
+     * differently than default multiple metadata_name_value_pairs, which use
396
+     * "(name = value) AND (name = value)" clauses.
397
+     *
398
+     * When in doubt, use name_value_pairs.
399
+     *
400
+     * To ask for entities that do not have a metadata value, use a custom
401
+     * where clause like this:
402
+     *
403
+     * 	$options['wheres'][] = "NOT EXISTS (
404
+     *			SELECT 1 FROM {$dbprefix}metadata md
405
+     *			WHERE md.entity_guid = e.guid
406
+     *				AND md.name = $name
407
+     *				AND md.value = $value)";
408
+     *
409
+     * Note the metadata name and value has been denormalized in the above example.
410
+     *
411
+     * @see elgg_get_entities
412
+     *
413
+     * @param array $options Array in format:
414
+     *
415
+     * 	metadata_names => null|ARR metadata names
416
+     *
417
+     * 	metadata_values => null|ARR metadata values
418
+     *
419
+     * 	metadata_name_value_pairs => null|ARR (
420
+     *                                         name => 'name',
421
+     *                                         value => 'value',
422
+     *                                         'operand' => '=',
423
+     *                                         'case_sensitive' => true
424
+     *                                        )
425
+     *                               Currently if multiple values are sent via
426
+     *                               an array (value => array('value1', 'value2')
427
+     *                               the pair's operand will be forced to "IN".
428
+     *                               If passing "IN" as the operand and a string as the value,
429
+     *                               the value must be a properly quoted and escaped string.
430
+     *
431
+     * 	metadata_name_value_pairs_operator => null|STR The operator to use for combining
432
+     *                                        (name = value) OPERATOR (name = value); default AND
433
+     *
434
+     * 	metadata_case_sensitive => BOOL Overall Case sensitive
435
+     *
436
+     *  order_by_metadata => null|ARR array(
437
+     *                                      'name' => 'metadata_text1',
438
+     *                                      'direction' => ASC|DESC,
439
+     *                                      'as' => text|integer
440
+     *                                     )
441
+     *                                Also supports array('name' => 'metadata_text1')
442
+     *
443
+     *  metadata_owner_guids => null|ARR guids for metadata owners
444
+     *
445
+     * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors.
446
+     */
447
+    function getEntities(array $options = []) {
448
+        $defaults = [
449
+            'metadata_names'                     => ELGG_ENTITIES_ANY_VALUE,
450
+            'metadata_values'                    => ELGG_ENTITIES_ANY_VALUE,
451
+            'metadata_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE,
452
+	
453
+            'metadata_name_value_pairs_operator' => 'AND',
454
+            'metadata_case_sensitive'            => true,
455
+            'order_by_metadata'                  => [],
456
+	
457
+            'metadata_owner_guids'               => ELGG_ENTITIES_ANY_VALUE,
458
+        ];
459
+	
460
+        $options = array_merge($defaults, $options);
461
+	
462
+        $singulars = ['metadata_name', 'metadata_value',
463
+            'metadata_name_value_pair', 'metadata_owner_guid'];
464
+	
465
+        $options = _elgg_normalize_plural_options_array($options, $singulars);
466
+	
467
+        if (!$options = _elgg_entities_get_metastrings_options('metadata', $options)) {
468
+            return false;
469
+        }
470
+	
471
+        return $this->entityTable->getEntities($options);
472
+    }
473
+	
474
+    /**
475
+     * Returns metadata name and value SQL where for entities.
476
+     * NB: $names and $values are not paired. Use $pairs for this.
477
+     * Pairs default to '=' operand.
478
+     *
479
+     * This function is reused for annotations because the tables are
480
+     * exactly the same.
481
+     *
482
+     * @param string     $e_table           Entities table name
483
+     * @param string     $n_table           Normalized metastrings table name (Where entities,
484
+     *                                    values, and names are joined. annotations / metadata)
485
+     * @param array|null $names             Array of names
486
+     * @param array|null $values            Array of values
487
+     * @param array|null $pairs             Array of names / values / operands
488
+     * @param string     $pair_operator     ("AND" or "OR") Operator to use to join the where clauses for pairs
489
+     * @param bool       $case_sensitive    Case sensitive metadata names?
490
+     * @param array|null $order_by_metadata Array of names / direction
491
+     * @param array|null $owner_guids       Array of owner GUIDs
492
+     *
493
+     * @return false|array False on fail, array('joins', 'wheres')
494
+     * @access private
495
+     */
496
+    function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null,
497
+            $pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null,
498
+            $owner_guids = null) {
499
+        // short circuit if nothing requested
500
+        // 0 is a valid (if not ill-conceived) metadata name.
501
+        // 0 is also a valid metadata value for false, null, or 0
502
+        // 0 is also a valid(ish) owner_guid
503
+        if ((!$names && $names !== 0)
504
+            && (!$values && $values !== 0)
505
+            && (!$pairs && $pairs !== 0)
506
+            && (!$owner_guids && $owner_guids !== 0)
507
+            && !$order_by_metadata) {
508
+            return '';
509
+        }
510
+	
511
+        // join counter for incremental joins.
512
+        $i = 1;
513
+	
514
+        // binary forces byte-to-byte comparision of strings, making
515
+        // it case- and diacritical-mark- sensitive.
516
+        // only supported on values.
517
+        $binary = ($case_sensitive) ? ' BINARY ' : '';
518
+	
519
+        $access = _elgg_get_access_where_sql([
520
+            'table_alias' => 'n_table',
521
+            'guid_column' => 'entity_guid',
522
+        ]);
523
+	
524
+        $return =  [
525
+            'joins' =>  [],
526
+            'wheres' => [],
527
+            'orders' => []
528
+        ];
529
+	
530
+        $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table on
531 531
 			{$e_table}.guid = n_table.entity_guid";
532 532
 	
533
-		$wheres = [];
534
-	
535
-		// get names wheres and joins
536
-		$names_where = '';
537
-		if ($names !== null) {
538
-			if (!is_array($names)) {
539
-				$names = [$names];
540
-			}
541
-	
542
-			$sanitised_names = [];
543
-			foreach ($names as $name) {
544
-				// normalise to 0.
545
-				if (!$name) {
546
-					$name = '0';
547
-				}
548
-				$sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
549
-			}
550
-	
551
-			if ($names_str = implode(',', $sanitised_names)) {
552
-				$names_where = "(n_table.name IN ($names_str))";
553
-			}
554
-		}
555
-	
556
-		// get values wheres and joins
557
-		$values_where = '';
558
-		if ($values !== null) {
559
-			if (!is_array($values)) {
560
-				$values = [$values];
561
-			}
562
-	
563
-			$sanitised_values = [];
564
-			foreach ($values as $value) {
565
-				// normalize to 0
566
-				if (!$value) {
567
-					$value = 0;
568
-				}
569
-				$sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
570
-			}
571
-	
572
-			if ($values_str = implode(',', $sanitised_values)) {
573
-				$values_where = "({$binary}n_table.value IN ($values_str))";
574
-			}
575
-		}
576
-	
577
-		if ($names_where && $values_where) {
578
-			$wheres[] = "($names_where AND $values_where AND $access)";
579
-		} elseif ($names_where) {
580
-			$wheres[] = "($names_where AND $access)";
581
-		} elseif ($values_where) {
582
-			$wheres[] = "($values_where AND $access)";
583
-		}
584
-	
585
-		// add pairs
586
-		// pairs must be in arrays.
587
-		if (is_array($pairs)) {
588
-			// check if this is an array of pairs or just a single pair.
589
-			if (isset($pairs['name']) || isset($pairs['value'])) {
590
-				$pairs = [$pairs];
591
-			}
592
-	
593
-			$pair_wheres = [];
594
-	
595
-			// @todo when the pairs are > 3 should probably split the query up to
596
-			// denormalize the strings table.
597
-	
598
-			foreach ($pairs as $index => $pair) {
599
-				// @todo move this elsewhere?
600
-				// support shortcut 'n' => 'v' method.
601
-				if (!is_array($pair)) {
602
-					$pair = [
603
-						'name' => $index,
604
-						'value' => $pair
605
-					];
606
-				}
607
-	
608
-				// must have at least a name and value
609
-				if (!isset($pair['name']) || !isset($pair['value'])) {
610
-					// @todo should probably return false.
611
-					continue;
612
-				}
613
-	
614
-				// case sensitivity can be specified per pair.
615
-				// default to higher level setting.
616
-				if (isset($pair['case_sensitive'])) {
617
-					$pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
618
-				} else {
619
-					$pair_binary = $binary;
620
-				}
621
-	
622
-				if (isset($pair['operand'])) {
623
-					$operand = $this->db->sanitizeString($pair['operand']);
624
-				} else {
625
-					$operand = ' = ';
626
-				}
627
-	
628
-				// for comparing
629
-				$trimmed_operand = trim(strtolower($operand));
630
-	
631
-				$access = _elgg_get_access_where_sql([
632
-					'table_alias' => "n_table{$i}",
633
-					'guid_column' => 'entity_guid',
634
-				]);
533
+        $wheres = [];
534
+	
535
+        // get names wheres and joins
536
+        $names_where = '';
537
+        if ($names !== null) {
538
+            if (!is_array($names)) {
539
+                $names = [$names];
540
+            }
541
+	
542
+            $sanitised_names = [];
543
+            foreach ($names as $name) {
544
+                // normalise to 0.
545
+                if (!$name) {
546
+                    $name = '0';
547
+                }
548
+                $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
549
+            }
550
+	
551
+            if ($names_str = implode(',', $sanitised_names)) {
552
+                $names_where = "(n_table.name IN ($names_str))";
553
+            }
554
+        }
555
+	
556
+        // get values wheres and joins
557
+        $values_where = '';
558
+        if ($values !== null) {
559
+            if (!is_array($values)) {
560
+                $values = [$values];
561
+            }
562
+	
563
+            $sanitised_values = [];
564
+            foreach ($values as $value) {
565
+                // normalize to 0
566
+                if (!$value) {
567
+                    $value = 0;
568
+                }
569
+                $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
570
+            }
571
+	
572
+            if ($values_str = implode(',', $sanitised_values)) {
573
+                $values_where = "({$binary}n_table.value IN ($values_str))";
574
+            }
575
+        }
576
+	
577
+        if ($names_where && $values_where) {
578
+            $wheres[] = "($names_where AND $values_where AND $access)";
579
+        } elseif ($names_where) {
580
+            $wheres[] = "($names_where AND $access)";
581
+        } elseif ($values_where) {
582
+            $wheres[] = "($values_where AND $access)";
583
+        }
584
+	
585
+        // add pairs
586
+        // pairs must be in arrays.
587
+        if (is_array($pairs)) {
588
+            // check if this is an array of pairs or just a single pair.
589
+            if (isset($pairs['name']) || isset($pairs['value'])) {
590
+                $pairs = [$pairs];
591
+            }
592
+	
593
+            $pair_wheres = [];
594
+	
595
+            // @todo when the pairs are > 3 should probably split the query up to
596
+            // denormalize the strings table.
597
+	
598
+            foreach ($pairs as $index => $pair) {
599
+                // @todo move this elsewhere?
600
+                // support shortcut 'n' => 'v' method.
601
+                if (!is_array($pair)) {
602
+                    $pair = [
603
+                        'name' => $index,
604
+                        'value' => $pair
605
+                    ];
606
+                }
607
+	
608
+                // must have at least a name and value
609
+                if (!isset($pair['name']) || !isset($pair['value'])) {
610
+                    // @todo should probably return false.
611
+                    continue;
612
+                }
613
+	
614
+                // case sensitivity can be specified per pair.
615
+                // default to higher level setting.
616
+                if (isset($pair['case_sensitive'])) {
617
+                    $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
618
+                } else {
619
+                    $pair_binary = $binary;
620
+                }
621
+	
622
+                if (isset($pair['operand'])) {
623
+                    $operand = $this->db->sanitizeString($pair['operand']);
624
+                } else {
625
+                    $operand = ' = ';
626
+                }
627
+	
628
+                // for comparing
629
+                $trimmed_operand = trim(strtolower($operand));
630
+	
631
+                $access = _elgg_get_access_where_sql([
632
+                    'table_alias' => "n_table{$i}",
633
+                    'guid_column' => 'entity_guid',
634
+                ]);
635 635
 
636
-				// certain operands can't work well with strings that can be interpreted as numbers
637
-				// for direct comparisons like IN, =, != we treat them as strings
638
-				// gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
639
-				// see https://github.com/Elgg/Elgg/issues/7009
640
-				$num_safe_operands = ['>', '<', '>=', '<='];
641
-				$num_test_operand = trim(strtoupper($operand));
642
-	
643
-				if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
644
-					$value = $this->db->sanitizeString($pair['value']);
645
-				} else if (is_bool($pair['value'])) {
646
-					$value = (int) $pair['value'];
647
-				} else if (is_array($pair['value'])) {
648
-					$values_array = [];
649
-	
650
-					foreach ($pair['value'] as $pair_value) {
651
-						if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
652
-							$values_array[] = $this->db->sanitizeString($pair_value);
653
-						} else {
654
-							$values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
655
-						}
656
-					}
657
-	
658
-					if ($values_array) {
659
-						$value = '(' . implode(', ', $values_array) . ')';
660
-					}
661
-	
662
-					// @todo allow support for non IN operands with array of values.
663
-					// will have to do more silly joins.
664
-					$operand = 'IN';
665
-				} else if ($trimmed_operand == 'in') {
666
-					$value = "({$pair['value']})";
667
-				} else {
668
-					$value = "'" . $this->db->sanitizeString($pair['value']) . "'";
669
-				}
670
-	
671
-				$name = $this->db->sanitizeString($pair['name']);
672
-	
673
-				$return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
636
+                // certain operands can't work well with strings that can be interpreted as numbers
637
+                // for direct comparisons like IN, =, != we treat them as strings
638
+                // gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
639
+                // see https://github.com/Elgg/Elgg/issues/7009
640
+                $num_safe_operands = ['>', '<', '>=', '<='];
641
+                $num_test_operand = trim(strtoupper($operand));
642
+	
643
+                if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
644
+                    $value = $this->db->sanitizeString($pair['value']);
645
+                } else if (is_bool($pair['value'])) {
646
+                    $value = (int) $pair['value'];
647
+                } else if (is_array($pair['value'])) {
648
+                    $values_array = [];
649
+	
650
+                    foreach ($pair['value'] as $pair_value) {
651
+                        if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
652
+                            $values_array[] = $this->db->sanitizeString($pair_value);
653
+                        } else {
654
+                            $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
655
+                        }
656
+                    }
657
+	
658
+                    if ($values_array) {
659
+                        $value = '(' . implode(', ', $values_array) . ')';
660
+                    }
661
+	
662
+                    // @todo allow support for non IN operands with array of values.
663
+                    // will have to do more silly joins.
664
+                    $operand = 'IN';
665
+                } else if ($trimmed_operand == 'in') {
666
+                    $value = "({$pair['value']})";
667
+                } else {
668
+                    $value = "'" . $this->db->sanitizeString($pair['value']) . "'";
669
+                }
670
+	
671
+                $name = $this->db->sanitizeString($pair['name']);
672
+	
673
+                $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
674 674
 					on {$e_table}.guid = n_table{$i}.entity_guid";
675 675
 					
676
-				$pair_wheres[] = "(n_table{$i}.name = '$name' AND {$pair_binary}n_table{$i}.value
676
+                $pair_wheres[] = "(n_table{$i}.name = '$name' AND {$pair_binary}n_table{$i}.value
677 677
 					$operand $value AND $access)";
678 678
 	
679
-				$i++;
680
-			}
681
-	
682
-			if ($where = implode(" $pair_operator ", $pair_wheres)) {
683
-				$wheres[] = "($where)";
684
-			}
685
-		}
686
-	
687
-		// add owner_guids
688
-		if ($owner_guids) {
689
-			if (is_array($owner_guids)) {
690
-				$sanitised = array_map('sanitise_int', $owner_guids);
691
-				$owner_str = implode(',', $sanitised);
692
-			} else {
693
-				$owner_str = (int) $owner_guids;
694
-			}
695
-	
696
-			$wheres[] = "(n_table.owner_guid IN ($owner_str))";
697
-		}
698
-	
699
-		if ($where = implode(' AND ', $wheres)) {
700
-			$return['wheres'][] = "($where)";
701
-		}
702
-	
703
-		if (is_array($order_by_metadata)) {
704
-			if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
705
-				// singleton, so fix
706
-				$order_by_metadata = [$order_by_metadata];
707
-			}
708
-			foreach ($order_by_metadata as $order_by) {
709
-				if (is_array($order_by) && isset($order_by['name'])) {
710
-					$name = $this->db->sanitizeString($order_by['name']);
711
-					if (isset($order_by['direction'])) {
712
-						$direction = $this->db->sanitizeString($order_by['direction']);
713
-					} else {
714
-						$direction = 'ASC';
715
-					}
716
-					$return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
679
+                $i++;
680
+            }
681
+	
682
+            if ($where = implode(" $pair_operator ", $pair_wheres)) {
683
+                $wheres[] = "($where)";
684
+            }
685
+        }
686
+	
687
+        // add owner_guids
688
+        if ($owner_guids) {
689
+            if (is_array($owner_guids)) {
690
+                $sanitised = array_map('sanitise_int', $owner_guids);
691
+                $owner_str = implode(',', $sanitised);
692
+            } else {
693
+                $owner_str = (int) $owner_guids;
694
+            }
695
+	
696
+            $wheres[] = "(n_table.owner_guid IN ($owner_str))";
697
+        }
698
+	
699
+        if ($where = implode(' AND ', $wheres)) {
700
+            $return['wheres'][] = "($where)";
701
+        }
702
+	
703
+        if (is_array($order_by_metadata)) {
704
+            if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
705
+                // singleton, so fix
706
+                $order_by_metadata = [$order_by_metadata];
707
+            }
708
+            foreach ($order_by_metadata as $order_by) {
709
+                if (is_array($order_by) && isset($order_by['name'])) {
710
+                    $name = $this->db->sanitizeString($order_by['name']);
711
+                    if (isset($order_by['direction'])) {
712
+                        $direction = $this->db->sanitizeString($order_by['direction']);
713
+                    } else {
714
+                        $direction = 'ASC';
715
+                    }
716
+                    $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
717 717
 						on {$e_table}.guid = n_table{$i}.entity_guid";
718 718
 	
719
-					$access = _elgg_get_access_where_sql([
720
-						'table_alias' => "n_table{$i}",
721
-						'guid_column' => 'entity_guid',
722
-					]);
723
-	
724
-					$return['wheres'][] = "(n_table{$i}.name = '$name' AND $access)";
725
-					if (isset($order_by['as']) && $order_by['as'] == 'integer') {
726
-						$return['orders'][] = "CAST(n_table{$i}.value AS SIGNED) $direction";
727
-					} else {
728
-						$return['orders'][] = "n_table{$i}.value $direction";
729
-					}
730
-					$i++;
731
-				}
732
-			}
733
-		}
734
-	
735
-		return $return;
736
-	}
737
-	
738
-	/**
739
-	 * Get the URL for this metadata
740
-	 *
741
-	 * By default this links to the export handler in the current view.
742
-	 *
743
-	 * @param int $id Metadata ID
744
-	 *
745
-	 * @return mixed
746
-	 */
747
-	function getUrl($id) {
748
-		$extender = $this->get($id);
719
+                    $access = _elgg_get_access_where_sql([
720
+                        'table_alias' => "n_table{$i}",
721
+                        'guid_column' => 'entity_guid',
722
+                    ]);
723
+	
724
+                    $return['wheres'][] = "(n_table{$i}.name = '$name' AND $access)";
725
+                    if (isset($order_by['as']) && $order_by['as'] == 'integer') {
726
+                        $return['orders'][] = "CAST(n_table{$i}.value AS SIGNED) $direction";
727
+                    } else {
728
+                        $return['orders'][] = "n_table{$i}.value $direction";
729
+                    }
730
+                    $i++;
731
+                }
732
+            }
733
+        }
734
+	
735
+        return $return;
736
+    }
737
+	
738
+    /**
739
+     * Get the URL for this metadata
740
+     *
741
+     * By default this links to the export handler in the current view.
742
+     *
743
+     * @param int $id Metadata ID
744
+     *
745
+     * @return mixed
746
+     */
747
+    function getUrl($id) {
748
+        $extender = $this->get($id);
749 749
 
750
-		return $extender ? $extender->getURL() : false;
751
-	}
752
-	
753
-	/**
754
-	 * Mark entities with a particular type and subtype as having access permissions
755
-	 * that can be changed independently from their parent entity
756
-	 *
757
-	 * @param string $type    The type - object, user, etc
758
-	 * @param string $subtype The subtype; all subtypes by default
759
-	 *
760
-	 * @return void
761
-	 */
762
-	function registerMetadataAsIndependent($type, $subtype = '*') {
763
-		if (!isset($this->independents[$type])) {
764
-			$this->independents[$type] = [];
765
-		}
750
+        return $extender ? $extender->getURL() : false;
751
+    }
752
+	
753
+    /**
754
+     * Mark entities with a particular type and subtype as having access permissions
755
+     * that can be changed independently from their parent entity
756
+     *
757
+     * @param string $type    The type - object, user, etc
758
+     * @param string $subtype The subtype; all subtypes by default
759
+     *
760
+     * @return void
761
+     */
762
+    function registerMetadataAsIndependent($type, $subtype = '*') {
763
+        if (!isset($this->independents[$type])) {
764
+            $this->independents[$type] = [];
765
+        }
766 766
 		
767
-		$this->independents[$type][$subtype] = true;
768
-	}
769
-	
770
-	/**
771
-	 * Determines whether entities of a given type and subtype should not change
772
-	 * their metadata in line with their parent entity
773
-	 *
774
-	 * @param string $type    The type - object, user, etc
775
-	 * @param string $subtype The entity subtype
776
-	 *
777
-	 * @return bool
778
-	 */
779
-	function isMetadataIndependent($type, $subtype) {
780
-		if (empty($this->independents[$type])) {
781
-			return false;
782
-		}
767
+        $this->independents[$type][$subtype] = true;
768
+    }
769
+	
770
+    /**
771
+     * Determines whether entities of a given type and subtype should not change
772
+     * their metadata in line with their parent entity
773
+     *
774
+     * @param string $type    The type - object, user, etc
775
+     * @param string $subtype The entity subtype
776
+     *
777
+     * @return bool
778
+     */
779
+    function isMetadataIndependent($type, $subtype) {
780
+        if (empty($this->independents[$type])) {
781
+            return false;
782
+        }
783 783
 
784
-		return !empty($this->independents[$type][$subtype])
785
-			|| !empty($this->independents[$type]['*']);
786
-	}
787
-	
788
-	/**
789
-	 * When an entity is updated, resets the access ID on all of its child metadata
790
-	 *
791
-	 * @param string      $event       The name of the event
792
-	 * @param string      $object_type The type of object
793
-	 * @param \ElggEntity $object      The entity itself
794
-	 *
795
-	 * @return true
796
-	 * @access private Set as private in 1.9.0
797
-	 */
798
-	function handleUpdate($event, $object_type, $object) {
799
-		if ($object instanceof \ElggEntity) {
800
-			if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
801
-				$access_id = (int) $object->access_id;
802
-				$guid = (int) $object->getGUID();
803
-				$query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
804
-				$this->db->updateData($query);
805
-			}
806
-		}
807
-		return true;
808
-	}
784
+        return !empty($this->independents[$type][$subtype])
785
+            || !empty($this->independents[$type]['*']);
786
+    }
787
+	
788
+    /**
789
+     * When an entity is updated, resets the access ID on all of its child metadata
790
+     *
791
+     * @param string      $event       The name of the event
792
+     * @param string      $object_type The type of object
793
+     * @param \ElggEntity $object      The entity itself
794
+     *
795
+     * @return true
796
+     * @access private Set as private in 1.9.0
797
+     */
798
+    function handleUpdate($event, $object_type, $object) {
799
+        if ($object instanceof \ElggEntity) {
800
+            if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
801
+                $access_id = (int) $object->access_id;
802
+                $guid = (int) $object->getGUID();
803
+                $query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
804
+                $this->db->updateData($query);
805
+            }
806
+        }
807
+        return true;
808
+    }
809 809
 	
810 810
 }
Please login to merge, or discard this patch.