PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/engine/lib/entities.php

https://github.com/fragilbert/Elgg
PHP | 2033 lines | 1049 code | 274 blank | 710 comment | 258 complexity | cf0093e371f745d297c258e4f306d4ff MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Procedural code for creating, loading, and modifying ElggEntity objects.
  4. *
  5. * @package Elgg.Core
  6. * @subpackage DataModel.Entities
  7. * @link http://docs.elgg.org/DataModel/Entities
  8. */
  9. /**
  10. * Cache entities in memory once loaded.
  11. *
  12. * @global array $ENTITY_CACHE
  13. * @access private
  14. */
  15. global $ENTITY_CACHE;
  16. $ENTITY_CACHE = array();
  17. /**
  18. * Cache subtypes and related class names.
  19. *
  20. * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null
  21. * @access private
  22. */
  23. global $SUBTYPE_CACHE;
  24. $SUBTYPE_CACHE = null;
  25. /**
  26. * Invalidate this class's entry in the cache.
  27. *
  28. * @param int $guid The entity guid
  29. *
  30. * @return null
  31. * @access private
  32. */
  33. function invalidate_cache_for_entity($guid) {
  34. global $ENTITY_CACHE;
  35. $guid = (int)$guid;
  36. unset($ENTITY_CACHE[$guid]);
  37. elgg_get_metadata_cache()->clear($guid);
  38. }
  39. /**
  40. * Cache an entity.
  41. *
  42. * Stores an entity in $ENTITY_CACHE;
  43. *
  44. * @param ElggEntity $entity Entity to cache
  45. *
  46. * @return null
  47. * @see retrieve_cached_entity()
  48. * @see invalidate_cache_for_entity()
  49. * @access private
  50. * TODO(evan): Use an ElggCache object
  51. */
  52. function cache_entity(ElggEntity $entity) {
  53. global $ENTITY_CACHE;
  54. // Don't cache non-plugin entities while access control is off, otherwise they could be
  55. // exposed to users who shouldn't see them when control is re-enabled.
  56. if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) {
  57. return;
  58. }
  59. // Don't store too many or we'll have memory problems
  60. // TODO(evan): Pick a less arbitrary limit
  61. if (count($ENTITY_CACHE) > 256) {
  62. $random_guid = array_rand($ENTITY_CACHE);
  63. unset($ENTITY_CACHE[$random_guid]);
  64. // Purge separate metadata cache. Original idea was to do in entity destructor, but that would
  65. // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way
  66. // to know that the expunged entity will be GCed (might be another reference living), but that's
  67. // OK; the metadata will reload if necessary.
  68. elgg_get_metadata_cache()->clear($random_guid);
  69. }
  70. $ENTITY_CACHE[$entity->guid] = $entity;
  71. }
  72. /**
  73. * Retrieve a entity from the cache.
  74. *
  75. * @param int $guid The guid
  76. *
  77. * @return ElggEntity|bool false if entity not cached, or not fully loaded
  78. * @see cache_entity()
  79. * @see invalidate_cache_for_entity()
  80. * @access private
  81. */
  82. function retrieve_cached_entity($guid) {
  83. global $ENTITY_CACHE;
  84. if (isset($ENTITY_CACHE[$guid])) {
  85. if ($ENTITY_CACHE[$guid]->isFullyLoaded()) {
  86. return $ENTITY_CACHE[$guid];
  87. }
  88. }
  89. return false;
  90. }
  91. /**
  92. * As retrieve_cached_entity, but returns the result as a stdClass
  93. * (compatible with load functions that expect a database row.)
  94. *
  95. * @param int $guid The guid
  96. *
  97. * @return mixed
  98. * @todo unused
  99. * @access private
  100. */
  101. function retrieve_cached_entity_row($guid) {
  102. $obj = retrieve_cached_entity($guid);
  103. if ($obj) {
  104. $tmp = new stdClass;
  105. foreach ($obj as $k => $v) {
  106. $tmp->$k = $v;
  107. }
  108. return $tmp;
  109. }
  110. return false;
  111. }
  112. /**
  113. * Return the id for a given subtype.
  114. *
  115. * ElggEntity objects have a type and a subtype. Subtypes
  116. * are defined upon creation and cannot be changed.
  117. *
  118. * Plugin authors generally don't need to use this function
  119. * unless writing their own SQL queries. Use {@link ElggEntity::getSubtype()}
  120. * to return the string subtype.
  121. *
  122. * @internal Subtypes are stored in the entity_subtypes table. There is a foreign
  123. * key in the entities table.
  124. *
  125. * @param string $type Type
  126. * @param string $subtype Subtype
  127. *
  128. * @return int Subtype ID
  129. * @link http://docs.elgg.org/DataModel/Entities/Subtypes
  130. * @see get_subtype_from_id()
  131. * @access private
  132. */
  133. function get_subtype_id($type, $subtype) {
  134. global $SUBTYPE_CACHE;
  135. if (!$subtype) {
  136. return false;
  137. }
  138. if ($SUBTYPE_CACHE === null) {
  139. _elgg_populate_subtype_cache();
  140. }
  141. // use the cache before hitting database
  142. $result = _elgg_retrieve_cached_subtype($type, $subtype);
  143. if ($result !== null) {
  144. return $result->id;
  145. }
  146. return false;
  147. }
  148. /**
  149. * Gets the denormalized string for a given subtype ID.
  150. *
  151. * @param int $subtype_id Subtype ID from database
  152. * @return string|false Subtype name, false if subtype not found
  153. * @link http://docs.elgg.org/DataModel/Entities/Subtypes
  154. * @see get_subtype_id()
  155. * @access private
  156. */
  157. function get_subtype_from_id($subtype_id) {
  158. global $SUBTYPE_CACHE;
  159. if (!$subtype_id) {
  160. return '';
  161. }
  162. if ($SUBTYPE_CACHE === null) {
  163. _elgg_populate_subtype_cache();
  164. }
  165. if (isset($SUBTYPE_CACHE[$subtype_id])) {
  166. return $SUBTYPE_CACHE[$subtype_id]->subtype;
  167. }
  168. return false;
  169. }
  170. /**
  171. * Retrieve subtype from the cache.
  172. *
  173. * @param string $type
  174. * @param string $subtype
  175. * @return stdClass|null
  176. *
  177. * @access private
  178. */
  179. function _elgg_retrieve_cached_subtype($type, $subtype) {
  180. global $SUBTYPE_CACHE;
  181. if ($SUBTYPE_CACHE === null) {
  182. _elgg_populate_subtype_cache();
  183. }
  184. foreach ($SUBTYPE_CACHE as $obj) {
  185. if ($obj->type === $type && $obj->subtype === $subtype) {
  186. return $obj;
  187. }
  188. }
  189. return null;
  190. }
  191. /**
  192. * Fetch all suptypes from DB to local cache.
  193. *
  194. * @access private
  195. */
  196. function _elgg_populate_subtype_cache() {
  197. global $CONFIG, $SUBTYPE_CACHE;
  198. $results = get_data("SELECT * FROM {$CONFIG->dbprefix}entity_subtypes");
  199. $SUBTYPE_CACHE = array();
  200. foreach ($results as $row) {
  201. $SUBTYPE_CACHE[$row->id] = $row;
  202. }
  203. }
  204. /**
  205. * Return the class name for a registered type and subtype.
  206. *
  207. * Entities can be registered to always be loaded as a certain class
  208. * with add_subtype() or update_subtype(). This function returns the class
  209. * name if found and NULL if not.
  210. *
  211. * @param string $type The type
  212. * @param string $subtype The subtype
  213. *
  214. * @return string|null a class name or null
  215. * @see get_subtype_from_id()
  216. * @see get_subtype_class_from_id()
  217. * @access private
  218. */
  219. function get_subtype_class($type, $subtype) {
  220. global $SUBTYPE_CACHE;
  221. if ($SUBTYPE_CACHE === null) {
  222. _elgg_populate_subtype_cache();
  223. }
  224. // use the cache before going to the database
  225. $obj = _elgg_retrieve_cached_subtype($type, $subtype);
  226. if ($obj) {
  227. return $obj->class;
  228. }
  229. return null;
  230. }
  231. /**
  232. * Returns the class name for a subtype id.
  233. *
  234. * @param int $subtype_id The subtype id
  235. *
  236. * @return string|null
  237. * @see get_subtype_class()
  238. * @see get_subtype_from_id()
  239. * @access private
  240. */
  241. function get_subtype_class_from_id($subtype_id) {
  242. global $SUBTYPE_CACHE;
  243. if (!$subtype_id) {
  244. return null;
  245. }
  246. if ($SUBTYPE_CACHE === null) {
  247. _elgg_populate_subtype_cache();
  248. }
  249. if (isset($SUBTYPE_CACHE[$subtype_id])) {
  250. return $SUBTYPE_CACHE[$subtype_id]->class;
  251. }
  252. return null;
  253. }
  254. /**
  255. * Register ElggEntities with a certain type and subtype to be loaded as a specific class.
  256. *
  257. * By default entities are loaded as one of the 4 parent objects: site, user, object, or group.
  258. * If you subclass any of these you can register the classname with add_subtype() so
  259. * it will be loaded as that class automatically when retrieved from the database with
  260. * {@link get_entity()}.
  261. *
  262. * @warning This function cannot be used to change the class for a type-subtype pair.
  263. * Use update_subtype() for that.
  264. *
  265. * @param string $type The type you're subtyping (site, user, object, or group)
  266. * @param string $subtype The subtype
  267. * @param string $class Optional class name for the object
  268. *
  269. * @return int
  270. * @link http://docs.elgg.org/Tutorials/Subclasses
  271. * @link http://docs.elgg.org/DataModel/Entities
  272. * @see update_subtype()
  273. * @see remove_subtype()
  274. * @see get_entity()
  275. */
  276. function add_subtype($type, $subtype, $class = "") {
  277. global $CONFIG, $SUBTYPE_CACHE;
  278. if (!$subtype) {
  279. return 0;
  280. }
  281. $id = get_subtype_id($type, $subtype);
  282. if (!$id) {
  283. // In cache we store non-SQL-escaped strings because that's what's returned by query
  284. $cache_obj = (object) array(
  285. 'type' => $type,
  286. 'subtype' => $subtype,
  287. 'class' => $class,
  288. );
  289. $type = sanitise_string($type);
  290. $subtype = sanitise_string($subtype);
  291. $class = sanitise_string($class);
  292. $id = insert_data("INSERT INTO {$CONFIG->dbprefix}entity_subtypes"
  293. . " (type, subtype, class) VALUES ('$type', '$subtype', '$class')");
  294. // add entry to cache
  295. $cache_obj->id = $id;
  296. $SUBTYPE_CACHE[$id] = $cache_obj;
  297. }
  298. return $id;
  299. }
  300. /**
  301. * Removes a registered ElggEntity type, subtype, and classname.
  302. *
  303. * @warning You do not want to use this function. If you want to unregister
  304. * a class for a subtype, use update_subtype(). Using this function will
  305. * permanently orphan all the objects created with the specified subtype.
  306. *
  307. * @param string $type Type
  308. * @param string $subtype Subtype
  309. *
  310. * @return bool
  311. * @see add_subtype()
  312. * @see update_subtype()
  313. */
  314. function remove_subtype($type, $subtype) {
  315. global $CONFIG;
  316. $type = sanitise_string($type);
  317. $subtype = sanitise_string($subtype);
  318. return delete_data("DELETE FROM {$CONFIG->dbprefix}entity_subtypes"
  319. . " WHERE type = '$type' AND subtype = '$subtype'");
  320. }
  321. /**
  322. * Update a registered ElggEntity type, subtype, and class name
  323. *
  324. * @param string $type Type
  325. * @param string $subtype Subtype
  326. * @param string $class Class name to use when loading this entity
  327. *
  328. * @return bool
  329. */
  330. function update_subtype($type, $subtype, $class = '') {
  331. global $CONFIG, $SUBTYPE_CACHE;
  332. $id = get_subtype_id($type, $subtype);
  333. if (!$id) {
  334. return false;
  335. }
  336. if ($SUBTYPE_CACHE === null) {
  337. _elgg_populate_subtype_cache();
  338. }
  339. $unescaped_class = $class;
  340. $type = sanitise_string($type);
  341. $subtype = sanitise_string($subtype);
  342. $class = sanitise_string($class);
  343. $success = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes
  344. SET type = '$type', subtype = '$subtype', class = '$class'
  345. WHERE id = $id
  346. ");
  347. if ($success && isset($SUBTYPE_CACHE[$id])) {
  348. $SUBTYPE_CACHE[$id]->class = $unescaped_class;
  349. }
  350. return $success;
  351. }
  352. /**
  353. * Determine if a given user can write to an entity container.
  354. *
  355. * An entity can be a container for any other entity by setting the
  356. * container_guid. container_guid can differ from owner_guid.
  357. *
  358. * A plugin hook container_permissions_check:$entity_type is emitted to allow granular
  359. * access controls in plugins.
  360. *
  361. * @param int $user_guid The user guid, or 0 for logged in user
  362. * @param int $container_guid The container, or 0 for the current page owner.
  363. * @param string $type The type of entity we're looking to write
  364. * @param string $subtype The subtype of the entity we're looking to write
  365. *
  366. * @return bool
  367. * @link http://docs.elgg.org/DataModel/Containers
  368. */
  369. function can_write_to_container($user_guid = 0, $container_guid = 0, $type = 'all', $subtype = 'all') {
  370. $container_guid = (int)$container_guid;
  371. if (!$container_guid) {
  372. $container_guid = elgg_get_page_owner_guid();
  373. }
  374. $return = false;
  375. if (!$container_guid) {
  376. $return = true;
  377. }
  378. $container = get_entity($container_guid);
  379. $user_guid = (int)$user_guid;
  380. $user = get_entity($user_guid);
  381. if (!$user) {
  382. $user = elgg_get_logged_in_user_entity();
  383. }
  384. if ($container) {
  385. // If the user can edit the container, they can also write to it
  386. if ($container->canEdit($user_guid)) {
  387. $return = true;
  388. }
  389. // If still not approved, see if the user is a member of the group
  390. // @todo this should be moved to the groups plugin/library
  391. if (!$return && $user && $container instanceof ElggGroup) {
  392. /* @var ElggGroup $container */
  393. if ($container->isMember($user)) {
  394. $return = true;
  395. }
  396. }
  397. }
  398. // See if anyone else has anything to say
  399. return elgg_trigger_plugin_hook(
  400. 'container_permissions_check',
  401. $type,
  402. array(
  403. 'container' => $container,
  404. 'user' => $user,
  405. 'subtype' => $subtype
  406. ),
  407. $return);
  408. }
  409. /**
  410. * Returns a database row from the entities table.
  411. *
  412. * @tip Use get_entity() to return the fully loaded entity.
  413. *
  414. * @warning This will only return results if a) it exists, b) you have access to it.
  415. * see {@link get_access_sql_suffix()}.
  416. *
  417. * @param int $guid The GUID of the object to extract
  418. *
  419. * @return stdClass|false
  420. * @link http://docs.elgg.org/DataModel/Entities
  421. * @see entity_row_to_elggstar()
  422. * @access private
  423. */
  424. function get_entity_as_row($guid) {
  425. global $CONFIG;
  426. if (!$guid) {
  427. return false;
  428. }
  429. $guid = (int) $guid;
  430. $access = get_access_sql_suffix();
  431. return get_data_row("SELECT * from {$CONFIG->dbprefix}entities where guid=$guid and $access");
  432. }
  433. /**
  434. * Create an Elgg* object from a given entity row.
  435. *
  436. * Handles loading all tables into the correct class.
  437. *
  438. * @param stdClass $row The row of the entry in the entities table.
  439. *
  440. * @return ElggEntity|false
  441. * @link http://docs.elgg.org/DataModel/Entities
  442. * @see get_entity_as_row()
  443. * @see add_subtype()
  444. * @see get_entity()
  445. * @access private
  446. *
  447. * @throws ClassException|InstallationException
  448. */
  449. function entity_row_to_elggstar($row) {
  450. if (!($row instanceof stdClass)) {
  451. return $row;
  452. }
  453. if ((!isset($row->guid)) || (!isset($row->subtype))) {
  454. return $row;
  455. }
  456. $new_entity = false;
  457. // Create a memcache cache if we can
  458. static $newentity_cache;
  459. if ((!$newentity_cache) && (is_memcache_available())) {
  460. $newentity_cache = new ElggMemcache('new_entity_cache');
  461. }
  462. if ($newentity_cache) {
  463. $new_entity = $newentity_cache->load($row->guid);
  464. }
  465. if ($new_entity) {
  466. return $new_entity;
  467. }
  468. // load class for entity if one is registered
  469. $classname = get_subtype_class_from_id($row->subtype);
  470. if ($classname != "") {
  471. if (class_exists($classname)) {
  472. $new_entity = new $classname($row);
  473. if (!($new_entity instanceof ElggEntity)) {
  474. $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, 'ElggEntity'));
  475. throw new ClassException($msg);
  476. }
  477. } else {
  478. error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname)));
  479. }
  480. }
  481. if (!$new_entity) {
  482. //@todo Make this into a function
  483. switch ($row->type) {
  484. case 'object' :
  485. $new_entity = new ElggObject($row);
  486. break;
  487. case 'user' :
  488. $new_entity = new ElggUser($row);
  489. break;
  490. case 'group' :
  491. $new_entity = new ElggGroup($row);
  492. break;
  493. case 'site' :
  494. $new_entity = new ElggSite($row);
  495. break;
  496. default:
  497. $msg = elgg_echo('InstallationException:TypeNotSupported', array($row->type));
  498. throw new InstallationException($msg);
  499. }
  500. }
  501. // Cache entity if we have a cache available
  502. if (($newentity_cache) && ($new_entity)) {
  503. $newentity_cache->save($new_entity->guid, $new_entity);
  504. }
  505. return $new_entity;
  506. }
  507. /**
  508. * Loads and returns an entity object from a guid.
  509. *
  510. * @param int $guid The GUID of the entity
  511. *
  512. * @return ElggEntity The correct Elgg or custom object based upon entity type and subtype
  513. * @link http://docs.elgg.org/DataModel/Entities
  514. */
  515. function get_entity($guid) {
  516. // This should not be a static local var. Notice that cache writing occurs in a completely
  517. // different instance outside this function.
  518. // @todo We need a single Memcache instance with a shared pool of namespace wrappers. This function would pull an instance from the pool.
  519. static $shared_cache;
  520. // We could also use: if (!(int) $guid) { return FALSE },
  521. // but that evaluates to a false positive for $guid = TRUE.
  522. // This is a bit slower, but more thorough.
  523. if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
  524. return false;
  525. }
  526. // Check local cache first
  527. $new_entity = retrieve_cached_entity($guid);
  528. if ($new_entity) {
  529. return $new_entity;
  530. }
  531. // Check shared memory cache, if available
  532. if (null === $shared_cache) {
  533. if (is_memcache_available()) {
  534. $shared_cache = new ElggMemcache('new_entity_cache');
  535. } else {
  536. $shared_cache = false;
  537. }
  538. }
  539. // until ACLs in memcache, DB query is required to determine access
  540. $entity_row = get_entity_as_row($guid);
  541. if (!$entity_row) {
  542. return false;
  543. }
  544. if ($shared_cache) {
  545. $cached_entity = $shared_cache->load($guid);
  546. // @todo store ACLs in memcache http://trac.elgg.org/ticket/3018#comment:3
  547. if ($cached_entity) {
  548. // @todo use ACL and cached entity access_id to determine if user can see it
  549. return $cached_entity;
  550. }
  551. }
  552. // don't let incomplete entities cause fatal exceptions
  553. try {
  554. $new_entity = entity_row_to_elggstar($entity_row);
  555. } catch (IncompleteEntityException $e) {
  556. return false;
  557. }
  558. if ($new_entity) {
  559. cache_entity($new_entity);
  560. }
  561. return $new_entity;
  562. }
  563. /**
  564. * Does an entity exist?
  565. *
  566. * This function checks for the existence of an entity independent of access
  567. * permissions. It is useful for situations when a user cannot access an entity
  568. * and it must be determined whether entity has been deleted or the access level
  569. * has changed.
  570. *
  571. * @param int $guid The GUID of the entity
  572. *
  573. * @return bool
  574. * @since 1.8.0
  575. */
  576. function elgg_entity_exists($guid) {
  577. global $CONFIG;
  578. $guid = sanitize_int($guid);
  579. $query = "SELECT count(*) as total FROM {$CONFIG->dbprefix}entities WHERE guid = $guid";
  580. $result = get_data_row($query);
  581. if ($result->total == 0) {
  582. return false;
  583. } else {
  584. return true;
  585. }
  586. }
  587. /**
  588. * Returns an array of entities with optional filtering.
  589. *
  590. * Entities are the basic unit of storage in Elgg. This function
  591. * provides the simplest way to get an array of entities. There
  592. * are many options available that can be passed to filter
  593. * what sorts of entities are returned.
  594. *
  595. * @tip To output formatted strings of entities, use {@link elgg_list_entities()} and
  596. * its cousins.
  597. *
  598. * @tip Plural arguments can be written as singular if only specifying a
  599. * single element. ('type' => 'object' vs 'types' => array('object')).
  600. *
  601. * @param array $options Array in format:
  602. *
  603. * types => NULL|STR entity type (type IN ('type1', 'type2')
  604. * Joined with subtypes by AND. See below)
  605. *
  606. * subtypes => NULL|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2))
  607. * Use ELGG_ENTITIES_NO_VALUE for no subtype.
  608. *
  609. * type_subtype_pairs => NULL|ARR (array('type' => 'subtype'))
  610. * (type = '$type' AND subtype = '$subtype') pairs
  611. *
  612. * guids => NULL|ARR Array of entity guids
  613. *
  614. * owner_guids => NULL|ARR Array of owner guids
  615. *
  616. * container_guids => NULL|ARR Array of container_guids
  617. *
  618. * site_guids => NULL (current_site)|ARR Array of site_guid
  619. *
  620. * order_by => NULL (time_created desc)|STR SQL order by clause
  621. *
  622. * reverse_order_by => BOOL Reverse the default order by clause
  623. *
  624. * limit => NULL (10)|INT SQL limit clause (0 means no limit)
  625. *
  626. * offset => NULL (0)|INT SQL offset clause
  627. *
  628. * created_time_lower => NULL|INT Created time lower boundary in epoch time
  629. *
  630. * created_time_upper => NULL|INT Created time upper boundary in epoch time
  631. *
  632. * modified_time_lower => NULL|INT Modified time lower boundary in epoch time
  633. *
  634. * modified_time_upper => NULL|INT Modified time upper boundary in epoch time
  635. *
  636. * count => TRUE|FALSE return a count instead of entities
  637. *
  638. * wheres => array() Additional where clauses to AND together
  639. *
  640. * joins => array() Additional joins
  641. *
  642. * callback => string A callback function to pass each row through
  643. *
  644. * @return mixed If count, int. If not count, array. false on errors.
  645. * @since 1.7.0
  646. * @see elgg_get_entities_from_metadata()
  647. * @see elgg_get_entities_from_relationship()
  648. * @see elgg_get_entities_from_access_id()
  649. * @see elgg_get_entities_from_annotations()
  650. * @see elgg_list_entities()
  651. * @link http://docs.elgg.org/DataModel/Entities/Getters
  652. */
  653. function elgg_get_entities(array $options = array()) {
  654. global $CONFIG;
  655. $defaults = array(
  656. 'types' => ELGG_ENTITIES_ANY_VALUE,
  657. 'subtypes' => ELGG_ENTITIES_ANY_VALUE,
  658. 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
  659. 'guids' => ELGG_ENTITIES_ANY_VALUE,
  660. 'owner_guids' => ELGG_ENTITIES_ANY_VALUE,
  661. 'container_guids' => ELGG_ENTITIES_ANY_VALUE,
  662. 'site_guids' => $CONFIG->site_guid,
  663. 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE,
  664. 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE,
  665. 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
  666. 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
  667. 'reverse_order_by' => false,
  668. 'order_by' => 'e.time_created desc',
  669. 'group_by' => ELGG_ENTITIES_ANY_VALUE,
  670. 'limit' => 10,
  671. 'offset' => 0,
  672. 'count' => FALSE,
  673. 'selects' => array(),
  674. 'wheres' => array(),
  675. 'joins' => array(),
  676. 'callback' => 'entity_row_to_elggstar',
  677. );
  678. $options = array_merge($defaults, $options);
  679. // can't use helper function with type_subtype_pair because
  680. // it's already an array...just need to merge it
  681. if (isset($options['type_subtype_pair'])) {
  682. if (isset($options['type_subtype_pairs'])) {
  683. $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
  684. $options['type_subtype_pair']);
  685. } else {
  686. $options['type_subtype_pairs'] = $options['type_subtype_pair'];
  687. }
  688. }
  689. $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid');
  690. $options = elgg_normalise_plural_options_array($options, $singulars);
  691. // evaluate where clauses
  692. if (!is_array($options['wheres'])) {
  693. $options['wheres'] = array($options['wheres']);
  694. }
  695. $wheres = $options['wheres'];
  696. $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'],
  697. $options['subtypes'], $options['type_subtype_pairs']);
  698. $wheres[] = elgg_get_guid_based_where_sql('e.guid', $options['guids']);
  699. $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']);
  700. $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']);
  701. $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']);
  702. $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'],
  703. $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
  704. // see if any functions failed
  705. // remove empty strings on successful functions
  706. foreach ($wheres as $i => $where) {
  707. if ($where === FALSE) {
  708. return FALSE;
  709. } elseif (empty($where)) {
  710. unset($wheres[$i]);
  711. }
  712. }
  713. // remove identical where clauses
  714. $wheres = array_unique($wheres);
  715. // evaluate join clauses
  716. if (!is_array($options['joins'])) {
  717. $options['joins'] = array($options['joins']);
  718. }
  719. // remove identical join clauses
  720. $joins = array_unique($options['joins']);
  721. foreach ($joins as $i => $join) {
  722. if ($join === FALSE) {
  723. return FALSE;
  724. } elseif (empty($join)) {
  725. unset($joins[$i]);
  726. }
  727. }
  728. // evalutate selects
  729. if ($options['selects']) {
  730. $selects = '';
  731. foreach ($options['selects'] as $select) {
  732. $selects .= ", $select";
  733. }
  734. } else {
  735. $selects = '';
  736. }
  737. if (!$options['count']) {
  738. $query = "SELECT DISTINCT e.*{$selects} FROM {$CONFIG->dbprefix}entities e ";
  739. } else {
  740. $query = "SELECT count(DISTINCT e.guid) as total FROM {$CONFIG->dbprefix}entities e ";
  741. }
  742. // add joins
  743. foreach ($joins as $j) {
  744. $query .= " $j ";
  745. }
  746. // add wheres
  747. $query .= ' WHERE ';
  748. foreach ($wheres as $w) {
  749. $query .= " $w AND ";
  750. }
  751. // Add access controls
  752. $query .= get_access_sql_suffix('e');
  753. // reverse order by
  754. if ($options['reverse_order_by']) {
  755. $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by']);
  756. }
  757. if (!$options['count']) {
  758. if ($options['group_by']) {
  759. $query .= " GROUP BY {$options['group_by']}";
  760. }
  761. if ($options['order_by']) {
  762. $query .= " ORDER BY {$options['order_by']}";
  763. }
  764. if ($options['limit']) {
  765. $limit = sanitise_int($options['limit'], false);
  766. $offset = sanitise_int($options['offset'], false);
  767. $query .= " LIMIT $offset, $limit";
  768. }
  769. if ($options['callback'] === 'entity_row_to_elggstar') {
  770. $dt = _elgg_fetch_entities_from_sql($query);
  771. } else {
  772. $dt = get_data($query, $options['callback']);
  773. }
  774. if ($dt) {
  775. // populate entity and metadata caches
  776. $guids = array();
  777. foreach ($dt as $item) {
  778. // A custom callback could result in items that aren't ElggEntity's, so check for them
  779. if ($item instanceof ElggEntity) {
  780. cache_entity($item);
  781. // plugins usually have only settings
  782. if (!$item instanceof ElggPlugin) {
  783. $guids[] = $item->guid;
  784. }
  785. }
  786. }
  787. // @todo Without this, recursive delete fails. See #4568
  788. reset($dt);
  789. if ($guids) {
  790. elgg_get_metadata_cache()->populateFromEntities($guids);
  791. }
  792. }
  793. return $dt;
  794. } else {
  795. $total = get_data_row($query);
  796. return (int)$total->total;
  797. }
  798. }
  799. /**
  800. * Return entities from an SQL query generated by elgg_get_entities.
  801. *
  802. * @param string $sql
  803. * @return ElggEntity[]
  804. *
  805. * @access private
  806. * @throws LogicException
  807. */
  808. function _elgg_fetch_entities_from_sql($sql) {
  809. static $plugin_subtype;
  810. if (null === $plugin_subtype) {
  811. $plugin_subtype = get_subtype_id('object', 'plugin');
  812. }
  813. // Keys are types, values are columns that, if present, suggest that the secondary
  814. // table is already JOINed
  815. $types_to_optimize = array(
  816. 'object' => 'title',
  817. 'user' => 'password',
  818. 'group' => 'name',
  819. );
  820. $rows = get_data($sql);
  821. // guids to look up in each type
  822. $lookup_types = array();
  823. // maps GUIDs to the $rows key
  824. $guid_to_key = array();
  825. if (isset($rows[0]->type, $rows[0]->subtype)
  826. && $rows[0]->type === 'object'
  827. && $rows[0]->subtype == $plugin_subtype) {
  828. // Likely the entire resultset is plugins, which have already been optimized
  829. // to JOIN the secondary table. In this case we allow retrieving from cache,
  830. // but abandon the extra queries.
  831. $types_to_optimize = array();
  832. }
  833. // First pass: use cache where possible, gather GUIDs that we're optimizing
  834. foreach ($rows as $i => $row) {
  835. if (empty($row->guid) || empty($row->type)) {
  836. throw new LogicException('Entity row missing guid or type');
  837. }
  838. if ($entity = retrieve_cached_entity($row->guid)) {
  839. $rows[$i] = $entity;
  840. continue;
  841. }
  842. if (isset($types_to_optimize[$row->type])) {
  843. // check if row already looks JOINed.
  844. if (isset($row->{$types_to_optimize[$row->type]})) {
  845. // Row probably already contains JOINed secondary table. Don't make another query just
  846. // to pull data that's already there
  847. continue;
  848. }
  849. $lookup_types[$row->type][] = $row->guid;
  850. $guid_to_key[$row->guid] = $i;
  851. }
  852. }
  853. // Do secondary queries and merge rows
  854. if ($lookup_types) {
  855. $dbprefix = elgg_get_config('dbprefix');
  856. foreach ($lookup_types as $type => $guids) {
  857. $set = "(" . implode(',', $guids) . ")";
  858. $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set";
  859. $secondary_rows = get_data($sql);
  860. if ($secondary_rows) {
  861. foreach ($secondary_rows as $secondary_row) {
  862. $key = $guid_to_key[$secondary_row->guid];
  863. // cast to arrays to merge then cast back
  864. $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row);
  865. }
  866. }
  867. }
  868. }
  869. // Second pass to finish conversion
  870. foreach ($rows as $i => $row) {
  871. if ($row instanceof ElggEntity) {
  872. continue;
  873. } else {
  874. try {
  875. $rows[$i] = entity_row_to_elggstar($row);
  876. } catch (IncompleteEntityException $e) {
  877. // don't let incomplete entities throw fatal errors
  878. unset($rows[$i]);
  879. }
  880. }
  881. }
  882. return $rows;
  883. }
  884. /**
  885. * Returns SQL where clause for type and subtype on main entity table
  886. *
  887. * @param string $table Entity table prefix as defined in SELECT...FROM entities $table
  888. * @param NULL|array $types Array of types or NULL if none.
  889. * @param NULL|array $subtypes Array of subtypes or NULL if none
  890. * @param NULL|array $pairs Array of pairs of types and subtypes
  891. *
  892. * @return FALSE|string
  893. * @since 1.7.0
  894. * @access private
  895. */
  896. function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pairs) {
  897. // subtype depends upon type.
  898. if ($subtypes && !$types) {
  899. elgg_log("Cannot set subtypes without type.", 'WARNING');
  900. return FALSE;
  901. }
  902. // short circuit if nothing is requested
  903. if (!$types && !$subtypes && !$pairs) {
  904. return '';
  905. }
  906. // these are the only valid types for entities in elgg
  907. $valid_types = elgg_get_config('entity_types');
  908. // pairs override
  909. $wheres = array();
  910. if (!is_array($pairs)) {
  911. if (!is_array($types)) {
  912. $types = array($types);
  913. }
  914. if ($subtypes && !is_array($subtypes)) {
  915. $subtypes = array($subtypes);
  916. }
  917. // decrementer for valid types. Return FALSE if no valid types
  918. $valid_types_count = count($types);
  919. $valid_subtypes_count = 0;
  920. // remove invalid types to get an accurate count of
  921. // valid types for the invalid subtype detection to use
  922. // below.
  923. // also grab the count of ALL subtypes on valid types to decrement later on
  924. // and check against.
  925. //
  926. // yes this is duplicating a foreach on $types.
  927. foreach ($types as $type) {
  928. if (!in_array($type, $valid_types)) {
  929. $valid_types_count--;
  930. unset($types[array_search($type, $types)]);
  931. } else {
  932. // do the checking (and decrementing) in the subtype section.
  933. $valid_subtypes_count += count($subtypes);
  934. }
  935. }
  936. // return false if nothing is valid.
  937. if (!$valid_types_count) {
  938. return FALSE;
  939. }
  940. // subtypes are based upon types, so we need to look at each
  941. // type individually to get the right subtype id.
  942. foreach ($types as $type) {
  943. $subtype_ids = array();
  944. if ($subtypes) {
  945. foreach ($subtypes as $subtype) {
  946. // check that the subtype is valid
  947. if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
  948. // subtype value is 0
  949. $subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
  950. } elseif (!$subtype) {
  951. // subtype is ignored.
  952. // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
  953. continue;
  954. } else {
  955. $subtype_id = get_subtype_id($type, $subtype);
  956. if ($subtype_id) {
  957. $subtype_ids[] = $subtype_id;
  958. } else {
  959. $valid_subtypes_count--;
  960. elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE');
  961. continue;
  962. }
  963. }
  964. }
  965. // return false if we're all invalid subtypes in the only valid type
  966. if ($valid_subtypes_count <= 0) {
  967. return FALSE;
  968. }
  969. }
  970. if (is_array($subtype_ids) && count($subtype_ids)) {
  971. $subtype_ids_str = implode(',', $subtype_ids);
  972. $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
  973. } else {
  974. $wheres[] = "({$table}.type = '$type')";
  975. }
  976. }
  977. } else {
  978. // using type/subtype pairs
  979. $valid_pairs_count = count($pairs);
  980. $valid_pairs_subtypes_count = 0;
  981. // same deal as above--we need to know how many valid types
  982. // and subtypes we have before hitting the subtype section.
  983. // also normalize the subtypes into arrays here.
  984. foreach ($pairs as $paired_type => $paired_subtypes) {
  985. if (!in_array($paired_type, $valid_types)) {
  986. $valid_pairs_count--;
  987. unset($pairs[array_search($paired_type, $pairs)]);
  988. } else {
  989. if ($paired_subtypes && !is_array($paired_subtypes)) {
  990. $pairs[$paired_type] = array($paired_subtypes);
  991. }
  992. $valid_pairs_subtypes_count += count($paired_subtypes);
  993. }
  994. }
  995. if ($valid_pairs_count <= 0) {
  996. return FALSE;
  997. }
  998. foreach ($pairs as $paired_type => $paired_subtypes) {
  999. // this will always be an array because of line 2027, right?
  1000. // no...some overly clever person can say pair => array('object' => null)
  1001. if (is_array($paired_subtypes)) {
  1002. $paired_subtype_ids = array();
  1003. foreach ($paired_subtypes as $paired_subtype) {
  1004. if (ELGG_ENTITIES_NO_VALUE === $paired_subtype
  1005. || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
  1006. $paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
  1007. ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
  1008. } else {
  1009. $valid_pairs_subtypes_count--;
  1010. elgg_log("Type-subtype '$paired_type:$paired_subtype' does not exist!", 'NOTICE');
  1011. // return false if we're all invalid subtypes in the only valid type
  1012. continue;
  1013. }
  1014. }
  1015. // return false if there are no valid subtypes.
  1016. if ($valid_pairs_subtypes_count <= 0) {
  1017. return FALSE;
  1018. }
  1019. if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
  1020. $wheres[] = "({$table}.type = '$paired_type'"
  1021. . " AND {$table}.subtype IN ($paired_subtype_ids_str))";
  1022. }
  1023. } else {
  1024. $wheres[] = "({$table}.type = '$paired_type')";
  1025. }
  1026. }
  1027. }
  1028. // pairs override the above. return false if they don't exist.
  1029. if (is_array($wheres) && count($wheres)) {
  1030. $where = implode(' OR ', $wheres);
  1031. return "($where)";
  1032. }
  1033. return '';
  1034. }
  1035. /**
  1036. * Returns SQL where clause for owner and containers.
  1037. *
  1038. * @param string $column Column name the guids should be checked against. Usually
  1039. * best to provide in table.column format.
  1040. * @param NULL|array $guids Array of GUIDs.
  1041. *
  1042. * @return false|string
  1043. * @since 1.8.0
  1044. * @access private
  1045. */
  1046. function elgg_get_guid_based_where_sql($column, $guids) {
  1047. // short circuit if nothing requested
  1048. // 0 is a valid guid
  1049. if (!$guids && $guids !== 0) {
  1050. return '';
  1051. }
  1052. // normalize and sanitise owners
  1053. if (!is_array($guids)) {
  1054. $guids = array($guids);
  1055. }
  1056. $guids_sanitized = array();
  1057. foreach ($guids as $guid) {
  1058. if ($guid !== ELGG_ENTITIES_NO_VALUE) {
  1059. $guid = sanitise_int($guid);
  1060. if (!$guid) {
  1061. return false;
  1062. }
  1063. }
  1064. $guids_sanitized[] = $guid;
  1065. }
  1066. $where = '';
  1067. $guid_str = implode(',', $guids_sanitized);
  1068. // implode(',', 0) returns 0.
  1069. if ($guid_str !== FALSE && $guid_str !== '') {
  1070. $where = "($column IN ($guid_str))";
  1071. }
  1072. return $where;
  1073. }
  1074. /**
  1075. * Returns SQL where clause for entity time limits.
  1076. *
  1077. * @param string $table Entity table prefix as defined in
  1078. * SELECT...FROM entities $table
  1079. * @param NULL|int $time_created_upper Time created upper limit
  1080. * @param NULL|int $time_created_lower Time created lower limit
  1081. * @param NULL|int $time_updated_upper Time updated upper limit
  1082. * @param NULL|int $time_updated_lower Time updated lower limit
  1083. *
  1084. * @return FALSE|string FALSE on fail, string on success.
  1085. * @since 1.7.0
  1086. * @access private
  1087. */
  1088. function elgg_get_entity_time_where_sql($table, $time_created_upper = NULL,
  1089. $time_created_lower = NULL, $time_updated_upper = NULL, $time_updated_lower = NULL) {
  1090. $wheres = array();
  1091. // exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
  1092. if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
  1093. $wheres[] = "{$table}.time_created <= $time_created_upper";
  1094. }
  1095. if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
  1096. $wheres[] = "{$table}.time_created >= $time_created_lower";
  1097. }
  1098. if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
  1099. $wheres[] = "{$table}.time_updated <= $time_updated_upper";
  1100. }
  1101. if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
  1102. $wheres[] = "{$table}.time_updated >= $time_updated_lower";
  1103. }
  1104. if (is_array($wheres) && count($wheres) > 0) {
  1105. $where_str = implode(' AND ', $wheres);
  1106. return "($where_str)";
  1107. }
  1108. return '';
  1109. }
  1110. /**
  1111. * Returns a string of parsed entities.
  1112. *
  1113. * Displays list of entities with formatting specified
  1114. * by the entity view.
  1115. *
  1116. * @tip Pagination is handled automatically.
  1117. *
  1118. * @internal This also provides the views for elgg_view_annotation().
  1119. *
  1120. * @param array $options Any options from $getter options plus:
  1121. * full_view => BOOL Display full view entities
  1122. * list_type => STR 'list' or 'gallery'
  1123. * list_type_toggle => BOOL Display gallery / list switch
  1124. * pagination => BOOL Display pagination links
  1125. *
  1126. * @param callback $getter The entity getter function to use to fetch the entities
  1127. * @param callback $viewer The function to use to view the entity list.
  1128. *
  1129. * @return string
  1130. * @since 1.7
  1131. * @see elgg_get_entities()
  1132. * @see elgg_view_entity_list()
  1133. * @link http://docs.elgg.org/Entities/Output
  1134. */
  1135. function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities',
  1136. $viewer = 'elgg_view_entity_list') {
  1137. global $autofeed;
  1138. $autofeed = true;
  1139. $defaults = array(
  1140. 'offset' => (int) max(get_input('offset', 0), 0),
  1141. 'limit' => (int) max(get_input('limit', 10), 0),
  1142. 'full_view' => TRUE,
  1143. 'list_type_toggle' => FALSE,
  1144. 'pagination' => TRUE,
  1145. );
  1146. $options = array_merge($defaults, $options);
  1147. // backward compatibility
  1148. if (isset($options['view_type_toggle'])) {
  1149. elgg_deprecated_notice("Option 'view_type_toggle' deprecated by 'list_type_toggle' in elgg_list* functions", 1.9);
  1150. $options['list_type_toggle'] = $options['view_type_toggle'];
  1151. }
  1152. $options['count'] = TRUE;
  1153. $count = call_user_func($getter, $options);
  1154. $options['count'] = FALSE;
  1155. $entities = call_user_func($getter, $options);
  1156. $options['count'] = $count;
  1157. return call_user_func($viewer, $entities, $options);
  1158. }
  1159. /**
  1160. * Returns a list of months in which entities were updated or created.
  1161. *
  1162. * @tip Use this to generate a list of archives by month for when entities were added or updated.
  1163. *
  1164. * @todo document how to pass in array for $subtype
  1165. *
  1166. * @warning Months are returned in the form YYYYMM.
  1167. *
  1168. * @param string $type The type of entity
  1169. * @param string $subtype The subtype of entity
  1170. * @param int $container_guid The container GUID that the entities belong to
  1171. * @param int $site_guid The site GUID
  1172. * @param string $order_by Order_by SQL order by clause
  1173. *
  1174. * @return array|false Either an array months as YYYYMM, or false on failure
  1175. */
  1176. function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0,
  1177. $order_by = 'time_created') {
  1178. global $CONFIG;
  1179. $site_guid = (int) $site_guid;
  1180. if ($site_guid == 0) {
  1181. $site_guid = $CONFIG->site_guid;
  1182. }
  1183. $where = array();
  1184. if ($type != "") {
  1185. $type = sanitise_string($type);
  1186. $where[] = "type='$type'";
  1187. }
  1188. if (is_array($subtype)) {
  1189. $tempwhere = "";
  1190. if (sizeof($subtype)) {
  1191. foreach ($subtype as $typekey => $subtypearray) {
  1192. foreach ($subtypearray as $subtypeval) {
  1193. $typekey = sanitise_string($typekey);
  1194. if (!empty($subtypeval)) {
  1195. if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
  1196. return false;
  1197. }
  1198. } else {
  1199. $subtypeval = 0;
  1200. }
  1201. if (!empty($tempwhere)) {
  1202. $tempwhere .= " or ";
  1203. }
  1204. $tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
  1205. }
  1206. }
  1207. }
  1208. if (!empty($tempwhere)) {
  1209. $where[] = "({$tempwhere})";
  1210. }
  1211. } else {
  1212. if ($subtype) {
  1213. if (!$subtype_id = get_subtype_id($type, $subtype)) {
  1214. return FALSE;
  1215. } else {
  1216. $where[] = "subtype=$subtype_id";
  1217. }
  1218. }
  1219. }
  1220. if ($container_guid !== 0) {
  1221. if (is_array($container_guid)) {
  1222. foreach ($container_guid as $key => $val) {
  1223. $container_guid[$key] = (int) $val;
  1224. }
  1225. $where[] = "container_guid in (" . implode(",", $container_guid) . ")";
  1226. } else {
  1227. $container_guid = (int) $container_guid;
  1228. $where[] = "container_guid = {$container_guid}";
  1229. }
  1230. }
  1231. if ($site_guid > 0) {
  1232. $where[] = "site_guid = {$site_guid}";
  1233. }
  1234. $where[] = get_access_sql_suffix();
  1235. $sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
  1236. FROM {$CONFIG->dbprefix}entities where ";
  1237. foreach ($where as $w) {
  1238. $sql .= " $w and ";
  1239. }
  1240. $sql .= "1=1 ORDER BY $order_by";
  1241. if ($result = get_data($sql)) {
  1242. $endresult = array();
  1243. foreach ($result as $res) {
  1244. $endresult[] = $res->yearmonth;
  1245. }
  1246. return $endresult;
  1247. }
  1248. return false;
  1249. }
  1250. /**
  1251. * Exports attributes generated on the fly (volatile) about an entity.
  1252. *
  1253. * @param string $hook volatile
  1254. * @param string $entity_type metadata
  1255. * @param string $returnvalue Return value from previous hook
  1256. * @param array $params The parameters, passed 'guid' and 'varname'
  1257. *
  1258. * @return ElggMetadata|null
  1259. * @elgg_plugin_hook_handler volatile metadata
  1260. * @todo investigate more.
  1261. * @access private
  1262. * @todo document
  1263. */
  1264. function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $params) {
  1265. $guid = (int)$params['guid'];
  1266. $variable_name = sanitise_string($params['varname']);
  1267. if (($hook == 'volatile') && ($entity_type == 'metadata')) {
  1268. if (($guid) && ($variable_name)) {
  1269. switch ($variable_name) {
  1270. case 'renderedentity' :
  1271. elgg_set_viewtype('default');
  1272. $view = elgg_view_entity(get_entity($guid));
  1273. elgg_set_viewtype();
  1274. $tmp = new ElggMetadata();
  1275. $tmp->type = 'volatile';
  1276. $tmp->name = 'renderedentity';
  1277. $tmp->value = $view;
  1278. $tmp->entity_guid = $guid;
  1279. return $tmp;
  1280. break;
  1281. }
  1282. }
  1283. }
  1284. }
  1285. /**
  1286. * Exports all attributes of an entity.
  1287. *
  1288. * @warning Only exports fields in the entity and entity type tables.
  1289. *
  1290. * @param string $hook export
  1291. * @param string $entity_type all
  1292. * @param mixed $returnvalue Previous hook return value
  1293. * @param array $params Parameters
  1294. *
  1295. * @elgg_event_handler export all
  1296. * @return mixed
  1297. * @access private
  1298. *
  1299. * @throws InvalidParameterException|InvalidClassException
  1300. */
  1301. function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {
  1302. // Sanity check values
  1303. if ((!is_array($params)) && (!isset($params['guid']))) {
  1304. throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport'));
  1305. }
  1306. if (!is_array($returnvalue)) {
  1307. throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue'));
  1308. }
  1309. $guid = (int)$params['guid'];
  1310. // Get the entity
  1311. $entity = get_entity($guid);
  1312. if (!($entity instanceof ElggEntity)) {
  1313. $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
  1314. throw new InvalidClassException($msg);
  1315. }
  1316. $export = $entity->export();
  1317. if (is_array($export)) {
  1318. foreach ($export as $e) {
  1319. $returnvalue[] = $e;
  1320. }
  1321. } else {
  1322. $returnvalue[] = $export;
  1323. }
  1324. return $returnvalue;
  1325. }
  1326. /**
  1327. * Utility function used by import_entity_plugin_hook() to
  1328. * process an ODDEntity into an unsaved ElggEntity.
  1329. *
  1330. * @param ODDEntity $element The OpenDD element
  1331. *
  1332. * @return ElggEntity the unsaved entity which should be populated by items.
  1333. * @todo Remove this.
  1334. * @access private
  1335. *
  1336. * @throws ClassException|InstallationException|ImportException
  1337. */
  1338. function oddentity_to_elggentity(ODDEntity $element) {
  1339. $class = $element->getAttribute('class');
  1340. $subclass = $element->getAttribute('subclass');
  1341. // See if we already have imported this uuid
  1342. $tmp = get_entity_from_uuid($element->getAttribute('uuid'));
  1343. if (!$tmp) {
  1344. // Construct new class with owner from session
  1345. $classname = get_subtype_class($class, $subclass);
  1346. if ($classname) {
  1347. if (class_exists($classname)) {
  1348. $tmp = new $classname();
  1349. if (!($tmp instanceof ElggEntity)) {
  1350. $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, get_class()));
  1351. throw new ClassException($msg);
  1352. }
  1353. } else {
  1354. error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname)));
  1355. }
  1356. } else {
  1357. switch ($class) {
  1358. case 'object' :
  1359. $tmp = new ElggObject($row);
  1360. break;
  1361. case 'user' :
  1362. $tmp = new ElggUser($row);
  1363. break;
  1364. case 'group' :
  1365. $tmp = new ElggGroup($row);
  1366. break;
  1367. case 'site' :
  1368. $tmp = new ElggSite($row);
  1369. break;
  1370. default:
  1371. $msg = elgg_echo('InstallationException:TypeNotSupported', array($class));
  1372. throw new InstallationException($msg);
  1373. }
  1374. }
  1375. }
  1376. if ($tmp) {
  1377. if (!$tmp->import($element)) {
  1378. $msg = elgg_echo('ImportException:ImportFailed', array($element->getAttribute('uuid')));
  1379. throw new ImportException($msg);
  1380. }
  1381. return $tmp;
  1382. }
  1383. return NULL;
  1384. }
  1385. /**
  1386. * Import an entity.
  1387. *
  1388. * This function checks the passed XML doc (as array) to see if it is
  1389. * a user, if so it constructs a new elgg user and returns "true"
  1390. * to inform the importer that it's been handled.
  1391. *
  1392. * @param string $hook import
  1393. * @param string $entity_type all
  1394. * @param mixed $returnvalue Value from previous hook
  1395. * @param mixed $params Array of params
  1396. *
  1397. * @return mixed
  1398. * @elgg_plugin_hook_handler import all
  1399. * @todo document
  1400. * @access private
  1401. *
  1402. * @throws ImportException
  1403. */
  1404. function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {
  1405. $element = $params['element'];
  1406. $tmp = null;
  1407. if ($element instanceof ODDEntity) {
  1408. $tmp = oddentity_to_elggentity($element);
  1409. if ($tmp) {
  1410. // Make sure its saved
  1411. if (!$tmp->save()) {
  1412. $msg = elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid')));
  1413. throw new ImportException($msg);
  1414. }
  1415. // Belts and braces
  1416. if (!$tmp->guid) {
  1417. throw new ImportException(elgg_echo('ImportException:NoGUID'));
  1418. }
  1419. // We have saved, so now tag
  1420. add_uuid_to_guid($tmp->guid, $element->getAttribute('uuid'));
  1421. return $tmp;
  1422. }
  1423. }
  1424. }
  1425. /**
  1426. * Sets the URL handler for a particular entity type and subtype
  1427. *
  1428. * @param string $entity_type The entity type
  1429. * @param string $entity_subtype The entity subtype
  1430. * @param string $function_name The function to register
  1431. *
  1432. * @return bool Depending on success
  1433. * @see get_entity_url()
  1434. * @see ElggEntity::getURL()
  1435. * @since 1.8.0
  1436. */
  1437. function elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) {
  1438. global $CONFIG;
  1439. if (!is_callable($function_name, true)) {
  1440. return false;
  1441. }
  1442. if (!isset($CONFIG->entity_url_handler)) {
  1443. $CONFIG->entity_url_handler = array();
  1444. }
  1445. if (!isset($CONFIG->entity_url_handler[$entity_type])) {
  1446. $CONFIG->entity_url_handler[$entity_type] = array();
  1447. }
  1448. $CONFIG->entity_url_handler[$entity_type][$entity_subtype] = $function_name;
  1449. return true;
  1450. }
  1451. /**
  1452. * Registers an entity type and subtype as a public-facing entity that should
  1453. * be shown in search and by {@link elgg_list_registered_entities()}.
  1454. *
  1455. * @warning Entities that aren't registered here will not show up in search.
  1456. *
  1457. * @tip Add a language string item:type:subtype to make sure the items are display properly.
  1458. *
  1459. * @param string $type The type of entity (object, site, user, group)
  1460. * @param string $subtype The subtype to register (may be blank)
  1461. *
  1462. * @return bool Depending on success
  1463. * @see get_registered_entity_types()
  1464. * @link http://docs.elgg.org/Search
  1465. * @link http://docs.elgg.org/Tutorials/Search
  1466. */
  1467. function elgg_register_entity_type($type, $subtype = null) {
  1468. global $CONFIG;
  1469. $type = strtolower($type);
  1470. if (!in_array($type, $CONFIG->entity_types)) {
  1471. return FALSE;
  1472. }
  1473. if (!isset($CONFIG->registered_entities)) {
  1474. $CONFIG->registered_entities = array();
  1475. }
  1476. if (!isset($CONFIG->registered_entities[$type])) {
  1477. $CONFIG->registered_entities[$type] = array();
  1478. }
  1479. if ($subtype) {
  1480. $CONFIG->registered_entities[$type][] = $subtype;
  1481. }
  1482. return TRUE;
  1483. }
  1484. /**
  1485. * Unregisters an entity type and subtype as a public-facing type.
  1486. *
  1487. * @warning With a blank subtype, it unregisters that entity type including
  1488. * all subtypes. This must be called after all subtypes have been registered.
  1489. *
  1490. * @param string $type The type of entity (object, site, user, group)
  1491. * @param string $subtype The subtype to register (may be blank)
  1492. *
  1493. * @return bool Depending on success
  1494. * @see elgg_register_entity_type()
  1495. */
  1496. function elgg_unregister_entity_type($type, $subtype = null) {
  1497. global $CONFIG;
  1498. $type = strtolower($type);
  1499. if (!in_array($type, $CONFIG->entity_types)) {
  1500. return FALSE;
  1501. }
  1502. if (!isset($CONFIG->registered_entities)) {
  1503. return FALSE;
  1504. }
  1505. if (!isset($CONFIG->registered_entities[$type])) {
  1506. return FALSE;
  1507. }
  1508. if ($subtype) {
  1509. if (in_array($subtype, $CONFIG->registered_entities[$type])) {
  1510. $key = array_search($subtype, $CONFIG->registered_entities[$type]);
  1511. unset($CONFIG->registered_entities[$type][$key]);
  1512. } else {
  1513. return FALSE;
  1514. }
  1515. } else {
  1516. unset($CONFIG->registered_entities[$type]);
  1517. }
  1518. return TRUE;
  1519. }
  1520. /**
  1521. * Returns registered entity types and subtypes
  1522. *
  1523. * @param string $type The type of entity (object, site, user, group) or blank for all
  1524. *
  1525. * @return array|false Depending on whether entities have been registered
  1526. * @see elgg_register_entity_type()
  1527. */
  1528. function get_registered_entity_types($type = null) {
  1529. global $CONFIG;
  1530. if (!isset($CONFIG->registered_entities)) {
  1531. return false;
  1532. }
  1533. if ($type) {
  1534. $type = strtolower($type);
  1535. }
  1536. if (!empty($type) && empty($CONFIG->registered_entities[$type])) {
  1537. return false;
  1538. }
  1539. if (empty($type)) {
  1540. return $CONFIG->registered_entities;
  1541. }
  1542. return $CONFIG->registered_entities[$type];
  1543. }
  1544. /**
  1545. * Returns if the entity type and subtype have been registered with {@see elgg_register_entity_type()}.
  1546. *
  1547. * @param string $type The type of entity (object, site, user, group)
  1548. * @param string $subtype The subtype (may be blank)
  1549. *
  1550. * @return bool Depending on whether or not the type has been registered
  1551. */
  1552. function is_registered_entity_type($type, $subtype = null) {
  1553. global $CONFIG;
  1554. if (!isset($CONFIG->registered_entities)) {
  1555. return false;
  1556. }
  1557. $type = strtolower($type);
  1558. // @todo registering a subtype implicitly registers the type.
  1559. // see #2684
  1560. if (!isset($CONFIG->registered_entities[$type])) {
  1561. return false;
  1562. }
  1563. if ($subtype && !in_array($subtype, $CONFIG->registered_entities[$type])) {
  1564. return false;
  1565. }
  1566. return true;
  1567. }
  1568. /**
  1569. * Page handler for generic entities view system
  1570. *
  1571. * @param array $page Page elements from pain page handler
  1572. *
  1573. * @return bool
  1574. * @elgg_page_handler view
  1575. * @access private
  1576. */
  1577. function entities_page_handler($page) {
  1578. if (isset($page[0])) {
  1579. global $CONFIG;
  1580. set_input('guid', $page[0]);
  1581. include($CONFIG->path . "pages/entities/index.php");
  1582. return true;
  1583. }
  1584. return false;
  1585. }
  1586. /**
  1587. * Returns a viewable list of entities based on the registered types.
  1588. *
  1589. * @see elgg_view_entity_list
  1590. *
  1591. * @param array $options Any elgg_get_entity() options plus:
  1592. *
  1593. * full_view => BOOL Display full view entities
  1594. *
  1595. * list_type_toggle => BOOL Display gallery / list switch
  1596. *
  1597. * allowed_types => TRUE|ARRAY True to show all types or an array of valid types.
  1598. *
  1599. * pagination => BOOL Display pagination links
  1600. *
  1601. * @return string A viewable list of entities
  1602. * @since 1.7.0
  1603. */
  1604. function elgg_list_registered_entities(array $options = array()) {
  1605. global $autofeed;
  1606. $autofeed = true;
  1607. $defaults = array(
  1608. 'full_view' => TRUE,
  1609. 'allowed_types' => TRUE,
  1610. 'list_type_toggle' => FALSE,
  1611. 'pagination' => TRUE,
  1612. 'offset' => 0,
  1613. 'types' => array(),
  1614. 'type_subtype_pairs' => array()
  1615. );
  1616. $options = array_merge($defaults, $options);
  1617. // backward compatibility
  1618. if (isset($options['view_type_toggle'])) {
  1619. elgg_deprecated_notice("Option 'view_type_toggle' deprecated by 'l…

Large files files are truncated, but you can click here to view the full file