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
|
|
|
public function __construct( |
57
|
|
|
Cache $cache, |
58
|
|
|
Database $db, |
59
|
|
|
EntityTable $entityTable, |
60
|
|
|
Events $events, |
61
|
|
|
MetastringsTable $metastringsTable, |
62
|
|
|
Session $session) { |
63
|
|
|
$this->cache = $cache; |
64
|
|
|
$this->db = $db; |
65
|
|
|
$this->entityTable = $entityTable; |
66
|
|
|
$this->events = $events; |
67
|
|
|
$this->metastringsTable = $metastringsTable; |
68
|
|
|
$this->session = $session; |
69
|
|
|
$this->table = $this->db->getTablePrefix() . "metadata"; |
70
|
|
|
} |
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
|
|
|
} |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.