From 4be522d7544b1aca0e08828bee42a9be7345bf9c Mon Sep 17 00:00:00 2001 From: brettp Date: Sun, 13 Feb 2011 22:59:36 +0000 Subject: Refs #2907. DRY'd up and abstracted out a lot of metadata / annotation functions so I can implement #1115 with a clean conscious. git-svn-id: http://code.elgg.org/elgg/trunk@8207 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/classes/ElggAnnotation.php | 22 +- engine/classes/ElggEntity.php | 5 +- engine/classes/ElggMetadata.php | 24 ++- engine/lib/annotations.php | 238 ++++++++------------- engine/lib/deprecated-1.8.php | 169 ++++++++++++++- engine/lib/elgglib.php | 45 +++- engine/lib/entities.php | 11 + engine/lib/metadata.php | 268 ++++++------------------ engine/lib/metastrings.php | 423 ++++++++++++++++++++++++++++++++++++-- engine/tests/api/metastrings.php | 139 +++++++++++++ 10 files changed, 965 insertions(+), 379 deletions(-) create mode 100644 engine/tests/api/metastrings.php diff --git a/engine/classes/ElggAnnotation.php b/engine/classes/ElggAnnotation.php index 10f4269cf..274cbb923 100644 --- a/engine/classes/ElggAnnotation.php +++ b/engine/classes/ElggAnnotation.php @@ -78,7 +78,27 @@ class ElggAnnotation extends ElggExtender { * @return bool */ function delete() { - return delete_annotation($this->id); + return elgg_delete_metastring_based_object_by_id($this->id, 'annotations'); + } + + /** + * Disable the annotation. + * + * @return bool + * @since 1.8 + */ + function disable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'annotations'); + } + + /** + * Enable the annotation. + * + * @return bool + * @since 1.8 + */ + function enable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'annotations'); } /** diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index ea2a329d9..4e1bc586c 100644 --- a/engine/classes/ElggEntity.php +++ b/engine/classes/ElggEntity.php @@ -292,9 +292,9 @@ abstract class ElggEntity extends ElggData implements public function setMetaData($name, $value, $value_type = "", $multiple = false) { if (is_array($value)) { unset($this->temp_metadata[$name]); - remove_metadata($this->getGUID(), $name); foreach ($value as $v) { if ((int) $this->guid > 0) { + elgg_delete_metadata(array('guid' => $this->guid, 'metadata_name' => $name)); $multiple = true; if (!create_metadata($this->getGUID(), $name, $v, $value_type, $this->getOwnerGUID(), $this->getAccessID(), $multiple)) { @@ -349,7 +349,7 @@ abstract class ElggEntity extends ElggData implements * * @return mixed bool */ - public function clearMetaData($name = "") { + public function clearMetaData($name = '') { if (empty($name)) { return clear_metadata($this->getGUID()); } else { @@ -357,7 +357,6 @@ abstract class ElggEntity extends ElggData implements } } - /** * Get a piece of volatile (non-persisted) data on this entity. * diff --git a/engine/classes/ElggMetadata.php b/engine/classes/ElggMetadata.php index 9c13e936e..f85bac6ce 100644 --- a/engine/classes/ElggMetadata.php +++ b/engine/classes/ElggMetadata.php @@ -75,12 +75,32 @@ class ElggMetadata extends ElggExtender { } /** - * Delete a given metadata. + * Delete the metadata * * @return bool */ function delete() { - return delete_metadata($this->id); + return elgg_delete_metastring_based_object_by_id($this->id, 'metadata'); + } + + /** + * Disable the metadata + * + * @return bool + * @since 1.8 + */ + function disable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'metadata'); + } + + /** + * Disable the metadata + * + * @return bool + * @since 1.8 + */ + function enable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'metadata'); } /** diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index a14853359..9b3b49626 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -23,25 +23,16 @@ function row_to_elggannotation($row) { } /** - * Get a specific annotation. + * Get a specific annotation by its id. + * If you want multiple annotation objects, use + * {@link elgg_get_annotations()}. * - * @param int $annotation_id Annotation ID + * @param int $id The id of the annotation object being retrieved. * - * @return ElggAnnotation + * @return false|ElggAnnotation */ -function get_annotation($annotation_id) { - global $CONFIG; - - $annotation_id = (int) $annotation_id; - $access = get_access_sql_suffix("a"); - - $query = "SELECT a.*, n.string as name, v.string as value" - . " from {$CONFIG->dbprefix}annotations a" - . " JOIN {$CONFIG->dbprefix}metastrings n on a.name_id = n.id" - . " JOIN {$CONFIG->dbprefix}metastrings v on a.value_id = v.id" - . " where a.id=$annotation_id and $access"; - - return row_to_elggannotation(get_data_row($query)); +function elgg_get_annotation_from_id($id) { + return elgg_get_metastring_based_object_by_id($id, 'annotations'); } /** @@ -195,29 +186,87 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu * @since 1.8.0 */ function elgg_get_annotations(array $options = array()) { - // map the annotation_* options to metastring_* options - $map = array( - 'annotation_names' => 'metastring_names', - 'annotation_values' => 'metastring_values', - 'annotation_case_sensitive' => 'metastring_case_sensitive', - 'annotation_owner_guids' => 'metastring_owner_guids', - 'annotation_created_time_lower' => 'metastring_created_time_lower', - 'annotation_created_time_upper' => 'metastring_created_time_upper', - 'annotation_calculation' => 'metastring_calculation' - ); + $options['metastring_type'] = 'annotations'; + return elgg_get_metastring_based_objects($options); +} - $singulars = array('annotation_name', 'annotation_value'); - $options = elgg_normalise_plural_options_array($options, $singulars); +/** + * Deletes annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return mixed + * @since 1.8 + */ +function elgg_delete_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; + } - foreach ($map as $ann => $ms) { - if (isset($options[$ann])) { - $options[$ms] = $options[$ann]; - } + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback'); +} + +/** + * Disables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return mixed + * @since 1.8 + */ +function elgg_disable_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; + } + + $options['metastrings_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback'); +} + +/** + * Enables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return mixed + * @since 1.8 + */ +function elgg_enable_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; } - return elgg_get_metastring_based_objects($options, 'annotations'); + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); +} + +/** + * Returns a rendered list of annotations with pagination. + * + * @param array $options Annotation getter and display options. + * {@see elgg_get_annotations()} and {@see elgg_list_entities()}. + * + * @return string The list of entities + * @since 1.8 + */ +function elgg_list_annotations($options) { + $defaults = array( + 'limit' => 25, + 'offset' => (int) max(get_input('annoff', 0), 0), + ); + + $options = array_merge($defaults, $options); + + return elgg_list_entities($options, 'elgg_get_annotations', 'elgg_view_annotation_list'); } +/** + * Entities interfaces + */ /** * Returns entities based upon annotations. Accepts the same values as @@ -254,6 +303,8 @@ function elgg_get_annotations(array $options = array()) { * * annotation_owner_guids => NULL|ARR guids for annotaiton owners * + * annotation_ids => NULL|ARR Annotation IDs + * * @return array * @since 1.7.0 */ @@ -272,6 +323,8 @@ function elgg_get_entities_from_annotations(array $options = array()) { 'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, + 'order_by' => 'maxtime desc', 'group_by' => 'a.entity_guid' ); @@ -279,7 +332,7 @@ function elgg_get_entities_from_annotations(array $options = array()) { $options = array_merge($defaults, $options); $singulars = array('annotation_name', 'annotation_value', - 'annotation_name_value_pair', 'annotation_owner_guid'); + 'annotation_name_value_pair', 'annotation_owner_guid', 'annotation_id'); $options = elgg_normalise_plural_options_array($options, $singulars); @@ -316,26 +369,6 @@ function elgg_list_entities_from_annotations($options = array()) { return elgg_list_entities($options, 'elgg_get_entities_from_annotations'); } -/** - * Returns a rendered list of annotations with pagination. - * - * @param array $options Annotation getter and display options. - * {@see elgg_get_annotations()} and {@see elgg_list_entities()}. - * - * @return string The list of entities - * @since 1.8 - */ -function elgg_list_annotations($options) { - $defaults = array( - 'limit' => 25, - 'offset' => (int) max(get_input('annoff', 0), 0), - ); - - $options = array_merge($defaults, $options); - - return elgg_list_entities($options, 'elgg_get_annotations', 'elgg_view_annotation_list'); -} - /** * Get entities ordered by a mathematical calculation * @@ -385,103 +418,6 @@ function elgg_list_entities_from_annotation_calculation($options) { return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); } - -/** - * Delete a given annotation. - * - * @param int $id The annotation id - * - * @return bool - */ -function delete_annotation($id) { - global $CONFIG; - - $id = (int)$id; - - $access = get_access_sql_suffix(); - $annotation = get_annotation($id); - - if (elgg_trigger_event('delete', 'annotation', $annotation)) { - remove_from_river_by_annotation($id); - return delete_data("DELETE from {$CONFIG->dbprefix}annotations where id=$id and $access"); - } - - return FALSE; -} - -/** - * Clear all the annotations for a given entity, assuming you have access to that metadata. - * - * @param int $guid The entity guid - * @param string $name The name of the annotation to delete. - * - * @return int Number of annotations deleted or false if an error - */ -function clear_annotations($guid, $name = "") { - global $CONFIG; - - $guid = (int)$guid; - - if (!empty($name)) { - $name = get_metastring_id($name); - if ($name === false) { - // name doesn't exist so 0 rows were deleted - return 0; - } - } - - $entity_guid = (int) $guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - $where = array(); - - if ($name != "") { - $where[] = " name_id='$name'"; - } - - $query = "DELETE from {$CONFIG->dbprefix}annotations where entity_guid=$guid "; - foreach ($where as $w) { - $query .= " and $w"; - } - - return delete_data($query); - } - } - - return FALSE; -} - -/** - * Clear all annotations belonging to a given owner_guid - * - * @param int $owner_guid The owner - * - * @return int Number of annotations deleted - */ -function clear_annotations_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $query = "SELECT id from {$CONFIG->dbprefix}annotations WHERE owner_guid=$owner_guid"; - - $annotations = get_data($query); - $deleted = 0; - - if (!$annotations) { - return 0; - } - - foreach ($annotations as $id) { - // Is this the best way? - if (delete_annotation($id->id)) { - $deleted++; - } - } - - return $deleted; -} - /** * Handler called by trigger_plugin_hook on the "export" event. * diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php index 9bca873c5..f86d94621 100644 --- a/engine/lib/deprecated-1.8.php +++ b/engine/lib/deprecated-1.8.php @@ -3534,7 +3534,6 @@ function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $en return elgg_get_metadata($options); } - /** * Get metadata objects by name. * @@ -3547,6 +3546,10 @@ function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $en function get_metadata_byname($entity_guid, $meta_name) { elgg_deprecated_notice('get_metadata_byname() is deprecated by elgg_get_metadata()', 1.8); + if (!$entity_guid || !$meta_name) { + return false; + } + $options = array( 'guid' => $entity_guid, 'metadata_name' => $meta_name, @@ -3573,6 +3576,10 @@ function get_metadata_byname($entity_guid, $meta_name) { function get_metadata_for_entity($entity_guid) { elgg_deprecated_notice('get_metadata_for_entity() is deprecated by elgg_get_metadata()', 1.8); + if (!$entity_guid) { + return false; + } + $options = array( 'guid' => $entity_guid, 'limit' => 0 @@ -3590,5 +3597,165 @@ function get_metadata_for_entity($entity_guid) { * @deprecated 1.8 Use elgg_get_metadata_from_id() */ function get_metadata($id) { + elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata_from_id()', 1.8); return elgg_get_metadata_from_id($id); } + +/** + * Clear all the metadata for a given entity, assuming you have access to that entity. + * + * @param int $guid Entity GUID + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata($guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$guid) { + return false; + } + return elgg_delete_metadata(array('guid' => $guid, 'limit' => 0)); +} + +/** + * Clear all metadata belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata_by_owner($owner_guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$owner_guid) { + return false; + } + return elgg_delete_metadata(array('metadata_owner' => $owner_guid, 'limit' => 0)); +} + +/** + * Delete a piece of metadata, where the current user has access. + * + * @param int $id The id of metadata to delete. + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function delete_metadata($id) { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_metadata(array('metadata_id' => $id)); +} + +/** + * Removes metadata on an entity with a particular name, optionally with a given value. + * + * @param int $guid The entity GUID + * @param string $name The name of the metadata + * @param string $value The value of the metadata (useful to remove a single item of a set) + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function remove_metadata($guid, $name, $value = "") { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + + // prevent them from deleting everything + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'metadata_name' => $name, + 'limit' => 0 + ); + + if ($value) { + $options['metadata_value'] = $value; + } + + return elgg_delete_metadata($options); +} + +/** + * Get a specific annotation. + * + * @param int $annotation_id Annotation ID + * + * @return ElggAnnotation + * @deprecated 1.8 Use elgg_get_annotation_from_id() + */ +function get_annotation($annotation_id) { + elgg_deprecated_notice('get_annotation() is deprecated by elgg_get_annotation_from_id()', 1.8); + return elgg_get_annotation_from_id($annotation_id); +} + +/** + * Delete a given annotation. + * + * @param int $id The annotation id + * + * @return bool + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function delete_annotation($id) { + elgg_deprecated_notice('delete_annotation() is deprecated by elgg_delete_annotations()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_annotations(array('annotation_id' => $annotation_id)); +} + +/** + * Clear all the annotations for a given entity, assuming you have access to that metadata. + * + * @param int $guid The entity guid + * @param string $name The name of the annotation to delete. + * + * @return int Number of annotations deleted or false if an error + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations($guid, $name = "") { + elgg_deprecated_notice('clear_annotations() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'limit' => 0 + ); + + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_delete_annotations($options); +} + +/** + * Clear all annotations belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return int Number of annotations deleted + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations_by_owner($owner_guid) { + elgg_deprecated_notice('clear_annotations_by_owner() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$owner_guid) { + return false; + } + + $options = array( + 'annotation_owner_guid' => $guid, + 'limit' => 0 + ); + + return elgg_delete_annotations($options); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 7ebf918b7..a00b21c52 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -1664,8 +1664,9 @@ function css_page_handler($page) { * * This is used mostly for elgg_get_entities() and other similar functions. * - * @access private * @param string $order_by An order by clause + * @access private + * @return string */ function elgg_sql_reverse_order_by_clause($order_by) { $order_by = strtolower($order_by); @@ -1682,6 +1683,48 @@ function elgg_sql_reverse_order_by_clause($order_by) { return $return; } +/** + * Enable objects with an enable() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to enable + * @access private + * @return bool + */ +function elgg_batch_enable_callback($object) { + // our db functions return the number of rows affected... + return $object->enable() ? true : false; +} + +/** + * Disable objects with a disable() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @access private + * @return bool + */ +function elgg_batch_disable_callback($object) { + // our db functions return the number of rows affected... + return $object->disable() ? true : false; +} + +/** + * Delete objects with a delete() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @access private + * @return bool + */ +function elgg_batch_delete_callback($object) { + // our db functions return the number of rows affected... + return $object->delete() ? true : false; +} + /** * Intercepts the index page when Walled Garden mode is enabled. * diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 781a744ad..4f58e96a8 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -1345,6 +1345,17 @@ function disable_entity($guid, $reason = "", $recursive = true) { } } + // disable annotations + // Now delete the entity itself +// $entity->clearMetadata(); +// $entity->clearAnnotations(); +// $entity->clearRelationships(); +// +// $res = delete_data("DELETE from {$CONFIG->dbprefix}entities where guid={$guid}"); +// if ($res) { +// $sub_table = ""; +// } + $__RECURSIVE_DELETE_TOKEN = null; } diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 6724bcb70..8a62929d5 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -32,56 +32,7 @@ function row_to_elggmetadata($row) { * @return false|ElggMetadata */ function elgg_get_metadata_from_id($id) { - $db_prefix = elgg_get_config('dbprefix'); - - $id = (int)$id; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); - - $query = "SELECT m.*, n.string as name, v.string as value from {$db_prefix}metadata m" - . " JOIN {$db_prefix}entities e on e.guid = m.entity_guid" - . " JOIN {$db_prefix}metastrings v on m.value_id = v.id" - . " JOIN {$db_prefix}metastrings n on m.name_id = n.id" - . " where m.id=$id and $access and $md_access"; - - return row_to_elggmetadata(get_data_row($query)); -} - -/** - * Removes metadata on an entity with a particular name, optionally with a given value. - * - * @param int $entity_guid The entity GUID - * @param string $name The name of the metadata - * @param string $value The value of the metadata (useful to remove a single item of a set) - * - * @return bool Depending on success - */ -function remove_metadata($entity_guid, $name, $value = "") { - global $CONFIG; - $entity_guid = (int) $entity_guid; - - $name_id = get_metastring_id($name); - if ($name_id === FALSE) { - // name doesn't exist - return FALSE; - } - - $query = "SELECT * from {$CONFIG->dbprefix}metadata WHERE entity_guid = '$entity_guid' and name_id = '$name_id'"; - if ($value != "") { - $value_id = get_metastring_id($value); - if ($value_id !== FALSE) { - $query .= " AND value_id = '$value_id'"; - } - } - - if ($existing = get_data($query)) { - foreach ($existing as $ex) { - delete_metadata($ex->id); - } - return true; - } - - return false; + return elgg_get_metastring_based_object_by_id($id, 'metadata'); } /** @@ -290,38 +241,6 @@ $access_id = ACCESS_PRIVATE, $allow_multiple = false) { return true; } -/** - * Delete a piece of metadata, where the current user has access. - * - * @param int $id The id of metadata to delete. - * - * @return bool - */ -function delete_metadata($id) { - global $CONFIG; - - $id = (int)$id; - $metadata = elgg_get_metadata_from_id($id); - - if ($metadata) { - // Tidy up if memcache is enabled. - static $metabyname_memcache; - if ((!$metabyname_memcache) && (is_memcache_available())) { - $metabyname_memcache = new ElggMemcache('metabyname_memcache'); - } - - if ($metabyname_memcache) { - $metabyname_memcache->delete("{$metadata->entity_guid}:{$metadata->name_id}"); - } - - if (($metadata->canEdit()) && (elgg_trigger_event('delete', 'metadata', $metadata))) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where id=$id"); - } - } - - return false; -} - /** * Returns metadata. Accepts all elgg_get_entities() options for entity * restraints. @@ -353,30 +272,69 @@ function delete_metadata($id) { * @return mixed * @since 1.8.0 */ -function elgg_get_metadata($options) { - // map the metadata_* options to metastring_* options - $map = array( - 'metadata_names' => 'metastring_names', - 'metadata_values' => 'metastring_values', - 'metadata_case_sensitive' => 'metastring_case_sensitive', - 'metadata_owner_guids' => 'metastring_owner_guids', - 'metadata_created_time_lower' => 'metastring_created_time_lower', - 'metadata_created_time_upper' => 'metastring_created_time_upper', - 'metadata_calculation' => 'metastring_calculation' - ); +function elgg_get_metadata(array $options = array()) { + $options['metastring_type'] = 'metadata'; + return elgg_get_metastring_based_objects($options); +} - $singulars = array('metadata_name', 'metadata_value'); - $options = elgg_normalise_plural_options_array($options, $singulars); +/** + * Deletes metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return mixed + * @since 1.8 + */ +function elgg_delete_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; + } - foreach ($map as $ann => $ms) { - if (isset($options[$ann])) { - $options[$ms] = $options[$ann]; - } + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback'); +} + +/** + * Disables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return mixed + * @since 1.8 + */ +function elgg_disable_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; } - return elgg_get_metastring_based_objects($options, 'metadata'); + $options['metastrings_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback'); } +/** + * Enables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return mixed + * @since 1.8 + */ +function elgg_enable_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; + } + + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); +} + +/** + * ElggEntities interfaces + */ + /** * Returns entities based upon metadata. Also accepts all * options available to elgg_get_entities(). Supports @@ -452,66 +410,6 @@ function elgg_get_entities_from_metadata(array $options = array()) { return elgg_get_entities($options); } -/** - * 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 - */ -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; -} - /** * Returns metadata name and value SQL where for entities. * NB: $names and $values are not paired. Use $pairs for this. @@ -789,50 +687,8 @@ function elgg_list_entities_from_metadata($options) { } /** - * Clear all the metadata for a given entity, assuming you have access to that metadata. - * - * @param int $entity_guid Entity GUID - * - * @return bool + * Other functions */ -function clear_metadata($entity_guid) { - global $CONFIG; - - $entity_guid = (int)$entity_guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where entity_guid={$entity_guid}"); - } - } - return false; -} - -/** - * Clear all annotations belonging to a given owner_guid - * - * @param int $owner_guid The owner - * - * @return bool - */ -function clear_metadata_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $metas = get_data("SELECT id from {$CONFIG->dbprefix}metadata WHERE owner_guid=$owner_guid"); - $deleted = 0; - - if (is_array($metas)) { - foreach ($metas as $id) { - // Is this the best way? - if (delete_metadata($id->id)) { - $deleted++; - } - } - } - - return $deleted; -} /** * Handler called by trigger_plugin_hook on the "export" event. @@ -983,10 +839,10 @@ function is_metadata_independent($type, $subtype) { function metadata_update($event, $object_type, $object) { if ($object instanceof ElggEntity) { if (!is_metadata_independent($object->getType(), $object->getSubtype())) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $access_id = (int) $object->access_id; $guid = (int) $object->getGUID(); - $query = "update {$CONFIG->dbprefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; + $query = "update {$db_prefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; update_data($query); } } @@ -1028,4 +884,4 @@ function metadata_test($hook, $type, $value, $params) { global $CONFIG; $value[] = $CONFIG->path . 'engine/tests/objects/metadata.php'; return $value; -} +} \ No newline at end of file diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index 23724b979..bcb15c338 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. * @@ -201,14 +203,13 @@ 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 * @@ -224,16 +225,35 @@ function delete_orphaned_metastrings() { * * metastring_calculation => STR Perform the MySQL function on the metastring values returned. * - * @param string $type Either metadata or annotations + * metastring_type => STR metadata or annotation(s) + * * @return mixed * @access private */ -function elgg_get_metastring_based_objects($options, $type = 'metadata') { +function elgg_get_metastring_based_objects($options) { - if ($type != 'metadata' && $type != 'annotations') { + if (!isset($options['metastring_type'])) { return false; } + 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; + } + + $options = elgg_normalize_metastrings_options($options); + $defaults = array( // entities 'types' => ELGG_ENTITIES_ANY_VALUE, @@ -265,6 +285,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 +296,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 @@ -291,7 +316,7 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid', - 'metastring_name', 'metastring_value' + 'metastring_name', 'metastring_value', 'metastring_id' ); $options = elgg_normalise_plural_options_array($options, $singulars); @@ -324,7 +349,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']); + $wheres[] = elgg_get_guid_based_where_sql('n_table.owner_guid', + $options['metastring_owner_guids']); // remove identical where clauses $wheres = array_unique($wheres); @@ -364,7 +390,8 @@ 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']); @@ -377,9 +404,11 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } 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.*, n.string as name, + v.string as value 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 DISTINCT v.string as value, + {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; } // add joins @@ -399,7 +428,8 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { // reverse order by if ($options['reverse_order_by']) { - $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'], $defaults['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) { @@ -425,7 +455,6 @@ function elgg_get_metastring_based_objects($options, $type = 'metadata') { } } - /** * Returns an array of joins and wheres for use in metastrings. * @@ -435,15 +464,17 @@ 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 */ 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 ''; @@ -512,6 +543,18 @@ 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)"; } elseif ($names_where) { @@ -526,3 +569,355 @@ function elgg_get_metastring_sql($table, $names = null, $values = null, return $return; } + +/** + * Normalizes metadata / annotation option names to their + * corresponding metastrings name. + * + * @param array $options An options array + * @since 1.8 + * @access private + * @return array + */ +function elgg_normalize_metastrings_options(array $options = array()) { + $prefixes = array('metadata_', 'annotation_'); + + // 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' + ); + + foreach ($prefixes as $prefix) { + $singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid"); + $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 anntations + * + * @return bool + * @since 1.8 + * @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_by_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; +} + +/** + * Enables or disables a metastrings-based objects by their entity_guid. + * + * @param int $guid 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 anntations + * @param string $name Optional metastring name. If not set, affects all metadata. + * + * @return bool + */ +function elgg_set_metastring_based_object_enabled_by_guid($guid, $enabled, $type, $name = null) { + $valid_types = array('annotations', 'annotation', 'metadata'); + $guid = (int)$guid; + $entity = get_entity($guid); + + if (!in_array($type, $valid_types)) { + return false; + } + + if (!$entity || !$entity->canEdit()) { + return false; + } + + switch($enabled) { + case true: + case 1: + case 'yes': + $callback = 'elgg_batch_enable_callback'; + break; + + case false: + case 0: + case 'no': + $callback = 'elgg_batch_disable_callback'; + break; + } + + $ashe = access_get_show_hidden_status(); + access_show_hidden_entities(true); + + switch($type) { + case 'annotation': + case 'annotations': + $getter = 'elgg_get_annotations'; + if ($name) { + $options['annotation_name'] = $name; + } + break; + + case 'metadata': + $getter = 'elgg_get_metadata'; + if ($name) { + $options['metadata_name'] = $name; + } + break; + } + + $options = array( + 'guid' => $guid, + 'limit' => 0 + ); + + $batch = new ElggBatch('elgg_get_metadata', $options, 'elgg_batch_disable_callback'); + $r = $batch->callbackResult; + + access_show_hidden_entities($ashe); + + return $r; +} + +/** + * Runs metastrings-based objects found using $options through $callback + * + * @warning Unlike elgg_get_metastring_based_objects() this will not accept an + * empty options array! + * + * @param array $options An options array. {@See elgg_get_metastring_based_objects()} + * @param string $callback The callback to pass each result through + * @return mixed + * @access private + * @since 1.8 + */ +function elgg_batch_metastring_based_objects(array $options, $callback) { + if (!$options || !is_array($options)) { + return false; + } + + $batch = new ElggBatch('elgg_get_metastring_based_objects', $options, $callback); + $r = $batch->callbackResult; + + return $r; +} + +/** + * 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 mixed + * + * @since 1.8 + * @access private + */ +function elgg_get_metastring_based_object_by_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 + * @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_by_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) { + $metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}"); + } + } + + if (($obj->canEdit()) && (elgg_trigger_event('delete', $type, $obj))) { + return 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 + */ +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 + */ +function metastrings_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/metastrings.php'; + return $value; +} diff --git a/engine/tests/api/metastrings.php b/engine/tests/api/metastrings.php new file mode 100644 index 000000000..c18e42eb8 --- /dev/null +++ b/engine/tests/api/metastrings.php @@ -0,0 +1,139 @@ +metastrings = array(); + $this->object = new ElggObject(); + $this->object->save(); + } + + public function createAnnotations($max = 1) { + $annotations = array(); + for ($i=0; $i<$max; $i++) { + $name = 'test_annotation_name' . rand(); + $value = 'test_annotation_value' . rand(); + $id = create_annotation($this->object->guid, $name, $value); + $annotations[] = $id; + } + + return $annotations; + } + + public function createMetadata($max = 1) { + $metadata = array(); + for ($i=0; $i<$max; $i++) { + $name = 'test_metadata_name' . rand(); + $value = 'test_metadata_value' . rand(); + $id = create_metadata($this->object->guid, $name, $value); + $metadata[] = $id; + } + + return $metadata; + } + + /** + * Called before each test method. + */ + public function setUp() { + + } + + /** + * Called after each test method. + */ + public function tearDown() { + // do not allow SimpleTest to interpret Elgg notices as exceptions + $this->swallowErrors(); + } + + /** + * Called after each test object. + */ + public function __destruct() { + $this->object->delete(); + + parent::__destruct(); + } + + /** + * A basic test that will be called and fail. + */ + public function testDeleteByID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $table = $db_prefix . $type; + $q = "SELECT * FROM $table WHERE id = $id"; + $test = get_data($q); + + $this->assertEqual($test[0]->id, $id); + $this->assertTrue(elgg_delete_metastring_based_object_by_id($id, $type)); + $this->assertFalse(get_data($q)); + } + } + + public function testGetMetastringObjectByID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $test = elgg_get_metastring_based_object_by_id($id, $type); + + $this->assertEqual($id, $test->id); + } + } + + /** + * A basic test that will be called and fail. + */ + public function testEnableDisableByID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $table = $db_prefix . $type; + $q = "SELECT * FROM $table WHERE id = $id"; + $test = get_data($q); + + // disable + $this->assertEqual($test[0]->enabled, 'yes'); + $this->assertTrue(elgg_set_metastring_based_object_enabled_by_id($id, 'no', $type)); + + $test = get_data($q); + $this->assertEqual($test[0]->enabled, 'no'); + + // enable + $ashe = access_get_show_hidden_status(); + access_show_hidden_entities(true); + flush(); + $this->assertTrue(elgg_set_metastring_based_object_enabled_by_id($id, 'yes', $type)); + + $test = get_data($q); + $this->assertEqual($test[0]->enabled, 'yes'); + + access_show_hidden_entities($ashe); + } + } + + +} -- cgit v1.2.3