| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 | 
							- <?php
 
- namespace Elgg\Database;
 
- use Elgg\Database;
 
- use Elgg\Database\EntityTable;
 
- use Elgg\Database\MetastringsTable;
 
- use Elgg\EventsService as Events;
 
- use ElggSession as Session;
 
- use Elgg\Cache\MetadataCache as Cache;
 
- /**
 
-  * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
 
-  *
 
-  * @access private
 
-  *
 
-  * @package    Elgg.Core
 
-  * @subpackage Database
 
-  * @since      1.10.0
 
-  */
 
- class MetadataTable {
 
- 	/** @var array */
 
- 	private $independents = array();
 
- 	
 
- 	/** @var Cache */
 
- 	private $cache;
 
- 	
 
- 	/** @var Database */
 
- 	private $db;
 
- 	
 
- 	/** @var EntityTable */
 
- 	private $entityTable;
 
- 	
 
- 	/** @var MetastringsTable */
 
- 	private $metastringsTable;
 
- 	
 
- 	/** @var Events */
 
- 	private $events;
 
- 	
 
- 	/** @var Session */
 
- 	private $session;
 
- 	
 
- 	/** @var string */
 
- 	private $table;
 
- 	/**
 
- 	 * Constructor
 
- 	 * 
 
- 	 * @param Cache            $cache            A cache for this table
 
- 	 * @param Database         $db               The Elgg database
 
- 	 * @param EntityTable      $entityTable      The entities table
 
- 	 * @param Events           $events           The events registry
 
- 	 * @param MetastringsTable $metastringsTable The metastrings table
 
- 	 * @param Session          $session          The session
 
- 	 */
 
- 	public function __construct(
 
- 			Cache $cache,
 
- 			Database $db,
 
- 			EntityTable $entityTable,
 
- 			Events $events,
 
- 			MetastringsTable $metastringsTable,
 
- 			Session $session) {
 
- 		$this->cache = $cache;
 
- 		$this->db = $db;
 
- 		$this->entityTable = $entityTable;
 
- 		$this->events = $events;
 
- 		$this->metastringsTable = $metastringsTable;
 
- 		$this->session = $session;
 
- 		$this->table = $this->db->getTablePrefix() . "metadata";
 
- 	}
 
- 	/**
 
- 	 * Get a specific metadata object by its id.
 
- 	 * If you want multiple metadata objects, use
 
- 	 * {@link elgg_get_metadata()}.
 
- 	 *
 
- 	 * @param int $id The id of the metadata object being retrieved.
 
- 	 *
 
- 	 * @return \ElggMetadata|false  false if not found
 
- 	 */
 
- 	function get($id) {
 
- 		return _elgg_get_metastring_based_object_from_id($id, 'metadata');
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Deletes metadata using its ID.
 
- 	 *
 
- 	 * @param int $id The metadata ID to delete.
 
- 	 * @return bool
 
- 	 */
 
- 	function delete($id) {
 
- 		$metadata = $this->get($id);
 
- 		return $metadata ? $metadata->delete() : false;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Create a new metadata object, or update an existing one.
 
- 	 *
 
- 	 * Metadata can be an array by setting allow_multiple to true, but it is an
 
- 	 * indexed array with no control over the indexing.
 
- 	 *
 
- 	 * @param int    $entity_guid    The entity to attach the metadata to
 
- 	 * @param string $name           Name of the metadata
 
- 	 * @param string $value          Value of the metadata
 
- 	 * @param string $value_type     'text', 'integer', or '' for automatic detection
 
- 	 * @param int    $owner_guid     GUID of entity that owns the metadata. Default is logged in user.
 
- 	 * @param int    $access_id      Default is ACCESS_PRIVATE
 
- 	 * @param bool   $allow_multiple Allow multiple values for one key. Default is false
 
- 	 *
 
- 	 * @return int|false id of metadata or false if failure
 
- 	 */
 
- 	function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0,
 
- 			$access_id = ACCESS_PRIVATE, $allow_multiple = false) {
 
- 		$entity_guid = (int)$entity_guid;
 
- 		// name and value are encoded in add_metastring()
 
- 		$value_type = detect_extender_valuetype($value, $this->db->sanitizeString(trim($value_type)));
 
- 		$time = time();
 
- 		$owner_guid = (int)$owner_guid;
 
- 		$allow_multiple = (boolean)$allow_multiple;
 
- 	
 
- 		if (!isset($value)) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		if ($owner_guid == 0) {
 
- 			$owner_guid = $this->session->getLoggedInUserGuid();
 
- 		}
 
- 	
 
- 		$access_id = (int)$access_id;
 
- 	
 
- 		$query = "SELECT * from {$this->table}"
 
- 			. " WHERE entity_guid = $entity_guid and name_id=" . $this->metastringsTable->getId($name) . " limit 1";
 
- 	
 
- 		$existing = $this->db->getDataRow($query);
 
- 		if ($existing && !$allow_multiple) {
 
- 			$id = (int)$existing->id;
 
- 			$result = $this->update($id, $name, $value, $value_type, $owner_guid, $access_id);
 
- 	
 
- 			if (!$result) {
 
- 				return false;
 
- 			}
 
- 		} else {
 
- 			// Support boolean types
 
- 			if (is_bool($value)) {
 
- 				$value = (int)$value;
 
- 			}
 
- 	
 
- 			// Add the metastrings
 
- 			$value_id = $this->metastringsTable->getId($value);
 
- 			if (!$value_id) {
 
- 				return false;
 
- 			}
 
- 	
 
- 			$name_id = $this->metastringsTable->getId($name);
 
- 			if (!$name_id) {
 
- 				return false;
 
- 			}
 
- 	
 
- 			// If ok then add it
 
- 			$query = "INSERT into {$this->table}"
 
- 				. " (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)"
 
- 				. " VALUES ($entity_guid, '$name_id','$value_id','$value_type', $owner_guid, $time, $access_id)";
 
- 	
 
- 			$id = $this->db->insertData($query);
 
- 	
 
- 			if ($id !== false) {
 
- 				$obj = $this->get($id);
 
- 				if ($this->events->trigger('create', 'metadata', $obj)) {
 
- 					$this->cache->clear($entity_guid);
 
- 	
 
- 					return $id;
 
- 				} else {
 
- 					$this->delete($id);
 
- 				}
 
- 			}
 
- 		}
 
- 	
 
- 		return $id;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Update a specific piece of metadata.
 
- 	 *
 
- 	 * @param int    $id         ID of the metadata to update
 
- 	 * @param string $name       Metadata name
 
- 	 * @param string $value      Metadata value
 
- 	 * @param string $value_type Value type
 
- 	 * @param int    $owner_guid Owner guid
 
- 	 * @param int    $access_id  Access ID
 
- 	 *
 
- 	 * @return bool
 
- 	 */
 
- 	function update($id, $name, $value, $value_type, $owner_guid, $access_id) {
 
- 		$id = (int)$id;
 
- 	
 
- 		if (!$md = $this->get($id)) {
 
- 			return false;
 
- 		}
 
- 		if (!$md->canEdit()) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		// If memcached then we invalidate the cache for this entry
 
- 		static $metabyname_memcache;
 
- 		if ((!$metabyname_memcache) && (is_memcache_available())) {
 
- 			$metabyname_memcache = new \ElggMemcache('metabyname_memcache');
 
- 		}
 
- 	
 
- 		if ($metabyname_memcache) {
 
- 			// @todo fix memcache (name_id is not a property of \ElggMetadata)
 
- 			$metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}");
 
- 		}
 
- 	
 
- 		$value_type = detect_extender_valuetype($value, $this->db->sanitizeString(trim($value_type)));
 
- 	
 
- 		$owner_guid = (int)$owner_guid;
 
- 		if ($owner_guid == 0) {
 
- 			$owner_guid = $this->session->getLoggedInUserGuid();
 
- 		}
 
- 	
 
- 		$access_id = (int)$access_id;
 
- 	
 
- 		// Support boolean types (as integers)
 
- 		if (is_bool($value)) {
 
- 			$value = (int)$value;
 
- 		}
 
- 	
 
- 		$value_id = $this->metastringsTable->getId($value);
 
- 		if (!$value_id) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		$name_id = $this->metastringsTable->getId($name);
 
- 		if (!$name_id) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		// If ok then add it
 
- 		$query = "UPDATE {$this->table}"
 
- 			. " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id,"
 
- 			. " owner_guid=$owner_guid where id=$id";
 
- 	
 
- 		$result = $this->db->updateData($query);
 
- 		if ($result !== false) {
 
- 	
 
- 			$this->cache->clear($md->entity_guid);
 
- 	
 
- 			// @todo this event tells you the metadata has been updated, but does not
 
- 			// let you do anything about it. What is needed is a plugin hook before
 
- 			// the update that passes old and new values.
 
- 			$obj = $this->get($id);
 
- 			$this->events->trigger('update', 'metadata', $obj);
 
- 		}
 
- 	
 
- 		return $result;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * This function creates metadata from an associative array of "key => value" pairs.
 
- 	 *
 
- 	 * To achieve an array for a single key, pass in the same key multiple times with
 
- 	 * allow_multiple set to true. This creates an indexed array. It does not support
 
- 	 * associative arrays and there is no guarantee on the ordering in the array.
 
- 	 *
 
- 	 * @param int    $entity_guid     The entity to attach the metadata to
 
- 	 * @param array  $name_and_values Associative array - a value can be a string, number, bool
 
- 	 * @param string $value_type      'text', 'integer', or '' for automatic detection
 
- 	 * @param int    $owner_guid      GUID of entity that owns the metadata
 
- 	 * @param int    $access_id       Default is ACCESS_PRIVATE
 
- 	 * @param bool   $allow_multiple  Allow multiple values for one key. Default is false
 
- 	 *
 
- 	 * @return bool
 
- 	 */
 
- 	function createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid,
 
- 			$access_id = ACCESS_PRIVATE, $allow_multiple = false) {
 
- 	
 
- 		foreach ($name_and_values as $k => $v) {
 
- 			$result = $this->create($entity_guid, $k, $v, $value_type, $owner_guid,
 
- 				$access_id, $allow_multiple);
 
- 			if (!$result) {
 
- 				return false;
 
- 			}
 
- 		}
 
- 		return true;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Returns metadata.  Accepts all elgg_get_entities() options for entity
 
- 	 * restraints.
 
- 	 *
 
- 	 * @see elgg_get_entities
 
- 	 *
 
- 	 * @warning 1.7's find_metadata() didn't support limits and returned all metadata.
 
- 	 *          This function defaults to a limit of 25. There is probably not a reason
 
- 	 *          for you to return all metadata unless you're exporting an entity,
 
- 	 *          have other restraints in place, or are doing something horribly
 
- 	 *          wrong in your code.
 
- 	 *
 
- 	 * @param array $options Array in format:
 
- 	 *
 
- 	 * metadata_names               => null|ARR metadata names
 
- 	 * metadata_values              => null|ARR metadata values
 
- 	 * metadata_ids                 => null|ARR metadata ids
 
- 	 * metadata_case_sensitive      => BOOL Overall Case sensitive
 
- 	 * metadata_owner_guids         => null|ARR guids for metadata owners
 
- 	 * metadata_created_time_lower  => INT Lower limit for created time.
 
- 	 * metadata_created_time_upper  => INT Upper limit for created time.
 
- 	 * metadata_calculation         => STR Perform the MySQL function on the metadata values returned.
 
- 	 *                                   The "metadata_calculation" option causes this function to
 
- 	 *                                   return the result of performing a mathematical calculation on
 
- 	 *                                   all metadata that match the query instead of returning
 
- 	 *                                   \ElggMetadata objects.
 
- 	 *
 
- 	 * @return \ElggMetadata[]|mixed
 
- 	 */
 
- 	function getAll(array $options = array()) {
 
- 	
 
- 		// @todo remove support for count shortcut - see #4393
 
- 		// support shortcut of 'count' => true for 'metadata_calculation' => 'count'
 
- 		if (isset($options['count']) && $options['count']) {
 
- 			$options['metadata_calculation'] = 'count';
 
- 			unset($options['count']);
 
- 		}
 
- 	
 
- 		$options['metastring_type'] = 'metadata';
 
- 		return _elgg_get_metastring_based_objects($options);
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Deletes metadata based on $options.
 
- 	 *
 
- 	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
 
- 	 *          This requires at least one constraint: metadata_owner_guid(s),
 
- 	 *          metadata_name(s), metadata_value(s), or guid(s) must be set.
 
- 	 *
 
- 	 * @param array $options An options array. {@link elgg_get_metadata()}
 
- 	 * @return bool|null true on success, false on failure, null if no metadata to delete.
 
- 	 */
 
- 	function deleteAll(array $options) {
 
- 		if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
 
- 			return false;
 
- 		}
 
- 		$options['metastring_type'] = 'metadata';
 
- 		$result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
 
- 	
 
- 		// This moved last in case an object's constructor sets metadata. Currently the batch
 
- 		// delete process has to create the entity to delete its metadata. See #5214
 
- 		$this->cache->invalidateByOptions($options);
 
- 	
 
- 		return $result;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Disables metadata based on $options.
 
- 	 *
 
- 	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
 
- 	 *
 
- 	 * @param array $options An options array. {@link elgg_get_metadata()}
 
- 	 * @return bool|null true on success, false on failure, null if no metadata disabled.
 
- 	 */
 
- 	function disableAll(array $options) {
 
- 		if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		$this->cache->invalidateByOptions($options);
 
- 	
 
- 		// if we can see hidden (disabled) we need to use the offset
 
- 		// otherwise we risk an infinite loop if there are more than 50
 
- 		$inc_offset = access_get_show_hidden_status();
 
- 	
 
- 		$options['metastring_type'] = 'metadata';
 
- 		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Enables metadata based on $options.
 
- 	 *
 
- 	 * @warning Unlike elgg_get_metadata() this will not accept an empty options array!
 
- 	 *
 
- 	 * @warning In order to enable metadata, you must first use
 
- 	 * {@link access_show_hidden_entities()}.
 
- 	 *
 
- 	 * @param array $options An options array. {@link elgg_get_metadata()}
 
- 	 * @return bool|null true on success, false on failure, null if no metadata enabled.
 
- 	 */
 
- 	function enableAll(array $options) {
 
- 		if (!$options || !is_array($options)) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		$this->cache->invalidateByOptions($options);
 
- 	
 
- 		$options['metastring_type'] = 'metadata';
 
- 		return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Returns entities based upon metadata.  Also accepts all
 
- 	 * options available to elgg_get_entities().  Supports
 
- 	 * the singular option shortcut.
 
- 	 *
 
- 	 * @note Using metadata_names and metadata_values results in a
 
- 	 * "names IN (...) AND values IN (...)" clause.  This is subtly
 
- 	 * differently than default multiple metadata_name_value_pairs, which use
 
- 	 * "(name = value) AND (name = value)" clauses.
 
- 	 *
 
- 	 * When in doubt, use name_value_pairs.
 
- 	 *
 
- 	 * To ask for entities that do not have a metadata value, use a custom
 
- 	 * where clause like this:
 
- 	 *
 
- 	 * 	$options['wheres'][] = "NOT EXISTS (
 
- 	 *			SELECT 1 FROM {$dbprefix}metadata md
 
- 	 *			WHERE md.entity_guid = e.guid
 
- 	 *				AND md.name_id = $name_metastring_id
 
- 	 *				AND md.value_id = $value_metastring_id)";
 
- 	 *
 
- 	 * Note the metadata name and value has been denormalized in the above example.
 
- 	 *
 
- 	 * @see elgg_get_entities
 
- 	 *
 
- 	 * @param array $options Array in format:
 
- 	 *
 
- 	 * 	metadata_names => null|ARR metadata names
 
- 	 *
 
- 	 * 	metadata_values => null|ARR metadata values
 
- 	 *
 
- 	 * 	metadata_name_value_pairs => null|ARR (
 
- 	 *                                         name => 'name',
 
- 	 *                                         value => 'value',
 
- 	 *                                         'operand' => '=',
 
- 	 *                                         'case_sensitive' => true
 
- 	 *                                        )
 
- 	 *                               Currently if multiple values are sent via
 
- 	 *                               an array (value => array('value1', 'value2')
 
- 	 *                               the pair's operand will be forced to "IN".
 
- 	 *                               If passing "IN" as the operand and a string as the value, 
 
- 	 *                               the value must be a properly quoted and escaped string.
 
- 	 *
 
- 	 * 	metadata_name_value_pairs_operator => null|STR The operator to use for combining
 
- 	 *                                        (name = value) OPERATOR (name = value); default AND
 
- 	 *
 
- 	 * 	metadata_case_sensitive => BOOL Overall Case sensitive
 
- 	 *
 
- 	 *  order_by_metadata => null|ARR array(
 
- 	 *                                      'name' => 'metadata_text1',
 
- 	 *                                      'direction' => ASC|DESC,
 
- 	 *                                      'as' => text|integer
 
- 	 *                                     )
 
- 	 *                                Also supports array('name' => 'metadata_text1')
 
- 	 *
 
- 	 *  metadata_owner_guids => null|ARR guids for metadata owners
 
- 	 *
 
- 	 * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors.
 
- 	 */
 
- 	function getEntities(array $options = array()) {
 
- 		$defaults = array(
 
- 			'metadata_names'                     => ELGG_ENTITIES_ANY_VALUE,
 
- 			'metadata_values'                    => ELGG_ENTITIES_ANY_VALUE,
 
- 			'metadata_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE,
 
- 	
 
- 			'metadata_name_value_pairs_operator' => 'AND',
 
- 			'metadata_case_sensitive'            => true,
 
- 			'order_by_metadata'                  => array(),
 
- 	
 
- 			'metadata_owner_guids'               => ELGG_ENTITIES_ANY_VALUE,
 
- 		);
 
- 	
 
- 		$options = array_merge($defaults, $options);
 
- 	
 
- 		$singulars = array('metadata_name', 'metadata_value',
 
- 			'metadata_name_value_pair', 'metadata_owner_guid');
 
- 	
 
- 		$options = _elgg_normalize_plural_options_array($options, $singulars);
 
- 	
 
- 		if (!$options = _elgg_entities_get_metastrings_options('metadata', $options)) {
 
- 			return false;
 
- 		}
 
- 	
 
- 		return $this->entityTable->getEntities($options);
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Returns metadata name and value SQL where for entities.
 
- 	 * NB: $names and $values are not paired. Use $pairs for this.
 
- 	 * Pairs default to '=' operand.
 
- 	 *
 
- 	 * This function is reused for annotations because the tables are
 
- 	 * exactly the same.
 
- 	 *
 
- 	 * @param string     $e_table           Entities table name
 
- 	 * @param string     $n_table           Normalized metastrings table name (Where entities,
 
- 	 *                                    values, and names are joined. annotations / metadata)
 
- 	 * @param array|null $names             Array of names
 
- 	 * @param array|null $values            Array of values
 
- 	 * @param array|null $pairs             Array of names / values / operands
 
- 	 * @param string     $pair_operator     ("AND" or "OR") Operator to use to join the where clauses for pairs
 
- 	 * @param bool       $case_sensitive    Case sensitive metadata names?
 
- 	 * @param array|null $order_by_metadata Array of names / direction
 
- 	 * @param array|null $owner_guids       Array of owner GUIDs
 
- 	 *
 
- 	 * @return false|array False on fail, array('joins', 'wheres')
 
- 	 * @access private
 
- 	 */
 
- 	function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null,
 
- 			$pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null,
 
- 			$owner_guids = null) {
 
- 		// short circuit if nothing requested
 
- 		// 0 is a valid (if not ill-conceived) metadata name.
 
- 		// 0 is also a valid metadata value for false, null, or 0
 
- 		// 0 is also a valid(ish) owner_guid
 
- 		if ((!$names && $names !== 0)
 
- 			&& (!$values && $values !== 0)
 
- 			&& (!$pairs && $pairs !== 0)
 
- 			&& (!$owner_guids && $owner_guids !== 0)
 
- 			&& !$order_by_metadata) {
 
- 			return '';
 
- 		}
 
- 	
 
- 		// 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 = _elgg_get_access_where_sql(array(
 
- 			'table_alias' => 'n_table',
 
- 			'guid_column' => 'entity_guid',
 
- 		));
 
- 	
 
- 		$return = array (
 
- 			'joins' => array (),
 
- 			'wheres' => array(),
 
- 			'orders' => array()
 
- 		);
 
- 	
 
- 		// will always want to join these tables if pulling metastrings.
 
- 		$return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table on
 
- 			{$e_table}.guid = n_table.entity_guid";
 
- 	
 
- 		$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[] = '\'' . $this->db->sanitizeString($name) . '\'';
 
- 			}
 
- 	
 
- 			if ($names_str = implode(',', $sanitised_names)) {
 
- 				$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn on n_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[] = '\'' . $this->db->sanitizeString($value) . '\'';
 
- 			}
 
- 	
 
- 			if ($values_str = implode(',', $sanitised_values)) {
 
- 				$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv on n_table.value_id = msv.id";
 
- 				$values_where = "({$binary}msv.string IN ($values_str))";
 
- 			}
 
- 		}
 
- 	
 
- 		if ($names_where && $values_where) {
 
- 			$wheres[] = "($names_where AND $values_where AND $access)";
 
- 		} elseif ($names_where) {
 
- 			$wheres[] = "($names_where AND $access)";
 
- 		} elseif ($values_where) {
 
- 			$wheres[] = "($values_where AND $access)";
 
- 		}
 
- 	
 
- 		// add pairs
 
- 		// pairs must be in arrays.
 
- 		if (is_array($pairs)) {
 
- 			// check if this is an array of pairs or just a single pair.
 
- 			if (isset($pairs['name']) || isset($pairs['value'])) {
 
- 				$pairs = array($pairs);
 
- 			}
 
- 	
 
- 			$pair_wheres = array();
 
- 	
 
- 			// @todo when the pairs are > 3 should probably split the query up to
 
- 			// denormalize the strings table.
 
- 	
 
- 			foreach ($pairs as $index => $pair) {
 
- 				// @todo move this elsewhere?
 
- 				// support shortcut 'n' => 'v' method.
 
- 				if (!is_array($pair)) {
 
- 					$pair = array(
 
- 						'name' => $index,
 
- 						'value' => $pair
 
- 					);
 
- 				}
 
- 	
 
- 				// must have at least a name and value
 
- 				if (!isset($pair['name']) || !isset($pair['value'])) {
 
- 					// @todo should probably return false.
 
- 					continue;
 
- 				}
 
- 	
 
- 				// case sensitivity can be specified per pair.
 
- 				// default to higher level setting.
 
- 				if (isset($pair['case_sensitive'])) {
 
- 					$pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
 
- 				} else {
 
- 					$pair_binary = $binary;
 
- 				}
 
- 	
 
- 				if (isset($pair['operand'])) {
 
- 					$operand = $this->db->sanitizeString($pair['operand']);
 
- 				} else {
 
- 					$operand = ' = ';
 
- 				}
 
- 	
 
- 				// for comparing
 
- 				$trimmed_operand = trim(strtolower($operand));
 
- 	
 
- 				$access = _elgg_get_access_where_sql(array(
 
- 					'table_alias' => "n_table{$i}",
 
- 					'guid_column' => 'entity_guid',
 
- 				));
 
- 				// certain operands can't work well with strings that can be interpreted as numbers
 
- 				// for direct comparisons like IN, =, != we treat them as strings
 
- 				// gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
 
- 				// see https://github.com/Elgg/Elgg/issues/7009
 
- 				$num_safe_operands = array('>', '<', '>=', '<=');
 
- 				$num_test_operand = trim(strtoupper($operand));
 
- 	
 
- 				if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
 
- 					$value = $this->db->sanitizeString($pair['value']);
 
- 				} else if (is_bool($pair['value'])) {
 
- 					$value = (int)$pair['value'];
 
- 				} else if (is_array($pair['value'])) {
 
- 					$values_array = array();
 
- 	
 
- 					foreach ($pair['value'] as $pair_value) {
 
- 						if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
 
- 							$values_array[] = $this->db->sanitizeString($pair_value);
 
- 						} else {
 
- 							$values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
 
- 						}
 
- 					}
 
- 	
 
- 					if ($values_array) {
 
- 						$value = '(' . implode(', ', $values_array) . ')';
 
- 					}
 
- 	
 
- 					// @todo allow support for non IN operands with array of values.
 
- 					// will have to do more silly joins.
 
- 					$operand = 'IN';
 
- 				} else if ($trimmed_operand == 'in') {
 
- 					$value = "({$pair['value']})";
 
- 				} else {
 
- 					$value = "'" . $this->db->sanitizeString($pair['value']) . "'";
 
- 				}
 
- 	
 
- 				$name = $this->db->sanitizeString($pair['name']);
 
- 	
 
- 				// @todo The multiple joins are only needed when the operator is AND
 
- 				$return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
 
- 					on {$e_table}.guid = n_table{$i}.entity_guid";
 
- 				$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
 
- 					on n_table{$i}.name_id = msn{$i}.id";
 
- 				$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
 
- 					on n_table{$i}.value_id = msv{$i}.id";
 
- 	
 
- 				$pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string
 
- 					$operand $value AND $access)";
 
- 	
 
- 				$i++;
 
- 			}
 
- 	
 
- 			if ($where = implode(" $pair_operator ", $pair_wheres)) {
 
- 				$wheres[] = "($where)";
 
- 			}
 
- 		}
 
- 	
 
- 		// add owner_guids
 
- 		if ($owner_guids) {
 
- 			if (is_array($owner_guids)) {
 
- 				$sanitised = array_map('sanitise_int', $owner_guids);
 
- 				$owner_str = implode(',', $sanitised);
 
- 			} else {
 
- 				$owner_str = (int)$owner_guids;
 
- 			}
 
- 	
 
- 			$wheres[] = "(n_table.owner_guid IN ($owner_str))";
 
- 		}
 
- 	
 
- 		if ($where = implode(' AND ', $wheres)) {
 
- 			$return['wheres'][] = "($where)";
 
- 		}
 
- 	
 
- 		if (is_array($order_by_metadata)) {
 
- 			if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
 
- 				// singleton, so fix
 
- 				$order_by_metadata = array($order_by_metadata);
 
- 			}
 
- 			foreach ($order_by_metadata as $order_by) {
 
- 				if (is_array($order_by) && isset($order_by['name'])) {
 
- 					$name = $this->db->sanitizeString($order_by['name']);
 
- 					if (isset($order_by['direction'])) {
 
- 						$direction = $this->db->sanitizeString($order_by['direction']);
 
- 					} else {
 
- 						$direction = 'ASC';
 
- 					}
 
- 					$return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
 
- 						on {$e_table}.guid = n_table{$i}.entity_guid";
 
- 					$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
 
- 						on n_table{$i}.name_id = msn{$i}.id";
 
- 					$return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
 
- 						on n_table{$i}.value_id = msv{$i}.id";
 
- 	
 
- 					$access = _elgg_get_access_where_sql(array(
 
- 						'table_alias' => "n_table{$i}",
 
- 						'guid_column' => 'entity_guid',
 
- 					));
 
- 	
 
- 					$return['wheres'][] = "(msn{$i}.string = '$name' AND $access)";
 
- 					if (isset($order_by['as']) && $order_by['as'] == 'integer') {
 
- 						$return['orders'][] = "CAST(msv{$i}.string AS SIGNED) $direction";
 
- 					} else {
 
- 						$return['orders'][] = "msv{$i}.string $direction";
 
- 					}
 
- 					$i++;
 
- 				}
 
- 			}
 
- 		}
 
- 	
 
- 		return $return;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Get the URL for this metadata
 
- 	 *
 
- 	 * By default this links to the export handler in the current view.
 
- 	 *
 
- 	 * @param int $id Metadata ID
 
- 	 *
 
- 	 * @return mixed
 
- 	 */
 
- 	function getUrl($id) {
 
- 		$extender = $this->get($id);
 
- 		return $extender ? $extender->getURL() : false;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Mark entities with a particular type and subtype as having access permissions
 
- 	 * that can be changed independently from their parent entity
 
- 	 *
 
- 	 * @param string $type    The type - object, user, etc
 
- 	 * @param string $subtype The subtype; all subtypes by default
 
- 	 *
 
- 	 * @return void
 
- 	 */
 
- 	function registerMetadataAsIndependent($type, $subtype = '*') {
 
- 		if (!isset($this->independents[$type])) {
 
- 			$this->independents[$type] = array();
 
- 		}
 
- 		
 
- 		$this->independents[$type][$subtype] = true;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Determines whether entities of a given type and subtype should not change
 
- 	 * their metadata in line with their parent entity
 
- 	 *
 
- 	 * @param string $type    The type - object, user, etc
 
- 	 * @param string $subtype The entity subtype
 
- 	 *
 
- 	 * @return bool
 
- 	 */
 
- 	function isMetadataIndependent($type, $subtype) {
 
- 		if (empty($this->independents[$type])) {
 
- 			return false;
 
- 		}
 
- 		return !empty($this->independents[$type][$subtype])
 
- 			|| !empty($this->independents[$type]['*']);
 
- 	}
 
- 	
 
- 	/**
 
- 	 * When an entity is updated, resets the access ID on all of its child metadata
 
- 	 *
 
- 	 * @param string      $event       The name of the event
 
- 	 * @param string      $object_type The type of object
 
- 	 * @param \ElggEntity $object      The entity itself
 
- 	 *
 
- 	 * @return true
 
- 	 * @access private Set as private in 1.9.0
 
- 	 */
 
- 	function handleUpdate($event, $object_type, $object) {
 
- 		if ($object instanceof \ElggEntity) {
 
- 			if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
 
- 				$access_id = (int)$object->access_id;
 
- 				$guid = (int)$object->getGUID();
 
- 				$query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
 
- 				$this->db->updateData($query);
 
- 			}
 
- 		}
 
- 		return true;
 
- 	}
 
- 	
 
- }
 
 
  |