123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242 |
- <?php
- namespace Elgg\Database;
- use IncompleteEntityException;
- /**
- * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
- *
- * @access private
- *
- * @package Elgg.Core
- * @subpackage Database
- * @since 1.10.0
- */
- class EntityTable {
- /**
- * Global Elgg configuration
- *
- * @var \stdClass
- */
- private $CONFIG;
- /**
- * Constructor
- */
- public function __construct() {
- global $CONFIG;
- $this->CONFIG = $CONFIG;
- }
- /**
- * Returns a database row from the entities table.
- *
- * @tip Use get_entity() to return the fully loaded entity.
- *
- * @warning This will only return results if a) it exists, b) you have access to it.
- * see {@link _elgg_get_access_where_sql()}.
- *
- * @param int $guid The GUID of the object to extract
- *
- * @return \stdClass|false
- * @see entity_row_to_elggstar()
- * @access private
- */
- function getRow($guid) {
-
-
- if (!$guid) {
- return false;
- }
-
- $guid = (int) $guid;
- $access = _elgg_get_access_where_sql(array('table_alias' => ''));
-
- return _elgg_services()->db->getDataRow("SELECT * from {$this->CONFIG->dbprefix}entities where guid=$guid and $access");
- }
-
- /**
- * Create an Elgg* object from a given entity row.
- *
- * Handles loading all tables into the correct class.
- *
- * @param \stdClass $row The row of the entry in the entities table.
- *
- * @return \ElggEntity|false
- * @see get_entity_as_row()
- * @see add_subtype()
- * @see get_entity()
- * @access private
- *
- * @throws \ClassException|\InstallationException
- */
- function rowToElggStar($row) {
- if (!($row instanceof \stdClass)) {
- return $row;
- }
-
- if ((!isset($row->guid)) || (!isset($row->subtype))) {
- return $row;
- }
-
- $new_entity = false;
-
- // Create a memcache cache if we can
- static $newentity_cache;
- if ((!$newentity_cache) && (is_memcache_available())) {
- $newentity_cache = new \ElggMemcache('new_entity_cache');
- }
- if ($newentity_cache) {
- $new_entity = $newentity_cache->load($row->guid);
- }
- if ($new_entity) {
- return $new_entity;
- }
-
- // load class for entity if one is registered
- $classname = get_subtype_class_from_id($row->subtype);
- if ($classname != "") {
- if (class_exists($classname)) {
- $new_entity = new $classname($row);
-
- if (!($new_entity instanceof \ElggEntity)) {
- $msg = $classname . " is not a " . '\ElggEntity' . ".";
- throw new \ClassException($msg);
- }
- } else {
- error_log("Class '" . $classname . "' was not found, missing plugin?");
- }
- }
-
- if (!$new_entity) {
- //@todo Make this into a function
- switch ($row->type) {
- case 'object' :
- $new_entity = new \ElggObject($row);
- break;
- case 'user' :
- $new_entity = new \ElggUser($row);
- break;
- case 'group' :
- $new_entity = new \ElggGroup($row);
- break;
- case 'site' :
- $new_entity = new \ElggSite($row);
- break;
- default:
- $msg = "Entity type " . $row->type . " is not supported.";
- throw new \InstallationException($msg);
- }
- }
-
- // Cache entity if we have a cache available
- if (($newentity_cache) && ($new_entity)) {
- $newentity_cache->save($new_entity->guid, $new_entity);
- }
-
- return $new_entity;
- }
-
- /**
- * Loads and returns an entity object from a guid.
- *
- * @param int $guid The GUID of the entity
- * @param string $type The type of the entity. If given, even an existing entity with the given GUID
- * will not be returned unless its type matches.
- *
- * @return \ElggEntity The correct Elgg or custom object based upon entity type and subtype
- */
- function get($guid, $type = '') {
- // We could also use: if (!(int) $guid) { return false },
- // but that evaluates to a false positive for $guid = true.
- // This is a bit slower, but more thorough.
- if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
- return false;
- }
-
- // Check local cache first
- $new_entity = _elgg_retrieve_cached_entity($guid);
- if ($new_entity) {
- if ($type) {
- return elgg_instanceof($new_entity, $type) ? $new_entity : false;
- }
- return $new_entity;
- }
- $options = [
- 'guid' => $guid,
- 'limit' => 1,
- 'site_guids' => ELGG_ENTITIES_ANY_VALUE, // for BC with get_entity, allow matching any site
- ];
- if ($type) {
- $options['type'] = $type;
- }
- $entities = $this->getEntities($options);
- return $entities ? $entities[0] : false;
- }
-
- /**
- * Does an entity exist?
- *
- * This function checks for the existence of an entity independent of access
- * permissions. It is useful for situations when a user cannot access an entity
- * and it must be determined whether entity has been deleted or the access level
- * has changed.
- *
- * @param int $guid The GUID of the entity
- *
- * @return bool
- */
- function exists($guid) {
-
-
- $guid = sanitize_int($guid);
-
- $query = "SELECT count(*) as total FROM {$this->CONFIG->dbprefix}entities WHERE guid = $guid";
- $result = _elgg_services()->db->getDataRow($query);
- if ($result->total == 0) {
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Enable an entity.
- *
- * @param int $guid GUID of entity to enable
- * @param bool $recursive Recursively enable all entities disabled with the entity?
- *
- * @return bool
- */
- function enable($guid, $recursive = true) {
-
- // Override access only visible entities
- $old_access_status = access_get_show_hidden_status();
- access_show_hidden_entities(true);
-
- $result = false;
- $entity = get_entity($guid);
- if ($entity) {
- $result = $entity->enable($recursive);
- }
-
- access_show_hidden_entities($old_access_status);
- return $result;
- }
-
- /**
- * Returns an array of entities with optional filtering.
- *
- * Entities are the basic unit of storage in Elgg. This function
- * provides the simplest way to get an array of entities. There
- * are many options available that can be passed to filter
- * what sorts of entities are returned.
- *
- * @tip To output formatted strings of entities, use {@link elgg_list_entities()} and
- * its cousins.
- *
- * @tip Plural arguments can be written as singular if only specifying a
- * single element. ('type' => 'object' vs 'types' => array('object')).
- *
- * @param array $options Array in format:
- *
- * types => null|STR entity type (type IN ('type1', 'type2')
- * Joined with subtypes by AND. See below)
- *
- * subtypes => null|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2))
- * Use ELGG_ENTITIES_NO_VALUE to match the default subtype.
- * Use ELGG_ENTITIES_ANY_VALUE to match any subtype.
- *
- * type_subtype_pairs => null|ARR (array('type' => 'subtype'))
- * array(
- * 'object' => array('blog', 'file'), // All objects with subtype of 'blog' or 'file'
- * 'user' => ELGG_ENTITY_ANY_VALUE, // All users irrespective of subtype
- * );
- *
- * guids => null|ARR Array of entity guids
- *
- * owner_guids => null|ARR Array of owner guids
- *
- * container_guids => null|ARR Array of container_guids
- *
- * site_guids => null (current_site)|ARR Array of site_guid
- *
- * order_by => null (time_created desc)|STR SQL order by clause
- *
- * reverse_order_by => BOOL Reverse the default order by clause
- *
- * limit => null (10)|INT SQL limit clause (0 means no limit)
- *
- * offset => null (0)|INT SQL offset clause
- *
- * created_time_lower => null|INT Created time lower boundary in epoch time
- *
- * created_time_upper => null|INT Created time upper boundary in epoch time
- *
- * modified_time_lower => null|INT Modified time lower boundary in epoch time
- *
- * modified_time_upper => null|INT Modified time upper boundary in epoch time
- *
- * count => true|false return a count instead of entities
- *
- * wheres => array() Additional where clauses to AND together
- *
- * joins => array() Additional joins
- *
- * preload_owners => bool (false) If set to true, this function will preload
- * all the owners of the returned entities resulting in better
- * performance if those owners need to be displayed
- *
- * preload_containers => bool (false) If set to true, this function will preload
- * all the containers of the returned entities resulting in better
- * performance if those containers need to be displayed
- *
- *
- * callback => string A callback function to pass each row through
- *
- * distinct => bool (true) If set to false, Elgg will drop the DISTINCT clause from
- * the MySQL query, which will improve performance in some situations.
- * Avoid setting this option without a full understanding of the underlying
- * SQL query Elgg creates.
- *
- * @return mixed If count, int. If not count, array. false on errors.
- * @see elgg_get_entities_from_metadata()
- * @see elgg_get_entities_from_relationship()
- * @see elgg_get_entities_from_access_id()
- * @see elgg_get_entities_from_annotations()
- * @see elgg_list_entities()
- */
- function getEntities(array $options = array()) {
-
-
- $defaults = array(
- 'types' => ELGG_ENTITIES_ANY_VALUE,
- 'subtypes' => ELGG_ENTITIES_ANY_VALUE,
- 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
-
- 'guids' => ELGG_ENTITIES_ANY_VALUE,
- 'owner_guids' => ELGG_ENTITIES_ANY_VALUE,
- 'container_guids' => ELGG_ENTITIES_ANY_VALUE,
- 'site_guids' => $this->CONFIG->site_guid,
-
- 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE,
- 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE,
- 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
- 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
-
- 'reverse_order_by' => false,
- 'order_by' => 'e.time_created desc',
- 'group_by' => ELGG_ENTITIES_ANY_VALUE,
- 'limit' => _elgg_services()->config->get('default_limit'),
- 'offset' => 0,
- 'count' => false,
- 'selects' => array(),
- 'wheres' => array(),
- 'joins' => array(),
-
- 'preload_owners' => false,
- 'preload_containers' => false,
- 'callback' => 'entity_row_to_elggstar',
- 'distinct' => true,
-
- // private API
- '__ElggBatch' => null,
- );
-
- $options = array_merge($defaults, $options);
-
- // can't use helper function with type_subtype_pair because
- // it's already an array...just need to merge it
- if (isset($options['type_subtype_pair'])) {
- if (isset($options['type_subtype_pairs'])) {
- $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
- $options['type_subtype_pair']);
- } else {
- $options['type_subtype_pairs'] = $options['type_subtype_pair'];
- }
- }
-
- $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid');
- $options = _elgg_normalize_plural_options_array($options, $singulars);
- $options = $this->autoJoinTables($options);
- // evaluate where clauses
- if (!is_array($options['wheres'])) {
- $options['wheres'] = array($options['wheres']);
- }
-
- $wheres = $options['wheres'];
-
- $wheres[] = _elgg_get_entity_type_subtype_where_sql('e', $options['types'],
- $options['subtypes'], $options['type_subtype_pairs']);
-
- $wheres[] = _elgg_get_guid_based_where_sql('e.guid', $options['guids']);
- $wheres[] = _elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']);
- $wheres[] = _elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']);
- $wheres[] = _elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']);
-
- $wheres[] = _elgg_get_entity_time_where_sql('e', $options['created_time_upper'],
- $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
-
- // see if any functions failed
- // remove empty strings on successful functions
- foreach ($wheres as $i => $where) {
- if ($where === false) {
- return false;
- } elseif (empty($where)) {
- unset($wheres[$i]);
- }
- }
-
- // remove identical where clauses
- $wheres = array_unique($wheres);
-
- // evaluate join clauses
- if (!is_array($options['joins'])) {
- $options['joins'] = array($options['joins']);
- }
-
- // remove identical join clauses
- $joins = array_unique($options['joins']);
-
- foreach ($joins as $i => $join) {
- if ($join === false) {
- return false;
- } elseif (empty($join)) {
- unset($joins[$i]);
- }
- }
-
- // evalutate selects
- if ($options['selects']) {
- $selects = '';
- foreach ($options['selects'] as $select) {
- $selects .= ", $select";
- }
- } else {
- $selects = '';
- }
-
- if (!$options['count']) {
- $distinct = $options['distinct'] ? "DISTINCT" : "";
- $query = "SELECT $distinct e.*{$selects} FROM {$this->CONFIG->dbprefix}entities e ";
- } else {
- // note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than GUIDs
- $count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*";
- $query = "SELECT COUNT($count_expr) as total FROM {$this->CONFIG->dbprefix}entities e ";
- }
-
- // add joins
- foreach ($joins as $j) {
- $query .= " $j ";
- }
-
- // add wheres
- $query .= ' WHERE ';
-
- foreach ($wheres as $w) {
- $query .= " $w AND ";
- }
-
- // Add access controls
- $query .= _elgg_get_access_where_sql();
-
- // reverse order by
- if ($options['reverse_order_by']) {
- $options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']);
- }
- if ($options['count']) {
- $total = _elgg_services()->db->getDataRow($query);
- return (int)$total->total;
- }
- if ($options['group_by']) {
- $query .= " GROUP BY {$options['group_by']}";
- }
- if ($options['order_by']) {
- $query .= " ORDER BY {$options['order_by']}";
- }
- if ($options['limit']) {
- $limit = sanitise_int($options['limit'], false);
- $offset = sanitise_int($options['offset'], false);
- $query .= " LIMIT $offset, $limit";
- }
- if ($options['callback'] === 'entity_row_to_elggstar') {
- $results = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']);
- } else {
- $results = _elgg_services()->db->getData($query, $options['callback']);
- }
- if (!$results) {
- // no results, no preloading
- return $results;
- }
- // populate entity and metadata caches, and prepare $entities for preloader
- $guids = array();
- foreach ($results as $item) {
- // A custom callback could result in items that aren't \ElggEntity's, so check for them
- if ($item instanceof \ElggEntity) {
- _elgg_cache_entity($item);
- // plugins usually have only settings
- if (!$item instanceof \ElggPlugin) {
- $guids[] = $item->guid;
- }
- }
- }
- // @todo Without this, recursive delete fails. See #4568
- reset($results);
- if ($guids) {
- // there were entities in the result set, preload metadata for them
- _elgg_services()->metadataCache->populateFromEntities($guids);
- }
- if (count($results) > 1) {
- $props_to_preload = [];
- if ($options['preload_owners']) {
- $props_to_preload[] = 'owner_guid';
- }
- if ($options['preload_containers']) {
- $props_to_preload[] = 'container_guid';
- }
- if ($props_to_preload) {
- // note, ElggEntityPreloaderIntegrationTest assumes it can swap out
- // the preloader after boot. If you inject this component at construction
- // time that unit test will break. :/
- _elgg_services()->entityPreloader->preload($results, $props_to_preload);
- }
- }
- return $results;
- }
- /**
- * Decorate getEntities() options in order to auto-join secondary tables where it's
- * safe to do so.
- *
- * @param array $options Options array in getEntities() after normalization
- * @return array
- */
- protected function autoJoinTables(array $options) {
- // we must be careful that the query doesn't specify any options that may join
- // tables or change the selected columns
- if (!is_array($options['types'])
- || count($options['types']) !== 1
- || !empty($options['selects'])
- || !empty($options['wheres'])
- || !empty($options['joins'])
- || $options['callback'] !== 'entity_row_to_elggstar'
- || $options['count']) {
- // Too dangerous to auto-join
- return $options;
- }
- $join_types = [
- // Each class must have a static getExternalAttributes() : array
- 'object' => 'ElggObject',
- 'user' => 'ElggUser',
- 'group' => 'ElggGroup',
- 'site' => 'ElggSite',
- ];
- // We use reset() because $options['types'] may not have a numeric key
- $type = reset($options['types']);
- if (empty($join_types[$type])) {
- return $options;
- }
- // Get the columns we'll need to select. We can't use st.* because the order_by
- // clause may reference "guid", which MySQL will complain about being ambiguous
- if (!is_callable([$join_types[$type], 'getExternalAttributes'])) {
- // for some reason can't get external attributes.
- return $options;
- }
- $attributes = $join_types[$type]::getExternalAttributes();
- foreach (array_keys($attributes) as $col) {
- $options['selects'][] = "st.$col";
- }
- // join the secondary table
- $options['joins'][] = "JOIN {$this->CONFIG->dbprefix}{$type}s_entity st ON (e.guid = st.guid)";
- return $options;
- }
-
- /**
- * Return entities from an SQL query generated by elgg_get_entities.
- *
- * @param string $sql
- * @param \ElggBatch $batch
- * @return \ElggEntity[]
- *
- * @access private
- * @throws \LogicException
- */
- function fetchFromSql($sql, \ElggBatch $batch = null) {
- static $plugin_subtype;
- if (null === $plugin_subtype) {
- $plugin_subtype = get_subtype_id('object', 'plugin');
- }
-
- // Keys are types, values are columns that, if present, suggest that the secondary
- // table is already JOINed. Note it's OK if guess incorrectly because entity load()
- // will fetch any missing attributes.
- $types_to_optimize = array(
- 'object' => 'title',
- 'user' => 'password',
- 'group' => 'name',
- 'site' => 'url',
- );
-
- $rows = _elgg_services()->db->getData($sql);
-
- // guids to look up in each type
- $lookup_types = array();
- // maps GUIDs to the $rows key
- $guid_to_key = array();
-
- if (isset($rows[0]->type, $rows[0]->subtype)
- && $rows[0]->type === 'object'
- && $rows[0]->subtype == $plugin_subtype) {
- // Likely the entire resultset is plugins, which have already been optimized
- // to JOIN the secondary table. In this case we allow retrieving from cache,
- // but abandon the extra queries.
- $types_to_optimize = array();
- }
-
- // First pass: use cache where possible, gather GUIDs that we're optimizing
- foreach ($rows as $i => $row) {
- if (empty($row->guid) || empty($row->type)) {
- throw new \LogicException('Entity row missing guid or type');
- }
- $entity = _elgg_retrieve_cached_entity($row->guid);
- if ($entity) {
- $entity->refresh($row);
- $rows[$i] = $entity;
- continue;
- }
- if (isset($types_to_optimize[$row->type])) {
- // check if row already looks JOINed.
- if (isset($row->{$types_to_optimize[$row->type]})) {
- // Row probably already contains JOINed secondary table. Don't make another query just
- // to pull data that's already there
- continue;
- }
- $lookup_types[$row->type][] = $row->guid;
- $guid_to_key[$row->guid] = $i;
- }
- }
- // Do secondary queries and merge rows
- if ($lookup_types) {
- $dbprefix = _elgg_services()->config->get('dbprefix');
-
- foreach ($lookup_types as $type => $guids) {
- $set = "(" . implode(',', $guids) . ")";
- $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set";
- $secondary_rows = _elgg_services()->db->getData($sql);
- if ($secondary_rows) {
- foreach ($secondary_rows as $secondary_row) {
- $key = $guid_to_key[$secondary_row->guid];
- // cast to arrays to merge then cast back
- $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row);
- }
- }
- }
- }
- // Second pass to finish conversion
- foreach ($rows as $i => $row) {
- if ($row instanceof \ElggEntity) {
- continue;
- } else {
- try {
- $rows[$i] = entity_row_to_elggstar($row);
- } catch (IncompleteEntityException $e) {
- // don't let incomplete entities throw fatal errors
- unset($rows[$i]);
-
- // report incompletes to the batch process that spawned this query
- if ($batch) {
- $batch->reportIncompleteEntity($row);
- }
- }
- }
- }
- return $rows;
- }
-
- /**
- * Returns SQL where clause for type and subtype on main entity table
- *
- * @param string $table Entity table prefix as defined in SELECT...FROM entities $table
- * @param null|array $types Array of types or null if none.
- * @param null|array $subtypes Array of subtypes or null if none
- * @param null|array $pairs Array of pairs of types and subtypes
- *
- * @return false|string
- * @access private
- */
- function getEntityTypeSubtypeWhereSql($table, $types, $subtypes, $pairs) {
- // subtype depends upon type.
- if ($subtypes && !$types) {
- _elgg_services()->logger->warn("Cannot set subtypes without type.");
- return false;
- }
-
- // short circuit if nothing is requested
- if (!$types && !$subtypes && !$pairs) {
- return '';
- }
-
- // these are the only valid types for entities in elgg
- $valid_types = _elgg_services()->config->get('entity_types');
-
- // pairs override
- $wheres = array();
- if (!is_array($pairs)) {
- if (!is_array($types)) {
- $types = array($types);
- }
-
- if ($subtypes && !is_array($subtypes)) {
- $subtypes = array($subtypes);
- }
-
- // decrementer for valid types. Return false if no valid types
- $valid_types_count = count($types);
- $valid_subtypes_count = 0;
- // remove invalid types to get an accurate count of
- // valid types for the invalid subtype detection to use
- // below.
- // also grab the count of ALL subtypes on valid types to decrement later on
- // and check against.
- //
- // yes this is duplicating a foreach on $types.
- foreach ($types as $type) {
- if (!in_array($type, $valid_types)) {
- $valid_types_count--;
- unset($types[array_search($type, $types)]);
- } else {
- // do the checking (and decrementing) in the subtype section.
- $valid_subtypes_count += count($subtypes);
- }
- }
-
- // return false if nothing is valid.
- if (!$valid_types_count) {
- return false;
- }
-
- // subtypes are based upon types, so we need to look at each
- // type individually to get the right subtype id.
- foreach ($types as $type) {
- $subtype_ids = array();
- if ($subtypes) {
- foreach ($subtypes as $subtype) {
- // check that the subtype is valid
- if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
- // subtype value is 0
- $subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
- } elseif (!$subtype) {
- // subtype is ignored.
- // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
- continue;
- } else {
- $subtype_id = get_subtype_id($type, $subtype);
-
- if ($subtype_id) {
- $subtype_ids[] = $subtype_id;
- } else {
- $valid_subtypes_count--;
- _elgg_services()->logger->notice("Type-subtype '$type:$subtype' does not exist!");
- continue;
- }
- }
- }
-
- // return false if we're all invalid subtypes in the only valid type
- if ($valid_subtypes_count <= 0) {
- return false;
- }
- }
-
- if (is_array($subtype_ids) && count($subtype_ids)) {
- $subtype_ids_str = implode(',', $subtype_ids);
- $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
- } else {
- $wheres[] = "({$table}.type = '$type')";
- }
- }
- } else {
- // using type/subtype pairs
- $valid_pairs_count = count($pairs);
- $valid_pairs_subtypes_count = 0;
-
- // same deal as above--we need to know how many valid types
- // and subtypes we have before hitting the subtype section.
- // also normalize the subtypes into arrays here.
- foreach ($pairs as $paired_type => $paired_subtypes) {
- if (!in_array($paired_type, $valid_types)) {
- $valid_pairs_count--;
- unset($pairs[array_search($paired_type, $pairs)]);
- } else {
- if ($paired_subtypes && !is_array($paired_subtypes)) {
- $pairs[$paired_type] = array($paired_subtypes);
- }
- $valid_pairs_subtypes_count += count($paired_subtypes);
- }
- }
-
- if ($valid_pairs_count <= 0) {
- return false;
- }
- foreach ($pairs as $paired_type => $paired_subtypes) {
- // this will always be an array because of line 2027, right?
- // no...some overly clever person can say pair => array('object' => null)
- if (is_array($paired_subtypes)) {
- $paired_subtype_ids = array();
- foreach ($paired_subtypes as $paired_subtype) {
- if (ELGG_ENTITIES_NO_VALUE === $paired_subtype
- || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
-
- $paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
- ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
- } else {
- $valid_pairs_subtypes_count--;
- _elgg_services()->logger->notice("Type-subtype '$paired_type:$paired_subtype' does not exist!");
- // return false if we're all invalid subtypes in the only valid type
- continue;
- }
- }
-
- // return false if there are no valid subtypes.
- if ($valid_pairs_subtypes_count <= 0) {
- return false;
- }
-
-
- if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
- $wheres[] = "({$table}.type = '$paired_type'"
- . " AND {$table}.subtype IN ($paired_subtype_ids_str))";
- }
- } else {
- $wheres[] = "({$table}.type = '$paired_type')";
- }
- }
- }
-
- // pairs override the above. return false if they don't exist.
- if (is_array($wheres) && count($wheres)) {
- $where = implode(' OR ', $wheres);
- return "($where)";
- }
-
- return '';
- }
-
- /**
- * Returns SQL where clause for owner and containers.
- *
- * @param string $column Column name the guids should be checked against. Usually
- * best to provide in table.column format.
- * @param null|array $guids Array of GUIDs.
- *
- * @return false|string
- * @access private
- */
- function getGuidBasedWhereSql($column, $guids) {
- // short circuit if nothing requested
- // 0 is a valid guid
- if (!$guids && $guids !== 0) {
- return '';
- }
-
- // normalize and sanitise owners
- if (!is_array($guids)) {
- $guids = array($guids);
- }
-
- $guids_sanitized = array();
- foreach ($guids as $guid) {
- if ($guid !== ELGG_ENTITIES_NO_VALUE) {
- $guid = sanitise_int($guid);
-
- if (!$guid) {
- return false;
- }
- }
- $guids_sanitized[] = $guid;
- }
-
- $where = '';
- $guid_str = implode(',', $guids_sanitized);
-
- // implode(',', 0) returns 0.
- if ($guid_str !== false && $guid_str !== '') {
- $where = "($column IN ($guid_str))";
- }
-
- return $where;
- }
-
- /**
- * Returns SQL where clause for entity time limits.
- *
- * @param string $table Entity table prefix as defined in
- * SELECT...FROM entities $table
- * @param null|int $time_created_upper Time created upper limit
- * @param null|int $time_created_lower Time created lower limit
- * @param null|int $time_updated_upper Time updated upper limit
- * @param null|int $time_updated_lower Time updated lower limit
- *
- * @return false|string false on fail, string on success.
- * @access private
- */
- function getEntityTimeWhereSql($table, $time_created_upper = null,
- $time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) {
-
- $wheres = array();
-
- // exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
- if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
- $wheres[] = "{$table}.time_created <= $time_created_upper";
- }
-
- if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
- $wheres[] = "{$table}.time_created >= $time_created_lower";
- }
-
- if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
- $wheres[] = "{$table}.time_updated <= $time_updated_upper";
- }
-
- if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
- $wheres[] = "{$table}.time_updated >= $time_updated_lower";
- }
-
- if (is_array($wheres) && count($wheres) > 0) {
- $where_str = implode(' AND ', $wheres);
- return "($where_str)";
- }
-
- return '';
- }
-
- /**
- * Gets entities based upon attributes in secondary tables.
- * Also accepts all options available to elgg_get_entities(),
- * elgg_get_entities_from_metadata(), and elgg_get_entities_from_relationship().
- *
- * @warning requires that the entity type be specified and there can only be one
- * type.
- *
- * @see elgg_get_entities
- * @see elgg_get_entities_from_metadata
- * @see elgg_get_entities_from_relationship
- *
- * @param array $options Array in format:
- *
- * attribute_name_value_pairs => ARR (
- * 'name' => 'name',
- * 'value' => 'value',
- * 'operand' => '=', (optional)
- * 'case_sensitive' => false (optional)
- * )
- * If multiple values are sent via
- * an array ('value' => array('value1', 'value2')
- * the pair's operand will be forced to "IN".
- *
- * attribute_name_value_pairs_operator => null|STR The operator to use for combining
- * (name = value) OPERATOR (name = value); default is AND
- *
- * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors.
- * @throws InvalidArgumentException
- * @todo Does not support ordering by attributes or using an attribute pair shortcut like this ('title' => 'foo')
- */
- function getEntitiesFromAttributes(array $options = array()) {
- $defaults = array(
- 'attribute_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
- 'attribute_name_value_pairs_operator' => 'AND',
- );
-
- $options = array_merge($defaults, $options);
-
- $singulars = array('type', 'attribute_name_value_pair');
- $options = _elgg_normalize_plural_options_array($options, $singulars);
-
- $clauses = _elgg_get_entity_attribute_where_sql($options);
-
- if ($clauses) {
- // merge wheres to pass to elgg_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 elgg_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']);
- }
-
- return elgg_get_entities_from_relationship($options);
- }
-
- /**
- * Get the join and where clauses for working with entity attributes
- *
- * @return false|array False on fail, array('joins', 'wheres')
- * @access private
- * @throws InvalidArgumentException
- */
- function getEntityAttributeWhereSql(array $options = array()) {
-
- if (!isset($options['types'])) {
- throw new \InvalidArgumentException("The entity type must be defined for elgg_get_entities_from_attributes()");
- }
-
- if (is_array($options['types']) && count($options['types']) !== 1) {
- throw new \InvalidArgumentException("Only one type can be passed to elgg_get_entities_from_attributes()");
- }
-
- // type can be passed as string or array
- $type = $options['types'];
- if (is_array($type)) {
- $type = $type[0];
- }
-
- // @todo the types should be defined somewhere (as constant on \ElggEntity?)
- if (!in_array($type, array('group', 'object', 'site', 'user'))) {
- throw new \InvalidArgumentException("Invalid type '$type' passed to elgg_get_entities_from_attributes()");
- }
-
-
- $type_table = "{$this->CONFIG->dbprefix}{$type}s_entity";
-
- $return = array(
- 'joins' => array(),
- 'wheres' => array(),
- );
-
- // short circuit if nothing requested
- if ($options['attribute_name_value_pairs'] == ELGG_ENTITIES_ANY_VALUE) {
- return $return;
- }
-
- if (!is_array($options['attribute_name_value_pairs'])) {
- throw new \InvalidArgumentException("attribute_name_value_pairs must be an array for elgg_get_entities_from_attributes()");
- }
-
- $wheres = array();
-
- // check if this is an array of pairs or just a single pair.
- $pairs = $options['attribute_name_value_pairs'];
- if (isset($pairs['name']) || isset($pairs['value'])) {
- $pairs = array($pairs);
- }
-
- $pair_wheres = array();
- foreach ($pairs as $index => $pair) {
- // must have at least a name and value
- if (!isset($pair['name']) || !isset($pair['value'])) {
- continue;
- }
-
- if (isset($pair['operand'])) {
- $operand = sanitize_string($pair['operand']);
- } else {
- $operand = '=';
- }
-
- if (is_numeric($pair['value'])) {
- $value = sanitize_string($pair['value']);
- } else if (is_array($pair['value'])) {
- $values_array = array();
- foreach ($pair['value'] as $pair_value) {
- if (is_numeric($pair_value)) {
- $values_array[] = sanitize_string($pair_value);
- } else {
- $values_array[] = "'" . sanitize_string($pair_value) . "'";
- }
- }
-
- $operand = 'IN';
- if ($values_array) {
- $value = '(' . implode(', ', $values_array) . ')';
- }
-
- } else {
- $value = "'" . sanitize_string($pair['value']) . "'";
- }
-
- $name = sanitize_string($pair['name']);
-
- // case sensitivity can be specified per pair
- $pair_binary = '';
- if (isset($pair['case_sensitive'])) {
- $pair_binary = ($pair['case_sensitive']) ? 'BINARY ' : '';
- }
-
- $pair_wheres[] = "({$pair_binary}type_table.$name $operand $value)";
- }
-
- if ($where = implode(" {$options['attribute_name_value_pairs_operator']} ", $pair_wheres)) {
- $return['wheres'][] = "($where)";
-
- $return['joins'][] = "JOIN $type_table type_table ON e.guid = type_table.guid";
- }
-
- return $return;
- }
-
- /**
- * Returns a list of months in which entities were updated or created.
- *
- * @tip Use this to generate a list of archives by month for when entities were added or updated.
- *
- * @todo document how to pass in array for $subtype
- *
- * @warning Months are returned in the form YYYYMM.
- *
- * @param string $type The type of entity
- * @param string $subtype The subtype of entity
- * @param int $container_guid The container GUID that the entities belong to
- * @param int $site_guid The site GUID
- * @param string $order_by Order_by SQL order by clause
- *
- * @return array|false Either an array months as YYYYMM, or false on failure
- */
- function getDates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0,
- $order_by = 'time_created') {
-
-
-
- $site_guid = (int) $site_guid;
- if ($site_guid == 0) {
- $site_guid = $this->CONFIG->site_guid;
- }
- $where = array();
-
- if ($type != "") {
- $type = sanitise_string($type);
- $where[] = "type='$type'";
- }
-
- if (is_array($subtype)) {
- $tempwhere = "";
- if (sizeof($subtype)) {
- foreach ($subtype as $typekey => $subtypearray) {
- foreach ($subtypearray as $subtypeval) {
- $typekey = sanitise_string($typekey);
- if (!empty($subtypeval)) {
- if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
- return false;
- }
- } else {
- $subtypeval = 0;
- }
- if (!empty($tempwhere)) {
- $tempwhere .= " or ";
- }
- $tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
- }
- }
- }
- if (!empty($tempwhere)) {
- $where[] = "({$tempwhere})";
- }
- } else {
- if ($subtype) {
- if (!$subtype_id = get_subtype_id($type, $subtype)) {
- return false;
- } else {
- $where[] = "subtype=$subtype_id";
- }
- }
- }
-
- if ($container_guid !== 0) {
- if (is_array($container_guid)) {
- foreach ($container_guid as $key => $val) {
- $container_guid[$key] = (int) $val;
- }
- $where[] = "container_guid in (" . implode(",", $container_guid) . ")";
- } else {
- $container_guid = (int) $container_guid;
- $where[] = "container_guid = {$container_guid}";
- }
- }
-
- if ($site_guid > 0) {
- $where[] = "site_guid = {$site_guid}";
- }
-
- $where[] = _elgg_get_access_where_sql(array('table_alias' => ''));
-
- $sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
- FROM {$this->CONFIG->dbprefix}entities where ";
-
- foreach ($where as $w) {
- $sql .= " $w and ";
- }
-
- $sql .= "1=1 ORDER BY $order_by";
- if ($result = _elgg_services()->db->getData($sql)) {
- $endresult = array();
- foreach ($result as $res) {
- $endresult[] = $res->yearmonth;
- }
- return $endresult;
- }
- return false;
- }
-
- /**
- * Update the last_action column in the entities table for $guid.
- *
- * @warning This is different to time_updated. Time_updated is automatically set,
- * while last_action is only set when explicitly called.
- *
- * @param int $guid Entity annotation|relationship action carried out on
- * @param int $posted Timestamp of last action
- *
- * @return bool
- * @access private
- */
- function updateLastAction($guid, $posted = null) {
-
- $guid = (int)$guid;
- $posted = (int)$posted;
-
- if (!$posted) {
- $posted = time();
- }
-
- if ($guid) {
- //now add to the river updated table
- $query = "UPDATE {$this->CONFIG->dbprefix}entities SET last_action = {$posted} WHERE guid = {$guid}";
- $result = _elgg_services()->db->updateData($query);
- if ($result) {
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
- }
|