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