Elgg  Version 6.3
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
7 use Elgg\Database;
13 use Elgg\Traits\TimeUsing;
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  $qb->addClause(MetadataWhereClause::factory(['ids' => $id]));
143 
144  $row = $this->db->getDataRow($qb);
145  return $row ? new \ElggMetadata($row) : null;
146  }
147 
155  public function delete(\ElggMetadata $metadata): bool {
156  if (!$metadata->id) {
157  return false;
158  }
159 
160  if (!$this->events->trigger('delete', 'metadata', $metadata)) {
161  return false;
162  }
163 
164  $qb = Delete::fromTable(self::TABLE_NAME);
165  $qb->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
166 
167  $deleted = $this->db->deleteData($qb);
168 
169  if ($deleted) {
170  $metadata->getEntity()?->invalidateCache();
171  }
172 
173  return $deleted !== false;
174  }
175 
188  public function create(\ElggMetadata $metadata, bool $allow_multiple = false): int|false {
189  if (!isset($metadata->value) || !isset($metadata->entity_guid)) {
190  elgg_log('Metadata must have a value and entity guid', \Psr\Log\LogLevel::ERROR);
191  return false;
192  }
193 
194  if (!$this->entityTable->exists($metadata->entity_guid)) {
195  elgg_log("Can't create metadata on a non-existing entity_guid", \Psr\Log\LogLevel::ERROR);
196  return false;
197  }
198 
199  if (!is_scalar($metadata->value)) {
200  elgg_log('To set multiple metadata values use ElggEntity::setMetadata', \Psr\Log\LogLevel::ERROR);
201  return false;
202  }
203 
204  if ($metadata->id) {
205  if ($this->update($metadata)) {
206  return $metadata->id;
207  }
208  }
209 
210  if (!$allow_multiple) {
211  $id = $this->getIDsByName($metadata->entity_guid, $metadata->name);
212 
213  if (is_array($id)) {
214  throw new LogicException("
215  Multiple '{$metadata->name}' metadata values exist for entity [guid: {$metadata->entity_guid}].
216  Use ElggEntity::setMetadata()
217  ");
218  }
219 
220  if ($id > 0) {
221  $metadata->id = $id;
222 
223  if ($this->update($metadata)) {
224  return $metadata->id;
225  }
226  }
227  }
228 
229  if (!$this->events->triggerBefore('create', 'metadata', $metadata)) {
230  return false;
231  }
232 
233  $time_created = $this->getCurrentTime()->getTimestamp();
234 
235  $qb = Insert::intoTable(self::TABLE_NAME);
236  $qb->values([
237  'name' => $qb->param($metadata->name, ELGG_VALUE_STRING),
238  'entity_guid' => $qb->param($metadata->entity_guid, ELGG_VALUE_INTEGER),
239  'value' => $qb->param($metadata->value, $metadata->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER),
240  'value_type' => $qb->param($metadata->value_type, ELGG_VALUE_STRING),
241  'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
242  ]);
243 
244  $id = $this->db->insertData($qb);
245 
246  if ($id === 0) {
247  return false;
248  }
249 
250  $metadata->id = (int) $id;
251  $metadata->time_created = $time_created;
252 
253  if (!$this->events->trigger('create', 'metadata', $metadata)) {
254  $this->delete($metadata);
255 
256  return false;
257  }
258 
259  $metadata->getEntity()?->invalidateCache();
260 
261  $this->events->triggerAfter('create', 'metadata', $metadata);
262 
263  return $id;
264  }
265 
273  public function update(\ElggMetadata $metadata): bool {
274  if (!$this->entityTable->exists($metadata->entity_guid)) {
275  elgg_log("Can't update metadata to a non-existing entity_guid", \Psr\Log\LogLevel::ERROR);
276  return false;
277  }
278 
279  if (!$this->events->triggerBefore('update', 'metadata', $metadata)) {
280  return false;
281  }
282 
283  $qb = Update::table(self::TABLE_NAME);
284  $qb->set('name', $qb->param($metadata->name, ELGG_VALUE_STRING))
285  ->set('value', $qb->param($metadata->value, $metadata->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER))
286  ->set('value_type', $qb->param($metadata->value_type, ELGG_VALUE_STRING))
287  ->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
288 
289  $result = $this->db->updateData($qb);
290 
291  if ($result === false) {
292  return false;
293  }
294 
295  $metadata->getEntity()?->invalidateCache();
296 
297  $this->events->trigger('update', 'metadata', $metadata);
298  $this->events->triggerAfter('update', 'metadata', $metadata);
299 
300  return true;
301  }
302 
314  public function getAll(array $options = []) {
315  return Metadata::find($options);
316  }
317 
329  public function getRowsForGuids(array $guids): array {
330  $qb = Select::fromTable(self::TABLE_NAME);
331  $qb->select('*')
332  ->where($qb->compare('entity_guid', 'IN', $guids, ELGG_VALUE_GUID))
333  ->orderBy('entity_guid', 'asc')
334  ->addOrderBy('time_created', 'asc')
335  ->addOrderBy('id', 'asc');
336 
337  return $this->db->getData($qb, function ($row) {
338  return new \ElggMetadata($row);
339  });
340  }
341 
357  public function deleteAll(array $options): bool {
358  $required = [
359  'guid', 'guids',
360  'metadata_name', 'metadata_names',
361  'metadata_value', 'metadata_values',
362  ];
363 
364  $found = false;
365  foreach ($required as $key) {
366  // check that it exists and is something.
367  if (isset($options[$key]) && !elgg_is_empty($options[$key])) {
368  $found = true;
369  break;
370  }
371  }
372 
373  if (!$found) {
374  // requirements not met
375  throw new InvalidArgumentException(__METHOD__ . ' requires at least one of the following keys in $options: ' . implode(', ', $required));
376  }
377 
378  // This moved last in case an object's constructor sets metadata. Currently the batch
379  // delete process has to create the entity to delete its metadata. See #5214
380  if (empty($options['guid'])) {
381  $this->access_cache->clear();
382  $this->metadata_cache->clear();
383  } else {
384  $this->entityTable->invalidateCache($options['guid']);
385  }
386 
387  $options['batch'] = true;
388  $options['batch_size'] = 50;
389  $options['batch_inc_offset'] = false;
390 
391  $metadata = Metadata::find($options);
392  $count = $metadata->count();
393 
394  if (!$count) {
395  return true;
396  }
397 
398  $success = 0;
399  /* @var $md \ElggMetadata */
400  foreach ($metadata as $md) {
401  if ($md->delete()) {
402  $success++;
403  }
404  }
405 
406  return $success === $count;
407  }
408 
417  public function getIDsByName(int $entity_guid, string $name) {
418  $cached_metadata = $this->metadata_cache->load($entity_guid);
419  if ($cached_metadata !== null) {
420  $ids = [];
421  foreach ($cached_metadata as $md) {
422  if ($md->name !== $name) {
423  continue;
424  }
425 
426  $ids[] = $md->id;
427  }
428  } else {
429  $qb = Select::fromTable(self::TABLE_NAME);
430  $qb->select('id')
431  ->where($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
432  ->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
433 
434  $callback = function (\stdClass $row) {
435  return (int) $row->id;
436  };
437 
438  $ids = $this->db->getData($qb, $callback);
439  }
440 
441  if (empty($ids)) {
442  return null;
443  }
444 
445  if (is_array($ids) && count($ids) === 1) {
446  return array_shift($ids);
447  }
448 
449  return $ids;
450  }
451 }
getCurrentTime($modifier='')
Get the (cloned) time.
Definition: TimeUsing.php:25
$deleted
Definition: delete.php:25
if(! $user||! $user->canDelete()) $name
Definition: delete.php:22
$id
Generic annotation delete action.
Definition: delete.php:6
$entity_guid
Action for adding and editing comments.
Definition: save.php:6
$guids
Activates all specified installed and inactive plugins.
Definition: activate_all.php:9
$count
Definition: ban.php:24
ElggMetadata.
In memory cache of known metadata values stored by entity.
Builds clauses for filtering entities by properties in metadata table.
Extends QueryBuilder with ORDER BY clauses.
static fromTable(string $table)
Returns a QueryBuilder for deleting data from a given table.
Definition: Delete.php:17
Entity table database service.
Definition: EntityTable.php:24
static intoTable(string $table)
Returns a QueryBuilder for inserting data in a given table.
Definition: Insert.php:17
This class interfaces with the database to perform CRUD operations on metadata.
deleteAll(array $options)
Deletes metadata based on $options.
getTags(array $options=[])
Get popular tags and their frequencies.
getAll(array $options=[])
Returns metadata.
getRowsForGuids(array $guids)
Returns metadata rows.
getIDsByName(int $entity_guid, string $name)
Returns ID(s) of metadata with a particular name attached to an entity.
update(\ElggMetadata $metadata)
Update a specific piece of metadata.
__construct(protected AccessCache $access_cache, protected MetadataCache $metadata_cache, protected Database $db, protected Events $events, protected EntityTable $entityTable)
Constructor.
create(\ElggMetadata $metadata, bool $allow_multiple=false)
Create a new metadata object, or update an existing one (if multiple is allowed)
Database abstraction query builder.
Query builder for fetching data from the database.
Definition: Select.php:8
static fromTable(string $table, ?string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
static table(string $table)
Returns a QueryBuilder for updating data in a given table.
Definition: Update.php:17
The Elgg database.
Definition: Database.php:26
Exception thrown if an argument is not of the expected type.
Exception that represents error in the program logic.
const ELGG_VALUE_STRING
Definition: constants.php:112
const ELGG_VALUE_GUID
Definition: constants.php:113
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:111
if($who_can_change_language==='nobody') elseif($who_can_change_language==='admin_only' &&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
if(! $user instanceof \ElggUser) $time_created
Definition: online.php:13
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
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:240
elgg_is_empty($value)
Check if a value isn't empty, but allow 0 and '0'.
Definition: input.php:176
$defaults
Generic entity header upload helper.
Definition: header.php:6
$data value
Definition: default.php:23
$required
Definition: label.php:12
$qb
Definition: queue.php:14
if($container instanceof ElggGroup && $container->guid !=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$metadata
Output annotation metadata.
Definition: metadata.php:9