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.

607 lines
19 KiB

4 years ago
4 years ago
4 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
4 years ago
4 years ago
11 years ago
11 years ago
11 years ago
11 years ago
5 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
6 years ago
4 years ago
4 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
  1. <?php
  2. use Friendica\App;
  3. use Friendica\Core\Config;
  4. require_once('include/queue_fn.php');
  5. require_once('include/html2plain.php');
  6. require_once('include/Scrape.php');
  7. require_once('include/diaspora.php');
  8. require_once('include/ostatus.php');
  9. require_once('include/salmon.php');
  10. /*
  11. * This file was at one time responsible for doing all deliveries, but this caused
  12. * big problems when the process was killed or stalled during the delivery process.
  13. * It now invokes separate queues that are delivering via delivery.php and pubsubpublish.php.
  14. */
  15. /*
  16. * The notifier is typically called with:
  17. *
  18. * proc_run(PRIORITY_HIGH, "include/notifier.php", COMMAND, ITEM_ID);
  19. *
  20. * where COMMAND is one of the following:
  21. *
  22. * activity (in diaspora.php, dfrn_confirm.php, profiles.php)
  23. * comment-import (in diaspora.php, items.php)
  24. * comment-new (in item.php)
  25. * drop (in diaspora.php, items.php, photos.php)
  26. * edit_post (in item.php)
  27. * event (in events.php)
  28. * expire (in items.php)
  29. * like (in like.php, poke.php)
  30. * mail (in message.php)
  31. * suggest (in fsuggest.php)
  32. * tag (in photos.php, poke.php, tagger.php)
  33. * tgroup (in items.php)
  34. * wall-new (in photos.php, item.php)
  35. * removeme (in Contact.php)
  36. * relocate (in uimport.php)
  37. *
  38. * and ITEM_ID is the id of the item in the database that needs to be sent to others.
  39. */
  40. function notifier_run(&$argv, &$argc){
  41. global $a;
  42. require_once('include/datetime.php');
  43. require_once('include/items.php');
  44. require_once('include/bbcode.php');
  45. require_once('include/email.php');
  46. if ($argc < 3) {
  47. return;
  48. }
  49. logger('notifier: invoked: ' . print_r($argv,true), LOGGER_DEBUG);
  50. $cmd = $argv[1];
  51. switch($cmd) {
  52. case 'mail':
  53. default:
  54. $item_id = intval($argv[2]);
  55. if (! $item_id) {
  56. return;
  57. }
  58. break;
  59. }
  60. $expire = false;
  61. $mail = false;
  62. $fsuggest = false;
  63. $relocate = false;
  64. $top_level = false;
  65. $recipients = array();
  66. $url_recipients = array();
  67. $normal_mode = true;
  68. if ($cmd === 'mail') {
  69. $normal_mode = false;
  70. $mail = true;
  71. $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1",
  72. intval($item_id)
  73. );
  74. if (! count($message)) {
  75. return;
  76. }
  77. $uid = $message[0]['uid'];
  78. $recipients[] = $message[0]['contact-id'];
  79. $item = $message[0];
  80. } elseif ($cmd === 'expire') {
  81. $normal_mode = false;
  82. $expire = true;
  83. $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1
  84. AND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 10 MINUTE",
  85. intval($item_id)
  86. );
  87. $uid = $item_id;
  88. $item_id = 0;
  89. if (! count($items)) {
  90. return;
  91. }
  92. } elseif ($cmd === 'suggest') {
  93. $normal_mode = false;
  94. $fsuggest = true;
  95. $suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1",
  96. intval($item_id)
  97. );
  98. if (! count($suggest)) {
  99. return;
  100. }
  101. $uid = $suggest[0]['uid'];
  102. $recipients[] = $suggest[0]['cid'];
  103. $item = $suggest[0];
  104. } elseif ($cmd === 'removeme') {
  105. $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
  106. `user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
  107. `user`.`page-flags`, `user`.`prvnets`, `user`.`account-type`, `user`.`guid`
  108. FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
  109. WHERE `contact`.`uid` = %d AND `contact`.`self` LIMIT 1",
  110. intval($item_id));
  111. if (!$r)
  112. return;
  113. $user = $r[0];
  114. $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($item_id));
  115. if (!$r)
  116. return;
  117. $self = $r[0];
  118. $r = q("SELECT * FROM `contact` WHERE NOT `self` AND `uid` = %d", intval($item_id));
  119. if (!$r) {
  120. return;
  121. }
  122. require_once('include/Contact.php');
  123. foreach ($r as $contact) {
  124. terminate_friendship($user, $self, $contact);
  125. }
  126. return;
  127. } elseif ($cmd === 'relocate') {
  128. $normal_mode = false;
  129. $relocate = true;
  130. $uid = $item_id;
  131. $recipients_relocate = q("SELECT * FROM contact WHERE uid = %d AND self = 0 AND network = '%s'" , intval($uid), NETWORK_DFRN);
  132. } else {
  133. // find ancestors
  134. $r = q("SELECT * FROM `item` WHERE `id` = %d and visible = 1 and moderated = 0 LIMIT 1",
  135. intval($item_id)
  136. );
  137. if ((! dbm::is_result($r)) || (! intval($r[0]['parent']))) {
  138. return;
  139. }
  140. $target_item = $r[0];
  141. $parent_id = intval($r[0]['parent']);
  142. $uid = $r[0]['uid'];
  143. $updated = $r[0]['edited'];
  144. $items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer`
  145. FROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d and visible = 1 and moderated = 0 ORDER BY `id` ASC",
  146. intval($parent_id)
  147. );
  148. if (! count($items)) {
  149. return;
  150. }
  151. // avoid race condition with deleting entries
  152. if ($items[0]['deleted']) {
  153. foreach ($items as $item) {
  154. $item['deleted'] = 1;
  155. }
  156. }
  157. if ((count($items) == 1) && ($items[0]['id'] === $target_item['id']) && ($items[0]['uri'] === $items[0]['parent-uri'])) {
  158. logger('notifier: top level post');
  159. $top_level = true;
  160. }
  161. }
  162. $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
  163. `user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
  164. `user`.`page-flags`, `user`.`prvnets`, `user`.`account-type`
  165. FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
  166. WHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1",
  167. intval($uid)
  168. );
  169. if (! dbm::is_result($r)) {
  170. return;
  171. }
  172. $owner = $r[0];
  173. $walltowall = ((($top_level) && ($owner['id'] != $items[0]['contact-id'])) ? true : false);
  174. $hub = get_config('system','huburl');
  175. // Should the post be transmitted to Diaspora?
  176. $diaspora_delivery = true;
  177. // If this is a public conversation, notify the feed hub
  178. $public_message = true;
  179. // Do a PuSH
  180. $push_notify = false;
  181. // fill this in with a single salmon slap if applicable
  182. $slap = '';
  183. if (! ($mail || $fsuggest || $relocate)) {
  184. $slap = ostatus::salmon($target_item,$owner);
  185. require_once('include/group.php');
  186. $parent = $items[0];
  187. $thr_parent = q("SELECT `network`, `author-link`, `owner-link` FROM `item` WHERE `uri` = '%s' AND `uid` = %d",
  188. dbesc($target_item["thr-parent"]), intval($target_item["uid"]));
  189. logger('GUID: '.$target_item["guid"].': Parent is '.$parent['network'].'. Thread parent is '.$thr_parent[0]['network'], LOGGER_DEBUG);
  190. // This is IMPORTANT!!!!
  191. // We will only send a "notify owner to relay" or followup message if the referenced post
  192. // originated on our system by virtue of having our hostname somewhere
  193. // in the URI, AND it was a comment (not top_level) AND the parent originated elsewhere.
  194. // if $parent['wall'] == 1 we will already have the parent message in our array
  195. // and we will relay the whole lot.
  196. // expire sends an entire group of expire messages and cannot be forwarded.
  197. // However the conversation owner will be a part of the conversation and will
  198. // be notified during this run.
  199. // Other DFRN conversation members will be alerted during polled updates.
  200. // Diaspora members currently are not notified of expirations, and other networks have
  201. // either limited or no ability to process deletions. We should at least fix Diaspora
  202. // by stringing togther an array of retractions and sending them onward.
  203. $localhost = str_replace('www.','',$a->get_hostname());
  204. if (strpos($localhost,':')) {
  205. $localhost = substr($localhost,0,strpos($localhost,':'));
  206. }
  207. /**
  208. *
  209. * Be VERY CAREFUL if you make any changes to the following several lines. Seemingly innocuous changes
  210. * have been known to cause runaway conditions which affected several servers, along with
  211. * permissions issues.
  212. *
  213. */
  214. $relay_to_owner = false;
  215. if (!$top_level && ($parent['wall'] == 0) && !$expire && (stristr($target_item['uri'],$localhost))) {
  216. $relay_to_owner = true;
  217. }
  218. if (($cmd === 'uplink') && (intval($parent['forum_mode']) == 1) && !$top_level) {
  219. $relay_to_owner = true;
  220. }
  221. // until the 'origin' flag has been in use for several months
  222. // we will just use it as a fallback test
  223. // later we will be able to use it as the primary test of whether or not to relay.
  224. if (! $target_item['origin']) {
  225. $relay_to_owner = false;
  226. }
  227. if ($parent['origin']) {
  228. $relay_to_owner = false;
  229. }
  230. if ($relay_to_owner) {
  231. logger('notifier: followup '.$target_item["guid"], LOGGER_DEBUG);
  232. // local followup to remote post
  233. $followup = true;
  234. $public_message = false; // not public
  235. $conversant_str = dbesc($parent['contact-id']);
  236. $recipients = array($parent['contact-id']);
  237. $recipients_followup = array($parent['contact-id']);
  238. //if (!$target_item['private'] AND $target_item['wall'] AND
  239. if (!$target_item['private'] AND
  240. (strlen($target_item['allow_cid'].$target_item['allow_gid'].
  241. $target_item['deny_cid'].$target_item['deny_gid']) == 0))
  242. $push_notify = true;
  243. if (($thr_parent AND ($thr_parent[0]['network'] == NETWORK_OSTATUS)) OR ($parent['network'] == NETWORK_OSTATUS)) {
  244. $push_notify = true;
  245. if ($parent["network"] == NETWORK_OSTATUS) {
  246. // Distribute the message to the DFRN contacts as if this wasn't a followup since OStatus can't relay comments
  247. // Currently it is work at progress
  248. $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' AND NOT `blocked` AND NOT `pending` AND NOT `archive`",
  249. intval($uid),
  250. dbesc(NETWORK_DFRN)
  251. );
  252. if (dbm::is_result($r)) {
  253. foreach ($r as $rr) {
  254. $recipients_followup[] = $rr['id'];
  255. }
  256. }
  257. }
  258. }
  259. logger("Notify ".$target_item["guid"]." via PuSH: ".($push_notify?"Yes":"No"), LOGGER_DEBUG);
  260. } else {
  261. $followup = false;
  262. logger('Distributing directly '.$target_item["guid"], LOGGER_DEBUG);
  263. // don't send deletions onward for other people's stuff
  264. if ($target_item['deleted'] && (! intval($target_item['wall']))) {
  265. logger('notifier: ignoring delete notification for non-wall item');
  266. return;
  267. }
  268. if ((strlen($parent['allow_cid']))
  269. || (strlen($parent['allow_gid']))
  270. || (strlen($parent['deny_cid']))
  271. || (strlen($parent['deny_gid']))) {
  272. $public_message = false; // private recipients, not public
  273. }
  274. $allow_people = expand_acl($parent['allow_cid']);
  275. $allow_groups = expand_groups(expand_acl($parent['allow_gid']),true);
  276. $deny_people = expand_acl($parent['deny_cid']);
  277. $deny_groups = expand_groups(expand_acl($parent['deny_gid']));
  278. // if our parent is a public forum (forum_mode == 1), uplink to the origional author causing
  279. // a delivery fork. private groups (forum_mode == 2) do not uplink
  280. if ((intval($parent['forum_mode']) == 1) && (! $top_level) && ($cmd !== 'uplink')) {
  281. proc_run(PRIORITY_HIGH,'include/notifier.php','uplink',$item_id);
  282. }
  283. $conversants = array();
  284. foreach ($items as $item) {
  285. $recipients[] = $item['contact-id'];
  286. $conversants[] = $item['contact-id'];
  287. // pull out additional tagged people to notify (if public message)
  288. if ($public_message && strlen($item['inform'])) {
  289. $people = explode(',',$item['inform']);
  290. foreach ($people as $person) {
  291. if (substr($person,0,4) === 'cid:') {
  292. $recipients[] = intval(substr($person,4));
  293. $conversants[] = intval(substr($person,4));
  294. } else {
  295. $url_recipients[] = substr($person,4);
  296. }
  297. }
  298. }
  299. }
  300. if (count($url_recipients))
  301. logger('notifier: '.$target_item["guid"].' url_recipients ' . print_r($url_recipients,true));
  302. $conversants = array_unique($conversants);
  303. $recipients = array_unique(array_merge($recipients,$allow_people,$allow_groups));
  304. $deny = array_unique(array_merge($deny_people,$deny_groups));
  305. $recipients = array_diff($recipients,$deny);
  306. $conversant_str = dbesc(implode(', ',$conversants));
  307. }
  308. // If the thread parent is OStatus then do some magic to distribute the messages.
  309. // We have not only to look at the parent, since it could be a Friendica thread.
  310. if (($thr_parent AND ($thr_parent[0]['network'] == NETWORK_OSTATUS)) OR ($parent['network'] == NETWORK_OSTATUS)) {
  311. $diaspora_delivery = false;
  312. logger('Some parent is OStatus for '.$target_item["guid"]." - Author: ".$thr_parent[0]['author-link']." - Owner: ".$thr_parent[0]['owner-link'], LOGGER_DEBUG);
  313. // Send a salmon to the parent author
  314. $r = q("SELECT `url`, `notify` FROM `contact` WHERE `nurl`='%s' AND `uid` IN (0, %d) AND `notify` != ''",
  315. dbesc(normalise_link($thr_parent[0]['author-link'])),
  316. intval($uid));
  317. if (dbm::is_result($r)) {
  318. $probed_contact = $r[0];
  319. } else {
  320. $probed_contact = probe_url($thr_parent[0]['author-link']);
  321. }
  322. if ($probed_contact["notify"] != "") {
  323. logger('Notify parent author '.$probed_contact["url"].': '.$probed_contact["notify"]);
  324. $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
  325. }
  326. // Send a salmon to the parent owner
  327. $r = q("SELECT `url`, `notify` FROM `contact` WHERE `nurl`='%s' AND `uid` IN (0, %d) AND `notify` != ''",
  328. dbesc(normalise_link($thr_parent[0]['owner-link'])),
  329. intval($uid));
  330. if (dbm::is_result($r)) {
  331. $probed_contact = $r[0];
  332. } else {
  333. $probed_contact = probe_url($thr_parent[0]['owner-link']);
  334. }
  335. if ($probed_contact["notify"] != "") {
  336. logger('Notify parent owner '.$probed_contact["url"].': '.$probed_contact["notify"]);
  337. $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
  338. }
  339. // Send a salmon notification to every person we mentioned in the post
  340. $arr = explode(',',$target_item['tag']);
  341. foreach ($arr as $x) {
  342. //logger('Checking tag '.$x, LOGGER_DEBUG);
  343. $matches = null;
  344. if (preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
  345. $probed_contact = probe_url($matches[1]);
  346. if ($probed_contact["notify"] != "") {
  347. logger('Notify mentioned user '.$probed_contact["url"].': '.$probed_contact["notify"]);
  348. $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
  349. }
  350. }
  351. }
  352. // It only makes sense to distribute answers to OStatus messages to Friendica and OStatus - but not Diaspora
  353. $sql_extra = " AND `network` IN ('".NETWORK_OSTATUS."', '".NETWORK_DFRN."')";
  354. } else {
  355. $sql_extra = " AND `network` IN ('".NETWORK_OSTATUS."', '".NETWORK_DFRN."', '".NETWORK_DIASPORA."', '".NETWORK_MAIL."', '".NETWORK_MAIL2."')";
  356. }
  357. } else {
  358. $public_message = false;
  359. }
  360. // If this is a public message and pubmail is set on the parent, include all your email contacts
  361. $mail_disabled = ((function_exists('imap_open') && (! get_config('system','imap_disabled'))) ? 0 : 1);
  362. if (! $mail_disabled) {
  363. if ((! strlen($target_item['allow_cid'])) && (! strlen($target_item['allow_gid']))
  364. && (! strlen($target_item['deny_cid'])) && (! strlen($target_item['deny_gid']))
  365. && (intval($target_item['pubmail']))) {
  366. $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s'",
  367. intval($uid),
  368. dbesc(NETWORK_MAIL)
  369. );
  370. if (dbm::is_result($r)) {
  371. foreach ($r as $rr) {
  372. $recipients[] = $rr['id'];
  373. }
  374. }
  375. }
  376. }
  377. if ($followup) {
  378. $recip_str = implode(', ', $recipients_followup);
  379. } else {
  380. $recip_str = implode(', ', $recipients);
  381. }
  382. if ($relocate) {
  383. $r = $recipients_relocate;
  384. } else {
  385. $r = q("SELECT `id`, `url`, `network`, `self` FROM `contact`
  386. WHERE `id` IN (%s) AND NOT `blocked` AND NOT `pending` AND NOT `archive`".$sql_extra,
  387. dbesc($recip_str)
  388. );
  389. }
  390. // delivery loop
  391. if (dbm::is_result($r)) {
  392. foreach ($r as $contact) {
  393. if ($contact['self']) {
  394. continue;
  395. }
  396. logger("Deliver ".$target_item["guid"]." to ".$contact['url']." via network ".$contact['network'], LOGGER_DEBUG);
  397. proc_run(PRIORITY_HIGH,'include/delivery.php', $cmd, $item_id, $contact['id']);
  398. }
  399. }
  400. // send salmon slaps to mentioned remote tags (@foo@example.com) in OStatus posts
  401. // They are especially used for notifications to OStatus users that don't follow us.
  402. if ($slap && count($url_recipients) && ($public_message || $push_notify) && $normal_mode) {
  403. if (!get_config('system','dfrn_only')) {
  404. foreach ($url_recipients as $url) {
  405. if ($url) {
  406. logger('notifier: urldelivery: ' . $url);
  407. $deliver_status = slapper($owner,$url,$slap);
  408. /// @TODO Redeliver/queue these items on failure, though there is no contact record
  409. }
  410. }
  411. }
  412. }
  413. if ($public_message) {
  414. $r0 = array();
  415. $r1 = array();
  416. if ($diaspora_delivery) {
  417. if (!$followup) {
  418. $r0 = Diaspora::relay_list();
  419. }
  420. $r1 = q("SELECT `batch`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`name`) AS `name`, ANY_VALUE(`network`) AS `network`
  421. FROM `contact` WHERE `network` = '%s'
  422. AND `uid` = %d AND `rel` != %d AND NOT `blocked` AND NOT `pending` AND NOT `archive` GROUP BY `batch` ORDER BY rand()",
  423. dbesc(NETWORK_DIASPORA),
  424. intval($owner['uid']),
  425. intval(CONTACT_IS_SHARING)
  426. );
  427. }
  428. $r2 = q("SELECT `id`, `name`,`network` FROM `contact`
  429. WHERE `network` in ( '%s', '%s') AND `uid` = %d AND NOT `blocked` AND NOT `pending` AND NOT `archive`
  430. AND `rel` != %d order by rand() ",
  431. dbesc(NETWORK_DFRN),
  432. dbesc(NETWORK_MAIL2),
  433. intval($owner['uid']),
  434. intval(CONTACT_IS_SHARING)
  435. );
  436. $r = array_merge($r2,$r1,$r0);
  437. if (dbm::is_result($r)) {
  438. logger('pubdeliver '.$target_item["guid"].': '.print_r($r,true), LOGGER_DEBUG);
  439. foreach ($r as $rr) {
  440. // except for Diaspora batch jobs
  441. // Don't deliver to folks who have already been delivered to
  442. if (($rr['network'] !== NETWORK_DIASPORA) && (in_array($rr['id'],$conversants))) {
  443. logger('notifier: already delivered id=' . $rr['id']);
  444. continue;
  445. }
  446. if ((! $mail) && (! $fsuggest) && (! $followup)) {
  447. logger('notifier: delivery agent: '.$rr['name'].' '.$rr['id'].' '.$rr['network'].' '.$target_item["guid"]);
  448. proc_run(PRIORITY_HIGH,'include/delivery.php',$cmd,$item_id,$rr['id']);
  449. }
  450. }
  451. }
  452. $push_notify = true;
  453. }
  454. // Notify PuSH subscribers (Used for OStatus distribution of regular posts)
  455. if ($push_notify AND strlen($hub)) {
  456. $hubs = explode(',', $hub);
  457. if (count($hubs)) {
  458. foreach ($hubs as $h) {
  459. $h = trim($h);
  460. if (! strlen($h)) {
  461. continue;
  462. }
  463. if ($h === '[internal]') {
  464. // Set push flag for PuSH subscribers to this topic,
  465. // they will be notified in queue.php
  466. q("UPDATE `push_subscriber` SET `push` = 1 ".
  467. "WHERE `nickname` = '%s' AND `push` = 0", dbesc($owner['nickname']));
  468. logger('Activating internal PuSH for item '.$item_id, LOGGER_DEBUG);
  469. } else {
  470. $params = 'hub.mode=publish&hub.url=' . urlencode( App::get_baseurl() . '/dfrn_poll/' . $owner['nickname'] );
  471. post_url($h,$params);
  472. logger('publish for item '.$item_id.' ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code());
  473. }
  474. if (count($hubs) > 1) {
  475. sleep(7); // try and avoid multiple hubs responding at precisely the same time
  476. }
  477. }
  478. }
  479. // Handling the pubsubhubbub requests
  480. proc_run(PRIORITY_HIGH,'include/pubsubpublish.php');
  481. }
  482. logger('notifier: calling hooks', LOGGER_DEBUG);
  483. if ($normal_mode) {
  484. call_hooks('notifier_normal',$target_item);
  485. }
  486. call_hooks('notifier_end',$target_item);
  487. return;
  488. }