|
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.