Elgg  Version 5.1
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
6 use Elgg\Database;
13 
20 
21  use TimeUsing;
22 
23  const MYSQL_TEXT_BYTE_LIMIT = 65535;
24 
26 
27  protected Database $db;
28 
29  protected Events $events;
30 
32 
41  public function __construct(
42  MetadataCache $metadata_cache,
43  Database $db,
44  Events $events,
45  EntityTable $entityTable
46  ) {
47  $this->metadata_cache = $metadata_cache;
48  $this->db = $db;
49  $this->events = $events;
50  $this->entityTable = $entityTable;
51  }
52 
67  public function getTags(array $options = []) {
68  $defaults = [
69  'threshold' => 1,
70  'tag_names' => [],
71  ];
72 
73  $options = array_merge($defaults, $options);
74 
75  $singulars = ['tag_name'];
76  $options = QueryOptions::normalizePluralOptions($options, $singulars);
77 
78  $tag_names = elgg_extract('tag_names', $options, ['tags'], false);
79 
80  $threshold = elgg_extract('threshold', $options, 1, false);
81 
82  unset($options['tag_names']);
83  unset($options['threshold']);
84 
85  // custom selects
86  $options['selects'] = [
87  function(QueryBuilder $qb, $main_alias) {
88  return "{$main_alias}.value AS tag";
89  },
90  function(QueryBuilder $qb, $main_alias) {
91  return "COUNT({$main_alias}.id) AS total";
92  },
93  ];
94 
95  // additional wheres
96  $wheres = (array) elgg_extract('wheres', $options, []);
97  $wheres[] = function(QueryBuilder $qb, $main_alias) use ($tag_names) {
98  return $qb->compare("{$main_alias}.name", 'in', $tag_names, ELGG_VALUE_STRING);
99  };
100  $wheres[] = function(QueryBuilder $qb, $main_alias) {
101  return $qb->compare("{$main_alias}.value", '!=', '', ELGG_VALUE_STRING);
102  };
103  $options['wheres'] = $wheres;
104 
105  // custom group by
106  $options['group_by'] = [
107  function(QueryBuilder $qb, $main_alias) {
108  return "{$main_alias}.value";
109  },
110  ];
111 
112  // having
113  $having = (array) elgg_extract('having', $options, []);
114  $having[] = function(QueryBuilder $qb, $main_alias) use ($threshold) {
115  return $qb->compare('total', '>=', $threshold, ELGG_VALUE_INTEGER);
116  };
117  $options['having'] = $having;
118 
119  // order by
120  $options['order_by'] = [
121  new OrderByClause('total', 'desc'),
122  ];
123 
124  // custom callback
125  $options['callback'] = function($row) {
126  $result = new \stdClass();
127  $result->tag = $row->tag;
128  $result->total = (int) $row->total;
129 
130  return $result;
131  };
132 
133  return $this->getAll($options);
134  }
135 
145  public function get(int $id): ?\ElggMetadata {
146  $qb = Select::fromTable('metadata');
147  $qb->select('*');
148 
149  $where = new MetadataWhereClause();
150  $where->ids = $id;
151  $qb->addClause($where);
152 
153  $row = $this->db->getDataRow($qb);
154  return $row ? new \ElggMetadata($row) : null;
155  }
156 
164  public function delete(\ElggMetadata $metadata): bool {
165  if (!$metadata->id) {
166  return false;
167  }
168 
169  if (!_elgg_services()->events->trigger('delete', 'metadata', $metadata)) {
170  return false;
171  }
172 
173  $qb = Delete::fromTable('metadata');
174  $qb->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
175 
176  $deleted = $this->db->deleteData($qb);
177 
178  if ($deleted) {
179  $this->metadata_cache->clear($metadata->entity_guid);
180  }
181 
182  return $deleted !== false;
183  }
184 
197  public function create(\ElggMetadata $metadata, bool $allow_multiple = false): int|false {
198  if (!isset($metadata->value) || !isset($metadata->entity_guid)) {
199  elgg_log('Metadata must have a value and entity guid', 'ERROR');
200  return false;
201  }
202 
203  if (!$this->entityTable->exists($metadata->entity_guid)) {
204  elgg_log("Can't create metadata on a non-existing entity_guid", 'ERROR');
205  return false;
206  }
207 
208  if (!is_scalar($metadata->value)) {
209  elgg_log('To set multiple metadata values use ElggEntity::setMetadata', 'ERROR');
210  return false;
211  }
212 
213  if ($metadata->id) {
214  if ($this->update($metadata)) {
215  return $metadata->id;
216  }
217  }
218 
219  if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) {
220  elgg_log("Metadata '{$metadata->name}' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
221  }
222 
223  if (!$allow_multiple) {
224  $id = $this->getIDsByName($metadata->entity_guid, $metadata->name);
225 
226  if (is_array($id)) {
227  throw new LogicException("
228  Multiple '{$metadata->name}' metadata values exist for entity [guid: {$metadata->entity_guid}].
229  Use ElggEntity::setMetadata()
230  ");
231  }
232 
233  if ($id > 0) {
234  $metadata->id = $id;
235 
236  if ($this->update($metadata)) {
237  return $metadata->id;
238  }
239  }
240  }
241 
242  if (!$this->events->triggerBefore('create', 'metadata', $metadata)) {
243  return false;
244  }
245 
246  $time_created = $this->getCurrentTime()->getTimestamp();
247 
248  $qb = Insert::intoTable('metadata');
249  $qb->values([
250  'name' => $qb->param($metadata->name, ELGG_VALUE_STRING),
251  'entity_guid' => $qb->param($metadata->entity_guid, ELGG_VALUE_INTEGER),
252  'value' => $qb->param($metadata->value, $metadata->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER),
253  'value_type' => $qb->param($metadata->value_type, ELGG_VALUE_STRING),
254  'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
255  ]);
256 
257  $id = $this->db->insertData($qb);
258 
259  if ($id === false) {
260  return false;
261  }
262 
263  $metadata->id = (int) $id;
264  $metadata->time_created = $time_created;
265 
266  if (!$this->events->trigger('create', 'metadata', $metadata)) {
267  $this->delete($metadata);
268 
269  return false;
270  }
271 
272  $this->metadata_cache->clear($metadata->entity_guid);
273 
274  $this->events->triggerAfter('create', 'metadata', $metadata);
275 
276  return $id;
277  }
278 
286  public function update(\ElggMetadata $metadata): bool {
287  if (!$this->entityTable->exists($metadata->entity_guid)) {
288  elgg_log("Can't update metadata to a non-existing entity_guid", 'ERROR');
289  return false;
290  }
291 
292  if (!$this->events->triggerBefore('update', 'metadata', $metadata)) {
293  return false;
294  }
295 
296  if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) {
297  elgg_log("Metadata '{$metadata->name}' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
298  }
299 
300  $qb = Update::table('metadata');
301  $qb->set('name', $qb->param($metadata->name, ELGG_VALUE_STRING))
302  ->set('value', $qb->param($metadata->value, $metadata->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING))
303  ->set('value_type', $qb->param($metadata->value_type, ELGG_VALUE_STRING))
304  ->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
305 
306  $result = $this->db->updateData($qb);
307 
308  if ($result === false) {
309  return false;
310  }
311 
312  $this->metadata_cache->clear($metadata->entity_guid);
313 
314  $this->events->trigger('update', 'metadata', $metadata);
315  $this->events->triggerAfter('update', 'metadata', $metadata);
316 
317  return true;
318  }
319 
331  public function getAll(array $options = []) {
332  $options['metastring_type'] = 'metadata';
333  $options = QueryOptions::normalizeMetastringOptions($options);
334 
335  return Metadata::find($options);
336  }
337 
349  public function getRowsForGuids(array $guids): array {
350  $qb = Select::fromTable('metadata');
351  $qb->select('*')
352  ->where($qb->compare('entity_guid', 'IN', $guids, ELGG_VALUE_GUID))
353  ->orderBy('entity_guid', 'asc')
354  ->addOrderBy('time_created', 'asc')
355  ->addOrderBy('id', 'asc');
356 
357  return $this->db->getData($qb, function ($row) {
358  return new \ElggMetadata($row);
359  });
360  }
361 
377  public function deleteAll(array $options): bool {
378  $required = [
379  'guid', 'guids',
380  'metadata_name', 'metadata_names',
381  'metadata_value', 'metadata_values',
382  ];
383 
384  $found = false;
385  foreach ($required as $key) {
386  // check that it exists and is something.
387  if (isset($options[$key]) && !elgg_is_empty($options[$key])) {
388  $found = true;
389  break;
390  }
391  }
392 
393  if (!$found) {
394  // requirements not met
395  throw new InvalidArgumentException(__METHOD__ . ' requires at least one of the following keys in $options: ' . implode(', ', $required));
396  }
397 
398  // This moved last in case an object's constructor sets metadata. Currently the batch
399  // delete process has to create the entity to delete its metadata. See #5214
400  $this->metadata_cache->invalidateByOptions($options);
401 
402  $options['batch'] = true;
403  $options['batch_size'] = 50;
404  $options['batch_inc_offset'] = false;
405 
406  $metadata = Metadata::find($options);
407  $count = $metadata->count();
408 
409  if (!$count) {
410  return true;
411  }
412 
413  $success = 0;
414  /* @var $md \ElggMetadata */
415  foreach ($metadata as $md) {
416  if ($md->delete()) {
417  $success++;
418  }
419  }
420 
421  return $success === $count;
422  }
423 
432  protected function getIDsByName(int $entity_guid, string $name) {
433  if ($this->metadata_cache->isLoaded($entity_guid)) {
434  $ids = $this->metadata_cache->getSingleId($entity_guid, $name);
435  } else {
436  $qb = Select::fromTable('metadata');
437  $qb->select('id')
438  ->where($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
439  ->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
440 
441  $callback = function (\stdClass $row) {
442  return (int) $row->id;
443  };
444 
445  $ids = $this->db->getData($qb, $callback);
446  }
447 
448  if (empty($ids)) {
449  return null;
450  }
451 
452  if (is_array($ids) && count($ids) === 1) {
453  return array_shift($ids);
454  }
455 
456  return $ids;
457  }
458 }
$deleted
Definition: delete.php:25
Exception thrown if an argument is not of the expected type.
create(\ElggMetadata $metadata, bool $allow_multiple=false)
Create a new metadata object, or update an existing one (if multiple is allowed)
$defaults
Generic entity header upload helper.
Definition: header.php:6
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: Repository.php:110
getAll(array $options=[])
Returns metadata.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
static table($table, $alias=null)
{}
Definition: Update.php:13
The Elgg database.
Definition: Database.php:25
if(!$user instanceof\ElggUser) $time_created
Definition: online.php:13
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:111
const ELGG_VALUE_GUID
Definition: constants.php:113
Database abstraction query builder.
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:10
$options
Elgg admin footer.
Definition: footer.php:6
elgg_is_empty($value)
Check if a value isn&#39;t empty, but allow 0 and &#39;0&#39;.
Definition: input.php:176
getIDsByName(int $entity_guid, string $name)
Returns ID(s) of metadata with a particular name attached to an entity.
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
$entity_guid
Action for adding and editing comments.
Definition: save.php:6
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
deleteAll(array $options)
Deletes metadata based on $options.
static intoTable($table)
{}
Definition: Insert.php:13
elgg_log($message, $level=\Psr\Log\LogLevel::NOTICE)
Log a message.
Definition: elgglib.php:86
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
$count
Definition: ban.php:24
ElggMetadata.
Exception that represents error in the program logic.
Extends QueryBuilder with ORDER BY clauses.
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
getTags(array $options=[])
Get popular tags and their frequencies.
$guids
Activates all specified installed and inactive plugins.
Definition: activate_all.php:9
__construct(MetadataCache $metadata_cache, Database $db, Events $events, EntityTable $entityTable)
Constructor.
const ELGG_VALUE_STRING
Definition: constants.php:112
$metadata
Output annotation metadata.
Definition: metadata.php:9
In memory cache of known metadata values stored by entity.
$required
Definition: label.php:12
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
$id
Generic annotation delete action.
Definition: delete.php:6
$qb
Definition: queue.php:11
Builds clauses for filtering entities by properties in metadata table.
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
This class interfaces with the database to perform CRUD operations on metadata.
Entity table database service.
Definition: EntityTable.php:26
update(\ElggMetadata $metadata)
Update a specific piece of metadata.
getRowsForGuids(array $guids)
Returns metadata rows.