Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

567 lines
15KB

  1. <?php
  2. /**
  3. * @file include/ping.php
  4. */
  5. use Friendica\App;
  6. use Friendica\Content\Feature;
  7. use Friendica\Content\ForumManager;
  8. use Friendica\Content\Text\BBCode;
  9. use Friendica\Core\Addon;
  10. use Friendica\Core\Cache;
  11. use Friendica\Core\Config;
  12. use Friendica\Core\L10n;
  13. use Friendica\Core\PConfig;
  14. use Friendica\Core\System;
  15. use Friendica\Database\DBA;
  16. use Friendica\Model\Contact;
  17. use Friendica\Model\Group;
  18. use Friendica\Model\Item;
  19. use Friendica\Util\DateTimeFormat;
  20. use Friendica\Util\Temporal;
  21. use Friendica\Util\Proxy as ProxyUtils;
  22. use Friendica\Util\XML;
  23. /**
  24. * @brief Outputs the counts and the lists of various notifications
  25. *
  26. * The output format can be controlled via the GET parameter 'format'. It can be
  27. * - xml (deprecated legacy default)
  28. * - json (outputs JSONP with the 'callback' GET parameter)
  29. *
  30. * Expected JSON structure:
  31. * {
  32. * "result": {
  33. * "intro": 0,
  34. * "mail": 0,
  35. * "net": 0,
  36. * "home": 0,
  37. * "register": 0,
  38. * "all-events": 0,
  39. * "all-events-today": 0,
  40. * "events": 0,
  41. * "events-today": 0,
  42. * "birthdays": 0,
  43. * "birthdays-today": 0,
  44. * "groups": [ ],
  45. * "forums": [ ],
  46. * "notify": 0,
  47. * "notifications": [ ],
  48. * "sysmsgs": {
  49. * "notice": [ ],
  50. * "info": [ ]
  51. * }
  52. * }
  53. * }
  54. *
  55. * @param App $a The Friendica App instance
  56. */
  57. function ping_init(App $a)
  58. {
  59. $format = 'xml';
  60. if (isset($_GET['format']) && $_GET['format'] == 'json') {
  61. $format = 'json';
  62. }
  63. $tags = [];
  64. $comments = [];
  65. $likes = [];
  66. $dislikes = [];
  67. $friends = [];
  68. $posts = [];
  69. $regs = [];
  70. $mails = [];
  71. $notifications = [];
  72. $intro_count = 0;
  73. $mail_count = 0;
  74. $home_count = 0;
  75. $network_count = 0;
  76. $register_count = 0;
  77. $sysnotify_count = 0;
  78. $groups_unseen = [];
  79. $forums_unseen = [];
  80. $all_events = 0;
  81. $all_events_today = 0;
  82. $events = 0;
  83. $events_today = 0;
  84. $birthdays = 0;
  85. $birthdays_today = 0;
  86. $data = [];
  87. $data['intro'] = $intro_count;
  88. $data['mail'] = $mail_count;
  89. $data['net'] = $network_count;
  90. $data['home'] = $home_count;
  91. $data['register'] = $register_count;
  92. $data['all-events'] = $all_events;
  93. $data['all-events-today'] = $all_events_today;
  94. $data['events'] = $events;
  95. $data['events-today'] = $events_today;
  96. $data['birthdays'] = $birthdays;
  97. $data['birthdays-today'] = $birthdays_today;
  98. if (local_user()) {
  99. // Different login session than the page that is calling us.
  100. if (!empty($_GET['uid']) && intval($_GET['uid']) != local_user()) {
  101. $data = ['result' => ['invalid' => 1]];
  102. if ($format == 'json') {
  103. if (isset($_GET['callback'])) {
  104. // JSONP support
  105. header("Content-type: application/javascript");
  106. echo $_GET['callback'] . '(' . json_encode($data) . ')';
  107. } else {
  108. header("Content-type: application/json");
  109. echo json_encode($data);
  110. }
  111. } else {
  112. header("Content-type: text/xml");
  113. echo XML::fromArray($data, $xml);
  114. }
  115. killme();
  116. }
  117. $notifs = ping_get_notifications(local_user());
  118. $condition = ["`unseen` AND `uid` = ? AND `contact-id` != ?", local_user(), local_user()];
  119. $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
  120. 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'wall'];
  121. $params = ['order' => ['created' => true]];
  122. $items = Item::selectForUser(local_user(), $fields, $condition, $params);
  123. if (DBA::isResult($items)) {
  124. $items_unseen = Item::inArray($items);
  125. $arr = ['items' => $items_unseen];
  126. Addon::callHooks('network_ping', $arr);
  127. foreach ($items_unseen as $item) {
  128. if ($item['wall']) {
  129. $home_count++;
  130. } else {
  131. $network_count++;
  132. }
  133. }
  134. }
  135. if ($network_count) {
  136. // Find out how unseen network posts are spread across groups
  137. $group_counts = Group::countUnseen();
  138. if (DBA::isResult($group_counts)) {
  139. foreach ($group_counts as $group_count) {
  140. if ($group_count['count'] > 0) {
  141. $groups_unseen[] = $group_count;
  142. }
  143. }
  144. }
  145. $forum_counts = ForumManager::countUnseenItems();
  146. if (DBA::isResult($forum_counts)) {
  147. foreach ($forum_counts as $forum_count) {
  148. if ($forum_count['count'] > 0) {
  149. $forums_unseen[] = $forum_count;
  150. }
  151. }
  152. }
  153. }
  154. $intros1 = q(
  155. "SELECT `intro`.`id`, `intro`.`datetime`,
  156. `fcontact`.`name`, `fcontact`.`url`, `fcontact`.`photo`
  157. FROM `intro` LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id`
  158. WHERE `intro`.`uid` = %d AND `intro`.`blocked` = 0 AND `intro`.`ignore` = 0 AND `intro`.`fid` != 0",
  159. intval(local_user())
  160. );
  161. $intros2 = q(
  162. "SELECT `intro`.`id`, `intro`.`datetime`,
  163. `contact`.`name`, `contact`.`url`, `contact`.`photo`
  164. FROM `intro` LEFT JOIN `contact` ON `intro`.`contact-id` = `contact`.`id`
  165. WHERE `intro`.`uid` = %d AND `intro`.`blocked` = 0 AND `intro`.`ignore` = 0 AND `intro`.`contact-id` != 0",
  166. intval(local_user())
  167. );
  168. $intro_count = count($intros1) + count($intros2);
  169. $intros = $intros1 + $intros2;
  170. $myurl = System::baseUrl() . '/profile/' . $a->user['nickname'];
  171. $mails = q(
  172. "SELECT `id`, `from-name`, `from-url`, `from-photo`, `created` FROM `mail`
  173. WHERE `uid` = %d AND `seen` = 0 AND `from-url` != '%s' ",
  174. intval(local_user()),
  175. DBA::escape($myurl)
  176. );
  177. $mail_count = count($mails);
  178. if (intval(Config::get('config', 'register_policy')) === REGISTER_APPROVE && is_site_admin()) {
  179. $regs = Friendica\Model\Register::getPending();
  180. if (DBA::isResult($regs)) {
  181. $register_count = count($regs);
  182. }
  183. }
  184. $cachekey = "ping_init:".local_user();
  185. $ev = Cache::get($cachekey);
  186. if (is_null($ev)) {
  187. $ev = q(
  188. "SELECT type, start, adjust FROM `event`
  189. WHERE `event`.`uid` = %d AND `start` < '%s' AND `finish` > '%s' and `ignore` = 0
  190. ORDER BY `start` ASC ",
  191. intval(local_user()),
  192. DBA::escape(DateTimeFormat::utc('now + 7 days')),
  193. DBA::escape(DateTimeFormat::utcNow())
  194. );
  195. if (DBA::isResult($ev)) {
  196. Cache::set($cachekey, $ev, Cache::HOUR);
  197. }
  198. }
  199. if (DBA::isResult($ev)) {
  200. $all_events = count($ev);
  201. if ($all_events) {
  202. $str_now = DateTimeFormat::timezoneNow($a->timezone, 'Y-m-d');
  203. foreach ($ev as $x) {
  204. $bd = false;
  205. if ($x['type'] === 'birthday') {
  206. $birthdays ++;
  207. $bd = true;
  208. } else {
  209. $events ++;
  210. }
  211. if (DateTimeFormat::convert($x['start'], ((intval($x['adjust'])) ? $a->timezone : 'UTC'), 'UTC', 'Y-m-d') === $str_now) {
  212. $all_events_today ++;
  213. if ($bd) {
  214. $birthdays_today ++;
  215. } else {
  216. $events_today ++;
  217. }
  218. }
  219. }
  220. }
  221. }
  222. $data['intro'] = $intro_count;
  223. $data['mail'] = $mail_count;
  224. $data['net'] = $network_count;
  225. $data['home'] = $home_count;
  226. $data['register'] = $register_count;
  227. $data['all-events'] = $all_events;
  228. $data['all-events-today'] = $all_events_today;
  229. $data['events'] = $events;
  230. $data['events-today'] = $events_today;
  231. $data['birthdays'] = $birthdays;
  232. $data['birthdays-today'] = $birthdays_today;
  233. if (DBA::isResult($notifs)) {
  234. foreach ($notifs as $notif) {
  235. if ($notif['seen'] == 0) {
  236. $sysnotify_count ++;
  237. }
  238. }
  239. }
  240. // merge all notification types in one array
  241. if (DBA::isResult($intros)) {
  242. foreach ($intros as $intro) {
  243. $notif = [
  244. 'id' => 0,
  245. 'href' => System::baseUrl() . '/notifications/intros/' . $intro['id'],
  246. 'name' => $intro['name'],
  247. 'url' => $intro['url'],
  248. 'photo' => $intro['photo'],
  249. 'date' => $intro['datetime'],
  250. 'seen' => false,
  251. 'message' => L10n::t('{0} wants to be your friend'),
  252. ];
  253. $notifs[] = $notif;
  254. }
  255. }
  256. if (DBA::isResult($mails)) {
  257. foreach ($mails as $mail) {
  258. $notif = [
  259. 'id' => 0,
  260. 'href' => System::baseUrl() . '/message/' . $mail['id'],
  261. 'name' => $mail['from-name'],
  262. 'url' => $mail['from-url'],
  263. 'photo' => $mail['from-photo'],
  264. 'date' => $mail['created'],
  265. 'seen' => false,
  266. 'message' => L10n::t('{0} sent you a message'),
  267. ];
  268. $notifs[] = $notif;
  269. }
  270. }
  271. if (DBA::isResult($regs)) {
  272. foreach ($regs as $reg) {
  273. $notif = [
  274. 'id' => 0,
  275. 'href' => System::baseUrl() . '/admin/users/',
  276. 'name' => $reg['name'],
  277. 'url' => $reg['url'],
  278. 'photo' => $reg['micro'],
  279. 'date' => $reg['created'],
  280. 'seen' => false,
  281. 'message' => L10n::t('{0} requested registration'),
  282. ];
  283. $notifs[] = $notif;
  284. }
  285. }
  286. // sort notifications by $[]['date']
  287. $sort_function = function ($a, $b) {
  288. $adate = strtotime($a['date']);
  289. $bdate = strtotime($b['date']);
  290. // Unseen messages are kept at the top
  291. // The value 31536000 means one year. This should be enough :-)
  292. if (!$a['seen']) {
  293. $adate += 31536000;
  294. }
  295. if (!$b['seen']) {
  296. $bdate += 31536000;
  297. }
  298. if ($adate == $bdate) {
  299. return 0;
  300. }
  301. return ($adate < $bdate) ? 1 : -1;
  302. };
  303. usort($notifs, $sort_function);
  304. if (DBA::isResult($notifs)) {
  305. // Are the nofications called from the regular process or via the friendica app?
  306. $regularnotifications = (!empty($_GET['uid']) && !empty($_GET['_']));
  307. foreach ($notifs as $notif) {
  308. if ($a->isFriendicaApp() || !$regularnotifications) {
  309. $notif['message'] = str_replace("{0}", $notif['name'], $notif['message']);
  310. }
  311. $contact = Contact::getDetailsByURL($notif['url']);
  312. if (isset($contact['micro'])) {
  313. $notif['photo'] = ProxyUtils::proxifyUrl($contact['micro'], false, ProxyUtils::SIZE_MICRO);
  314. } else {
  315. $notif['photo'] = ProxyUtils::proxifyUrl($notif['photo'], false, ProxyUtils::SIZE_MICRO);
  316. }
  317. $local_time = DateTimeFormat::local($notif['date']);
  318. $notifications[] = [
  319. 'id' => $notif['id'],
  320. 'href' => $notif['href'],
  321. 'name' => $notif['name'],
  322. 'url' => $notif['url'],
  323. 'photo' => $notif['photo'],
  324. 'date' => Temporal::getRelativeDate($notif['date']),
  325. 'message' => $notif['message'],
  326. 'seen' => $notif['seen'],
  327. 'timestamp' => strtotime($local_time)
  328. ];
  329. }
  330. }
  331. }
  332. $sysmsgs = [];
  333. $sysmsgs_info = [];
  334. if (!empty($_SESSION['sysmsg'])) {
  335. $sysmsgs = $_SESSION['sysmsg'];
  336. unset($_SESSION['sysmsg']);
  337. }
  338. if (!empty($_SESSION['sysmsg_info'])) {
  339. $sysmsgs_info = $_SESSION['sysmsg_info'];
  340. unset($_SESSION['sysmsg_info']);
  341. }
  342. if ($format == 'json') {
  343. $data['groups'] = $groups_unseen;
  344. $data['forums'] = $forums_unseen;
  345. $data['notify'] = $sysnotify_count + $intro_count + $mail_count + $register_count;
  346. $data['notifications'] = $notifications;
  347. $data['sysmsgs'] = [
  348. 'notice' => $sysmsgs,
  349. 'info' => $sysmsgs_info
  350. ];
  351. $json_payload = json_encode(["result" => $data]);
  352. if (isset($_GET['callback'])) {
  353. // JSONP support
  354. header("Content-type: application/javascript");
  355. echo $_GET['callback'] . '(' . $json_payload . ')';
  356. } else {
  357. header("Content-type: application/json");
  358. echo $json_payload;
  359. }
  360. } else {
  361. // Legacy slower XML format output
  362. $data = ping_format_xml_data($data, $sysnotify_count, $notifications, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen);
  363. header("Content-type: text/xml");
  364. echo XML::fromArray(["result" => $data], $xml);
  365. }
  366. killme();
  367. }
  368. /**
  369. * @brief Retrieves the notifications array for the given user ID
  370. *
  371. * @param int $uid User id
  372. * @return array Associative array of notifications
  373. */
  374. function ping_get_notifications($uid)
  375. {
  376. $result = [];
  377. $offset = 0;
  378. $seen = false;
  379. $seensql = "NOT";
  380. $order = "DESC";
  381. $quit = false;
  382. $a = get_app();
  383. do {
  384. $r = q(
  385. "SELECT `notify`.*, `item`.`visible`, `item`.`deleted`
  386. FROM `notify` LEFT JOIN `item` ON `item`.`id` = `notify`.`iid`
  387. WHERE `notify`.`uid` = %d AND `notify`.`msg` != ''
  388. AND NOT (`notify`.`type` IN (%d, %d))
  389. AND $seensql `notify`.`seen` ORDER BY `notify`.`date` $order LIMIT %d, 50",
  390. intval($uid),
  391. intval(NOTIFY_INTRO),
  392. intval(NOTIFY_MAIL),
  393. intval($offset)
  394. );
  395. if (!$r && !$seen) {
  396. $seen = true;
  397. $seensql = "";
  398. $order = "DESC";
  399. $offset = 0;
  400. } elseif (!$r) {
  401. $quit = true;
  402. } else {
  403. $offset += 50;
  404. }
  405. foreach ($r as $notification) {
  406. if (is_null($notification["visible"])) {
  407. $notification["visible"] = true;
  408. }
  409. if (is_null($notification["deleted"])) {
  410. $notification["deleted"] = 0;
  411. }
  412. if ($notification["msg_cache"]) {
  413. $notification["name"] = $notification["name_cache"];
  414. $notification["message"] = $notification["msg_cache"];
  415. } else {
  416. $notification["name"] = strip_tags(BBCode::convert($notification["name"]));
  417. $notification["message"] = format_notification_message($notification["name"], strip_tags(BBCode::convert($notification["msg"])));
  418. q(
  419. "UPDATE `notify` SET `name_cache` = '%s', `msg_cache` = '%s' WHERE `id` = %d",
  420. DBA::escape($notification["name"]),
  421. DBA::escape($notification["message"]),
  422. intval($notification["id"])
  423. );
  424. }
  425. $notification["href"] = System::baseUrl() . "/notify/view/" . $notification["id"];
  426. if ($notification["visible"]
  427. && !$notification["deleted"]
  428. && empty($result[$notification["parent"]])
  429. ) {
  430. // Should we condense the notifications or show them all?
  431. if (PConfig::get(local_user(), 'system', 'detailed_notif')) {
  432. $result[$notification["id"]] = $notification;
  433. } else {
  434. $result[$notification["parent"]] = $notification;
  435. }
  436. }
  437. }
  438. } while ((count($result) < 50) && !$quit);
  439. return($result);
  440. }
  441. /**
  442. * @brief Backward-compatible XML formatting for ping.php output
  443. * @deprecated
  444. *
  445. * @param array $data The initial ping data array
  446. * @param int $sysnotify_count Number of unseen system notifications
  447. * @param array $notifs Complete list of notification
  448. * @param array $sysmsgs List of system notice messages
  449. * @param array $sysmsgs_info List of system info messages
  450. * @param int $groups_unseen Number of unseen group items
  451. * @param int $forums_unseen Number of unseen forum items
  452. *
  453. * @return array XML-transform ready data array
  454. */
  455. function ping_format_xml_data($data, $sysnotify_count, $notifs, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen)
  456. {
  457. $notifications = [];
  458. foreach ($notifs as $key => $notif) {
  459. $notifications[$key . ':note'] = $notif['message'];
  460. $notifications[$key . ':@attributes'] = [
  461. 'id' => $notif['id'],
  462. 'href' => $notif['href'],
  463. 'name' => $notif['name'],
  464. 'url' => $notif['url'],
  465. 'photo' => $notif['photo'],
  466. 'date' => $notif['date'],
  467. 'seen' => $notif['seen'],
  468. 'timestamp' => $notif['timestamp']
  469. ];
  470. }
  471. $sysmsg = [];
  472. foreach ($sysmsgs as $key => $m) {
  473. $sysmsg[$key . ':notice'] = $m;
  474. }
  475. foreach ($sysmsgs_info as $key => $m) {
  476. $sysmsg[$key . ':info'] = $m;
  477. }
  478. $data['notif'] = $notifications;
  479. $data['@attributes'] = ['count' => $sysnotify_count + $data['intro'] + $data['mail'] + $data['register']];
  480. $data['sysmsgs'] = $sysmsg;
  481. if ($data['register'] == 0) {
  482. unset($data['register']);
  483. }
  484. $groups = [];
  485. if (count($groups_unseen)) {
  486. foreach ($groups_unseen as $key => $item) {
  487. $groups[$key . ':group'] = $item['count'];
  488. $groups[$key . ':@attributes'] = ['id' => $item['id']];
  489. }
  490. $data['groups'] = $groups;
  491. }
  492. $forums = [];
  493. if (count($forums_unseen)) {
  494. foreach ($forums_unseen as $key => $item) {
  495. $forums[$key . ':forum'] = $item['count'];
  496. $forums[$key . ':@attributes'] = ['id' => $item['id']];
  497. }
  498. $data['forums'] = $forums;
  499. }
  500. return $data;
  501. }