diff options
Diffstat (limited to 'engine/lib/metastrings.php')
| -rw-r--r-- | engine/lib/metastrings.php | 483 |
1 files changed, 429 insertions, 54 deletions
diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index 23724b979..57d876c06 100644 --- a/engine/lib/metastrings.php +++ b/engine/lib/metastrings.php @@ -15,6 +15,8 @@ $METASTRINGS_CACHE = array(); global $METASTRINGS_DEADNAME_CACHE; $METASTRINGS_DEADNAME_CACHE = array(); + + /** * Return the meta string id for a given tag, or false. * @@ -65,7 +67,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { } $row = FALSE; - $metaStrings = get_data($query, "entity_row_to_elggstar"); + $metaStrings = get_data($query); if (is_array($metaStrings)) { if (sizeof($metaStrings) > 1) { $ids = array(); @@ -73,7 +75,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { $ids[] = $metaString->id; } return $ids; - } else { + } else if (isset($metaStrings[0])) { $row = $metaStrings[0]; } } @@ -159,6 +161,7 @@ function add_metastring($string, $case_sensitive = true) { * Delete any orphaned entries in metastrings. This is run by the garbage collector. * * @return bool + * @access private */ function delete_orphaned_metastrings() { global $CONFIG; @@ -201,37 +204,58 @@ function delete_orphaned_metastrings() { 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: + * @param array $options Array in format: + * + * metastring_names => NULL|ARR metastring names * - * metastring_names => NULL|ARR metastring names + * metastring_values => NULL|ARR metastring values * - * metastring_values => NULL|ARR metastring values + * metastring_ids => NULL|ARR metastring ids * - * metastring_case_sensitive => BOOL Overall Case sensitive + * metastring_case_sensitive => BOOL Overall Case sensitive * - * metastring_owner_guids => NULL|ARR guids for metadata owners + * metastring_owner_guids => NULL|ARR Guids for metadata owners * - * metastring_created_time_lower => INT Lower limit for created time. + * metastring_created_time_lower => INT Lower limit for created time. * - * metastring_created_time_upper => INT Upper 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. + * 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) * - * @param string $type Either metadata or annotations * @return mixed * @access private */ -function elgg_get_metastring_based_objects($options, $type = 'metadata') { - - if ($type != 'metadata' && $type != 'annotations') { - return false; +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( @@ -265,6 +289,8 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { 'metastring_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'metastring_ids' => ELGG_ENTITIES_ANY_VALUE, + // sql 'order_by' => 'n_table.time_created asc', 'limit' => 10, @@ -274,9 +300,12 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { 'wheres' => array(), 'joins' => array(), - 'callback' => ($type == 'annotations') ? 'row_to_elggannotation' : 'row_to_elggmetadata' + '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 @@ -290,9 +319,14 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } } - $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid', - 'metastring_name', 'metastring_value' - ); + $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) { @@ -324,10 +358,8 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { $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']); - - // remove identical where clauses - $wheres = array_unique($wheres); + $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 @@ -339,20 +371,39 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } } + // 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"; - $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"; + // evaluate selects + if (!is_array($options['selects'])) { + $options['selects'] = array($options['selects']); + } - // remove identical join clauses - $joins = array_unique($joins); + $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) { @@ -364,24 +415,37 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { // metastrings $metastring_clauses = elgg_get_metastring_sql('n_table', $options['metastring_names'], - $options['metastring_values'], $options['metastring_case_sensitive']); + $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'); } - // check for calculations - if ($options['count']) { - $options['metastring_calculation'] = 'count'; - } + 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"; + } + } - if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { - $query = "SELECT DISTINCT n_table.*, n.string as name, v.string as value FROM {$db_prefix}$type n_table"; + $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 DISTINCT v.string as value, {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; + $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 "; @@ -398,22 +462,25 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { $query .= get_access_sql_suffix('e'); // reverse order by - if ($options['reverse_order_by']) { - $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'], $defaults['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) { - if ($options['group_by'] = sanitise_string($options['group_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 ($options['order_by'] = sanitise_string($options['order_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']); + $offset = sanitise_int($options['offset'], false); $query .= " LIMIT $offset, $limit"; } @@ -425,7 +492,6 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } } - /** * Returns an array of joins and wheres for use in metastrings. * @@ -435,32 +501,30 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { * @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, $case_sensitive = false) { + $pairs = null, $ids = null, $case_sensitive = false) { if ((!$names && $names !== 0) && (!$values && $values !== 0) + && !$ids && (!$pairs && $pairs !== 0)) { - return ''; + return array(); } $db_prefix = elgg_get_config('dbprefix'); - // join counter for incremental joins. - $i = 1; - // binary forces byte-to-byte comparision of strings, making // it case- and diacritical-mark- sensitive. // only supported on values. $binary = ($case_sensitive) ? ' BINARY ' : ''; - $access = get_access_sql_suffix($table); - $return = array ( 'joins' => array (), 'wheres' => array() @@ -512,17 +576,328 @@ function elgg_get_metastring_sql($table, $names = null, $values = null, } } + 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 AND $access)"; + $wheres[] = "($names_where AND $values_where)"; } elseif ($names_where) { - $wheres[] = "($names_where AND $access)"; + $wheres[] = $names_where; } elseif ($values_where) { - $wheres[] = "($values_where AND $access)"; + $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; +} |
