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.
 
 
 
 
 
 

543 lines
16 KiB

  1. <?php
  2. /**
  3. * @file include/delivery.php
  4. */
  5. use Friendica\App;
  6. use Friendica\Core\System;
  7. use Friendica\Core\Config;
  8. use Friendica\Database\DBM;
  9. use Friendica\Protocol\Diaspora;
  10. use Friendica\Protocol\DFRN;
  11. require_once 'include/queue_fn.php';
  12. require_once 'include/html2plain.php';
  13. function delivery_run(&$argv, &$argc){
  14. global $a;
  15. require_once 'include/datetime.php';
  16. require_once 'include/items.php';
  17. require_once 'include/bbcode.php';
  18. require_once 'include/email.php';
  19. if ($argc < 3) {
  20. return;
  21. }
  22. logger('delivery: invoked: '. print_r($argv,true), LOGGER_DEBUG);
  23. $cmd = $argv[1];
  24. $item_id = intval($argv[2]);
  25. for ($x = 3; $x < $argc; $x ++) {
  26. $contact_id = intval($argv[$x]);
  27. if (!$item_id || !$contact_id) {
  28. continue;
  29. }
  30. $expire = false;
  31. $mail = false;
  32. $fsuggest = false;
  33. $relocate = false;
  34. $top_level = false;
  35. $recipients = array();
  36. $url_recipients = array();
  37. $followup = false;
  38. $normal_mode = true;
  39. $recipients[] = $contact_id;
  40. if ($cmd === 'mail') {
  41. $normal_mode = false;
  42. $mail = true;
  43. $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1",
  44. intval($item_id)
  45. );
  46. if (!count($message)) {
  47. return;
  48. }
  49. $uid = $message[0]['uid'];
  50. $recipients[] = $message[0]['contact-id'];
  51. $item = $message[0];
  52. } elseif ($cmd === 'expire') {
  53. $normal_mode = false;
  54. $expire = true;
  55. $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1
  56. AND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 30 MINUTE",
  57. intval($item_id)
  58. );
  59. $uid = $item_id;
  60. $item_id = 0;
  61. if (!count($items)) {
  62. continue;
  63. }
  64. } elseif ($cmd === 'suggest') {
  65. $normal_mode = false;
  66. $fsuggest = true;
  67. $suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1",
  68. intval($item_id)
  69. );
  70. if (!count($suggest)) {
  71. return;
  72. }
  73. $uid = $suggest[0]['uid'];
  74. $recipients[] = $suggest[0]['cid'];
  75. $item = $suggest[0];
  76. } elseif ($cmd === 'relocate') {
  77. $normal_mode = false;
  78. $relocate = true;
  79. $uid = $item_id;
  80. } else {
  81. // find ancestors
  82. $r = q("SELECT * FROM `item` WHERE `id` = %d AND visible = 1 AND moderated = 0 LIMIT 1",
  83. intval($item_id)
  84. );
  85. if ((!DBM::is_result($r)) || (!intval($r[0]['parent']))) {
  86. continue;
  87. }
  88. $target_item = $r[0];
  89. $parent_id = intval($r[0]['parent']);
  90. $uid = $r[0]['uid'];
  91. $updated = $r[0]['edited'];
  92. $items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer`
  93. FROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d AND visible = 1 AND moderated = 0 ORDER BY `id` ASC",
  94. intval($parent_id)
  95. );
  96. if (!count($items)) {
  97. continue;
  98. }
  99. $icontacts = null;
  100. $contacts_arr = array();
  101. foreach ($items as $item) {
  102. if (!in_array($item['contact-id'],$contacts_arr)) {
  103. $contacts_arr[] = intval($item['contact-id']);
  104. }
  105. }
  106. if (count($contacts_arr)) {
  107. $str_contacts = implode(',',$contacts_arr);
  108. $icontacts = q("SELECT * FROM `contact`
  109. WHERE `id` IN ( $str_contacts ) "
  110. );
  111. }
  112. if ( !($icontacts && count($icontacts))) {
  113. continue;
  114. }
  115. // avoid race condition with deleting entries
  116. if ($items[0]['deleted']) {
  117. foreach ($items as $item) {
  118. $item['deleted'] = 1;
  119. }
  120. }
  121. // When commenting too fast after delivery, a post wasn't recognized as top level post.
  122. // The count then showed more than one entry. The additional check should help.
  123. // The check for the "count" should be superfluous, but I'm not totally sure by now, so we keep it.
  124. if ((($items[0]['id'] == $item_id) || (count($items) == 1)) && ($items[0]['uri'] === $items[0]['parent-uri'])) {
  125. logger('delivery: top level post');
  126. $top_level = true;
  127. }
  128. }
  129. $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
  130. `user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
  131. `user`.`page-flags`, `user`.`account-type`, `user`.`prvnets`
  132. FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
  133. WHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1",
  134. intval($uid)
  135. );
  136. if (!DBM::is_result($r)) {
  137. continue;
  138. }
  139. $owner = $r[0];
  140. $walltowall = ((($top_level) && ($owner['id'] != $items[0]['contact-id'])) ? true : false);
  141. $public_message = true;
  142. if (!($mail || $fsuggest || $relocate)) {
  143. require_once 'include/group.php';
  144. $parent = $items[0];
  145. // This is IMPORTANT!!!!
  146. // We will only send a "notify owner to relay" or followup message if the referenced post
  147. // originated on our system by virtue of having our hostname somewhere
  148. // in the URI, AND it was a comment (not top_level) AND the parent originated elsewhere.
  149. // if $parent['wall'] == 1 we will already have the parent message in our array
  150. // and we will relay the whole lot.
  151. // expire sends an entire group of expire messages and cannot be forwarded.
  152. // However the conversation owner will be a part of the conversation and will
  153. // be notified during this run.
  154. // Other DFRN conversation members will be alerted during polled updates.
  155. // Diaspora members currently are not notified of expirations, and other networks have
  156. // either limited or no ability to process deletions. We should at least fix Diaspora
  157. // by stringing togther an array of retractions and sending them onward.
  158. $localhost = $a->get_hostname();
  159. if (strpos($localhost,':')) {
  160. $localhost = substr($localhost,0,strpos($localhost,':'));
  161. }
  162. /**
  163. *
  164. * Be VERY CAREFUL if you make any changes to the following line. Seemingly innocuous changes
  165. * have been known to cause runaway conditions which affected several servers, along with
  166. * permissions issues.
  167. *
  168. */
  169. $relay_to_owner = false;
  170. if (!$top_level && ($parent['wall'] == 0) && !$expire && stristr($target_item['uri'],$localhost)) {
  171. $relay_to_owner = true;
  172. }
  173. if ($relay_to_owner) {
  174. logger('followup '.$target_item["guid"], LOGGER_DEBUG);
  175. // local followup to remote post
  176. $followup = true;
  177. }
  178. if ((strlen($parent['allow_cid']))
  179. || (strlen($parent['allow_gid']))
  180. || (strlen($parent['deny_cid']))
  181. || (strlen($parent['deny_gid']))
  182. || $parent["private"]) {
  183. $public_message = false; // private recipients, not public
  184. }
  185. }
  186. $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `blocked` = 0 AND `pending` = 0",
  187. intval($contact_id)
  188. );
  189. if (DBM::is_result($r)) {
  190. $contact = $r[0];
  191. }
  192. if ($contact['self']) {
  193. continue;
  194. }
  195. $deliver_status = 0;
  196. logger("main delivery by delivery: followup=$followup mail=$mail fsuggest=$fsuggest relocate=$relocate - network ".$contact['network']);
  197. switch($contact['network']) {
  198. case NETWORK_DFRN:
  199. logger('notifier: '.$target_item["guid"].' dfrndelivery: '.$contact['name']);
  200. if ($mail) {
  201. $item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
  202. $atom = DFRN::mail($item, $owner);
  203. } elseif ($fsuggest) {
  204. $atom = DFRN::fsuggest($item, $owner);
  205. q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
  206. } elseif ($relocate) {
  207. $atom = DFRN::relocate($owner, $uid);
  208. } elseif ($followup) {
  209. $msgitems = array();
  210. foreach ($items as $item) { // there is only one item
  211. if (!$item['parent']) {
  212. continue;
  213. }
  214. if ($item['id'] == $item_id) {
  215. logger('followup: item: '. print_r($item,true), LOGGER_DATA);
  216. $msgitems[] = $item;
  217. }
  218. }
  219. $atom = DFRN::entries($msgitems,$owner);
  220. } else {
  221. $msgitems = array();
  222. foreach ($items as $item) {
  223. if (!$item['parent']) {
  224. continue;
  225. }
  226. // private emails may be in included in public conversations. Filter them.
  227. if ($public_message && $item['private']) {
  228. continue;
  229. }
  230. $item_contact = get_item_contact($item,$icontacts);
  231. if (!$item_contact) {
  232. continue;
  233. }
  234. if ($normal_mode) {
  235. if ($item_id == $item['id'] || $item['id'] == $item['parent']) {
  236. $item["entry:comment-allow"] = true;
  237. $item["entry:cid"] = (($top_level) ? $contact['id'] : 0);
  238. $msgitems[] = $item;
  239. }
  240. } else {
  241. $item["entry:comment-allow"] = true;
  242. $msgitems[] = $item;
  243. }
  244. }
  245. $atom = DFRN::entries($msgitems,$owner);
  246. }
  247. logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
  248. logger('notifier: '.$atom, LOGGER_DATA);
  249. $basepath = implode('/', array_slice(explode('/',$contact['url']),0,3));
  250. // perform local delivery if we are on the same site
  251. if (link_compare($basepath,System::baseUrl())) {
  252. $nickname = basename($contact['url']);
  253. if ($contact['issued-id']) {
  254. $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id']));
  255. } else {
  256. $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id']));
  257. }
  258. $x = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
  259. `contact`.`pubkey` AS `cpubkey`,
  260. `contact`.`prvkey` AS `cprvkey`,
  261. `contact`.`thumb` AS `thumb`,
  262. `contact`.`url` as `url`,
  263. `contact`.`name` as `senderName`,
  264. `user`.*
  265. FROM `contact`
  266. INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`
  267. WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  268. AND `contact`.`network` = '%s' AND `user`.`nickname` = '%s'
  269. $sql_extra
  270. AND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 LIMIT 1",
  271. dbesc(NETWORK_DFRN),
  272. dbesc($nickname)
  273. );
  274. if ($x && count($x)) {
  275. $write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false);
  276. if ((($owner['page-flags'] == PAGE_COMMUNITY) || $write_flag) && !$x[0]['writable']) {
  277. q("UPDATE `contact` SET `writable` = 1 WHERE `id` = %d",
  278. intval($x[0]['id'])
  279. );
  280. $x[0]['writable'] = 1;
  281. }
  282. $ssl_policy = Config::get('system','ssl_policy');
  283. fix_contact_ssl_policy($x[0],$ssl_policy);
  284. // If we are setup as a soapbox we aren't accepting top level posts from this person
  285. if (($x[0]['page-flags'] == PAGE_SOAPBOX) && $top_level) {
  286. break;
  287. }
  288. logger('mod-delivery: local delivery');
  289. DFRN::import($atom, $x[0]);
  290. break;
  291. }
  292. }
  293. if (!was_recently_delayed($contact['id'])) {
  294. $deliver_status = DFRN::deliver($owner,$contact,$atom);
  295. } else {
  296. $deliver_status = (-1);
  297. }
  298. logger('notifier: dfrn_delivery to '.$contact["url"].' with guid '.$target_item["guid"].' returns '.$deliver_status);
  299. if ($deliver_status < 0) {
  300. logger('notifier: delivery failed: queuing message');
  301. add_to_queue($contact['id'],NETWORK_DFRN,$atom);
  302. // The message could not be delivered. We mark the contact as "dead"
  303. mark_for_death($contact);
  304. } else {
  305. // We successfully delivered a message, the contact is alive
  306. unmark_for_death($contact);
  307. }
  308. break;
  309. case NETWORK_OSTATUS:
  310. // Do not send to otatus if we are not configured to send to public networks
  311. if ($owner['prvnets']) {
  312. break;
  313. }
  314. if (Config::get('system','ostatus_disabled') || Config::get('system','dfrn_only')) {
  315. break;
  316. }
  317. // There is currently no code here to distribute anything to OStatus.
  318. // This is done in "notifier.php" (See "url_recipients" and "push_notify")
  319. break;
  320. case NETWORK_MAIL:
  321. case NETWORK_MAIL2:
  322. if (Config::get('system','dfrn_only')) {
  323. break;
  324. }
  325. // WARNING: does not currently convert to RFC2047 header encodings, etc.
  326. $addr = $contact['addr'];
  327. if (!strlen($addr)) {
  328. break;
  329. }
  330. if ($cmd === 'wall-new' || $cmd === 'comment-new') {
  331. $it = null;
  332. if ($cmd === 'wall-new') {
  333. $it = $items[0];
  334. } else {
  335. $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  336. intval($argv[2]),
  337. intval($uid)
  338. );
  339. if (DBM::is_result($r))
  340. $it = $r[0];
  341. }
  342. if (!$it)
  343. break;
  344. $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
  345. intval($uid)
  346. );
  347. if (!count($local_user))
  348. break;
  349. $reply_to = '';
  350. $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1",
  351. intval($uid)
  352. );
  353. if ($r1 && $r1[0]['reply_to'])
  354. $reply_to = $r1[0]['reply_to'];
  355. $subject = (($it['title']) ? email_header_encode($it['title'],'UTF-8') : t("\x28no subject\x29")) ;
  356. // only expose our real email address to true friends
  357. if (($contact['rel'] == CONTACT_IS_FRIEND) && !$contact['blocked']) {
  358. if ($reply_to) {
  359. $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$reply_to.'>'."\n";
  360. $headers .= 'Sender: '.$local_user[0]['email']."\n";
  361. } else {
  362. $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$local_user[0]['email'].'>'."\n";
  363. }
  364. } else {
  365. $headers = 'From: '. email_header_encode($local_user[0]['username'],'UTF-8') .' <'. t('noreply') .'@'.$a->get_hostname() .'>'. "\n";
  366. }
  367. //if ($reply_to)
  368. // $headers .= 'Reply-to: '.$reply_to . "\n";
  369. $headers .= 'Message-Id: <'. iri2msgid($it['uri']).'>'. "\n";
  370. //logger("Mail: uri: ".$it['uri']." parent-uri ".$it['parent-uri'], LOGGER_DEBUG);
  371. //logger("Mail: Data: ".print_r($it, true), LOGGER_DEBUG);
  372. //logger("Mail: Data: ".print_r($it, true), LOGGER_DATA);
  373. if ($it['uri'] !== $it['parent-uri']) {
  374. $headers .= "References: <".iri2msgid($it["parent-uri"]).">";
  375. // If Threading is enabled, write down the correct parent
  376. if (($it["thr-parent"] != "") && ($it["thr-parent"] != $it["parent-uri"]))
  377. $headers .= " <".iri2msgid($it["thr-parent"]).">";
  378. $headers .= "\n";
  379. if (!$it['title']) {
  380. $r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  381. dbesc($it['parent-uri']),
  382. intval($uid));
  383. if (DBM::is_result($r) && ($r[0]['title'] != '')) {
  384. $subject = $r[0]['title'];
  385. } else {
  386. $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1",
  387. dbesc($it['parent-uri']),
  388. intval($uid));
  389. if (DBM::is_result($r) && ($r[0]['title'] != ''))
  390. $subject = $r[0]['title'];
  391. }
  392. }
  393. if (strncasecmp($subject,'RE:',3))
  394. $subject = 'Re: '.$subject;
  395. }
  396. email_send($addr, $subject, $headers, $it);
  397. }
  398. break;
  399. case NETWORK_DIASPORA:
  400. if ($public_message)
  401. $loc = 'public batch '.$contact['batch'];
  402. else
  403. $loc = $contact['name'];
  404. logger('delivery: diaspora batch deliver: '.$loc);
  405. if (Config::get('system','dfrn_only') || (!Config::get('system','diaspora_enabled')))
  406. break;
  407. if ($mail) {
  408. Diaspora::send_mail($item,$owner,$contact);
  409. break;
  410. }
  411. if (!$normal_mode)
  412. break;
  413. if (!$contact['pubkey'] && !$public_message)
  414. break;
  415. if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) {
  416. // top-level retraction
  417. logger('diaspora retract: '.$loc);
  418. Diaspora::send_retraction($target_item,$owner,$contact,$public_message);
  419. break;
  420. } elseif ($relocate) {
  421. Diaspora::sendAccountMigration($owner, $contact, $uid);
  422. break;
  423. } elseif ($followup) {
  424. // send comments and likes to owner to relay
  425. logger('diaspora followup: '.$loc);
  426. Diaspora::send_followup($target_item,$owner,$contact,$public_message);
  427. break;
  428. } elseif ($target_item['uri'] !== $target_item['parent-uri']) {
  429. // we are the relay - send comments, likes and relayable_retractions to our conversants
  430. logger('diaspora relay: '.$loc);
  431. Diaspora::send_relay($target_item,$owner,$contact,$public_message);
  432. break;
  433. } elseif ($top_level && !$walltowall) {
  434. // currently no workable solution for sending walltowall
  435. logger('diaspora status: '.$loc);
  436. Diaspora::send_status($target_item,$owner,$contact,$public_message);
  437. break;
  438. }
  439. logger('delivery: diaspora unknown mode: '.$contact['name']);
  440. break;
  441. default:
  442. break;
  443. }
  444. }
  445. return;
  446. }