Elgg  Version 6.1
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
7 use Elgg\Database;
14 
21 
22  use TimeUsing;
23 
24  public const TABLE_NAME = 'metadata';
25 
26  public const DEFAULT_JOIN_ALIAS = 'n_table';
27 
37  public function __construct(
38  protected AccessCache $access_cache,
39  protected MetadataCache $metadata_cache,
40  protected Database $db,
41  protected Events $events,
42  protected EntityTable $entityTable
43  ) {
44  }
45 
60  public function getTags(array $options = []) {
61  $defaults = [
62  'threshold' => 1,
63  'tag_names' => [],
64  ];
65 
66  $options = array_merge($defaults, $options);
67 
68  $singulars = ['tag_name'];
69  $options = QueryOptions::normalizePluralOptions($options, $singulars);
70 
71  $tag_names = elgg_extract('tag_names', $options, ['tags'], false);
72 
73  $threshold = elgg_extract('threshold', $options, 1, false);
74 
75  unset($options['tag_names']);
76  unset($options['threshold']);
77 
78  // custom selects
79  $options['selects'] = [
80  function(QueryBuilder $qb, $main_alias) {
81  return "{$main_alias}.value AS tag";
82  },
83  function(QueryBuilder $qb, $main_alias) {
84  return "COUNT({$main_alias}.id) AS total";
85  },
86  ];
87 
88  // additional wheres
89  $wheres = (array) elgg_extract('wheres', $options, []);
90  $wheres[] = function(QueryBuilder $qb, $main_alias) use ($tag_names) {
91  return $qb->compare("{$main_alias}.name", 'in', $tag_names, ELGG_VALUE_STRING);
92  };
93  $wheres[] = function(QueryBuilder $qb, $main_alias) {
94  return $qb->compare("{$main_alias}.value", '!=', '', ELGG_VALUE_STRING);
95  };
96  $options['wheres'] = $wheres;
97 
98  // custom group by
99  $options['group_by'] = [
100  function(QueryBuilder $qb, $main_alias) {
101  return "{$main_alias}.value";
102  },
103  ];
104 
105  // having
106  $having = (array) elgg_extract('having', $options, []);
107  $having[] = function(QueryBuilder $qb, $main_alias) use ($threshold) {
108  return $qb->compare('total', '>=', $threshold, ELGG_VALUE_INTEGER);
109  };
110  $options['having'] = $having;
111 
112  // order by
113  $options['order_by'] = [
114  new OrderByClause('total', 'desc'),
115  ];
116 
117  // custom callback
118  $options['callback'] = function($row) {
119  $result = new \stdClass();
120  $result->tag = $row->tag;
121  $result->total = (int) $row->total;
122 
123  return $result;
124  };
125 
126  return $this->getAll($options);
127  }
128 
138  public function get(int $id): ?\ElggMetadata {
139  $qb = Select::fromTable(self::TABLE_NAME);
140  $qb->select('*');
141 
142  $where = new MetadataWhereClause();
143  $where->ids = $id;
144  $qb->addClause($where);
145 
146  $row = $this->db->getDataRow($qb);
147  return $row ? new \ElggMetadata($row) : null;
148  }
149 
157  public function delete(\ElggMetadata $metadata): bool {
158  if (!$metadata->id) {
159  return false;
160  }
161 
162  if (!$this->events->trigger('delete', 'metadata', $metadata)) {
163  return false;
164  }
165 
166  $qb = Delete::fromTable(self::TABLE_NAME);
167  $qb->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
168 
169  $deleted = $this->db->deleteData($qb);
170 
171  if ($deleted) {
172  $metadata->getEntity()?->invalidateCache();
173  }
174 
175  return $deleted !== false;
176  }
177 
190  public function create(\ElggMetadata $metadata, bool $allow_multiple = false): int|false {
191  if (!isset($metadata->value) || !isset($metadata->entity_guid)) {
192  elgg_log('Metadata must have a value and entity guid', \Psr\Log\LogLevel::ERROR);
193  return false;
194  }
195 
196  if (!$this->entityTable->exists($metadata->entity_guid)) {
197  elgg_log("Can't create metadata on a non-existing entity_guid", \Psr\Log\LogLevel::ERROR);
198  return false;
199  }
200 
201  if (!is_scalar($metadata->value)) {
202  elgg_log('To set multiple metadata values use ElggEntity::setMetadata', \Psr\Log\LogLevel::ERROR);
203  return false;
204  }
205 
206  if ($metadata->id) {
207  if ($this->update($metadata)) {
208  return $metadata->id;
209  }
210  }
211 
212  if (!$allow_multiple) {
213  $id = $this->getIDsByName($metadata->entity_guid, $metadata->name);
214 
215  if (is_array($id)) {
216  throw new LogicException("
217  Multiple '{$metadata->name}' metadata values exist for entity [guid: {$metadata->entity_guid}].
218  Use ElggEntity::setMetadata()
219  ");
220  }
221 
222  if ($id > 0) {
223  $metadata->id = $id;
224 
225  if ($this->update($metadata)) {
226  return $metadata->id;
227  }
228  }
229  }
230 
231  if (!$this->events->triggerBefore('create', 'metadata', $metadata)) {
232  return false;
233  }
234 
235  $time_created = $this->getCurrentTime()->getTimestamp();
236 
237  $qb = Insert::intoTable(self::TABLE_NAME);
238  $qb->values([
239  'name' => $qb->param($metadata->name, ELGG_VALUE_STRING),
240  'entity_guid' => $qb->param($metadata->entity_guid, ELGG_VALUE_INTEGER),
241  'value' => $qb->param($metadata->value, $metadata->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER),
242  'value_type' => $qb->param($metadata->value_type, ELGG_VALUE_STRING),
243  'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
244  ]);
245 
246  $id = $this->db->insertData($qb);
247 
248  if ($id === 0) {
249  return false;
250  }
251 
252  $metadata->id = (int) $id;
253  $metadata->time_created = $time_created;
254 
255  if (!$this->events->trigger('create', 'metadata', $metadata)) {
256  $this->delete($metadata);
257 
258  return false;
259  }
260 
261  $metadata->getEntity()?->invalidateCache();
262 
263  $this->events->triggerAfter('create', 'metadata', $metadata);
264 
265  return $id;
266  }
267 
275  public function update(\ElggMetadata $metadata): bool {
276  if (!$this->entityTable->exists($metadata->entity_guid)) {
277  elgg_log("Can't update metadata to a non-existing entity_guid", \Psr\Log\LogLevel::ERROR);
278  return false;
279  }
280 
281  if (!$this->events->triggerBefore('update', 'metadata', $metadata)) {
282  return false;
283  }
284 
285  $qb = Update::table(self::TABLE_NAME);
286  $qb->set('name', $qb->param($metadata->name, ELGG_VALUE_STRING))
287  ->set('value', $qb->param($metadata->value, $metadata->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING))
288  ->set('value_type', $qb->param($metadata->value_type, ELGG_VALUE_STRING))
289  ->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
290 
291  $result = $this->db->updateData($qb);
292 
293  if ($result === false) {
294  return false;
295  }
296 
297  $metadata->getEntity()?->invalidateCache();
298 
299  $this->events->trigger('update', 'metadata', $metadata);
300  $this->events->triggerAfter('update', 'metadata', $metadata);
301 
302  return true;
303  }
304 
316  public function getAll(array $options = []) {
317  $options['metastring_type'] = 'metadata';
318  $options = QueryOptions::normalizeMetastringOptions($options);
319 
320  return Metadata::find($options);
321  }
322 
334  public function getRowsForGuids(array $guids): array {
335  $qb = Select::fromTable(self::TABLE_NAME);
336  $qb->select('*')
337  ->where($qb->compare('entity_guid', 'IN', $guids, ELGG_VALUE_GUID))
338  ->orderBy('entity_guid', 'asc')
339  ->addOrderBy('time_created', 'asc')
340  ->addOrderBy('id', 'asc');
341 
342  return $this->db->getData($qb, function ($row) {
343  return new \ElggMetadata($row);
344  });
345  }
346 
362  public function deleteAll(array $options): bool {
363  $required = [
364  'guid', 'guids',
365  'metadata_name', 'metadata_names',
366  'metadata_value', 'metadata_values',
367  ];
368 
369  $found = false;
370  foreach ($required as $key) {
371  // check that it exists and is something.
372  if (isset($options[$key]) && !elgg_is_empty($options[$key])) {
373  $found = true;
374  break;
375  }
376  }
377 
378  if (!$found) {
379  // requirements not met
380  throw new InvalidArgumentException(__METHOD__ . ' requires at least one of the following keys in $options: ' . implode(', ', $required));
381  }
382 
383  // This moved last in case an object's constructor sets metadata. Currently the batch
384  // delete process has to create the entity to delete its metadata. See #5214
385  if (empty($options['guid'])) {
386  $this->access_cache->clear();
387  $this->metadata_cache->clear();
388  } else {
389  $this->entityTable->invalidateCache($options['guid']);
390  }
391 
392  $options['batch'] = true;
393  $options['batch_size'] = 50;
394  $options['batch_inc_offset'] = false;
395 
396  $metadata = Metadata::find($options);
397  $count = $metadata->count();
398 
399  if (!$count) {
400  return true;
401  }
402 
403  $success = 0;
404  /* @var $md \ElggMetadata */
405  foreach ($metadata as $md) {
406  if ($md->delete()) {
407  $success++;
408  }
409  }
410 
411  return $success === $count;
412  }
413 
422  public function getIDsByName(int $entity_guid, string $name) {
423  $cached_metadata = $this->metadata_cache->load($entity_guid);
424  if ($cached_metadata !== null) {
425  $ids = [];
426  foreach ($cached_metadata as $md) {
427  if ($md->name !== $name) {
428  continue;
429  }
430 
431  $ids[] = $md->id;
432  }
433  } else {
434  $qb = Select::fromTable(self::TABLE_NAME);
435  $qb->select('id')
436  ->where($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
437  ->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
438 
439  $callback = function (\stdClass $row) {
440  return (int) $row->id;
441  };
442 
443  $ids = $this->db->getData($qb, $callback);
444  }
445 
446  if (empty($ids)) {
447  return null;
448  }
449 
450  if (is_array($ids) && count($ids) === 1) {
451  return array_shift($ids);
452  }
453 
454  return $ids;
455  }
456 }
static table(string $table)
Returns a QueryBuilder for updating data in a given table.
Definition: Update.php:17
$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)
getEntity()
Get the entity this describes.
$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
The Elgg database.
Definition: Database.php:26
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
static intoTable(string $table)
Returns a QueryBuilder for inserting data in a given table.
Definition: Insert.php:17
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:256
$entity_guid
Action for adding and editing comments.
Definition: save.php:6
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
if($who_can_change_language=== 'nobody') elseif($who_can_change_language=== 'admin_only'&&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
deleteAll(array $options)
Deletes metadata based on $options.
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
elgg_log($message, $level=\Psr\Log\LogLevel::NOTICE)
Log a message.
Definition: elgglib.php:88
compare(string $x, string $comparison, $y=null, string $type=null, bool $case_sensitive=null)
Build value comparison clause.
__construct(protected AccessCache $access_cache, protected MetadataCache $metadata_cache, protected Database $db, protected Events $events, protected EntityTable $entityTable)
Constructor.
$count
Definition: ban.php:24
ElggMetadata.
static fromTable(string $table)
Returns a QueryBuilder for deleting data from a given table.
Definition: Delete.php:17
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
getTags(array $options=[])
Get popular tags and their frequencies.
$guids
Activates all specified installed and inactive plugins.
Definition: activate_all.php:9
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
static fromTable(string $table, string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
$id
Generic annotation delete action.
Definition: delete.php:6
$qb
Definition: queue.php:12
Builds clauses for filtering entities by properties in metadata table.
This class interfaces with the database to perform CRUD operations on metadata.
Entity table database service.
Definition: EntityTable.php:24
update(\ElggMetadata $metadata)
Update a specific piece of metadata.
getRowsForGuids(array $guids)
Returns metadata rows.