aboutsummaryrefslogtreecommitdiff
path: root/engine/lib/metastrings.php
diff options
context:
space:
mode:
Diffstat (limited to 'engine/lib/metastrings.php')
-rw-r--r--engine/lib/metastrings.php903
1 files changed, 903 insertions, 0 deletions
diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php
new file mode 100644
index 000000000..57d876c06
--- /dev/null
+++ b/engine/lib/metastrings.php
@@ -0,0 +1,903 @@
+<?php
+/**
+ * Elgg metastrngs
+ * Functions to manage object metastrings.
+ *
+ * @package Elgg.Core
+ * @subpackage DataModel.MetaStrings
+ */
+
+/** Cache metastrings for a page */
+global $METASTRINGS_CACHE;
+$METASTRINGS_CACHE = array();
+
+/** Keep a record of strings we know don't exist */
+global $METASTRINGS_DEADNAME_CACHE;
+$METASTRINGS_DEADNAME_CACHE = array();
+
+
+
+/**
+ * Return the meta string id for a given tag, or false.
+ *
+ * @param string $string The value to store
+ * @param bool $case_sensitive Do we want to make the query case sensitive?
+ * If not there may be more than one result
+ *
+ * @return int|array|false meta string id, array of ids or false if none found
+ */
+function get_metastring_id($string, $case_sensitive = TRUE) {
+ global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE;
+
+ $string = sanitise_string($string);
+
+ // caching doesn't work for case insensitive searches
+ if ($case_sensitive) {
+ $result = array_search($string, $METASTRINGS_CACHE, true);
+
+ if ($result !== false) {
+ elgg_log("** Returning id for string:$string from cache.");
+ return $result;
+ }
+
+ // See if we have previously looked for this and found nothing
+ if (in_array($string, $METASTRINGS_DEADNAME_CACHE, true)) {
+ return false;
+ }
+
+ // Experimental memcache
+ $msfc = null;
+ static $metastrings_memcache;
+ if ((!$metastrings_memcache) && (is_memcache_available())) {
+ $metastrings_memcache = new ElggMemcache('metastrings_memcache');
+ }
+ if ($metastrings_memcache) {
+ $msfc = $metastrings_memcache->load($string);
+ }
+ if ($msfc) {
+ return $msfc;
+ }
+ }
+
+ // Case sensitive
+ if ($case_sensitive) {
+ $query = "SELECT * from {$CONFIG->dbprefix}metastrings where string= BINARY '$string' limit 1";
+ } else {
+ $query = "SELECT * from {$CONFIG->dbprefix}metastrings where string = '$string'";
+ }
+
+ $row = FALSE;
+ $metaStrings = get_data($query);
+ if (is_array($metaStrings)) {
+ if (sizeof($metaStrings) > 1) {
+ $ids = array();
+ foreach ($metaStrings as $metaString) {
+ $ids[] = $metaString->id;
+ }
+ return $ids;
+ } else if (isset($metaStrings[0])) {
+ $row = $metaStrings[0];
+ }
+ }
+
+ if ($row) {
+ $METASTRINGS_CACHE[$row->id] = $row->string; // Cache it
+
+ // Attempt to memcache it if memcache is available
+ if ($metastrings_memcache) {
+ $metastrings_memcache->save($row->string, $row->id);
+ }
+
+ elgg_log("** Cacheing string '{$row->string}'");
+
+ return $row->id;
+ } else {
+ $METASTRINGS_DEADNAME_CACHE[$string] = $string;
+ }
+
+ return false;
+}
+
+/**
+ * When given an ID, returns the corresponding metastring
+ *
+ * @param int $id Metastring ID
+ *
+ * @return string Metastring
+ */
+function get_metastring($id) {
+ global $CONFIG, $METASTRINGS_CACHE;
+
+ $id = (int) $id;
+
+ if (isset($METASTRINGS_CACHE[$id])) {
+ elgg_log("** Returning string for id:$id from cache.");
+
+ return $METASTRINGS_CACHE[$id];
+ }
+
+ $row = get_data_row("SELECT * from {$CONFIG->dbprefix}metastrings where id='$id' limit 1");
+ if ($row) {
+ $METASTRINGS_CACHE[$id] = $row->string; // Cache it
+ elgg_log("** Cacheing string '{$row->string}'");
+
+ return $row->string;
+ }
+
+ return false;
+}
+
+/**
+ * Add a metastring.
+ * It returns the id of the tag, whether by creating it or updating it.
+ *
+ * @param string $string The value (whatever that is) to be stored
+ * @param bool $case_sensitive Do we want to make the query case sensitive?
+ *
+ * @return mixed Integer tag or false.
+ */
+function add_metastring($string, $case_sensitive = true) {
+ global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE;
+
+ $sanstring = sanitise_string($string);
+
+ $id = get_metastring_id($string, $case_sensitive);
+ if ($id) {
+ return $id;
+ }
+
+ $result = insert_data("INSERT into {$CONFIG->dbprefix}metastrings (string) values ('$sanstring')");
+ if ($result) {
+ $METASTRINGS_CACHE[$result] = $string;
+ if (isset($METASTRINGS_DEADNAME_CACHE[$string])) {
+ unset($METASTRINGS_DEADNAME_CACHE[$string]);
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Delete any orphaned entries in metastrings. This is run by the garbage collector.
+ *
+ * @return bool
+ * @access private
+ */
+function delete_orphaned_metastrings() {
+ global $CONFIG;
+
+ // If memcache is enabled then we need to flush it of deleted values
+ if (is_memcache_available()) {
+ $select_query = "
+ SELECT *
+ from {$CONFIG->dbprefix}metastrings where
+ (
+ (id not in (select name_id from {$CONFIG->dbprefix}metadata)) AND
+ (id not in (select value_id from {$CONFIG->dbprefix}metadata)) AND
+ (id not in (select name_id from {$CONFIG->dbprefix}annotations)) AND
+ (id not in (select value_id from {$CONFIG->dbprefix}annotations))
+ )";
+
+ $dead = get_data($select_query);
+ if ($dead) {
+ static $metastrings_memcache;
+ if (!$metastrings_memcache) {
+ $metastrings_memcache = new ElggMemcache('metastrings_memcache');
+ }
+
+ foreach ($dead as $d) {
+ $metastrings_memcache->delete($d->string);
+ }
+ }
+ }
+
+ $query = "
+ DELETE
+ from {$CONFIG->dbprefix}metastrings where
+ (
+ (id not in (select name_id from {$CONFIG->dbprefix}metadata)) AND
+ (id not in (select value_id from {$CONFIG->dbprefix}metadata)) AND
+ (id not in (select name_id from {$CONFIG->dbprefix}annotations)) AND
+ (id not in (select value_id from {$CONFIG->dbprefix}annotations))
+ )";
+
+ return delete_data($query);
+}
+
+/**
+ * Returns an array of either ElggAnnotation or ElggMetadata objects.
+ * Accepts all elgg_get_entities() options for entity restraints.
+ *
+ * @see elgg_get_entities
+ *
+ * @param array $options Array in format:
+ *
+ * metastring_names => NULL|ARR metastring names
+ *
+ * metastring_values => NULL|ARR metastring values
+ *
+ * metastring_ids => NULL|ARR metastring ids
+ *
+ * metastring_case_sensitive => BOOL Overall Case sensitive
+ *
+ * metastring_owner_guids => NULL|ARR Guids for metadata owners
+ *
+ * metastring_created_time_lower => INT Lower limit for created time.
+ *
+ * metastring_created_time_upper => INT Upper limit for created time.
+ *
+ * metastring_calculation => STR Perform the MySQL function on the metastring values
+ * returned.
+ * This differs from egef_annotation_calculation in that
+ * it returns only the calculation of all annotation values.
+ * You can sum, avg, count, etc. egef_annotation_calculation()
+ * returns ElggEntities ordered by a calculation on their
+ * annotation values.
+ *
+ * metastring_type => STR metadata or annotation(s)
+ *
+ * @return mixed
+ * @access private
+ */
+function elgg_get_metastring_based_objects($options) {
+ $options = elgg_normalize_metastrings_options($options);
+
+ switch ($options['metastring_type']) {
+ case 'metadata':
+ $type = 'metadata';
+ $callback = 'row_to_elggmetadata';
+ break;
+
+ case 'annotations':
+ case 'annotation':
+ $type = 'annotations';
+ $callback = 'row_to_elggannotation';
+ break;
+
+ default:
+ return false;
+ }
+
+ $defaults = array(
+ // entities
+ 'types' => ELGG_ENTITIES_ANY_VALUE,
+ 'subtypes' => ELGG_ENTITIES_ANY_VALUE,
+ 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
+
+ 'guids' => ELGG_ENTITIES_ANY_VALUE,
+ 'owner_guids' => ELGG_ENTITIES_ANY_VALUE,
+ 'container_guids' => ELGG_ENTITIES_ANY_VALUE,
+ 'site_guids' => get_config('site_guid'),
+
+ 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE,
+ 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE,
+ 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
+ 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
+
+ // options are normalized to the plural in case we ever add support for them.
+ 'metastring_names' => ELGG_ENTITIES_ANY_VALUE,
+ 'metastring_values' => ELGG_ENTITIES_ANY_VALUE,
+ //'metastring_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
+ //'metastring_name_value_pairs_operator' => 'AND',
+
+ 'metastring_case_sensitive' => TRUE,
+ //'order_by_metastring' => array(),
+ 'metastring_calculation' => ELGG_ENTITIES_NO_VALUE,
+
+ 'metastring_created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
+ 'metastring_created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
+
+ 'metastring_owner_guids' => ELGG_ENTITIES_ANY_VALUE,
+
+ 'metastring_ids' => ELGG_ENTITIES_ANY_VALUE,
+
+ // sql
+ 'order_by' => 'n_table.time_created asc',
+ 'limit' => 10,
+ 'offset' => 0,
+ 'count' => FALSE,
+ 'selects' => array(),
+ 'wheres' => array(),
+ 'joins' => array(),
+
+ 'callback' => $callback
+ );
+
+ // @todo Ignore site_guid right now because of #2910
+ $options['site_guid'] = ELGG_ENTITIES_ANY_VALUE;
+
+ $options = array_merge($defaults, $options);
+
+ // can't use helper function with type_subtype_pair because
+ // it's already an array...just need to merge it
+ if (isset($options['type_subtype_pair'])) {
+ if (isset($options['type_subtype_pairs'])) {
+ $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
+ $options['type_subtype_pair']);
+ } else {
+ $options['type_subtype_pairs'] = $options['type_subtype_pair'];
+ }
+ }
+
+ $singulars = array(
+ 'type', 'subtype', 'type_subtype_pair',
+ 'guid', 'owner_guid', 'container_guid', 'site_guid',
+ 'metastring_name', 'metastring_value',
+ 'metastring_owner_guid', 'metastring_id',
+ 'select', 'where', 'join'
+ );
+
+ $options = elgg_normalise_plural_options_array($options, $singulars);
+
+ if (!$options) {
+ return false;
+ }
+
+ $db_prefix = elgg_get_config('dbprefix');
+
+ // evaluate where clauses
+ if (!is_array($options['wheres'])) {
+ $options['wheres'] = array($options['wheres']);
+ }
+
+ $wheres = $options['wheres'];
+
+ // entities
+ $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'],
+ $options['subtypes'], $options['type_subtype_pairs']);
+
+ $wheres[] = elgg_get_guid_based_where_sql('e.guid', $options['guids']);
+ $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']);
+ $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']);
+ $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']);
+
+ $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'],
+ $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
+
+
+ $wheres[] = elgg_get_entity_time_where_sql('n_table', $options['metastring_created_time_upper'],
+ $options['metastring_created_time_lower'], null, null);
+
+ $wheres[] = elgg_get_guid_based_where_sql('n_table.owner_guid',
+ $options['metastring_owner_guids']);
+
+ // see if any functions failed
+ // remove empty strings on successful functions
+ foreach ($wheres as $i => $where) {
+ if ($where === FALSE) {
+ return FALSE;
+ } elseif (empty($where)) {
+ unset($wheres[$i]);
+ }
+ }
+
+ // remove identical where clauses
+ $wheres = array_unique($wheres);
+
+ // evaluate join clauses
+ if (!is_array($options['joins'])) {
+ $options['joins'] = array($options['joins']);
+ }
+
+ $joins = $options['joins'];
+ $joins[] = "JOIN {$db_prefix}entities e ON n_table.entity_guid = e.guid";
+
+ // evaluate selects
+ if (!is_array($options['selects'])) {
+ $options['selects'] = array($options['selects']);
+ }
+
+ $selects = $options['selects'];
+
+ // For performance reasons we don't want the joins required for metadata / annotations
+ // unless we're going through one of their callbacks.
+ // this means we expect the functions passing different callbacks to pass their required joins.
+ // If we're doing a calculation
+ $custom_callback = ($options['callback'] == 'row_to_elggmetadata'
+ || $options['callback'] == 'row_to_elggannotation');
+ $is_calculation = $options['metastring_calculation'] ? true : false;
+
+ if ($custom_callback || $is_calculation) {
+ $joins[] = "JOIN {$db_prefix}metastrings n on n_table.name_id = n.id";
+ $joins[] = "JOIN {$db_prefix}metastrings v on n_table.value_id = v.id";
+
+ $selects[] = 'n.string as name';
+ $selects[] = 'v.string as value';
+ }
+
+ foreach ($joins as $i => $join) {
+ if ($join === FALSE) {
+ return FALSE;
+ } elseif (empty($join)) {
+ unset($joins[$i]);
+ }
+ }
+
+ // metastrings
+ $metastring_clauses = elgg_get_metastring_sql('n_table', $options['metastring_names'],
+ $options['metastring_values'], null, $options['metastring_ids'],
+ $options['metastring_case_sensitive']);
+
+ if ($metastring_clauses) {
+ $wheres = array_merge($wheres, $metastring_clauses['wheres']);
+ $joins = array_merge($joins, $metastring_clauses['joins']);
+ } else {
+ $wheres[] = get_access_sql_suffix('n_table');
+ }
+
+ if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {
+ $selects = array_unique($selects);
+ // evalutate selects
+ $select_str = '';
+ if ($selects) {
+ foreach ($selects as $select) {
+ $select_str .= ", $select";
+ }
+ }
+
+ $query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table";
+ } elseif ($options['count']) {
+ // count is over the entities
+ $query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table";
+ } else {
+ $query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table";
+ }
+
+ // remove identical join clauses
+ $joins = array_unique($joins);
+
+ // add joins
+ foreach ($joins as $j) {
+ $query .= " $j ";
+ }
+
+ // add wheres
+ $query .= ' WHERE ';
+
+ foreach ($wheres as $w) {
+ $query .= " $w AND ";
+ }
+
+ // Add access controls
+ $query .= get_access_sql_suffix('e');
+
+ // reverse order by
+ if (isset($options['reverse_order_by']) && $options['reverse_order_by']) {
+ $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'],
+ $defaults['order_by']);
+ }
+
+ if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {
+ if (isset($options['group_by'])) {
+ $options['group_by'] = sanitise_string($options['group_by']);
+ $query .= " GROUP BY {$options['group_by']}";
+ }
+
+ if (isset($options['order_by']) && $options['order_by']) {
+ $options['order_by'] = sanitise_string($options['order_by']);
+ $query .= " ORDER BY {$options['order_by']}, n_table.id";
+ }
+
+ if ($options['limit']) {
+ $limit = sanitise_int($options['limit']);
+ $offset = sanitise_int($options['offset'], false);
+ $query .= " LIMIT $offset, $limit";
+ }
+
+ $dt = get_data($query, $options['callback']);
+ return $dt;
+ } else {
+ $result = get_data_row($query);
+ return $result->calculation;
+ }
+}
+
+/**
+ * Returns an array of joins and wheres for use in metastrings.
+ *
+ * @note The $pairs is reserved for name/value pairs if we want to implement those.
+ *
+ * @param string $table The annotation or metadata table name or alias
+ * @param array $names An array of names
+ * @param array $values An array of values
+ * @param array $pairs Name / value pairs. Not currently used.
+ * @param array $ids Metastring IDs
+ * @param bool $case_sensitive Should name and values be case sensitive?
+ *
+ * @return array
+ * @access private
+ */
+function elgg_get_metastring_sql($table, $names = null, $values = null,
+ $pairs = null, $ids = null, $case_sensitive = false) {
+
+ if ((!$names && $names !== 0)
+ && (!$values && $values !== 0)
+ && !$ids
+ && (!$pairs && $pairs !== 0)) {
+
+ return array();
+ }
+
+ $db_prefix = elgg_get_config('dbprefix');
+
+ // binary forces byte-to-byte comparision of strings, making
+ // it case- and diacritical-mark- sensitive.
+ // only supported on values.
+ $binary = ($case_sensitive) ? ' BINARY ' : '';
+
+ $return = array (
+ 'joins' => array (),
+ 'wheres' => array()
+ );
+
+ $wheres = array();
+
+ // get names wheres and joins
+ $names_where = '';
+ if ($names !== NULL) {
+ if (!is_array($names)) {
+ $names = array($names);
+ }
+
+ $sanitised_names = array();
+ foreach ($names as $name) {
+ // normalise to 0.
+ if (!$name) {
+ $name = '0';
+ }
+ $sanitised_names[] = '\'' . sanitise_string($name) . '\'';
+ }
+
+ if ($names_str = implode(',', $sanitised_names)) {
+ $return['joins'][] = "JOIN {$db_prefix}metastrings msn on $table.name_id = msn.id";
+ $names_where = "(msn.string IN ($names_str))";
+ }
+ }
+
+ // get values wheres and joins
+ $values_where = '';
+ if ($values !== NULL) {
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ $sanitised_values = array();
+ foreach ($values as $value) {
+ // normalize to 0
+ if (!$value) {
+ $value = 0;
+ }
+ $sanitised_values[] = '\'' . sanitise_string($value) . '\'';
+ }
+
+ if ($values_str = implode(',', $sanitised_values)) {
+ $return['joins'][] = "JOIN {$db_prefix}metastrings msv on $table.value_id = msv.id";
+ $values_where = "({$binary}msv.string IN ($values_str))";
+ }
+ }
+
+ if ($ids !== NULL) {
+ if (!is_array($ids)) {
+ $ids = array($ids);
+ }
+
+ $ids_str = implode(',', $ids);
+
+ if ($ids_str) {
+ $wheres[] = "n_table.id IN ($ids_str)";
+ }
+ }
+
+ if ($names_where && $values_where) {
+ $wheres[] = "($names_where AND $values_where)";
+ } elseif ($names_where) {
+ $wheres[] = $names_where;
+ } elseif ($values_where) {
+ $wheres[] = $values_where;
+ }
+
+ $wheres[] = get_access_sql_suffix($table);
+
+ if ($where = implode(' AND ', $wheres)) {
+ $return['wheres'][] = "($where)";
+ }
+
+ return $return;
+}
+
+/**
+ * Normalizes metadata / annotation option names to their corresponding metastrings name.
+ *
+ * @param array $options An options array
+ * @since 1.8.0
+ * @return array
+ * @access private
+ */
+function elgg_normalize_metastrings_options(array $options = array()) {
+
+ // support either metastrings_type or metastring_type
+ // because I've made this mistake many times and hunting it down is a pain...
+ $type = elgg_extract('metastring_type', $options, null);
+ $type = elgg_extract('metastrings_type', $options, $type);
+
+ $options['metastring_type'] = $type;
+
+ // support annotation_ and annotations_ because they're way too easy to confuse
+ $prefixes = array('metadata_', 'annotation_', 'annotations_');
+
+ // map the metadata_* options to metastring_* options
+ $map = array(
+ 'names' => 'metastring_names',
+ 'values' => 'metastring_values',
+ 'case_sensitive' => 'metastring_case_sensitive',
+ 'owner_guids' => 'metastring_owner_guids',
+ 'created_time_lower' => 'metastring_created_time_lower',
+ 'created_time_upper' => 'metastring_created_time_upper',
+ 'calculation' => 'metastring_calculation',
+ 'ids' => 'metastring_ids'
+ );
+
+ foreach ($prefixes as $prefix) {
+ $singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id");
+ $options = elgg_normalise_plural_options_array($options, $singulars);
+
+ foreach ($map as $specific => $normalized) {
+ $key = $prefix . $specific;
+ if (isset($options[$key])) {
+ $options[$normalized] = $options[$key];
+ }
+ }
+ }
+
+ return $options;
+}
+
+/**
+ * Enables or disables a metastrings-based object by its id.
+ *
+ * @warning To enable disabled metastrings you must first use
+ * {@link access_show_hidden_entities()}.
+ *
+ * @param int $id The object's ID
+ * @param string $enabled Value to set to: yes or no
+ * @param string $type The type of table to use: metadata or annotations
+ *
+ * @return bool
+ * @throws InvalidParameterException
+ * @since 1.8.0
+ * @access private
+ */
+function elgg_set_metastring_based_object_enabled_by_id($id, $enabled, $type) {
+ $id = (int)$id;
+ $db_prefix = elgg_get_config('dbprefix');
+
+ $object = elgg_get_metastring_based_object_from_id($id, $type);
+
+ switch($type) {
+ case 'annotation':
+ case 'annotations':
+ $table = "{$db_prefix}annotations";
+ break;
+
+ case 'metadata':
+ $table = "{$db_prefix}metadata";
+ break;
+ }
+
+ if ($enabled === 'yes' || $enabled === 1 || $enabled === true) {
+ $enabled = 'yes';
+ $event = 'enable';
+ } elseif ($enabled === 'no' || $enabled === 0 || $enabled === false) {
+ $enabled = 'no';
+ $event = 'disable';
+ } else {
+ return false;
+ }
+
+ $return = false;
+
+ if ($object) {
+ // don't set it if it's already set.
+ if ($object->enabled == $enabled) {
+ $return = false;
+ } elseif ($object->canEdit() && (elgg_trigger_event($event, $type, $object))) {
+ $return = update_data("UPDATE $table SET enabled = '$enabled' where id = $id");
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Runs metastrings-based objects found using $options through $callback
+ *
+ * @warning Unlike elgg_get_metastring_based_objects() this will not accept an
+ * empty options array!
+ *
+ * @warning This returns null on no ops.
+ *
+ * @param array $options An options array. {@See elgg_get_metastring_based_objects()}
+ * @param string $callback The callback to pass each result through
+ * @param bool $inc_offset Increment the offset? Pass false for callbacks that delete / disable
+ *
+ * @return bool|null true on success, false on failure, null if no objects are found.
+ * @since 1.8.0
+ * @access private
+ */
+function elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset = true) {
+ if (!$options || !is_array($options)) {
+ return false;
+ }
+
+ $batch = new ElggBatch('elgg_get_metastring_based_objects', $options, $callback, 50, $inc_offset);
+ return $batch->callbackResult;
+}
+
+/**
+ * Returns a singular metastring-based object by its ID.
+ *
+ * @param int $id The metastring-based object's ID
+ * @param string $type The type: annotation or metadata
+ * @return ElggMetadata|ElggAnnotation
+ *
+ * @since 1.8.0
+ * @access private
+ */
+function elgg_get_metastring_based_object_from_id($id, $type) {
+ $id = (int)$id;
+ if (!$id) {
+ return false;
+ }
+
+ $options = array(
+ 'metastring_type' => $type,
+ 'metastring_id' => $id
+ );
+
+ $obj = elgg_get_metastring_based_objects($options);
+
+ if ($obj && count($obj) == 1) {
+ return $obj[0];
+ }
+
+ return false;
+}
+
+/**
+ * Deletes a metastring-based object by its id
+ *
+ * @param int $id The object's ID
+ * @param string $type The object's metastring type: annotation or metadata
+ * @return bool
+ *
+ * @since 1.8.0
+ * @access private
+ */
+function elgg_delete_metastring_based_object_by_id($id, $type) {
+ $id = (int)$id;
+ $db_prefix = elgg_get_config('dbprefix');
+
+ switch ($type) {
+ case 'annotation':
+ case 'annotations':
+ $type = 'annotations';
+ break;
+
+ case 'metadata':
+ $type = 'metadata';
+ break;
+
+ default:
+ return false;
+ }
+
+ $obj = elgg_get_metastring_based_object_from_id($id, $type);
+ $table = $db_prefix . $type;
+
+ if ($obj) {
+ // Tidy up if memcache is enabled.
+ // @todo only metadata is supported
+ if ($type == 'metadata') {
+ static $metabyname_memcache;
+ if ((!$metabyname_memcache) && (is_memcache_available())) {
+ $metabyname_memcache = new ElggMemcache('metabyname_memcache');
+ }
+
+ if ($metabyname_memcache) {
+ // @todo why name_id? is that even populated?
+ $metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}");
+ }
+ }
+
+ if (($obj->canEdit()) && (elgg_trigger_event('delete', $type, $obj))) {
+ return (bool)delete_data("DELETE from $table where id=$id");
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Entities interface helpers
+ */
+
+/**
+ * Returns options to pass to elgg_get_entities() for metastrings operations.
+ *
+ * @param string $type Metastring type: annotations or metadata
+ * @param array $options Options
+ *
+ * @return array
+ * @since 1.7.0
+ * @access private
+ */
+function elgg_entities_get_metastrings_options($type, $options) {
+ $valid_types = array('metadata', 'annotation');
+ if (!in_array($type, $valid_types)) {
+ return FALSE;
+ }
+
+ // the options for annotations are singular (annotation_name) but the table
+ // is plural (elgg_annotations) so rewrite for the table name.
+ $n_table = ($type == 'annotation') ? 'annotations' : $type;
+
+ $singulars = array("{$type}_name", "{$type}_value",
+ "{$type}_name_value_pair", "{$type}_owner_guid");
+ $options = elgg_normalise_plural_options_array($options, $singulars);
+
+ $clauses = elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"],
+ $options["{$type}_values"], $options["{$type}_name_value_pairs"],
+ $options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"],
+ $options["order_by_{$type}"], $options["{$type}_owner_guids"]);
+
+ if ($clauses) {
+ // merge wheres to pass to get_entities()
+ if (isset($options['wheres']) && !is_array($options['wheres'])) {
+ $options['wheres'] = array($options['wheres']);
+ } elseif (!isset($options['wheres'])) {
+ $options['wheres'] = array();
+ }
+
+ $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
+
+ // merge joins to pass to get_entities()
+ if (isset($options['joins']) && !is_array($options['joins'])) {
+ $options['joins'] = array($options['joins']);
+ } elseif (!isset($options['joins'])) {
+ $options['joins'] = array();
+ }
+
+ $options['joins'] = array_merge($options['joins'], $clauses['joins']);
+
+ if ($clauses['orders']) {
+ $order_by_metadata = implode(", ", $clauses['orders']);
+ if (isset($options['order_by']) && $options['order_by']) {
+ $options['order_by'] = "$order_by_metadata, {$options['order_by']}";
+ } else {
+ $options['order_by'] = "$order_by_metadata, e.time_created DESC";
+ }
+ }
+ }
+
+ return $options;
+}
+
+// unit testing
+elgg_register_plugin_hook_handler('unit_test', 'system', 'metastrings_test');
+
+/**
+ * Metadata unit test
+ *
+ * @param string $hook unit_test
+ * @param string $type system
+ * @param mixed $value Array of other tests
+ * @param mixed $params Params
+ *
+ * @return array
+ * @access private
+ */
+function metastrings_test($hook, $type, $value, $params) {
+ global $CONFIG;
+ $value[] = $CONFIG->path . 'engine/tests/api/metastrings.php';
+ return $value;
+}