implicit conversion of array to boolean.
1 | <?php |
||
2 | |||
3 | namespace Elgg\Database; |
||
4 | |||
5 | use Elgg\Cache\MetadataCache; |
||
6 | use Elgg\Database; |
||
7 | use Elgg\Database\Clauses\MetadataWhereClause; |
||
8 | use Elgg\EventsService as Events; |
||
9 | use Elgg\TimeUsing; |
||
10 | use ElggMetadata; |
||
11 | use ElggEntity; |
||
12 | |||
13 | /** |
||
14 | * This class interfaces with the database to perform CRUD operations on metadata |
||
15 | * |
||
16 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
17 | * |
||
18 | * @access private |
||
19 | */ |
||
20 | class MetadataTable { |
||
21 | |||
22 | use TimeUsing; |
||
23 | |||
24 | /** |
||
25 | * @var MetadataCache |
||
26 | */ |
||
27 | protected $metadata_cache; |
||
28 | |||
29 | /** |
||
30 | * @var Database |
||
31 | */ |
||
32 | protected $db; |
||
33 | |||
34 | /** |
||
35 | * @var Events |
||
36 | */ |
||
37 | protected $events; |
||
38 | |||
39 | /** |
||
40 | * @var string[] |
||
41 | */ |
||
42 | protected $tag_names = []; |
||
43 | |||
44 | const MYSQL_TEXT_BYTE_LIMIT = 65535; |
||
45 | |||
46 | /** |
||
47 | * Constructor |
||
48 | * |
||
49 | * @param MetadataCache $metadata_cache A cache for this table |
||
50 | * @param Database $db The Elgg database |
||
51 | * @param Events $events The events registry |
||
52 | */ |
||
53 | 4417 | public function __construct( |
|
54 | MetadataCache $metadata_cache, |
||
55 | Database $db, |
||
56 | Events $events |
||
57 | ) { |
||
58 | 4417 | $this->metadata_cache = $metadata_cache; |
|
59 | 4417 | $this->db = $db; |
|
60 | 4417 | $this->events = $events; |
|
61 | 4417 | } |
|
62 | |||
63 | /** |
||
64 | * Registers a metadata name as containing tags for an entity. |
||
65 | * |
||
66 | * @param string $name Tag name |
||
67 | * |
||
68 | * @return bool |
||
69 | */ |
||
70 | 32 | public function registerTagName($name) { |
|
71 | 32 | if (!in_array($name, $this->tag_names)) { |
|
72 | 19 | $this->tag_names[] = $name; |
|
73 | } |
||
74 | |||
75 | 32 | return true; |
|
76 | } |
||
77 | |||
78 | /** |
||
79 | * Unregisters a metadata tag name |
||
80 | * |
||
81 | * @param string $name Tag name |
||
82 | * |
||
83 | * @return bool |
||
84 | */ |
||
85 | 1 | public function unregisterTagName($name) { |
|
86 | 1 | $index = array_search($name, $this->tag_names); |
|
87 | 1 | if ($index >= 0) { |
|
88 | 1 | unset($this->tag_names[$index]); |
|
89 | |||
90 | 1 | return true; |
|
91 | } |
||
92 | |||
93 | return false; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * Returns an array of valid metadata names for tags. |
||
98 | * |
||
99 | * @return string[] |
||
100 | */ |
||
101 | 3 | public function getTagNames() { |
|
102 | 3 | return $this->tag_names; |
|
103 | } |
||
104 | |||
105 | /** |
||
106 | * Get a specific metadata object by its id |
||
107 | * |
||
108 | * @see MetadataTable::getAll() |
||
109 | * |
||
110 | * @param int $id The id of the metadata object being retrieved. |
||
111 | * |
||
112 | * @return ElggMetadata|false false if not found |
||
113 | */ |
||
114 | 9 | public function get($id) { |
|
115 | 9 | $qb = Select::fromTable('metadata'); |
|
116 | 9 | $qb->select('*'); |
|
117 | |||
118 | 9 | $where = new MetadataWhereClause(); |
|
119 | 9 | $where->ids = $id; |
|
120 | 9 | $qb->addClause($where); |
|
121 | |||
122 | 9 | $row = $this->db->getDataRow($qb); |
|
123 | 9 | if ($row) { |
|
0 ignored issues
–
show
|
|||
124 | 9 | return new ElggMetadata($row); |
|
125 | } |
||
126 | |||
127 | return false; |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Deletes metadata using its ID |
||
132 | * |
||
133 | * @param ElggMetadata $metadata Metadata |
||
134 | * |
||
135 | * @return bool |
||
136 | */ |
||
137 | 199 | public function delete(ElggMetadata $metadata) { |
|
138 | 199 | if (!$metadata->id || !$metadata->canEdit()) { |
|
139 | return false; |
||
140 | } |
||
141 | |||
142 | 199 | if (!elgg_trigger_event('delete', 'metadata', $metadata)) { |
|
143 | return false; |
||
144 | } |
||
145 | |||
146 | 199 | $qb = Delete::fromTable('metadata'); |
|
147 | 199 | $qb->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER)); |
|
148 | |||
149 | 199 | $deleted = $this->db->deleteData($qb); |
|
150 | |||
151 | 199 | if ($deleted) { |
|
152 | 199 | $this->metadata_cache->clear($metadata->entity_guid); |
|
153 | } |
||
154 | |||
155 | 199 | return $deleted; |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * Create a new metadata object, or update an existing one (if multiple is allowed) |
||
160 | * |
||
161 | * Metadata can be an array by setting allow_multiple to true, but it is an |
||
162 | * indexed array with no control over the indexing |
||
163 | * |
||
164 | * @param ElggMetadata $metadata Metadata |
||
165 | * @param bool $allow_multiple Allow multiple values for one key. Default is false |
||
166 | * |
||
167 | * @return int|false id of metadata or false if failure |
||
168 | */ |
||
169 | 1028 | public function create(ElggMetadata $metadata, $allow_multiple = false) { |
|
170 | 1028 | if (!isset($metadata->value) || !isset($metadata->entity_guid)) { |
|
171 | elgg_log("Metadata must have a value and entity guid", 'ERROR'); |
||
172 | return false; |
||
173 | } |
||
174 | |||
175 | 1028 | if (!is_scalar($metadata->value)) { |
|
176 | elgg_log("To set multiple metadata values use ElggEntity::setMetadata", 'ERROR'); |
||
177 | return false; |
||
178 | } |
||
179 | |||
180 | 1028 | if ($metadata->id) { |
|
181 | if ($this->update($metadata)) { |
||
182 | return $metadata->id; |
||
183 | } |
||
184 | } |
||
185 | |||
186 | 1028 | if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) { |
|
187 | elgg_log("Metadata '$metadata->name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING'); |
||
188 | } |
||
189 | |||
190 | 1028 | if (!$allow_multiple) { |
|
191 | 1027 | $id = $this->getIdsByName($metadata->entity_guid, $metadata->name); |
|
192 | |||
193 | 1027 | if (is_array($id)) { |
|
194 | throw new \LogicException("Multiple '{$metadata->name}' metadata values exist for entity [guid: {$metadata->entity_guid}]. Use ElggEntity::setMetadata()"); |
||
195 | } |
||
196 | |||
197 | 1027 | if ($id) { |
|
198 | 198 | $metadata->id = $id; |
|
199 | |||
200 | 198 | if ($this->update($metadata)) { |
|
201 | 198 | return $metadata->id; |
|
202 | } |
||
203 | } |
||
204 | } |
||
205 | |||
206 | 1028 | if (!$this->events->triggerBefore('create', 'metadata', $metadata)) { |
|
207 | return false; |
||
208 | } |
||
209 | |||
210 | 1028 | $time_created = $this->getCurrentTime()->getTimestamp(); |
|
211 | |||
212 | 1028 | $qb = Insert::intoTable('metadata'); |
|
213 | 1028 | $qb->values([ |
|
214 | 1028 | 'name' => $qb->param($metadata->name, ELGG_VALUE_STRING), |
|
215 | 1028 | 'entity_guid' => $qb->param($metadata->entity_guid, ELGG_VALUE_INTEGER), |
|
216 | 1028 | 'value' => $qb->param($metadata->value, $metadata->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING), |
|
217 | 1028 | 'value_type' => $qb->param($metadata->value_type, ELGG_VALUE_STRING), |
|
218 | 1028 | 'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER), |
|
219 | ]); |
||
220 | |||
221 | 1028 | $id = $this->db->insertData($qb); |
|
222 | |||
223 | 1028 | if ($id === false) { |
|
224 | return false; |
||
225 | } |
||
226 | |||
227 | 1028 | $metadata->id = (int) $id; |
|
228 | 1028 | $metadata->time_created = $time_created; |
|
229 | |||
230 | 1028 | if ($this->events->trigger('create', 'metadata', $metadata)) { |
|
231 | 1028 | $this->metadata_cache->clear($metadata->entity_guid); |
|
232 | |||
233 | 1028 | $this->events->triggerAfter('create', 'metadata', $metadata); |
|
234 | |||
235 | 1028 | return $id; |
|
236 | } else { |
||
237 | $this->delete($metadata); |
||
238 | |||
239 | return false; |
||
240 | } |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Update a specific piece of metadata |
||
245 | * |
||
246 | * @param ElggMetadata $metadata Updated metadata |
||
247 | * |
||
248 | * @return bool |
||
249 | */ |
||
250 | 198 | public function update(ElggMetadata $metadata) { |
|
251 | 198 | if (!$metadata->canEdit()) { |
|
252 | 1 | return false; |
|
253 | } |
||
254 | |||
255 | 198 | if (!$this->events->triggerBefore('update', 'metadata', $metadata)) { |
|
256 | return false; |
||
257 | } |
||
258 | |||
259 | 198 | if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) { |
|
260 | elgg_log("Metadata '$metadata->name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING'); |
||
261 | } |
||
262 | |||
263 | 198 | $qb = Update::table('metadata'); |
|
264 | 198 | $qb->set('name', $qb->param($metadata->name, ELGG_VALUE_STRING)) |
|
265 | 198 | ->set('value', $qb->param($metadata->value, $metadata->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING)) |
|
266 | 198 | ->set('value_type', $qb->param($metadata->value_type, ELGG_VALUE_STRING)) |
|
267 | 198 | ->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER)); |
|
268 | |||
269 | 198 | $result = $this->db->updateData($qb); |
|
270 | |||
271 | 198 | if ($result === false) { |
|
272 | return false; |
||
273 | } |
||
274 | |||
275 | 198 | $this->metadata_cache->clear($metadata->entity_guid); |
|
276 | |||
277 | 198 | $this->events->trigger('update', 'metadata', $metadata); |
|
278 | 198 | $this->events->triggerAfter('update', 'metadata', $metadata); |
|
279 | |||
280 | 198 | return true; |
|
281 | } |
||
282 | |||
283 | /** |
||
284 | * Returns metadata |
||
285 | * |
||
286 | * Accepts all {@link elgg_get_entities()} options for entity restraints. |
||
287 | * |
||
288 | * @see elgg_get_entities() |
||
289 | * |
||
290 | * @param array $options Options |
||
291 | * |
||
292 | * @return ElggMetadata[]|mixed |
||
293 | */ |
||
294 | 590 | public function getAll(array $options = []) { |
|
295 | |||
296 | 590 | $options['metastring_type'] = 'metadata'; |
|
297 | 590 | $options = _elgg_normalize_metastrings_options($options); |
|
298 | |||
299 | 590 | return Metadata::find($options); |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * Deletes metadata based on $options. |
||
304 | * |
||
305 | * @warning Unlike elgg_get_metadata() this will not accept an empty options array! |
||
306 | * This requires at least one constraint: |
||
307 | * metadata_name(s), metadata_value(s), or guid(s) must be set. |
||
308 | * |
||
309 | * @see elgg_get_metadata() |
||
310 | * @see elgg_get_entities() |
||
311 | * |
||
312 | * @param array $options Options |
||
313 | * |
||
314 | * @return bool|null true on success, false on failure, null if no metadata to delete. |
||
315 | */ |
||
316 | 271 | public function deleteAll(array $options) { |
|
317 | 271 | if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) { |
|
318 | return false; |
||
319 | } |
||
320 | |||
321 | // This moved last in case an object's constructor sets metadata. Currently the batch |
||
322 | // delete process has to create the entity to delete its metadata. See #5214 |
||
323 | 271 | $this->metadata_cache->invalidateByOptions($options); |
|
324 | |||
325 | 271 | $options['batch'] = true; |
|
326 | 271 | $options['batch_size'] = 50; |
|
327 | 271 | $options['batch_inc_offset'] = false; |
|
328 | |||
329 | 271 | $metadata = Metadata::find($options); |
|
330 | 271 | $count = $metadata->count(); |
|
331 | |||
332 | 271 | if (!$count) { |
|
333 | 91 | return; |
|
334 | } |
||
335 | |||
336 | 199 | $success = 0; |
|
337 | 199 | foreach ($metadata as $md) { |
|
338 | 199 | if ($md->delete()) { |
|
339 | 199 | $success++; |
|
340 | } |
||
341 | } |
||
342 | |||
343 | 199 | return $success == $count; |
|
344 | } |
||
345 | |||
346 | /** |
||
347 | * Returns ID(s) of metadata with a particular name attached to an entity |
||
348 | * |
||
349 | * @param int $entity_guid Entity guid |
||
350 | * @param string $name Metadata name |
||
351 | * |
||
352 | * @return int[]|int|null |
||
353 | */ |
||
354 | 1027 | public function getIdsByName($entity_guid, $name) { |
|
355 | 1027 | if ($this->metadata_cache->isLoaded($entity_guid)) { |
|
356 | 666 | $ids = $this->metadata_cache->getSingleId($entity_guid, $name); |
|
357 | } else { |
||
358 | 1027 | $qb = Select::fromTable('metadata'); |
|
359 | 1027 | $qb->select('id') |
|
360 | 1027 | ->where($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER)) |
|
361 | 1027 | ->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING)); |
|
362 | |||
363 | 1027 | $callback = function (\stdClass $row) { |
|
364 | 1 | return (int) $row->id; |
|
365 | 1027 | }; |
|
366 | |||
367 | $ids = $this->db->getData($qb, $callback); |
||
368 | } |
||
369 | |||
370 | if (empty($ids)) { |
||
371 | return null; |
||
372 | } |
||
373 | |||
374 | if (is_array($ids) && count($ids) === 1) { |
||
375 | return array_shift($ids); |
||
376 | } |
||
377 | |||
378 | return $ids; |
||
379 | } |
||
380 | } |
||
381 |
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.