river.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. <?php
  2. /**
  3. * Elgg river.
  4. * Activity stream functions.
  5. *
  6. * @package Elgg.Core
  7. * @subpackage River
  8. */
  9. /**
  10. * Adds an item to the river.
  11. *
  12. * @tip Read the item like "Lisa (subject) posted (action)
  13. * a comment (object) on John's blog (target)".
  14. *
  15. * @param array $options Array in format:
  16. *
  17. * view => STR The view that will handle the river item (must exist)
  18. *
  19. * action_type => STR An arbitrary string to define the action (eg 'comment', 'create')
  20. *
  21. * subject_guid => INT The GUID of the entity doing the action
  22. *
  23. * object_guid => INT The GUID of the entity being acted upon
  24. *
  25. * target_guid => INT The GUID of the the object entity's container
  26. *
  27. * access_id => INT The access ID of the river item (default: same as the object)
  28. *
  29. * posted => INT The UNIX epoch timestamp of the river item (default: now)
  30. *
  31. * annotation_id INT The annotation ID associated with this river entry
  32. *
  33. * @return int|bool River ID or false on failure
  34. * @since 1.9
  35. */
  36. function elgg_create_river_item(array $options = array()) {
  37. $view = elgg_extract('view', $options);
  38. // use default viewtype for when called from web services api
  39. if (empty($view) || !(elgg_view_exists($view, 'default'))) {
  40. return false;
  41. }
  42. $action_type = elgg_extract('action_type', $options);
  43. if (empty($action_type)) {
  44. return false;
  45. }
  46. $subject_guid = elgg_extract('subject_guid', $options, 0);
  47. if (!($subject = get_entity($subject_guid))) {
  48. return false;
  49. }
  50. $object_guid = elgg_extract('object_guid', $options, 0);
  51. if (!($object = get_entity($object_guid))) {
  52. return false;
  53. }
  54. $target_guid = elgg_extract('target_guid', $options, 0);
  55. if ($target_guid) {
  56. // target_guid is not a required parameter so check
  57. // it only if it is included in the parameters
  58. if (!($target = get_entity($target_guid))) {
  59. return false;
  60. }
  61. }
  62. $access_id = elgg_extract('access_id', $options, $object->access_id);
  63. $posted = elgg_extract('posted', $options, time());
  64. $annotation_id = elgg_extract('annotation_id', $options, 0);
  65. if ($annotation_id) {
  66. if (!elgg_get_annotation_from_id($annotation_id)) {
  67. return false;
  68. }
  69. }
  70. $values = array(
  71. 'type' => $object->getType(),
  72. 'subtype' => $object->getSubtype(),
  73. 'action_type' => $action_type,
  74. 'access_id' => $access_id,
  75. 'view' => $view,
  76. 'subject_guid' => $subject_guid,
  77. 'object_guid' => $object_guid,
  78. 'target_guid' => $target_guid,
  79. 'annotation_id' => $annotation_id,
  80. 'posted' => $posted,
  81. );
  82. $col_types = array(
  83. 'type' => 'string',
  84. 'subtype' => 'string',
  85. 'action_type' => 'string',
  86. 'access_id' => 'int',
  87. 'view' => 'string',
  88. 'subject_guid' => 'int',
  89. 'object_guid' => 'int',
  90. 'target_guid' => 'int',
  91. 'annotation_id' => 'int',
  92. 'posted' => 'int',
  93. );
  94. // return false to stop insert
  95. $values = elgg_trigger_plugin_hook('creating', 'river', null, $values);
  96. if ($values == false) {
  97. // inserting did not fail - it was just prevented
  98. return true;
  99. }
  100. $dbprefix = elgg_get_config('dbprefix');
  101. // escape values array and build INSERT assignments
  102. $assignments = array();
  103. foreach ($col_types as $name => $type) {
  104. $values[$name] = ($type === 'int') ? (int)$values[$name] : sanitize_string($values[$name]);
  105. $assignments[] = "$name = '{$values[$name]}'";
  106. }
  107. $id = insert_data("INSERT INTO {$dbprefix}river SET " . implode(',', $assignments));
  108. // update the entities which had the action carried out on it
  109. // @todo shouldn't this be done elsewhere? Like when an annotation is saved?
  110. if ($id) {
  111. update_entity_last_action($values['object_guid'], $values['posted']);
  112. $river_items = elgg_get_river(array('id' => $id));
  113. if ($river_items) {
  114. elgg_trigger_event('created', 'river', $river_items[0]);
  115. }
  116. return $id;
  117. } else {
  118. return false;
  119. }
  120. }
  121. /**
  122. * Delete river items
  123. *
  124. * @warning not checking access (should we?)
  125. *
  126. * @param array $options Parameters:
  127. * ids => INT|ARR River item id(s)
  128. * subject_guids => INT|ARR Subject guid(s)
  129. * object_guids => INT|ARR Object guid(s)
  130. * target_guids => INT|ARR Target guid(s)
  131. * annotation_ids => INT|ARR The identifier of the annotation(s)
  132. * action_types => STR|ARR The river action type(s) identifier
  133. * views => STR|ARR River view(s)
  134. *
  135. * types => STR|ARR Entity type string(s)
  136. * subtypes => STR|ARR Entity subtype string(s)
  137. * type_subtype_pairs => ARR Array of type => subtype pairs where subtype
  138. * can be an array of subtype strings
  139. *
  140. * posted_time_lower => INT The lower bound on the time posted
  141. * posted_time_upper => INT The upper bound on the time posted
  142. *
  143. * @return bool
  144. * @since 1.8.0
  145. */
  146. function elgg_delete_river(array $options = array()) {
  147. global $CONFIG;
  148. $defaults = array(
  149. 'ids' => ELGG_ENTITIES_ANY_VALUE,
  150. 'subject_guids' => ELGG_ENTITIES_ANY_VALUE,
  151. 'object_guids' => ELGG_ENTITIES_ANY_VALUE,
  152. 'target_guids' => ELGG_ENTITIES_ANY_VALUE,
  153. 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE,
  154. 'views' => ELGG_ENTITIES_ANY_VALUE,
  155. 'action_types' => ELGG_ENTITIES_ANY_VALUE,
  156. 'types' => ELGG_ENTITIES_ANY_VALUE,
  157. 'subtypes' => ELGG_ENTITIES_ANY_VALUE,
  158. 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
  159. 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE,
  160. 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE,
  161. 'wheres' => array(),
  162. 'joins' => array(),
  163. );
  164. $options = array_merge($defaults, $options);
  165. $singulars = array('id', 'subject_guid', 'object_guid', 'target_guid', 'annotation_id', 'action_type', 'view', 'type', 'subtype');
  166. $options = _elgg_normalize_plural_options_array($options, $singulars);
  167. $wheres = $options['wheres'];
  168. $wheres[] = _elgg_get_guid_based_where_sql('rv.id', $options['ids']);
  169. $wheres[] = _elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']);
  170. $wheres[] = _elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']);
  171. $wheres[] = _elgg_get_guid_based_where_sql('rv.target_guid', $options['target_guids']);
  172. $wheres[] = _elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']);
  173. $wheres[] = _elgg_river_get_action_where_sql($options['action_types']);
  174. $wheres[] = _elgg_river_get_view_where_sql($options['views']);
  175. $wheres[] = _elgg_get_river_type_subtype_where_sql('rv', $options['types'],
  176. $options['subtypes'], $options['type_subtype_pairs']);
  177. if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) {
  178. $wheres[] = "rv.posted >= {$options['posted_time_lower']}";
  179. }
  180. if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) {
  181. $wheres[] = "rv.posted <= {$options['posted_time_upper']}";
  182. }
  183. // see if any functions failed
  184. // remove empty strings on successful functions
  185. foreach ($wheres as $i => $where) {
  186. if ($where === false) {
  187. return false;
  188. } elseif (empty($where)) {
  189. unset($wheres[$i]);
  190. }
  191. }
  192. // remove identical where clauses
  193. $wheres = array_unique($wheres);
  194. $query = "DELETE rv.* FROM {$CONFIG->dbprefix}river rv ";
  195. // remove identical join clauses
  196. $joins = array_unique($options['joins']);
  197. // add joins
  198. foreach ($joins as $j) {
  199. $query .= " $j ";
  200. }
  201. // add wheres
  202. $query .= ' WHERE ';
  203. foreach ($wheres as $w) {
  204. $query .= " $w AND ";
  205. }
  206. $query .= "1=1";
  207. return delete_data($query);
  208. }
  209. /**
  210. * Get river items
  211. *
  212. * @note If using types and subtypes in a query, they are joined with an AND.
  213. *
  214. * @param array $options Parameters:
  215. * ids => INT|ARR River item id(s)
  216. * subject_guids => INT|ARR Subject guid(s)
  217. * object_guids => INT|ARR Object guid(s)
  218. * target_guids => INT|ARR Target guid(s)
  219. * annotation_ids => INT|ARR The identifier of the annotation(s)
  220. * action_types => STR|ARR The river action type(s) identifier
  221. * posted_time_lower => INT The lower bound on the time posted
  222. * posted_time_upper => INT The upper bound on the time posted
  223. *
  224. * types => STR|ARR Entity type string(s)
  225. * subtypes => STR|ARR Entity subtype string(s)
  226. * type_subtype_pairs => ARR Array of type => subtype pairs where subtype
  227. * can be an array of subtype strings
  228. *
  229. * relationship => STR Relationship identifier
  230. * relationship_guid => INT|ARR Entity guid(s)
  231. * inverse_relationship => BOOL Subject or object of the relationship (false)
  232. *
  233. * limit => INT Number to show per page (20)
  234. * offset => INT Offset in list (0)
  235. * count => BOOL Count the river items? (false)
  236. * order_by => STR Order by clause (rv.posted desc)
  237. * group_by => STR Group by clause
  238. *
  239. * distinct => BOOL If set to false, Elgg will drop the DISTINCT
  240. * clause from the MySQL query, which will improve
  241. * performance in some situations. Avoid setting this
  242. * option without a full understanding of the
  243. * underlying SQL query Elgg creates. (true)
  244. *
  245. * @return array|int
  246. * @since 1.8.0
  247. */
  248. function elgg_get_river(array $options = array()) {
  249. global $CONFIG;
  250. $defaults = array(
  251. 'ids' => ELGG_ENTITIES_ANY_VALUE,
  252. 'subject_guids' => ELGG_ENTITIES_ANY_VALUE,
  253. 'object_guids' => ELGG_ENTITIES_ANY_VALUE,
  254. 'target_guids' => ELGG_ENTITIES_ANY_VALUE,
  255. 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE,
  256. 'action_types' => ELGG_ENTITIES_ANY_VALUE,
  257. 'relationship' => null,
  258. 'relationship_guid' => null,
  259. 'inverse_relationship' => false,
  260. 'types' => ELGG_ENTITIES_ANY_VALUE,
  261. 'subtypes' => ELGG_ENTITIES_ANY_VALUE,
  262. 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
  263. 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE,
  264. 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE,
  265. 'limit' => 20,
  266. 'offset' => 0,
  267. 'count' => false,
  268. 'distinct' => true,
  269. 'order_by' => 'rv.posted desc',
  270. 'group_by' => ELGG_ENTITIES_ANY_VALUE,
  271. 'wheres' => array(),
  272. 'joins' => array(),
  273. );
  274. $options = array_merge($defaults, $options);
  275. $singulars = array('id', 'subject_guid', 'object_guid', 'target_guid', 'annotation_id', 'action_type', 'type', 'subtype');
  276. $options = _elgg_normalize_plural_options_array($options, $singulars);
  277. $wheres = $options['wheres'];
  278. $wheres[] = _elgg_get_guid_based_where_sql('rv.id', $options['ids']);
  279. $wheres[] = _elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']);
  280. $wheres[] = _elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']);
  281. $wheres[] = _elgg_get_guid_based_where_sql('rv.target_guid', $options['target_guids']);
  282. $wheres[] = _elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']);
  283. $wheres[] = _elgg_river_get_action_where_sql($options['action_types']);
  284. $wheres[] = _elgg_get_river_type_subtype_where_sql('rv', $options['types'],
  285. $options['subtypes'], $options['type_subtype_pairs']);
  286. if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) {
  287. $wheres[] = "rv.posted >= {$options['posted_time_lower']}";
  288. }
  289. if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) {
  290. $wheres[] = "rv.posted <= {$options['posted_time_upper']}";
  291. }
  292. if (!access_get_show_hidden_status()) {
  293. $wheres[] = "rv.enabled = 'yes'";
  294. }
  295. $joins = $options['joins'];
  296. $dbprefix = elgg_get_config('dbprefix');
  297. $joins[] = "JOIN {$dbprefix}entities oe ON rv.object_guid = oe.guid";
  298. // LEFT JOIN is used because all river items do not necessarily have target
  299. $joins[] = "LEFT JOIN {$dbprefix}entities te ON rv.target_guid = te.guid";
  300. if ($options['relationship_guid']) {
  301. $clauses = elgg_get_entity_relationship_where_sql(
  302. 'rv.subject_guid',
  303. $options['relationship'],
  304. $options['relationship_guid'],
  305. $options['inverse_relationship']);
  306. if ($clauses) {
  307. $wheres = array_merge($wheres, $clauses['wheres']);
  308. $joins = array_merge($joins, $clauses['joins']);
  309. }
  310. }
  311. // see if any functions failed
  312. // remove empty strings on successful functions
  313. foreach ($wheres as $i => $where) {
  314. if ($where === false) {
  315. return false;
  316. } elseif (empty($where)) {
  317. unset($wheres[$i]);
  318. }
  319. }
  320. // remove identical where clauses
  321. $wheres = array_unique($wheres);
  322. if (!$options['count']) {
  323. $distinct = $options['distinct'] ? "DISTINCT" : "";
  324. $query = "SELECT $distinct rv.* FROM {$CONFIG->dbprefix}river rv ";
  325. } else {
  326. // note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than IDs
  327. $count_expr = $options['distinct'] ? "DISTINCT rv.id" : "*";
  328. $query = "SELECT COUNT($count_expr) as total FROM {$CONFIG->dbprefix}river rv ";
  329. }
  330. // add joins
  331. foreach ($joins as $j) {
  332. $query .= " $j ";
  333. }
  334. // add wheres
  335. $query .= ' WHERE ';
  336. foreach ($wheres as $w) {
  337. $query .= " $w AND ";
  338. }
  339. // Make sure that user has access to all the entities referenced by each river item
  340. $object_access_where = _elgg_get_access_where_sql(array('table_alias' => 'oe'));
  341. $target_access_where = _elgg_get_access_where_sql(array('table_alias' => 'te'));
  342. // We use LEFT JOIN with entities table but the WHERE clauses are used
  343. // regardless if a JOIN is successfully made. The "te.guid IS NULL" is
  344. // needed because of this.
  345. $query .= "$object_access_where AND ($target_access_where OR te.guid IS NULL) ";
  346. if (!$options['count']) {
  347. $options['group_by'] = sanitise_string($options['group_by']);
  348. if ($options['group_by']) {
  349. $query .= " GROUP BY {$options['group_by']}";
  350. }
  351. $options['order_by'] = sanitise_string($options['order_by']);
  352. $query .= " ORDER BY {$options['order_by']}";
  353. if ($options['limit']) {
  354. $limit = sanitise_int($options['limit']);
  355. $offset = sanitise_int($options['offset'], false);
  356. $query .= " LIMIT $offset, $limit";
  357. }
  358. $river_items = get_data($query, '_elgg_row_to_elgg_river_item');
  359. _elgg_prefetch_river_entities($river_items);
  360. return $river_items;
  361. } else {
  362. $total = get_data_row($query);
  363. return (int)$total->total;
  364. }
  365. }
  366. /**
  367. * Prefetch entities that will be displayed in the river.
  368. *
  369. * @param \ElggRiverItem[] $river_items
  370. * @access private
  371. */
  372. function _elgg_prefetch_river_entities(array $river_items) {
  373. // prefetch objects, subjects and targets
  374. $guids = array();
  375. foreach ($river_items as $item) {
  376. if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) {
  377. $guids[$item->subject_guid] = true;
  378. }
  379. if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) {
  380. $guids[$item->object_guid] = true;
  381. }
  382. if ($item->target_guid && !_elgg_retrieve_cached_entity($item->target_guid)) {
  383. $guids[$item->target_guid] = true;
  384. }
  385. }
  386. if ($guids) {
  387. // The entity cache only holds 256. We don't want to bump out any plugins.
  388. $guids = array_slice($guids, 0, 200, true);
  389. // return value unneeded, just priming cache
  390. elgg_get_entities(array(
  391. 'guids' => array_keys($guids),
  392. 'limit' => 0,
  393. 'distinct' => false,
  394. ));
  395. }
  396. // prefetch object containers, in case they were not in the targets
  397. $guids = array();
  398. foreach ($river_items as $item) {
  399. $object = $item->getObjectEntity();
  400. if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) {
  401. $guids[$object->container_guid] = true;
  402. }
  403. }
  404. if ($guids) {
  405. $guids = array_slice($guids, 0, 200, true);
  406. elgg_get_entities(array(
  407. 'guids' => array_keys($guids),
  408. 'limit' => 0,
  409. 'distinct' => false,
  410. // Why specify? user containers are likely already loaded via the owners, and
  411. // specifying groups allows ege() to auto-join the groups_entity table
  412. 'type' => 'group',
  413. ));
  414. }
  415. // Note: We've tried combining the above ege() calls into one (pulling containers at the same time).
  416. // Although it seems like it would reduce queries, it added some. o_O
  417. }
  418. /**
  419. * List river items
  420. *
  421. * @param array $options Any options from elgg_get_river() plus:
  422. * item_view => STR Alternative view to render list items
  423. * pagination => BOOL Display pagination links (true)
  424. * no_results => STR|Closure Message to display if no items
  425. *
  426. * @return string
  427. * @since 1.8.0
  428. */
  429. function elgg_list_river(array $options = array()) {
  430. global $autofeed;
  431. $autofeed = true;
  432. $defaults = array(
  433. 'offset' => (int) max(get_input('offset', 0), 0),
  434. 'limit' => (int) max(get_input('limit', max(20, elgg_get_config('default_limit'))), 0),
  435. 'pagination' => true,
  436. 'list_class' => 'elgg-list-river',
  437. 'no_results' => '',
  438. );
  439. $options = array_merge($defaults, $options);
  440. if (!$options["limit"] && !$options["offset"]) {
  441. // no need for pagination if listing is unlimited
  442. $options["pagination"] = false;
  443. }
  444. $options['count'] = true;
  445. $count = elgg_get_river($options);
  446. if ($count > 0) {
  447. $options['count'] = false;
  448. $items = elgg_get_river($options);
  449. } else {
  450. $items = array();
  451. }
  452. $options['count'] = $count;
  453. $options['items'] = $items;
  454. return elgg_view('page/components/list', $options);
  455. }
  456. /**
  457. * Convert a database row to a new \ElggRiverItem
  458. *
  459. * @param \stdClass $row Database row from the river table
  460. *
  461. * @return \ElggRiverItem
  462. * @since 1.8.0
  463. * @access private
  464. */
  465. function _elgg_row_to_elgg_river_item($row) {
  466. if (!($row instanceof \stdClass)) {
  467. return null;
  468. }
  469. return new \ElggRiverItem($row);
  470. }
  471. /**
  472. * Returns SQL where clause for type and subtype on river table
  473. *
  474. * @internal This is a simplified version of elgg_get_entity_type_subtype_where_sql()
  475. * which could be used for all queries once the subtypes have been denormalized.
  476. *
  477. * @param string $table 'rv'
  478. * @param null|array $types Array of types or null if none.
  479. * @param null|array $subtypes Array of subtypes or null if none
  480. * @param null|array $pairs Array of pairs of types and subtypes
  481. *
  482. * @return string
  483. * @since 1.8.0
  484. * @access private
  485. */
  486. function _elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs) {
  487. // short circuit if nothing is requested
  488. if (!$types && !$subtypes && !$pairs) {
  489. return '';
  490. }
  491. $wheres = array();
  492. $types_wheres = array();
  493. $subtypes_wheres = array();
  494. // if no pairs, use types and subtypes
  495. if (!is_array($pairs)) {
  496. if ($types) {
  497. if (!is_array($types)) {
  498. $types = array($types);
  499. }
  500. foreach ($types as $type) {
  501. $type = sanitise_string($type);
  502. $types_wheres[] = "({$table}.type = '$type')";
  503. }
  504. }
  505. if ($subtypes) {
  506. if (!is_array($subtypes)) {
  507. $subtypes = array($subtypes);
  508. }
  509. foreach ($subtypes as $subtype) {
  510. $subtype = sanitise_string($subtype);
  511. $subtypes_wheres[] = "({$table}.subtype = '$subtype')";
  512. }
  513. }
  514. if (is_array($types_wheres) && count($types_wheres)) {
  515. $types_wheres = array(implode(' OR ', $types_wheres));
  516. }
  517. if (is_array($subtypes_wheres) && count($subtypes_wheres)) {
  518. $subtypes_wheres = array('(' . implode(' OR ', $subtypes_wheres) . ')');
  519. }
  520. $wheres = array(implode(' AND ', array_merge($types_wheres, $subtypes_wheres)));
  521. } else {
  522. // using type/subtype pairs
  523. foreach ($pairs as $paired_type => $paired_subtypes) {
  524. $paired_type = sanitise_string($paired_type);
  525. if (is_array($paired_subtypes)) {
  526. $paired_subtypes = array_map('sanitise_string', $paired_subtypes);
  527. $paired_subtype_str = implode("','", $paired_subtypes);
  528. if ($paired_subtype_str) {
  529. $wheres[] = "({$table}.type = '$paired_type'"
  530. . " AND {$table}.subtype IN ('$paired_subtype_str'))";
  531. }
  532. } else {
  533. $paired_subtype = sanitise_string($paired_subtypes);
  534. $wheres[] = "({$table}.type = '$paired_type'"
  535. . " AND {$table}.subtype = '$paired_subtype')";
  536. }
  537. }
  538. }
  539. if (is_array($wheres) && count($wheres)) {
  540. $where = implode(' OR ', $wheres);
  541. return "($where)";
  542. }
  543. return '';
  544. }
  545. /**
  546. * Get the where clause based on river action type strings
  547. *
  548. * @param array $types Array of action type strings
  549. *
  550. * @return string
  551. * @since 1.8.0
  552. * @access private
  553. */
  554. function _elgg_river_get_action_where_sql($types) {
  555. if (!$types) {
  556. return '';
  557. }
  558. if (!is_array($types)) {
  559. $types = sanitise_string($types);
  560. return "(rv.action_type = '$types')";
  561. }
  562. // sanitize types array
  563. $types_sanitized = array();
  564. foreach ($types as $type) {
  565. $types_sanitized[] = sanitise_string($type);
  566. }
  567. $type_str = implode("','", $types_sanitized);
  568. return "(rv.action_type IN ('$type_str'))";
  569. }
  570. /**
  571. * Get the where clause based on river view strings
  572. *
  573. * @param array $views Array of view strings
  574. *
  575. * @return string
  576. * @since 1.8.0
  577. * @access private
  578. */
  579. function _elgg_river_get_view_where_sql($views) {
  580. if (!$views) {
  581. return '';
  582. }
  583. if (!is_array($views)) {
  584. $views = sanitise_string($views);
  585. return "(rv.view = '$views')";
  586. }
  587. // sanitize views array
  588. $views_sanitized = array();
  589. foreach ($views as $view) {
  590. $views_sanitized[] = sanitise_string($view);
  591. }
  592. $view_str = implode("','", $views_sanitized);
  593. return "(rv.view IN ('$view_str'))";
  594. }
  595. /**
  596. * Sets the access ID on river items for a particular object
  597. *
  598. * @param int $object_guid The GUID of the entity
  599. * @param int $access_id The access ID
  600. *
  601. * @return bool Depending on success
  602. */
  603. function update_river_access_by_object($object_guid, $access_id) {
  604. // Sanitise
  605. $object_guid = (int) $object_guid;
  606. $access_id = (int) $access_id;
  607. // Load config
  608. global $CONFIG;
  609. $query = "UPDATE {$CONFIG->dbprefix}river
  610. SET access_id = {$access_id}
  611. WHERE object_guid = {$object_guid}";
  612. return update_data($query);
  613. }
  614. /**
  615. * Page handler for activity
  616. *
  617. * @param array $page
  618. * @return bool
  619. * @access private
  620. */
  621. function _elgg_river_page_handler($page) {
  622. global $CONFIG;
  623. elgg_set_page_owner_guid(elgg_get_logged_in_user_guid());
  624. // make a URL segment available in page handler script
  625. $page_type = elgg_extract(0, $page, 'all');
  626. $page_type = preg_replace('[\W]', '', $page_type);
  627. if ($page_type == 'owner') {
  628. elgg_gatekeeper();
  629. $page_username = elgg_extract(1, $page, '');
  630. if ($page_username == elgg_get_logged_in_user_entity()->username) {
  631. $page_type = 'mine';
  632. } else {
  633. set_input('subject_username', $page_username);
  634. }
  635. }
  636. set_input('page_type', $page_type);
  637. require_once("{$CONFIG->path}pages/river.php");
  638. return true;
  639. }
  640. /**
  641. * Register river unit tests
  642. * @access private
  643. */
  644. function _elgg_river_test($hook, $type, $value) {
  645. global $CONFIG;
  646. $value[] = $CONFIG->path . 'engine/tests/ElggCoreRiverAPITest.php';
  647. return $value;
  648. }
  649. /**
  650. * Disable river entries that reference a disabled entity as subject/object/target
  651. *
  652. * @param string $event The event 'disable'
  653. * @param string $type Type of entity being disabled 'all'
  654. * @param mixed $entity The entity being disabled
  655. * @return boolean
  656. * @access private
  657. */
  658. function _elgg_river_disable($event, $type, $entity) {
  659. if (!elgg_instanceof($entity)) {
  660. return true;
  661. }
  662. $dbprefix = elgg_get_config('dbprefix');
  663. $query = <<<QUERY
  664. UPDATE {$dbprefix}river AS rv
  665. SET rv.enabled = 'no'
  666. WHERE (rv.subject_guid = {$entity->guid} OR rv.object_guid = {$entity->guid} OR rv.target_guid = {$entity->guid});
  667. QUERY;
  668. update_data($query);
  669. return true;
  670. }
  671. /**
  672. * Enable river entries that reference a re-enabled entity as subject/object/target
  673. *
  674. * @param string $event The event 'enable'
  675. * @param string $type Type of entity being enabled 'all'
  676. * @param mixed $entity The entity being enabled
  677. * @return boolean
  678. * @access private
  679. */
  680. function _elgg_river_enable($event, $type, $entity) {
  681. if (!elgg_instanceof($entity)) {
  682. return true;
  683. }
  684. $dbprefix = elgg_get_config('dbprefix');
  685. $query = <<<QUERY
  686. UPDATE {$dbprefix}river AS rv
  687. LEFT JOIN {$dbprefix}entities AS se ON se.guid = rv.subject_guid
  688. LEFT JOIN {$dbprefix}entities AS oe ON oe.guid = rv.object_guid
  689. LEFT JOIN {$dbprefix}entities AS te ON te.guid = rv.target_guid
  690. SET rv.enabled = 'yes'
  691. WHERE (
  692. (se.enabled = 'yes' OR se.guid IS NULL) AND
  693. (oe.enabled = 'yes' OR oe.guid IS NULL) AND
  694. (te.enabled = 'yes' OR te.guid IS NULL)
  695. )
  696. AND (se.guid = {$entity->guid} OR oe.guid = {$entity->guid} OR te.guid = {$entity->guid});
  697. QUERY;
  698. update_data($query);
  699. return true;
  700. }
  701. /**
  702. * Initialize river library
  703. * @access private
  704. */
  705. function _elgg_river_init() {
  706. elgg_register_page_handler('activity', '_elgg_river_page_handler');
  707. $item = new \ElggMenuItem('activity', elgg_echo('activity'), 'activity');
  708. elgg_register_menu_item('site', $item);
  709. elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description'));
  710. elgg_register_action('river/delete', '', 'admin');
  711. elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_river_test');
  712. }
  713. return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
  714. $events->registerHandler('init', 'system', '_elgg_river_init');
  715. $events->registerHandler('disable:after', 'all', '_elgg_river_disable');
  716. $events->registerHandler('enable:after', 'all', '_elgg_river_enable');
  717. };