Elgg  Version 4.3
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
6 use Elgg\Database;
12 
19 
20  use TimeUsing;
21 
25  protected $metadata_cache;
26 
30  protected $db;
31 
35  protected $events;
36 
40  protected $entityTable;
41 
42  const MYSQL_TEXT_BYTE_LIMIT = 65535;
43 
52  public function __construct(
54  Database $db,
57  ) {
58  $this->metadata_cache = $metadata_cache;
59  $this->db = $db;
60  $this->events = $events;
61  $this->entityTable = $entityTable;
62  }
63 
78  public function getTags(array $options = []) {
79  $defaults = [
80  'threshold' => 1,
81  'tag_names' => [],
82  ];
83 
84  $options = array_merge($defaults, $options);
85 
86  $singulars = ['tag_name'];
87  $options = QueryOptions::normalizePluralOptions($options, $singulars);
88 
89  $tag_names = elgg_extract('tag_names', $options, ['tags'], false);
90 
91  $threshold = elgg_extract('threshold', $options, 1, false);
92 
93  unset($options['tag_names']);
94  unset($options['threshold']);
95 
96  // custom selects
97  $options['selects'] = [
98  function(QueryBuilder $qb, $main_alias) {
99  return "{$main_alias}.value AS tag";
100  },
101  function(QueryBuilder $qb, $main_alias) {
102  return "COUNT({$main_alias}.id) AS total";
103  },
104  ];
105 
106  // additional wheres
107  $wheres = (array) elgg_extract('wheres', $options, []);
108  $wheres[] = function(QueryBuilder $qb, $main_alias) use ($tag_names) {
109  return $qb->compare("{$main_alias}.name", 'in', $tag_names, ELGG_VALUE_STRING);
110  };
111  $wheres[] = function(QueryBuilder $qb, $main_alias) {
112  return $qb->compare("{$main_alias}.value", '!=', '', ELGG_VALUE_STRING);
113  };
114  $options['wheres'] = $wheres;
115 
116  // custom group by
117  $options['group_by'] = [
118  function(QueryBuilder $qb, $main_alias) {
119  return "{$main_alias}.value";
120  },
121  ];
122 
123  // having
124  $having = (array) elgg_extract('having', $options, []);
125  $having[] = function(QueryBuilder $qb, $main_alias) use ($threshold) {
126  return $qb->compare('total', '>=', $threshold, ELGG_VALUE_INTEGER);
127  };
128  $options['having'] = $having;
129 
130  // order by
131  $options['order_by'] = [
132  new OrderByClause('total', 'desc'),
133  ];
134 
135  // custom callback
136  $options['callback'] = function($row) {
137  $result = new \stdClass();
138  $result->tag = $row->tag;
139  $result->total = (int) $row->total;
140 
141  return $result;
142  };
143 
144  return $this->getAll($options);
145  }
146 
156  public function get($id) {
157  $qb = Select::fromTable('metadata');
158  $qb->select('*');
159 
160  $where = new MetadataWhereClause();
161  $where->ids = $id;
162  $qb->addClause($where);
163 
164  $row = $this->db->getDataRow($qb);
165  if (!empty($row)) {
166  return new \ElggMetadata($row);
167  }
168 
169  return false;
170  }
171 
179  public function delete(\ElggMetadata $metadata) {
180  if (!$metadata->id) {
181  return false;
182  }
183 
184  if (!_elgg_services()->events->trigger('delete', 'metadata', $metadata)) {
185  return false;
186  }
187 
188  $qb = Delete::fromTable('metadata');
189  $qb->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
190 
191  $deleted = $this->db->deleteData($qb);
192 
193  if ($deleted) {
194  $this->metadata_cache->clear($metadata->entity_guid);
195  }
196 
197  return $deleted !== false;
198  }
199 
212  public function create(\ElggMetadata $metadata, $allow_multiple = false) {
213  if (!isset($metadata->value) || !isset($metadata->entity_guid)) {
214  elgg_log("Metadata must have a value and entity guid", 'ERROR');
215  return false;
216  }
217 
218  if (!$this->entityTable->exists($metadata->entity_guid)) {
219  elgg_log("Can't create metadata on a non-existing entity_guid", 'ERROR');
220  return false;
221  }
222 
223  if (!is_scalar($metadata->value)) {
224  elgg_log("To set multiple metadata values use ElggEntity::setMetadata", 'ERROR');
225  return false;
226  }
227 
228  if ($metadata->id) {
229  if ($this->update($metadata)) {
230  return $metadata->id;
231  }
232  }
233 
234  if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) {
235  elgg_log("Metadata '{$metadata->name}' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
236  }
237 
238  if (!$allow_multiple) {
239  $id = $this->getIDsByName($metadata->entity_guid, $metadata->name);
240 
241  if (is_array($id)) {
242  throw new LogicException("
243  Multiple '{$metadata->name}' metadata values exist for entity [guid: {$metadata->entity_guid}].
244  Use ElggEntity::setMetadata()
245  ");
246  }
247 
248  if ($id > 0) {
249  $metadata->id = $id;
250 
251  if ($this->update($metadata)) {
252  return $metadata->id;
253  }
254  }
255  }
256 
257  if (!$this->events->triggerBefore('create', 'metadata', $metadata)) {
258  return false;
259  }
260 
261  $time_created = $this->getCurrentTime()->getTimestamp();
262 
263  $qb = Insert::intoTable('metadata');
264  $qb->values([
265  'name' => $qb->param($metadata->name, ELGG_VALUE_STRING),
266  'entity_guid' => $qb->param($metadata->entity_guid, ELGG_VALUE_INTEGER),
267  'value' => $qb->param($metadata->value, $metadata->value_type === 'text' ? ELGG_VALUE_STRING : ELGG_VALUE_INTEGER),
268  'value_type' => $qb->param($metadata->value_type, ELGG_VALUE_STRING),
269  'time_created' => $qb->param($time_created, ELGG_VALUE_INTEGER),
270  ]);
271 
272  $id = $this->db->insertData($qb);
273 
274  if ($id === false) {
275  return false;
276  }
277 
278  $metadata->id = (int) $id;
279  $metadata->time_created = $time_created;
280 
281  if ($this->events->trigger('create', 'metadata', $metadata)) {
282  $this->metadata_cache->clear($metadata->entity_guid);
283 
284  $this->events->triggerAfter('create', 'metadata', $metadata);
285 
286  return $id;
287  } else {
288  $this->delete($metadata);
289 
290  return false;
291  }
292  }
293 
301  public function update(\ElggMetadata $metadata) {
302 
303  if (!$this->entityTable->exists($metadata->entity_guid)) {
304  elgg_log("Can't update metadata to a non-existing entity_guid", 'ERROR');
305  return false;
306  }
307 
308  if (!$this->events->triggerBefore('update', 'metadata', $metadata)) {
309  return false;
310  }
311 
312  if (strlen($metadata->value) > self::MYSQL_TEXT_BYTE_LIMIT) {
313  elgg_log("Metadata '{$metadata->name}' is above the MySQL TEXT size limit and may be truncated.", 'WARNING');
314  }
315 
316  $qb = Update::table('metadata');
317  $qb->set('name', $qb->param($metadata->name, ELGG_VALUE_STRING))
318  ->set('value', $qb->param($metadata->value, $metadata->value_type === 'integer' ? ELGG_VALUE_INTEGER : ELGG_VALUE_STRING))
319  ->set('value_type', $qb->param($metadata->value_type, ELGG_VALUE_STRING))
320  ->where($qb->compare('id', '=', $metadata->id, ELGG_VALUE_INTEGER));
321 
322  $result = $this->db->updateData($qb);
323 
324  if ($result === false) {
325  return false;
326  }
327 
328  $this->metadata_cache->clear($metadata->entity_guid);
329 
330  $this->events->trigger('update', 'metadata', $metadata);
331  $this->events->triggerAfter('update', 'metadata', $metadata);
332 
333  return true;
334  }
335 
347  public function getAll(array $options = []) {
348 
349  $options['metastring_type'] = 'metadata';
350  $options = QueryOptions::normalizeMetastringOptions($options);
351 
352  return Metadata::find($options);
353  }
354 
366  public function getRowsForGuids(array $guids) {
367 
368  $qb = Select::fromTable('metadata');
369  $qb->select('*')
370  ->where($qb->compare('entity_guid', 'IN', $guids, ELGG_VALUE_GUID))
371  ->orderBy('entity_guid', 'asc')
372  ->addOrderBy('time_created', 'asc')
373  ->addOrderBy('id', 'asc');
374 
375  return $this->db->getData($qb, function ($row) {
376  return new \ElggMetadata($row);
377  });
378  }
379 
394  public function deleteAll(array $options) {
395  $required = [
396  'guid', 'guids',
397  'metadata_name', 'metadata_names',
398  'metadata_value', 'metadata_values',
399  ];
400 
401  $found = false;
402  foreach ($required as $key) {
403  // check that it exists and is something.
404  if (isset($options[$key]) && !elgg_is_empty($options[$key])) {
405  $found = true;
406  break;
407  }
408  }
409 
410  if (!$found) {
411  // requirements not met
412  return false;
413  }
414 
415  // This moved last in case an object's constructor sets metadata. Currently the batch
416  // delete process has to create the entity to delete its metadata. See #5214
417  $this->metadata_cache->invalidateByOptions($options);
418 
419  $options['batch'] = true;
420  $options['batch_size'] = 50;
421  $options['batch_inc_offset'] = false;
422 
423  $metadata = Metadata::find($options);
424  $count = $metadata->count();
425 
426  if (!$count) {
427  return;
428  }
429 
430  $success = 0;
431  /* @var $md \ElggMetadata */
432  foreach ($metadata as $md) {
433  if ($md->delete()) {
434  $success++;
435  }
436  }
437 
438  return $success == $count;
439  }
440 
449  protected function getIDsByName($entity_guid, $name) {
450  if ($this->metadata_cache->isLoaded($entity_guid)) {
451  $ids = $this->metadata_cache->getSingleId($entity_guid, $name);
452  } else {
453  $qb = Select::fromTable('metadata');
454  $qb->select('id')
455  ->where($qb->compare('entity_guid', '=', $entity_guid, ELGG_VALUE_INTEGER))
456  ->andWhere($qb->compare('name', '=', $name, ELGG_VALUE_STRING));
457 
458  $callback = function (\stdClass $row) {
459  return (int) $row->id;
460  };
461 
462  $ids = $this->db->getData($qb, $callback);
463  }
464 
465  if (empty($ids)) {
466  return null;
467  }
468 
469  if (is_array($ids) && count($ids) === 1) {
470  return array_shift($ids);
471  }
472 
473  return $ids;
474  }
475 }
$deleted
Definition: delete.php:25
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: Repository.php:86
getAll(array $options=[])
Returns metadata.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
static table($table, $alias=null)
{}
Definition: Update.php:13
$defaults
The Elgg database.
Definition: Database.php:25
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:126
const ELGG_VALUE_GUID
Definition: constants.php:128
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:179
$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:399
$time_created
Definition: online.php:18
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.
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:547
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:127
$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:638
getIDsByName($entity_guid, $name)
Returns ID(s) of metadata with a particular name attached to an entity.
$id
Generic annotation delete action.
Definition: delete.php:6
$qb
Definition: queue.php:11
Builds clauses for filtering entities by properties in metadata table.
create(\ElggMetadata $metadata, $allow_multiple=false)
Create a new metadata object, or update an existing one (if multiple is allowed)
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.