session = $session; } /** * Set the visible metadata for an entity in the cache * * Note this does NOT invalidate any other part of the cache. * * @param int $entity_guid The GUID of the entity * @param array $values The metadata values to cache * @return void * * @access private For testing only */ public function inject($entity_guid, array $values) { $this->values[$this->getAccessKey()][$entity_guid] = $values; } /** * Get the metadata for a particular name. Note, this can return an array of values. * * Warning: You should always call isLoaded() beforehand to verify that this * function's return value can be trusted. * * @see isLoaded * * @param int $entity_guid The GUID of the entity * @param string $name The metadata name * * @return array|string|int|null null = value does not exist */ public function getSingle($entity_guid, $name) { $access_key = $this->getAccessKey(); if (isset($this->values[$access_key][$entity_guid]) && array_key_exists($name, $this->values[$access_key][$entity_guid])) { return $this->values[$access_key][$entity_guid][$name]; } else { return null; } } /** * Forget about all metadata for an entity. For safety this affects all access states. * * @param int $entity_guid The GUID of the entity * @return void */ public function clear($entity_guid) { foreach (array_keys($this->values) as $access_key) { unset($this->values[$access_key][$entity_guid]); } } /** * If true, getSingle() will return an accurate values from the DB * * @param int $entity_guid The GUID of the entity * @return bool */ public function isLoaded($entity_guid) { $access_key = $this->getAccessKey(); if (empty($this->values[$access_key])) { return false; } return array_key_exists($entity_guid, $this->values[$access_key]); } /** * Clear entire cache * * @return void */ public function clearAll() { $this->values = array(); } /** * Invalidate based on options passed to the global *_metadata functions * * @param array $options Options passed to elgg_(delete|disable|enable)_metadata * "guid" if given, invalidation will be limited to this entity * @return void */ public function invalidateByOptions(array $options) { if (empty($options['guid'])) { $this->clearAll(); } else { $this->clear($options['guid']); } } /** * Populate the cache from a set of entities * * @param int|array $guids Array of or single GUIDs * @return void */ public function populateFromEntities($guids) { if (empty($guids)) { return; } $access_key = $this->getAccessKey(); if (!is_array($guids)) { $guids = array($guids); } $guids = array_unique($guids); // could be useful at some point in future //$guids = $this->filterMetadataHeavyEntities($guids); $db_prefix = _elgg_services()->db->getTablePrefix(); $options = array( 'guids' => $guids, 'limit' => 0, 'callback' => false, 'distinct' => false, 'joins' => array( "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", "JOIN {$db_prefix}metastrings n ON n_table.name_id = n.id", ), 'selects' => array('n.string AS name', 'v.string AS value'), 'order_by' => 'n_table.entity_guid, n_table.time_created ASC, n_table.id ASC', // @todo don't know why this is necessary 'wheres' => array(_elgg_get_access_where_sql(array( 'table_alias' => 'n_table', 'guid_column' => 'entity_guid', ))), ); $data = _elgg_services()->metadataTable->getAll($options); // make sure we show all entities as loaded foreach ($guids as $guid) { $this->values[$access_key][$guid] = null; } // build up metadata for each entity, save when GUID changes (or data ends) $last_guid = null; $metadata = array(); $last_row_idx = count($data) - 1; foreach ($data as $i => $row) { $name = $row->name; $value = ($row->value_type === 'text') ? $row->value : (int) $row->value; $guid = $row->entity_guid; if ($guid !== $last_guid) { if ($last_guid) { $this->values[$access_key][$last_guid] = $metadata; } $metadata = array(); } if (isset($metadata[$name])) { $metadata[$name] = (array) $metadata[$name]; $metadata[$name][] = $value; } else { $metadata[$name] = $value; } if (($i == $last_row_idx)) { $this->values[$access_key][$guid] = $metadata; } $last_guid = $guid; } } /** * Filter out entities whose concatenated metadata values (INTs casted as string) * exceed a threshold in characters. This could be used to avoid overpopulating the * cache if RAM usage becomes an issue. * * @param array $guids GUIDs of entities to examine * @param int $limit Limit in characters of all metadata (with ints casted to strings) * @return array */ public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) { $db_prefix = _elgg_services()->config->get('dbprefix'); $options = array( 'guids' => $guids, 'limit' => 0, 'callback' => false, 'joins' => "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", 'selects' => array('SUM(LENGTH(v.string)) AS bytes'), 'order_by' => 'n_table.entity_guid, n_table.time_created ASC', 'group_by' => 'n_table.entity_guid', ); $data = _elgg_services()->metadataTable->getAll($options); // don't cache if metadata for entity is over 10MB (or rolled INT) foreach ($data as $row) { if ($row->bytes > $limit || $row->bytes < 0) { array_splice($guids, array_search($row->entity_guid, $guids), 1); } } return $guids; } /** * Get a key to represent the access ability of the system. This is used to shard the cache array. * * @return string E.g. "ignored" or "123" */ protected function getAccessKey() { if ($this->session->getIgnoreAccess()) { return "ignored"; } return (string)$this->session->getLoggedInUserGuid(); } }