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.
 
 
 
 
 
 

535 lines
15 KiB

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