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.
 
 
 
 
 
 

5263 lines
173 KiB

  1. <?php
  2. require_once('include/bbcode.php');
  3. require_once('include/oembed.php');
  4. require_once('include/salmon.php');
  5. require_once('include/crypto.php');
  6. require_once('include/Photo.php');
  7. require_once('include/tags.php');
  8. require_once('include/files.php');
  9. require_once('include/text.php');
  10. require_once('include/email.php');
  11. require_once('include/threads.php');
  12. require_once('include/socgraph.php');
  13. require_once('include/plaintext.php');
  14. require_once('include/ostatus.php');
  15. require_once('include/feed.php');
  16. require_once('mod/share.php');
  17. require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
  18. function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
  19. $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
  20. $public_feed = (($dfrn_id) ? false : true);
  21. $starred = false; // not yet implemented, possible security issues
  22. $converse = false;
  23. if($public_feed && $a->argc > 2) {
  24. for($x = 2; $x < $a->argc; $x++) {
  25. if($a->argv[$x] == 'converse')
  26. $converse = true;
  27. if($a->argv[$x] == 'starred')
  28. $starred = true;
  29. if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
  30. $category = $a->argv[$x+1];
  31. }
  32. }
  33. // default permissions - anonymous user
  34. $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
  35. $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
  36. FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
  37. WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
  38. dbesc($owner_nick)
  39. );
  40. if(! count($r))
  41. killme();
  42. $owner = $r[0];
  43. $owner_id = $owner['user_uid'];
  44. $owner_nick = $owner['nickname'];
  45. $birthday = feed_birthday($owner_id,$owner['timezone']);
  46. $sql_post_table = "";
  47. $visibility = "";
  48. if(! $public_feed) {
  49. $sql_extra = '';
  50. switch($direction) {
  51. case (-1):
  52. $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
  53. $my_id = $dfrn_id;
  54. break;
  55. case 0:
  56. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  57. $my_id = '1:' . $dfrn_id;
  58. break;
  59. case 1:
  60. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  61. $my_id = '0:' . $dfrn_id;
  62. break;
  63. default:
  64. return false;
  65. break; // NOTREACHED
  66. }
  67. $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
  68. intval($owner_id)
  69. );
  70. if(! count($r))
  71. killme();
  72. $contact = $r[0];
  73. require_once('include/security.php');
  74. $groups = init_groups_visitor($contact['id']);
  75. if(count($groups)) {
  76. for($x = 0; $x < count($groups); $x ++)
  77. $groups[$x] = '<' . intval($groups[$x]) . '>' ;
  78. $gs = implode('|', $groups);
  79. }
  80. else
  81. $gs = '<<>>' ; // Impossible to match
  82. $sql_extra = sprintf("
  83. AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
  84. AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
  85. AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
  86. AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
  87. ",
  88. intval($contact['id']),
  89. intval($contact['id']),
  90. dbesc($gs),
  91. dbesc($gs)
  92. );
  93. }
  94. if($public_feed)
  95. $sort = 'DESC';
  96. else
  97. $sort = 'ASC';
  98. // Include answers to status.net posts in pubsub feeds
  99. if($forpubsub) {
  100. $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
  101. LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
  102. $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
  103. dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
  104. $date_field = "`received`";
  105. $sql_order = "`item`.`received` DESC";
  106. } else {
  107. $date_field = "`changed`";
  108. $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
  109. }
  110. if(! strlen($last_update))
  111. $last_update = 'now -30 days';
  112. if(isset($category)) {
  113. $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
  114. dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
  115. //$sql_extra .= file_tag_file_query('item',$category,'category');
  116. }
  117. if($public_feed) {
  118. if(! $converse)
  119. $sql_extra .= " AND `contact`.`self` = 1 ";
  120. }
  121. $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
  122. // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
  123. // dbesc($check_date),
  124. $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
  125. `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
  126. `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
  127. `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  128. `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
  129. `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
  130. FROM `item` $sql_post_table
  131. INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  132. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  133. LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
  134. WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
  135. AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
  136. $sql_extra
  137. ORDER BY $sql_order LIMIT 0, 300",
  138. intval($owner_id),
  139. dbesc($check_date),
  140. dbesc($sort)
  141. );
  142. // Will check further below if this actually returned results.
  143. // We will provide an empty feed if that is the case.
  144. $items = $r;
  145. $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
  146. $atom = '';
  147. $hubxml = feed_hublinks();
  148. $salmon = feed_salmonlinks($owner_nick);
  149. $alternatelink = $owner['url'];
  150. if(isset($category))
  151. $alternatelink .= "/category/".$category;
  152. $atom .= replace_macros($feed_template, array(
  153. '$version' => xmlify(FRIENDICA_VERSION),
  154. '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
  155. '$feed_title' => xmlify($owner['name']),
  156. '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
  157. '$hub' => $hubxml,
  158. '$salmon' => $salmon,
  159. '$alternatelink' => xmlify($alternatelink),
  160. '$name' => xmlify($owner['name']),
  161. '$profile_page' => xmlify($owner['url']),
  162. '$photo' => xmlify($owner['photo']),
  163. '$thumb' => xmlify($owner['thumb']),
  164. '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
  165. '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
  166. '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
  167. '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
  168. '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
  169. ));
  170. call_hooks('atom_feed', $atom);
  171. if(! count($items)) {
  172. call_hooks('atom_feed_end', $atom);
  173. $atom .= '</feed>' . "\r\n";
  174. return $atom;
  175. }
  176. foreach($items as $item) {
  177. // prevent private email from leaking.
  178. if($item['network'] === NETWORK_MAIL)
  179. continue;
  180. // public feeds get html, our own nodes use bbcode
  181. if($public_feed) {
  182. $type = 'html';
  183. // catch any email that's in a public conversation and make sure it doesn't leak
  184. if($item['private'])
  185. continue;
  186. }
  187. else {
  188. $type = 'text';
  189. }
  190. $atom .= atom_entry($item,$type,null,$owner,true);
  191. }
  192. call_hooks('atom_feed_end', $atom);
  193. $atom .= '</feed>' . "\r\n";
  194. return $atom;
  195. }
  196. function construct_verb($item) {
  197. if($item['verb'])
  198. return $item['verb'];
  199. return ACTIVITY_POST;
  200. }
  201. function construct_activity_object($item) {
  202. if($item['object']) {
  203. $o = '<as:object>' . "\r\n";
  204. $r = parse_xml_string($item['object'],false);
  205. if(! $r)
  206. return '';
  207. if($r->type)
  208. $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
  209. if($r->id)
  210. $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
  211. if($r->title)
  212. $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
  213. if($r->link) {
  214. if(substr($r->link,0,1) === '<') {
  215. // patch up some facebook "like" activity objects that got stored incorrectly
  216. // for a couple of months prior to 9-Jun-2011 and generated bad XML.
  217. // we can probably remove this hack here and in the following function in a few months time.
  218. if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
  219. $r->link = str_replace('&','&amp;', $r->link);
  220. $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
  221. $o .= $r->link;
  222. }
  223. else
  224. $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
  225. }
  226. if($r->content)
  227. $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
  228. $o .= '</as:object>' . "\r\n";
  229. return $o;
  230. }
  231. return '';
  232. }
  233. function construct_activity_target($item) {
  234. if($item['target']) {
  235. $o = '<as:target>' . "\r\n";
  236. $r = parse_xml_string($item['target'],false);
  237. if(! $r)
  238. return '';
  239. if($r->type)
  240. $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
  241. if($r->id)
  242. $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
  243. if($r->title)
  244. $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
  245. if($r->link) {
  246. if(substr($r->link,0,1) === '<') {
  247. if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
  248. $r->link = str_replace('&','&amp;', $r->link);
  249. $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
  250. $o .= $r->link;
  251. }
  252. else
  253. $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
  254. }
  255. if($r->content)
  256. $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
  257. $o .= '</as:target>' . "\r\n";
  258. return $o;
  259. }
  260. return '';
  261. }
  262. /* limit_body_size()
  263. *
  264. * The purpose of this function is to apply system message length limits to
  265. * imported messages without including any embedded photos in the length
  266. */
  267. if(! function_exists('limit_body_size')) {
  268. function limit_body_size($body) {
  269. // logger('limit_body_size: start', LOGGER_DEBUG);
  270. $maxlen = get_max_import_size();
  271. // If the length of the body, including the embedded images, is smaller
  272. // than the maximum, then don't waste time looking for the images
  273. if($maxlen && (strlen($body) > $maxlen)) {
  274. logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
  275. $orig_body = $body;
  276. $new_body = '';
  277. $textlen = 0;
  278. $max_found = false;
  279. $img_start = strpos($orig_body, '[img');
  280. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  281. $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
  282. while(($img_st_close !== false) && ($img_end !== false)) {
  283. $img_st_close++; // make it point to AFTER the closing bracket
  284. $img_end += $img_start;
  285. $img_end += strlen('[/img]');
  286. if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
  287. // This is an embedded image
  288. if( ($textlen + $img_start) > $maxlen ) {
  289. if($textlen < $maxlen) {
  290. logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
  291. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  292. $textlen = $maxlen;
  293. }
  294. }
  295. else {
  296. $new_body = $new_body . substr($orig_body, 0, $img_start);
  297. $textlen += $img_start;
  298. }
  299. $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
  300. }
  301. else {
  302. if( ($textlen + $img_end) > $maxlen ) {
  303. if($textlen < $maxlen) {
  304. logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
  305. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  306. $textlen = $maxlen;
  307. }
  308. }
  309. else {
  310. $new_body = $new_body . substr($orig_body, 0, $img_end);
  311. $textlen += $img_end;
  312. }
  313. }
  314. $orig_body = substr($orig_body, $img_end);
  315. if($orig_body === false) // in case the body ends on a closing image tag
  316. $orig_body = '';
  317. $img_start = strpos($orig_body, '[img');
  318. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  319. $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
  320. }
  321. if( ($textlen + strlen($orig_body)) > $maxlen) {
  322. if($textlen < $maxlen) {
  323. logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
  324. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  325. $textlen = $maxlen;
  326. }
  327. }
  328. else {
  329. logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
  330. $new_body = $new_body . $orig_body;
  331. $textlen += strlen($orig_body);
  332. }
  333. return $new_body;
  334. }
  335. else
  336. return $body;
  337. }}
  338. function title_is_body($title, $body) {
  339. $title = strip_tags($title);
  340. $title = trim($title);
  341. $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
  342. $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
  343. $body = strip_tags($body);
  344. $body = trim($body);
  345. $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
  346. $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
  347. if (strlen($title) < strlen($body))
  348. $body = substr($body, 0, strlen($title));
  349. if (($title != $body) and (substr($title, -3) == "...")) {
  350. $pos = strrpos($title, "...");
  351. if ($pos > 0) {
  352. $title = substr($title, 0, $pos);
  353. $body = substr($body, 0, $pos);
  354. }
  355. }
  356. return($title == $body);
  357. }
  358. function get_atom_elements($feed, $item, $contact = array()) {
  359. require_once('library/HTMLPurifier.auto.php');
  360. require_once('include/html2bbcode.php');
  361. $best_photo = array();
  362. $res = array();
  363. $author = $item->get_author();
  364. if($author) {
  365. $res['author-name'] = unxmlify($author->get_name());
  366. $res['author-link'] = unxmlify($author->get_link());
  367. }
  368. else {
  369. $res['author-name'] = unxmlify($feed->get_title());
  370. $res['author-link'] = unxmlify($feed->get_permalink());
  371. }
  372. $res['uri'] = unxmlify($item->get_id());
  373. $res['title'] = unxmlify($item->get_title());
  374. $res['body'] = unxmlify($item->get_content());
  375. $res['plink'] = unxmlify($item->get_link(0));
  376. if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
  377. logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
  378. $res['title'] = "";
  379. $res['body'] = nl2br($res['body']);
  380. }
  381. // removing the content of the title if its identically to the body
  382. // This helps with auto generated titles e.g. from tumblr
  383. if (title_is_body($res["title"], $res["body"]))
  384. $res['title'] = "";
  385. if($res['plink'])
  386. $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
  387. else
  388. $base_url = '';
  389. // look for a photo. We should check media size and find the best one,
  390. // but for now let's just find any author photo
  391. // Additionally we look for an alternate author link. On OStatus this one is the one we want.
  392. $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
  393. if (is_array($authorlinks)) {
  394. foreach ($authorlinks as $link) {
  395. $linkdata = array_shift($link["attribs"]);
  396. if ($linkdata["rel"] == "alternate")
  397. $res["author-link"] = $linkdata["href"];
  398. };
  399. }
  400. $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
  401. if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
  402. $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
  403. foreach($base as $link) {
  404. if($link['attribs']['']['rel'] === 'alternate')
  405. $res['author-link'] = unxmlify($link['attribs']['']['href']);
  406. if(!x($res, 'author-avatar') || !$res['author-avatar']) {
  407. if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
  408. $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
  409. }
  410. }
  411. }
  412. $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
  413. if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
  414. $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
  415. if($base && count($base)) {
  416. foreach($base as $link) {
  417. if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
  418. $res['author-link'] = unxmlify($link['attribs']['']['href']);
  419. if(!x($res, 'author-avatar') || !$res['author-avatar']) {
  420. if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
  421. $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
  422. }
  423. }
  424. }
  425. }
  426. // No photo/profile-link on the item - look at the feed level
  427. if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
  428. $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
  429. if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
  430. $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
  431. foreach($base as $link) {
  432. if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
  433. $res['author-link'] = unxmlify($link['attribs']['']['href']);
  434. if(! $res['author-avatar']) {
  435. if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
  436. $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
  437. }
  438. }
  439. }
  440. $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
  441. if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
  442. $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
  443. if($base && count($base)) {
  444. foreach($base as $link) {
  445. if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
  446. $res['author-link'] = unxmlify($link['attribs']['']['href']);
  447. if(! (x($res,'author-avatar'))) {
  448. if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
  449. $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
  450. }
  451. }
  452. }
  453. }
  454. }
  455. $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
  456. if($apps && $apps[0]['attribs']['']['source']) {
  457. $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
  458. if($res['app'] === 'web')
  459. $res['app'] = 'OStatus';
  460. }
  461. // base64 encoded json structure representing Diaspora signature
  462. $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
  463. if($dsig) {
  464. $res['dsprsig'] = unxmlify($dsig[0]['data']);
  465. }
  466. $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
  467. if($dguid)
  468. $res['guid'] = unxmlify($dguid[0]['data']);
  469. $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
  470. if($bm)
  471. $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
  472. /**
  473. * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
  474. */
  475. $have_real_body = false;
  476. $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
  477. if($rawenv) {
  478. $have_real_body = true;
  479. $res['body'] = $rawenv[0]['data'];
  480. $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
  481. // make sure nobody is trying to sneak some html tags by us
  482. $res['body'] = notags(base64url_decode($res['body']));
  483. }
  484. $res['body'] = limit_body_size($res['body']);
  485. // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
  486. // the content type. Our own network only emits text normally, though it might have been converted to
  487. // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
  488. // have to assume it is all html and needs to be purified.
  489. // It doesn't matter all that much security wise - because before this content is used anywhere, we are
  490. // going to escape any tags we find regardless, but this lets us import a limited subset of html from
  491. // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
  492. // html.
  493. if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
  494. $res['body'] = reltoabs($res['body'],$base_url);
  495. $res['body'] = html2bb_video($res['body']);
  496. $res['body'] = oembed_html2bbcode($res['body']);
  497. $config = HTMLPurifier_Config::createDefault();
  498. $config->set('Cache.DefinitionImpl', null);
  499. // we shouldn't need a whitelist, because the bbcode converter
  500. // will strip out any unsupported tags.
  501. $purifier = new HTMLPurifier($config);
  502. $res['body'] = $purifier->purify($res['body']);
  503. $res['body'] = @html2bbcode($res['body']);
  504. }
  505. elseif(! $have_real_body) {
  506. // it's not one of our messages and it has no tags
  507. // so it's probably just text. We'll escape it just to be safe.
  508. $res['body'] = escape_tags($res['body']);
  509. }
  510. // this tag is obsolete but we keep it for really old sites
  511. $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
  512. if($allow && $allow[0]['data'] == 1)
  513. $res['last-child'] = 1;
  514. else
  515. $res['last-child'] = 0;
  516. $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
  517. if($private && intval($private[0]['data']) > 0)
  518. $res['private'] = intval($private[0]['data']);
  519. else
  520. $res['private'] = 0;
  521. $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
  522. if($extid && $extid[0]['data'])
  523. $res['extid'] = $extid[0]['data'];
  524. $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
  525. if($rawlocation)
  526. $res['location'] = unxmlify($rawlocation[0]['data']);
  527. $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
  528. if($rawcreated)
  529. $res['created'] = unxmlify($rawcreated[0]['data']);
  530. $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
  531. if($rawedited)
  532. $res['edited'] = unxmlify($rawedited[0]['data']);
  533. if((x($res,'edited')) && (! (x($res,'created'))))
  534. $res['created'] = $res['edited'];
  535. if(! $res['created'])
  536. $res['created'] = $item->get_date('c');
  537. if(! $res['edited'])
  538. $res['edited'] = $item->get_date('c');
  539. // Disallow time travelling posts
  540. $d1 = strtotime($res['created']);
  541. $d2 = strtotime($res['edited']);
  542. $d3 = strtotime('now');
  543. if($d1 > $d3)
  544. $res['created'] = datetime_convert();
  545. if($d2 > $d3)
  546. $res['edited'] = datetime_convert();
  547. $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
  548. if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
  549. $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
  550. elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
  551. $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
  552. if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
  553. $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
  554. elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
  555. $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
  556. if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
  557. $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
  558. foreach($base as $link) {
  559. if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
  560. if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
  561. $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
  562. }
  563. }
  564. }
  565. $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
  566. if($rawgeo)
  567. $res['coord'] = unxmlify($rawgeo[0]['data']);
  568. if ($contact["network"] == NETWORK_FEED) {
  569. $res['verb'] = ACTIVITY_POST;
  570. $res['object-type'] = ACTIVITY_OBJ_NOTE;
  571. }
  572. $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
  573. // select between supported verbs
  574. if($rawverb) {
  575. $res['verb'] = unxmlify($rawverb[0]['data']);
  576. }
  577. // translate OStatus unfollow to activity streams if it happened to get selected
  578. if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
  579. $res['verb'] = ACTIVITY_UNFOLLOW;
  580. $cats = $item->get_categories();
  581. if($cats) {
  582. $tag_arr = array();
  583. foreach($cats as $cat) {
  584. $term = $cat->get_term();
  585. if(! $term)
  586. $term = $cat->get_label();
  587. $scheme = $cat->get_scheme();
  588. if($scheme && $term && stristr($scheme,'X-DFRN:'))
  589. $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
  590. elseif($term)
  591. $tag_arr[] = notags(trim($term));
  592. }
  593. $res['tag'] = implode(',', $tag_arr);
  594. }
  595. $attach = $item->get_enclosures();
  596. if($attach) {
  597. $att_arr = array();
  598. foreach($attach as $att) {
  599. $len = intval($att->get_length());
  600. $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
  601. $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
  602. $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
  603. if(strpos($type,';'))
  604. $type = substr($type,0,strpos($type,';'));
  605. if((! $link) || (strpos($link,'http') !== 0))
  606. continue;
  607. if(! $title)
  608. $title = ' ';
  609. if(! $type)
  610. $type = 'application/octet-stream';
  611. $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
  612. }
  613. $res['attach'] = implode(',', $att_arr);
  614. }
  615. $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
  616. if($rawobj) {
  617. $res['object'] = '<object>' . "\n";
  618. $child = $rawobj[0]['child'];
  619. if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
  620. $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
  621. $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
  622. }
  623. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
  624. $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
  625. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
  626. $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
  627. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
  628. $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
  629. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
  630. $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
  631. if(! $body)
  632. $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
  633. // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
  634. $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
  635. if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
  636. $body = html2bb_video($body);
  637. $config = HTMLPurifier_Config::createDefault();
  638. $config->set('Cache.DefinitionImpl', null);
  639. $purifier = new HTMLPurifier($config);
  640. $body = $purifier->purify($body);
  641. $body = html2bbcode($body);
  642. }
  643. $res['object'] .= '<content>' . $body . '</content>' . "\n";
  644. }
  645. $res['object'] .= '</object>' . "\n";
  646. }
  647. $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
  648. if($rawobj) {
  649. $res['target'] = '<target>' . "\n";
  650. $child = $rawobj[0]['child'];
  651. if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
  652. $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
  653. }
  654. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
  655. $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
  656. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
  657. $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
  658. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
  659. $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
  660. if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
  661. $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
  662. if(! $body)
  663. $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
  664. // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
  665. $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
  666. if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
  667. $body = html2bb_video($body);
  668. $config = HTMLPurifier_Config::createDefault();
  669. $config->set('Cache.DefinitionImpl', null);
  670. $purifier = new HTMLPurifier($config);
  671. $body = $purifier->purify($body);
  672. $body = html2bbcode($body);
  673. }
  674. $res['target'] .= '<content>' . $body . '</content>' . "\n";
  675. }
  676. $res['target'] .= '</target>' . "\n";
  677. }
  678. // This is some experimental stuff. By now retweets are shown with "RT:"
  679. // But: There is data so that the message could be shown similar to native retweets
  680. // There is some better way to parse this array - but it didn't worked for me.
  681. $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
  682. if (is_array($child)) {
  683. logger('get_atom_elements: Looking for status.net repeated message');
  684. $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
  685. $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
  686. $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
  687. $uri = $author["uri"][0]["data"];
  688. $name = $author["name"][0]["data"];
  689. $avatar = @array_shift($author["link"][2]["attribs"]);
  690. $avatar = $avatar["href"];
  691. if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
  692. logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
  693. if (!intval(get_config('system','wall-to-wall_share'))) {
  694. $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
  695. $res["body"] = $prefix.html2bbcode($message)."[/share]";
  696. } else {
  697. $res["owner-name"] = $res["author-name"];
  698. $res["owner-link"] = $res["author-link"];
  699. $res["owner-avatar"] = $res["author-avatar"];
  700. $res["author-name"] = $name;
  701. $res["author-link"] = $uri;
  702. $res["author-avatar"] = $avatar;
  703. $res["body"] = html2bbcode($message);
  704. }
  705. }
  706. }
  707. if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
  708. $preview = "";
  709. // Handle enclosures and treat them as preview picture
  710. if (isset($attach))
  711. foreach ($attach AS $attachment)
  712. if ($attachment->type == "image/jpeg")
  713. $preview = $attachment->link;
  714. $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
  715. $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
  716. $res["title"] = "";
  717. $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
  718. unset($res["attach"]);
  719. } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
  720. $res["body"] = add_page_info_to_body($res["body"]);
  721. elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
  722. $res["body"] = add_page_info_to_body($res["body"]);
  723. }
  724. $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
  725. call_hooks('parse_atom', $arr);
  726. return $res;
  727. }
  728. function add_page_info_data($data) {
  729. call_hooks('page_info_data', $data);
  730. // It maybe is a rich content, but if it does have everything that a link has,
  731. // then treat it that way
  732. if (($data["type"] == "rich") AND is_string($data["title"]) AND
  733. is_string($data["text"]) AND (sizeof($data["images"]) > 0))
  734. $data["type"] = "link";
  735. if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
  736. return("");
  737. if ($no_photos AND ($data["type"] == "photo"))
  738. return("");
  739. // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
  740. if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
  741. require_once("include/network.php");
  742. $data["url"] = short_link($data["url"]);
  743. }
  744. if (($data["type"] != "photo") AND is_string($data["title"]))
  745. $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
  746. if (($data["type"] != "video") AND ($photo != ""))
  747. $text .= '[img]'.$photo.'[/img]';
  748. elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
  749. $imagedata = $data["images"][0];
  750. $text .= '[img]'.$imagedata["src"].'[/img]';
  751. }
  752. if (($data["type"] != "photo") AND is_string($data["text"]))
  753. $text .= "[quote]".$data["text"]."[/quote]";
  754. $hashtags = "";
  755. if (isset($data["keywords"]) AND count($data["keywords"])) {
  756. $a = get_app();
  757. $hashtags = "\n";
  758. foreach ($data["keywords"] AS $keyword) {
  759. $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
  760. array("","", "", "", "", ""), $keyword);
  761. $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
  762. }
  763. }
  764. return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
  765. }
  766. function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  767. require_once("mod/parse_url.php");
  768. $data = Cache::get("parse_url:".$url);
  769. if (is_null($data)){
  770. $data = parseurl_getsiteinfo($url, true);
  771. Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
  772. } else
  773. $data = unserialize($data);
  774. if ($photo != "")
  775. $data["images"][0]["src"] = $photo;
  776. logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
  777. if (!$keywords AND isset($data["keywords"]))
  778. unset($data["keywords"]);
  779. if (($keyword_blacklist != "") AND isset($data["keywords"])) {
  780. $list = explode(",", $keyword_blacklist);
  781. foreach ($list AS $keyword) {
  782. $keyword = trim($keyword);
  783. $index = array_search($keyword, $data["keywords"]);
  784. if ($index !== false)
  785. unset($data["keywords"][$index]);
  786. }
  787. }
  788. return($data);
  789. }
  790. function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  791. $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
  792. $tags = "";
  793. if (isset($data["keywords"]) AND count($data["keywords"])) {
  794. $a = get_app();
  795. foreach ($data["keywords"] AS $keyword) {
  796. $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
  797. array("","", "", "", "", ""), $keyword);
  798. if ($tags != "")
  799. $tags .= ",";
  800. $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
  801. }
  802. }
  803. return($tags);
  804. }
  805. function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  806. $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
  807. $text = add_page_info_data($data);
  808. return($text);
  809. }
  810. function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
  811. logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
  812. $URLSearchString = "^\[\]";
  813. // Adding these spaces is a quick hack due to my problems with regular expressions :)
  814. preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
  815. if (!$matches)
  816. preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
  817. // Convert urls without bbcode elements
  818. if (!$matches AND $texturl) {
  819. preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
  820. // Yeah, a hack. I really hate regular expressions :)
  821. if ($matches)
  822. $matches[1] = $matches[2];
  823. }
  824. if ($matches)
  825. $footer = add_page_info($matches[1], $no_photos);
  826. // Remove the link from the body if the link is attached at the end of the post
  827. if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
  828. $removedlink = trim(str_replace($matches[1], "", $body));
  829. if (($removedlink == "") OR strstr($body, $removedlink))
  830. $body = $removedlink;
  831. $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
  832. $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
  833. if (($removedlink == "") OR strstr($body, $removedlink))
  834. $body = $removedlink;
  835. }
  836. // Add the page information to the bottom
  837. if (isset($footer) AND (trim($footer) != ""))
  838. $body .= $footer;
  839. return $body;
  840. }
  841. function encode_rel_links($links) {
  842. $o = '';
  843. if(! ((is_array($links)) && (count($links))))
  844. return $o;
  845. foreach($links as $link) {
  846. $o .= '<link ';
  847. if($link['attribs']['']['rel'])
  848. $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
  849. if($link['attribs']['']['type'])
  850. $o .= 'type="' . $link['attribs']['']['type'] . '" ';
  851. if($link['attribs']['']['href'])
  852. $o .= 'href="' . $link['attribs']['']['href'] . '" ';
  853. if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
  854. $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
  855. if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
  856. $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
  857. $o .= ' />' . "\n" ;
  858. }
  859. return xmlify($o);
  860. }
  861. function add_guid($item) {
  862. $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
  863. if ($r)
  864. return;
  865. q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
  866. dbesc($item["guid"]), dbesc($item["plink"]),
  867. dbesc($item["uri"]), dbesc($item["network"]));
  868. }
  869. // Adds a "lang" specification in a "postopts" element of given $arr,
  870. // if possible and not already present.
  871. // Expects "body" element to exist in $arr.
  872. // TODO: add a parameter to request forcing override
  873. function item_add_language_opt(&$arr) {
  874. if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
  875. if ( x($arr, 'postopts') )
  876. {
  877. if ( strstr($arr['postopts'], 'lang=') )
  878. {
  879. // do not override
  880. // TODO: add parameter to request overriding
  881. return;
  882. }
  883. $postopts = $arr['postopts'];
  884. }
  885. else
  886. {
  887. $postopts = "";
  888. }
  889. require_once('library/langdet/Text/LanguageDetect.php');
  890. $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
  891. $l = new Text_LanguageDetect;
  892. //$lng = $l->detectConfidence($naked_body);
  893. //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
  894. $lng = $l->detect($naked_body, 3);
  895. if (sizeof($lng) > 0) {
  896. if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
  897. $postopts .= 'lang=';
  898. $sep = "";
  899. foreach ($lng as $language => $score) {
  900. $postopts .= $sep . $language.";".$score;
  901. $sep = ':';
  902. }
  903. $arr['postopts'] = $postopts;
  904. }
  905. }
  906. function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
  907. // If it is a posting where users should get notifications, then define it as wall posting
  908. if ($notify) {
  909. $arr['wall'] = 1;
  910. $arr['type'] = 'wall';
  911. $arr['origin'] = 1;
  912. $arr['last-child'] = 1;
  913. $arr['network'] = NETWORK_DFRN;
  914. }
  915. // If a Diaspora signature structure was passed in, pull it out of the
  916. // item array and set it aside for later storage.
  917. $dsprsig = null;
  918. if(x($arr,'dsprsig')) {
  919. $dsprsig = json_decode(base64_decode($arr['dsprsig']));
  920. unset($arr['dsprsig']);
  921. }
  922. // Converting the plink
  923. if ($arr['network'] == NETWORK_OSTATUS) {
  924. if (isset($arr['plink']))
  925. $arr['plink'] = ostatus_convert_href($arr['plink']);
  926. elseif (isset($arr['uri']))
  927. $arr['plink'] = ostatus_convert_href($arr['uri']);
  928. }
  929. if(x($arr, 'gravity'))
  930. $arr['gravity'] = intval($arr['gravity']);
  931. elseif($arr['parent-uri'] === $arr['uri'])
  932. $arr['gravity'] = 0;
  933. elseif(activity_match($arr['verb'],ACTIVITY_POST))
  934. $arr['gravity'] = 6;
  935. else
  936. $arr['gravity'] = 6; // extensible catchall
  937. if(! x($arr,'type'))
  938. $arr['type'] = 'remote';
  939. /* check for create date and expire time */
  940. $uid = intval($arr['uid']);
  941. $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
  942. if(count($r)) {
  943. $expire_interval = $r[0]['expire'];
  944. if ($expire_interval>0) {
  945. $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
  946. $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
  947. if ($created_date < $expire_date) {
  948. logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
  949. return 0;
  950. }
  951. }
  952. }
  953. // If there is no guid then take the same guid that was taken before for the same uri
  954. if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
  955. logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
  956. $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
  957. dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
  958. if(count($r)) {
  959. $arr['guid'] = $r[0]["guid"];
  960. logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
  961. }
  962. }
  963. // If there is no guid then take the same guid that was taken before for the same plink
  964. if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
  965. logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
  966. $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
  967. dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
  968. if(count($r)) {
  969. $arr['guid'] = $r[0]["guid"];
  970. logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
  971. if ($r[0]["uri"] != $arr['uri'])
  972. logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
  973. }
  974. }
  975. // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
  976. // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
  977. //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
  978. // $arr['body'] = strip_tags($arr['body']);
  979. item_add_language_opt($arr);
  980. if ($notify)
  981. $guid_prefix = "";
  982. else
  983. $guid_prefix = $arr['network'];
  984. $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
  985. $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
  986. $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
  987. $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
  988. $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
  989. $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
  990. $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
  991. $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
  992. $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
  993. $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
  994. $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
  995. $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
  996. $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
  997. $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
  998. $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
  999. $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
  1000. $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
  1001. $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
  1002. $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
  1003. $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
  1004. $arr['deleted'] = 0;
  1005. $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
  1006. $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
  1007. $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
  1008. $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
  1009. $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
  1010. $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
  1011. $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
  1012. $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
  1013. $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
  1014. $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
  1015. $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
  1016. $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
  1017. $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
  1018. $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
  1019. $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
  1020. $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
  1021. $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
  1022. $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
  1023. $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
  1024. $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
  1025. $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
  1026. $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
  1027. $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
  1028. $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
  1029. if ($arr['plink'] == "") {
  1030. $a = get_app();
  1031. $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
  1032. }
  1033. if ($arr['network'] == "") {
  1034. $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
  1035. dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
  1036. dbesc(normalise_link($arr['author-link'])),
  1037. intval($arr['uid'])
  1038. );
  1039. if(!count($r))
  1040. $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
  1041. dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
  1042. dbesc(normalise_link($arr['author-link']))
  1043. );
  1044. if(!count($r))
  1045. $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  1046. intval($arr['contact-id']),
  1047. intval($arr['uid'])
  1048. );
  1049. if(count($r))
  1050. $arr['network'] = $r[0]["network"];
  1051. // Fallback to friendica (why is it empty in some cases?)
  1052. if ($arr['network'] == "")
  1053. $arr['network'] = NETWORK_DFRN;
  1054. logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
  1055. }
  1056. if ($arr['guid'] != "") {
  1057. // Checking if there is already an item with the same guid
  1058. logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
  1059. $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
  1060. dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
  1061. if(count($r)) {
  1062. logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
  1063. return 0;
  1064. }
  1065. }
  1066. // Check for hashtags in the body and repair or add hashtag links
  1067. item_body_set_hashtags($arr);
  1068. $arr['thr-parent'] = $arr['parent-uri'];
  1069. if($arr['parent-uri'] === $arr['uri']) {
  1070. $parent_id = 0;
  1071. $parent_deleted = 0;
  1072. $allow_cid = $arr['allow_cid'];
  1073. $allow_gid = $arr['allow_gid'];
  1074. $deny_cid = $arr['deny_cid'];
  1075. $deny_gid = $arr['deny_gid'];
  1076. $notify_type = 'wall-new';
  1077. }
  1078. else {
  1079. // find the parent and snarf the item id and ACLs
  1080. // and anything else we need to inherit
  1081. $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
  1082. dbesc($arr['parent-uri']),
  1083. intval($arr['uid'])
  1084. );
  1085. if(count($r)) {
  1086. // is the new message multi-level threaded?
  1087. // even though we don't support it now, preserve the info
  1088. // and re-attach to the conversation parent.
  1089. if($r[0]['uri'] != $r[0]['parent-uri']) {
  1090. $arr['parent-uri'] = $r[0]['parent-uri'];
  1091. $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
  1092. ORDER BY `id` ASC LIMIT 1",
  1093. dbesc($r[0]['parent-uri']),
  1094. dbesc($r[0]['parent-uri']),
  1095. intval($arr['uid'])
  1096. );
  1097. if($z && count($z))
  1098. $r = $z;
  1099. }
  1100. $parent_id = $r[0]['id'];
  1101. $parent_deleted = $r[0]['deleted'];
  1102. $allow_cid = $r[0]['allow_cid'];
  1103. $allow_gid = $r[0]['allow_gid'];
  1104. $deny_cid = $r[0]['deny_cid'];
  1105. $deny_gid = $r[0]['deny_gid'];
  1106. $arr['wall'] = $r[0]['wall'];
  1107. $notify_type = 'comment-new';
  1108. // if the parent is private, force privacy for the entire conversation
  1109. // This differs from the above settings as it subtly allows comments from
  1110. // email correspondents to be private even if the overall thread is not.
  1111. if($r[0]['private'])
  1112. $arr['private'] = $r[0]['private'];
  1113. // Edge case. We host a public forum that was originally posted to privately.
  1114. // The original author commented, but as this is a comment, the permissions
  1115. // weren't fixed up so it will still show the comment as private unless we fix it here.
  1116. if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
  1117. $arr['private'] = 0;
  1118. // If its a post from myself then tag the thread as "mention"
  1119. logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
  1120. $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
  1121. if(count($u)) {
  1122. $a = get_app();
  1123. $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
  1124. logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
  1125. if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
  1126. q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
  1127. logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
  1128. }
  1129. }
  1130. }
  1131. else {
  1132. // Allow one to see reply tweets from status.net even when
  1133. // we don't have or can't see the original post.
  1134. if($force_parent) {
  1135. logger('item_store: $force_parent=true, reply converted to top-level post.');
  1136. $parent_id = 0;
  1137. $arr['parent-uri'] = $arr['uri'];
  1138. $arr['gravity'] = 0;
  1139. }
  1140. else {
  1141. logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
  1142. return 0;
  1143. }
  1144. $parent_deleted = 0;
  1145. }
  1146. }
  1147. $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
  1148. dbesc($arr['uri']),
  1149. dbesc($arr['network']),
  1150. intval($arr['uid'])
  1151. );
  1152. if($r && count($r)) {
  1153. logger('duplicated item with the same uri found. ' . print_r($arr,true));
  1154. return 0;
  1155. }
  1156. // Check for an existing post with the same content. There seems to be a problem with OStatus.
  1157. $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
  1158. dbesc($arr['body']),
  1159. dbesc($arr['network']),
  1160. dbesc($arr['created']),
  1161. intval($arr['contact-id']),
  1162. intval($arr['uid'])
  1163. );
  1164. if($r && count($r)) {
  1165. logger('duplicated item with the same body found. ' . print_r($arr,true));
  1166. return 0;
  1167. }
  1168. // Is this item available in the global items (with uid=0)?
  1169. if ($arr["uid"] == 0) {
  1170. $arr["global"] = true;
  1171. q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
  1172. } else {
  1173. $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
  1174. $arr["global"] = (count($isglobal) > 0);
  1175. }
  1176. // Fill the cache field
  1177. put_item_in_cache($arr);
  1178. if ($notify)
  1179. call_hooks('post_local',$arr);
  1180. else
  1181. call_hooks('post_remote',$arr);
  1182. if(x($arr,'cancel')) {
  1183. logger('item_store: post cancelled by plugin.');
  1184. return 0;
  1185. }
  1186. // Store the unescaped version
  1187. $unescaped = $arr;
  1188. dbesc_array($arr);
  1189. logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
  1190. $r = dbq("INSERT INTO `item` (`"
  1191. . implode("`, `", array_keys($arr))
  1192. . "`) VALUES ('"
  1193. . implode("', '", array_values($arr))
  1194. . "')" );
  1195. // And restore it
  1196. $arr = $unescaped;
  1197. // find the item we just created
  1198. $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC ",
  1199. dbesc($arr['uri']),
  1200. intval($arr['uid']),
  1201. dbesc($arr['network'])
  1202. );
  1203. if(count($r)) {
  1204. // Store the guid and other relevant data
  1205. add_guid($arr);
  1206. $current_post = $r[0]['id'];
  1207. logger('item_store: created item ' . $current_post);
  1208. // Set "success_update" and "last-item" to the date of the last time we heard from this contact
  1209. // This can be used to filter for inactive contacts.
  1210. // Only do this for public postings to avoid privacy problems, since poco data is public.
  1211. // Don't set this value if it isn't from the owner (could be an author that we don't know)
  1212. $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
  1213. // Is it a forum? Then we don't care about the rules from above
  1214. if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
  1215. $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
  1216. intval($arr['contact-id']));
  1217. if ($isforum)
  1218. $update = true;
  1219. }
  1220. if ($update)
  1221. q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
  1222. dbesc($arr['received']),
  1223. dbesc($arr['received']),
  1224. intval($arr['contact-id'])
  1225. );
  1226. } else {
  1227. logger('item_store: could not locate created item');
  1228. return 0;
  1229. }
  1230. if(count($r) > 1) {
  1231. logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
  1232. q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
  1233. dbesc($arr['uri']),
  1234. intval($arr['uid']),
  1235. intval($current_post)
  1236. );
  1237. }
  1238. if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
  1239. $parent_id = $current_post;
  1240. if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
  1241. $private = 1;
  1242. else
  1243. $private = $arr['private'];
  1244. // Set parent id - and also make sure to inherit the parent's ACLs.
  1245. $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
  1246. `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
  1247. intval($parent_id),
  1248. dbesc($allow_cid),
  1249. dbesc($allow_gid),
  1250. dbesc($deny_cid),
  1251. dbesc($deny_gid),
  1252. intval($private),
  1253. intval($parent_deleted),
  1254. intval($current_post)
  1255. );
  1256. $arr['id'] = $current_post;
  1257. $arr['parent'] = $parent_id;
  1258. $arr['allow_cid'] = $allow_cid;
  1259. $arr['allow_gid'] = $allow_gid;
  1260. $arr['deny_cid'] = $deny_cid;
  1261. $arr['deny_gid'] = $deny_gid;
  1262. $arr['private'] = $private;
  1263. $arr['deleted'] = $parent_deleted;
  1264. // update the commented timestamp on the parent
  1265. // Only update "commented" if it is really a comment
  1266. if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
  1267. q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
  1268. dbesc(datetime_convert()),
  1269. dbesc(datetime_convert()),
  1270. intval($parent_id)
  1271. );
  1272. else
  1273. q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
  1274. dbesc(datetime_convert()),
  1275. intval($parent_id)
  1276. );
  1277. if($dsprsig) {
  1278. q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
  1279. intval($current_post),
  1280. dbesc($dsprsig->signed_text),
  1281. dbesc($dsprsig->signature),
  1282. dbesc($dsprsig->signer)
  1283. );
  1284. }
  1285. /**
  1286. * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
  1287. */
  1288. if($arr['last-child']) {
  1289. $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
  1290. dbesc($arr['uri']),
  1291. intval($arr['uid']),
  1292. intval($current_post)
  1293. );
  1294. }
  1295. $deleted = tag_deliver($arr['uid'],$current_post);
  1296. // current post can be deleted if is for a community page and no mention are
  1297. // in it.
  1298. if (!$deleted AND !$dontcache) {
  1299. $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
  1300. if (count($r) == 1) {
  1301. if ($notify)
  1302. call_hooks('post_local_end', $r[0]);
  1303. else
  1304. call_hooks('post_remote_end', $r[0]);
  1305. } else
  1306. logger('item_store: new item not found in DB, id ' . $current_post);
  1307. }
  1308. // Add every contact of the post to the global contact table
  1309. poco_store($arr);
  1310. create_tags_from_item($current_post);
  1311. create_files_from_item($current_post);
  1312. // Only check for notifications on start posts
  1313. if ($arr['parent-uri'] === $arr['uri']) {
  1314. add_thread($current_post);
  1315. logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
  1316. // Send a notification for every new post?
  1317. $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
  1318. intval($arr['contact-id']),
  1319. intval($arr['uid'])
  1320. );
  1321. $send_notification = count($r);
  1322. if (!$send_notification) {
  1323. $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
  1324. intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
  1325. if (count($tags)) {
  1326. foreach ($tags AS $tag) {
  1327. $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
  1328. normalise_link($tag["url"]), intval($arr['uid']));
  1329. if (count($r))
  1330. $send_notification = true;
  1331. }
  1332. }
  1333. }
  1334. if ($send_notification) {
  1335. logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
  1336. $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
  1337. intval($arr['uid']));
  1338. $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
  1339. intval($current_post),
  1340. intval($arr['uid'])
  1341. );
  1342. $a = get_app();
  1343. require_once('include/enotify.php');
  1344. notification(array(
  1345. 'type' => NOTIFY_SHARE,
  1346. 'notify_flags' => $u[0]['notify-flags'],
  1347. 'language' => $u[0]['language'],
  1348. 'to_name' => $u[0]['username'],
  1349. 'to_email' => $u[0]['email'],
  1350. 'uid' => $u[0]['uid'],
  1351. 'item' => $item[0],
  1352. 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
  1353. 'source_name' => $item[0]['author-name'],
  1354. 'source_link' => $item[0]['author-link'],
  1355. 'source_photo' => $item[0]['author-avatar'],
  1356. 'verb' => ACTIVITY_TAG,
  1357. 'otype' => 'item',
  1358. 'parent' => $arr['parent']
  1359. ));
  1360. logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
  1361. }
  1362. } else {
  1363. update_thread($parent_id);
  1364. add_shadow_entry($arr);
  1365. }
  1366. if ($notify)
  1367. proc_run('php', "include/notifier.php", $notify_type, $current_post);
  1368. return $current_post;
  1369. }
  1370. function item_body_set_hashtags(&$item) {
  1371. $tags = get_tags($item["body"]);
  1372. // No hashtags?
  1373. if(!count($tags))
  1374. return(false);
  1375. // This sorting is important when there are hashtags that are part of other hashtags
  1376. // Otherwise there could be problems with hashtags like #test and #test2
  1377. rsort($tags);
  1378. $a = get_app();
  1379. $URLSearchString = "^\[\]";
  1380. // All hashtags should point to the home server
  1381. //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
  1382. // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
  1383. //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
  1384. // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
  1385. // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
  1386. $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
  1387. function ($match){
  1388. return("[url=".str_replace("#", "&num;", $match[1])."]".str_replace("#", "&num;", $match[2])."[/url]");
  1389. },$item["body"]);
  1390. $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
  1391. function ($match){
  1392. return("[bookmark=".str_replace("#", "&num;", $match[1])."]".str_replace("#", "&num;", $match[2])."[/bookmark]");
  1393. },$item["body"]);
  1394. $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
  1395. function ($match){
  1396. return("[attachment ".str_replace("#", "&num;", $match[1])."]".$match[2]."[/attachment]");
  1397. },$item["body"]);
  1398. // Repair recursive urls
  1399. $item["body"] = preg_replace("/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
  1400. "&num;$2", $item["body"]);
  1401. foreach($tags as $tag) {
  1402. if(strpos($tag,'#') !== 0)
  1403. continue;
  1404. if(strpos($tag,'[url='))
  1405. continue;
  1406. $basetag = str_replace('_',' ',substr($tag,1));
  1407. $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
  1408. $item["body"] = str_replace($tag, $newtag, $item["body"]);
  1409. if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
  1410. if(strlen($item["tag"]))
  1411. $item["tag"] = ','.$item["tag"];
  1412. $item["tag"] = $newtag.$item["tag"];
  1413. }
  1414. }
  1415. // Convert back the masked hashtags
  1416. $item["body"] = str_replace("&num;", "#", $item["body"]);
  1417. }
  1418. function get_item_guid($id) {
  1419. $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
  1420. if (count($r))
  1421. return($r[0]["guid"]);
  1422. else
  1423. return("");
  1424. }
  1425. function get_item_id($guid, $uid = 0) {
  1426. $nick = "";
  1427. $id = 0;
  1428. if ($uid == 0)
  1429. $uid == local_user();
  1430. // Does the given user have this item?
  1431. if ($uid) {
  1432. $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
  1433. WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
  1434. AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
  1435. if (count($r)) {
  1436. $id = $r[0]["id"];
  1437. $nick = $r[0]["nickname"];
  1438. }
  1439. }
  1440. // Or is it anywhere on the server?
  1441. if ($nick == "") {
  1442. $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
  1443. WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
  1444. AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
  1445. AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
  1446. AND `item`.`private` = 0 AND `item`.`wall` = 1
  1447. AND `item`.`guid` = '%s'", dbesc($guid));
  1448. if (count($r)) {
  1449. $id = $r[0]["id"];
  1450. $nick = $r[0]["nickname"];
  1451. }
  1452. }
  1453. return(array("nick" => $nick, "id" => $id));
  1454. }
  1455. // return - test
  1456. function get_item_contact($item,$contacts) {
  1457. if(! count($contacts) || (! is_array($item)))
  1458. return false;
  1459. foreach($contacts as $contact) {
  1460. if($contact['id'] == $item['contact-id']) {
  1461. return $contact;
  1462. break; // NOTREACHED
  1463. }
  1464. }
  1465. return false;
  1466. }
  1467. /**
  1468. * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
  1469. * @param int $uid
  1470. * @param int $item_id
  1471. * @return bool true if item was deleted, else false
  1472. */
  1473. function tag_deliver($uid,$item_id) {
  1474. //
  1475. $a = get_app();
  1476. $mention = false;
  1477. $u = q("select * from user where uid = %d limit 1",
  1478. intval($uid)
  1479. );
  1480. if(! count($u))
  1481. return;
  1482. $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
  1483. $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
  1484. $i = q("select * from item where id = %d and uid = %d limit 1",
  1485. intval($item_id),
  1486. intval($uid)
  1487. );
  1488. if(! count($i))
  1489. return;
  1490. $item = $i[0];
  1491. $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
  1492. // Diaspora uses their own hardwired link URL in @-tags
  1493. // instead of the one we supply with webfinger
  1494. $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
  1495. $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
  1496. if($cnt) {
  1497. foreach($matches as $mtch) {
  1498. if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
  1499. $mention = true;
  1500. logger('tag_deliver: mention found: ' . $mtch[2]);
  1501. }
  1502. }
  1503. }
  1504. if(! $mention){
  1505. if ( ($community_page || $prvgroup) &&
  1506. (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
  1507. // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
  1508. // delete it!
  1509. logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
  1510. q("DELETE FROM item WHERE id = %d and uid = %d",
  1511. intval($item_id),
  1512. intval($uid)
  1513. );
  1514. return true;
  1515. }
  1516. return;
  1517. }
  1518. // send a notification
  1519. // use a local photo if we have one
  1520. $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
  1521. intval($u[0]['uid']),
  1522. dbesc(normalise_link($item['author-link']))
  1523. );
  1524. $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
  1525. require_once('include/enotify.php');
  1526. notification(array(
  1527. 'type' => NOTIFY_TAGSELF,
  1528. 'notify_flags' => $u[0]['notify-flags'],
  1529. 'language' => $u[0]['language'],
  1530. 'to_name' => $u[0]['username'],
  1531. 'to_email' => $u[0]['email'],
  1532. 'uid' => $u[0]['uid'],
  1533. 'item' => $item,
  1534. 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
  1535. 'source_name' => $item['author-name'],
  1536. 'source_link' => $item['author-link'],
  1537. 'source_photo' => $photo,
  1538. 'verb' => ACTIVITY_TAG,
  1539. 'otype' => 'item',
  1540. 'parent' => $item['parent']
  1541. ));
  1542. $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
  1543. call_hooks('tagged', $arr);
  1544. if((! $community_page) && (! $prvgroup))
  1545. return;
  1546. // tgroup delivery - setup a second delivery chain
  1547. // prevent delivery looping - only proceed
  1548. // if the message originated elsewhere and is a top-level post
  1549. if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
  1550. return;
  1551. // now change this copy of the post to a forum head message and deliver to all the tgroup members
  1552. $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
  1553. intval($u[0]['uid'])
  1554. );
  1555. if(! count($c))
  1556. return;
  1557. // also reset all the privacy bits to the forum default permissions
  1558. $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
  1559. $forum_mode = (($prvgroup) ? 2 : 1);
  1560. q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
  1561. `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
  1562. intval($forum_mode),
  1563. dbesc($c[0]['name']),
  1564. dbesc($c[0]['url']),
  1565. dbesc($c[0]['thumb']),
  1566. intval($private),
  1567. dbesc($u[0]['allow_cid']),
  1568. dbesc($u[0]['allow_gid']),
  1569. dbesc($u[0]['deny_cid']),
  1570. dbesc($u[0]['deny_gid']),
  1571. intval($item_id)
  1572. );
  1573. update_thread($item_id);
  1574. proc_run('php','include/notifier.php','tgroup',$item_id);
  1575. }
  1576. function tgroup_check($uid,$item) {
  1577. $a = get_app();
  1578. $mention = false;
  1579. // check that the message originated elsewhere and is a top-level post
  1580. if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
  1581. return false;
  1582. $u = q("select * from user where uid = %d limit 1",
  1583. intval($uid)
  1584. );
  1585. if(! count($u))
  1586. return false;
  1587. $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
  1588. $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
  1589. $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
  1590. // Diaspora uses their own hardwired link URL in @-tags
  1591. // instead of the one we supply with webfinger
  1592. $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
  1593. $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
  1594. if($cnt) {
  1595. foreach($matches as $mtch) {
  1596. if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
  1597. $mention = true;
  1598. logger('tgroup_check: mention found: ' . $mtch[2]);
  1599. }
  1600. }
  1601. }
  1602. if(! $mention)
  1603. return false;
  1604. if((! $community_page) && (! $prvgroup))
  1605. return false;
  1606. return true;
  1607. }
  1608. function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
  1609. $a = get_app();
  1610. $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
  1611. if($contact['duplex'] && $contact['dfrn-id'])
  1612. $idtosend = '0:' . $orig_id;
  1613. if($contact['duplex'] && $contact['issued-id'])
  1614. $idtosend = '1:' . $orig_id;
  1615. $rino = get_config('system','rino_encrypt');
  1616. $rino = intval($rino);
  1617. // use RINO1 if mcrypt isn't installed and RINO2 was selected
  1618. if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
  1619. logger("Local rino version: ". $rino, LOGGER_DEBUG);
  1620. $ssl_val = intval(get_config('system','ssl_policy'));
  1621. $ssl_policy = '';
  1622. switch($ssl_val){
  1623. case SSL_POLICY_FULL:
  1624. $ssl_policy = 'full';
  1625. break;
  1626. case SSL_POLICY_SELFSIGN:
  1627. $ssl_policy = 'self';
  1628. break;
  1629. case SSL_POLICY_NONE:
  1630. default:
  1631. $ssl_policy = 'none';
  1632. break;
  1633. }
  1634. $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
  1635. logger('dfrn_deliver: ' . $url);
  1636. $xml = fetch_url($url);
  1637. $curl_stat = $a->get_curl_code();
  1638. if(! $curl_stat)
  1639. return(-1); // timed out
  1640. logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
  1641. if(! $xml)
  1642. return 3;
  1643. if(strpos($xml,'<?xml') === false) {
  1644. logger('dfrn_deliver: no valid XML returned');
  1645. logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
  1646. return 3;
  1647. }
  1648. $res = parse_xml_string($xml);
  1649. if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
  1650. return (($res->status) ? $res->status : 3);
  1651. $postvars = array();
  1652. $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
  1653. $challenge = hex2bin((string) $res->challenge);
  1654. $perm = (($res->perm) ? $res->perm : null);
  1655. $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
  1656. $rino_remote_version = intval($res->rino);
  1657. $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
  1658. logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
  1659. if($owner['page-flags'] == PAGE_PRVGROUP)
  1660. $page = 2;
  1661. $final_dfrn_id = '';
  1662. if($perm) {
  1663. if((($perm == 'rw') && (! intval($contact['writable'])))
  1664. || (($perm == 'r') && (intval($contact['writable'])))) {
  1665. q("update contact set writable = %d where id = %d",
  1666. intval(($perm == 'rw') ? 1 : 0),
  1667. intval($contact['id'])
  1668. );
  1669. $contact['writable'] = (string) 1 - intval($contact['writable']);
  1670. }
  1671. }
  1672. if(($contact['duplex'] && strlen($contact['pubkey']))
  1673. || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
  1674. || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
  1675. openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
  1676. openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
  1677. }
  1678. else {
  1679. openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
  1680. openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
  1681. }
  1682. $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
  1683. if(strpos($final_dfrn_id,':') == 1)
  1684. $final_dfrn_id = substr($final_dfrn_id,2);
  1685. if($final_dfrn_id != $orig_id) {
  1686. logger('dfrn_deliver: wrong dfrn_id.');
  1687. // did not decode properly - cannot trust this site
  1688. return 3;
  1689. }
  1690. $postvars['dfrn_id'] = $idtosend;
  1691. $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
  1692. if($dissolve)
  1693. $postvars['dissolve'] = '1';
  1694. if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
  1695. $postvars['data'] = $atom;
  1696. $postvars['perm'] = 'rw';
  1697. }
  1698. else {
  1699. $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
  1700. $postvars['perm'] = 'r';
  1701. }
  1702. $postvars['ssl_policy'] = $ssl_policy;
  1703. if($page)
  1704. $postvars['page'] = $page;
  1705. if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
  1706. logger('rino version: '. $rino_remote_version);
  1707. switch($rino_remote_version) {
  1708. case 1:
  1709. // Deprecated rino version!
  1710. $key = substr(random_string(),0,16);
  1711. $data = aes_encrypt($postvars['data'],$key);
  1712. break;
  1713. case 2:
  1714. // RINO 2 based on php-encryption
  1715. try {
  1716. $key = Crypto::createNewRandomKey();
  1717. } catch (CryptoTestFailed $ex) {
  1718. logger('Cannot safely create a key');
  1719. return -1;
  1720. } catch (CannotPerformOperation $ex) {
  1721. logger('Cannot safely create a key');
  1722. return -1;
  1723. }
  1724. try {
  1725. $data = Crypto::encrypt($postvars['data'], $key);
  1726. } catch (CryptoTestFailed $ex) {
  1727. logger('Cannot safely perform encryption');
  1728. return -1;
  1729. } catch (CannotPerformOperation $ex) {
  1730. logger('Cannot safely perform encryption');
  1731. return -1;
  1732. }
  1733. break;
  1734. default:
  1735. logger("rino: invalid requested verision '$rino_remote_version'");
  1736. return -1;
  1737. }
  1738. $postvars['rino'] = $rino_remote_version;
  1739. $postvars['data'] = bin2hex($data);
  1740. #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
  1741. if($dfrn_version >= 2.1) {
  1742. if(($contact['duplex'] && strlen($contact['pubkey']))
  1743. || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
  1744. || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
  1745. openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
  1746. }
  1747. else {
  1748. openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
  1749. }
  1750. }
  1751. else {
  1752. if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
  1753. openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
  1754. }
  1755. else {
  1756. openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
  1757. }
  1758. }
  1759. logger('md5 rawkey ' . md5($postvars['key']));
  1760. $postvars['key'] = bin2hex($postvars['key']);
  1761. }
  1762. logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
  1763. $xml = post_url($contact['notify'],$postvars);
  1764. logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
  1765. $curl_stat = $a->get_curl_code();
  1766. if((! $curl_stat) || (! strlen($xml)))
  1767. return(-1); // timed out
  1768. if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
  1769. return(-1);
  1770. if(strpos($xml,'<?xml') === false) {
  1771. logger('dfrn_deliver: phase 2: no valid XML returned');
  1772. logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
  1773. return 3;
  1774. }
  1775. if($contact['term-date'] != '0000-00-00 00:00:00') {
  1776. logger("dfrn_deliver: $url back from the dead - removing mark for death");
  1777. require_once('include/Contact.php');
  1778. unmark_for_death($contact);
  1779. }
  1780. $res = parse_xml_string($xml);
  1781. return $res->status;
  1782. }
  1783. /*
  1784. This function returns true if $update has an edited timestamp newer
  1785. than $existing, i.e. $update contains new data which should override
  1786. what's already there. If there is no timestamp yet, the update is
  1787. assumed to be newer. If the update has no timestamp, the existing
  1788. item is assumed to be up-to-date. If the timestamps are equal it
  1789. assumes the update has been seen before and should be ignored.
  1790. */
  1791. function edited_timestamp_is_newer($existing, $update) {
  1792. if (!x($existing,'edited') || !$existing['edited']) {
  1793. return true;
  1794. }
  1795. if (!x($update,'edited') || !$update['edited']) {
  1796. return false;
  1797. }
  1798. $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
  1799. $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
  1800. return (strcmp($existing_edited, $update_edited) < 0);
  1801. }
  1802. /**
  1803. *
  1804. * consume_feed - process atom feed and update anything/everything we might need to update
  1805. *
  1806. * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
  1807. *
  1808. * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
  1809. * It is this person's stuff that is going to be updated.
  1810. * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
  1811. * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
  1812. * have a contact record.
  1813. * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
  1814. * might not) try and subscribe to it.
  1815. * $datedir sorts in reverse order
  1816. * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
  1817. * imported prior to its children being seen in the stream unless we are certain
  1818. * of how the feed is arranged/ordered.
  1819. * With $pass = 1, we only pull parent items out of the stream.
  1820. * With $pass = 2, we only pull children (comments/likes).
  1821. *
  1822. * So running this twice, first with pass 1 and then with pass 2 will do the right
  1823. * thing regardless of feed ordering. This won't be adequate in a fully-threaded
  1824. * model where comments can have sub-threads. That would require some massive sorting
  1825. * to get all the feed items into a mostly linear ordering, and might still require
  1826. * recursion.
  1827. */
  1828. function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
  1829. if ($contact['network'] === NETWORK_OSTATUS) {
  1830. if ($pass < 2) {
  1831. logger("Consume OStatus messages ", LOGGER_DEBUG);
  1832. ostatus_import($xml,$importer,$contact, $hub);
  1833. }
  1834. return;
  1835. }
  1836. if ($contact['network'] === NETWORK_FEED) {
  1837. if ($pass < 2) {
  1838. logger("Consume feeds", LOGGER_DEBUG);
  1839. feed_import($xml,$importer,$contact, $hub);
  1840. }
  1841. return;
  1842. }
  1843. require_once('library/simplepie/simplepie.inc');
  1844. require_once('include/contact_selectors.php');
  1845. if(! strlen($xml)) {
  1846. logger('consume_feed: empty input');
  1847. return;
  1848. }
  1849. $feed = new SimplePie();
  1850. $feed->set_raw_data($xml);
  1851. if($datedir)
  1852. $feed->enable_order_by_date(true);
  1853. else
  1854. $feed->enable_order_by_date(false);
  1855. $feed->init();
  1856. if($feed->error())
  1857. logger('consume_feed: Error parsing XML: ' . $feed->error());
  1858. $permalink = $feed->get_permalink();
  1859. // Check at the feed level for updated contact name and/or photo
  1860. $name_updated = '';
  1861. $new_name = '';
  1862. $photo_timestamp = '';
  1863. $photo_url = '';
  1864. $birthday = '';
  1865. $contact_updated = '';
  1866. $hubs = $feed->get_links('hub');
  1867. logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
  1868. if(count($hubs))
  1869. $hub = implode(',', $hubs);
  1870. $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
  1871. if(! $rawtags)
  1872. $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
  1873. if($rawtags) {
  1874. $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
  1875. if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
  1876. $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
  1877. $new_name = $elems['name'][0]['data'];
  1878. // Manually checking for changed contact names
  1879. if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
  1880. $name_updated = date("c");
  1881. $photo_timestamp = date("c");
  1882. }
  1883. }
  1884. if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
  1885. if ($photo_timestamp == "")
  1886. $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
  1887. $photo_url = $elems['link'][0]['attribs']['']['href'];
  1888. }
  1889. if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
  1890. $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
  1891. }
  1892. }
  1893. if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
  1894. logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
  1895. $contact_updated = $photo_timestamp;
  1896. require_once("include/Photo.php");
  1897. $photo_failure = false;
  1898. $have_photo = false;
  1899. $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
  1900. intval($contact['id']),
  1901. intval($contact['uid'])
  1902. );
  1903. if(count($r)) {
  1904. $resource_id = $r[0]['resource-id'];
  1905. $have_photo = true;
  1906. }
  1907. else {
  1908. $resource_id = photo_new_resource();
  1909. }
  1910. $img_str = fetch_url($photo_url,true);
  1911. // guess mimetype from headers or filename
  1912. $type = guess_image_type($photo_url,true);
  1913. $img = new Photo($img_str, $type);
  1914. if($img->is_valid()) {
  1915. if($have_photo) {
  1916. q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
  1917. dbesc($resource_id),
  1918. intval($contact['id']),
  1919. intval($contact['uid'])
  1920. );
  1921. }
  1922. $img->scaleImageSquare(175);
  1923. $hash = $resource_id;
  1924. $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
  1925. $img->scaleImage(80);
  1926. $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
  1927. $img->scaleImage(48);
  1928. $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
  1929. $a = get_app();
  1930. q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
  1931. WHERE `uid` = %d AND `id` = %d",
  1932. dbesc(datetime_convert()),
  1933. dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
  1934. dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
  1935. dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
  1936. intval($contact['uid']),
  1937. intval($contact['id'])
  1938. );
  1939. }
  1940. }
  1941. if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
  1942. if ($name_updated > $contact_updated)
  1943. $contact_updated = $name_updated;
  1944. $r = q("select * from contact where uid = %d and id = %d limit 1",
  1945. intval($contact['uid']),
  1946. intval($contact['id'])
  1947. );
  1948. $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
  1949. dbesc(notags(trim($new_name))),
  1950. dbesc(datetime_convert()),
  1951. intval($contact['uid']),
  1952. intval($contact['id'])
  1953. );
  1954. // do our best to update the name on content items
  1955. if(count($r)) {
  1956. q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
  1957. dbesc(notags(trim($new_name))),
  1958. dbesc($r[0]['name']),
  1959. dbesc($r[0]['url']),
  1960. intval($contact['uid'])
  1961. );
  1962. }
  1963. }
  1964. if ($contact_updated AND $new_name AND $photo_url)
  1965. poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
  1966. if(strlen($birthday)) {
  1967. if(substr($birthday,0,4) != $contact['bdyear']) {
  1968. logger('consume_feed: updating birthday: ' . $birthday);
  1969. /**
  1970. *
  1971. * Add new birthday event for this person
  1972. *
  1973. * $bdtext is just a readable placeholder in case the event is shared
  1974. * with others. We will replace it during presentation to our $importer
  1975. * to contain a sparkle link and perhaps a photo.
  1976. *
  1977. */
  1978. $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
  1979. $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
  1980. $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
  1981. VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
  1982. intval($contact['uid']),
  1983. intval($contact['id']),
  1984. dbesc(datetime_convert()),
  1985. dbesc(datetime_convert()),
  1986. dbesc(datetime_convert('UTC','UTC', $birthday)),
  1987. dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
  1988. dbesc($bdtext),
  1989. dbesc($bdtext2),
  1990. dbesc('birthday')
  1991. );
  1992. // update bdyear
  1993. q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
  1994. dbesc(substr($birthday,0,4)),
  1995. intval($contact['uid']),
  1996. intval($contact['id'])
  1997. );
  1998. // This function is called twice without reloading the contact
  1999. // Make sure we only create one event. This is why &$contact
  2000. // is a reference var in this function
  2001. $contact['bdyear'] = substr($birthday,0,4);
  2002. }
  2003. }
  2004. $community_page = 0;
  2005. $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
  2006. if($rawtags) {
  2007. $community_page = intval($rawtags[0]['data']);
  2008. }
  2009. if(is_array($contact) && intval($contact['forum']) != $community_page) {
  2010. q("update contact set forum = %d where id = %d",
  2011. intval($community_page),
  2012. intval($contact['id'])
  2013. );
  2014. $contact['forum'] = (string) $community_page;
  2015. }
  2016. // process any deleted entries
  2017. $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
  2018. if(is_array($del_entries) && count($del_entries) && $pass != 2) {
  2019. foreach($del_entries as $dentry) {
  2020. $deleted = false;
  2021. if(isset($dentry['attribs']['']['ref'])) {
  2022. $uri = $dentry['attribs']['']['ref'];
  2023. $deleted = true;
  2024. if(isset($dentry['attribs']['']['when'])) {
  2025. $when = $dentry['attribs']['']['when'];
  2026. $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
  2027. }
  2028. else
  2029. $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
  2030. }
  2031. if($deleted && is_array($contact)) {
  2032. $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
  2033. WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
  2034. dbesc($uri),
  2035. intval($importer['uid']),
  2036. intval($contact['id'])
  2037. );
  2038. if(count($r)) {
  2039. $item = $r[0];
  2040. if(! $item['deleted'])
  2041. logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
  2042. if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
  2043. logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
  2044. event_delete($item['event-id']);
  2045. }
  2046. if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
  2047. $xo = parse_xml_string($item['object'],false);
  2048. $xt = parse_xml_string($item['target'],false);
  2049. if($xt->type === ACTIVITY_OBJ_NOTE) {
  2050. $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
  2051. dbesc($xt->id),
  2052. intval($importer['importer_uid'])
  2053. );
  2054. if(count($i)) {
  2055. // For tags, the owner cannot remove the tag on the author's copy of the post.
  2056. $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
  2057. $author_remove = (($item['origin'] && $item['self']) ? true : false);
  2058. $author_copy = (($item['origin']) ? true : false);
  2059. if($owner_remove && $author_copy)
  2060. continue;
  2061. if($author_remove || $owner_remove) {
  2062. $tags = explode(',',$i[0]['tag']);
  2063. $newtags = array();
  2064. if(count($tags)) {
  2065. foreach($tags as $tag)
  2066. if(trim($tag) !== trim($xo->body))
  2067. $newtags[] = trim($tag);
  2068. }
  2069. q("update item set tag = '%s' where id = %d",
  2070. dbesc(implode(',',$newtags)),
  2071. intval($i[0]['id'])
  2072. );
  2073. create_tags_from_item($i[0]['id']);
  2074. }
  2075. }
  2076. }
  2077. }
  2078. if($item['uri'] == $item['parent-uri']) {
  2079. $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
  2080. `body` = '', `title` = ''
  2081. WHERE `parent-uri` = '%s' AND `uid` = %d",
  2082. dbesc($when),
  2083. dbesc(datetime_convert()),
  2084. dbesc($item['uri']),
  2085. intval($importer['uid'])
  2086. );
  2087. create_tags_from_itemuri($item['uri'], $importer['uid']);
  2088. create_files_from_itemuri($item['uri'], $importer['uid']);
  2089. update_thread_uri($item['uri'], $importer['uid']);
  2090. }
  2091. else {
  2092. $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
  2093. `body` = '', `title` = ''
  2094. WHERE `uri` = '%s' AND `uid` = %d",
  2095. dbesc($when),
  2096. dbesc(datetime_convert()),
  2097. dbesc($uri),
  2098. intval($importer['uid'])
  2099. );
  2100. create_tags_from_itemuri($uri, $importer['uid']);
  2101. create_files_from_itemuri($uri, $importer['uid']);
  2102. if($item['last-child']) {
  2103. // ensure that last-child is set in case the comment that had it just got wiped.
  2104. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
  2105. dbesc(datetime_convert()),
  2106. dbesc($item['parent-uri']),
  2107. intval($item['uid'])
  2108. );
  2109. // who is the last child now?
  2110. $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
  2111. ORDER BY `created` DESC LIMIT 1",
  2112. dbesc($item['parent-uri']),
  2113. intval($importer['uid'])
  2114. );
  2115. if(count($r)) {
  2116. q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
  2117. intval($r[0]['id'])
  2118. );
  2119. }
  2120. }
  2121. }
  2122. }
  2123. }
  2124. }
  2125. }
  2126. // Now process the feed
  2127. if($feed->get_item_quantity()) {
  2128. logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
  2129. // in inverse date order
  2130. if ($datedir)
  2131. $items = array_reverse($feed->get_items());
  2132. else
  2133. $items = $feed->get_items();
  2134. foreach($items as $item) {
  2135. $is_reply = false;
  2136. $item_id = $item->get_id();
  2137. $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
  2138. if(isset($rawthread[0]['attribs']['']['ref'])) {
  2139. $is_reply = true;
  2140. $parent_uri = $rawthread[0]['attribs']['']['ref'];
  2141. }
  2142. if(($is_reply) && is_array($contact)) {
  2143. if($pass == 1)
  2144. continue;
  2145. // not allowed to post
  2146. if($contact['rel'] == CONTACT_IS_FOLLOWER)
  2147. continue;
  2148. // Have we seen it? If not, import it.
  2149. $item_id = $item->get_id();
  2150. $datarray = get_atom_elements($feed, $item, $contact);
  2151. if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
  2152. $datarray['author-name'] = $contact['name'];
  2153. if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
  2154. $datarray['author-link'] = $contact['url'];
  2155. if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
  2156. $datarray['author-avatar'] = $contact['thumb'];
  2157. if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
  2158. logger('consume_feed: no author information! ' . print_r($datarray,true));
  2159. continue;
  2160. }
  2161. $force_parent = false;
  2162. if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
  2163. if($contact['network'] === NETWORK_OSTATUS)
  2164. $force_parent = true;
  2165. if(strlen($datarray['title']))
  2166. unset($datarray['title']);
  2167. $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
  2168. dbesc(datetime_convert()),
  2169. dbesc($parent_uri),
  2170. intval($importer['uid'])
  2171. );
  2172. $datarray['last-child'] = 1;
  2173. update_thread_uri($parent_uri, $importer['uid']);
  2174. }
  2175. $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  2176. dbesc($item_id),
  2177. intval($importer['uid'])
  2178. );
  2179. // Update content if 'updated' changes
  2180. if(count($r)) {
  2181. if (edited_timestamp_is_newer($r[0], $datarray)) {
  2182. // do not accept (ignore) an earlier edit than one we currently have.
  2183. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
  2184. continue;
  2185. $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  2186. dbesc($datarray['title']),
  2187. dbesc($datarray['body']),
  2188. dbesc($datarray['tag']),
  2189. dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
  2190. dbesc(datetime_convert()),
  2191. dbesc($item_id),
  2192. intval($importer['uid'])
  2193. );
  2194. create_tags_from_itemuri($item_id, $importer['uid']);
  2195. update_thread_uri($item_id, $importer['uid']);
  2196. }
  2197. // update last-child if it changes
  2198. $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
  2199. if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
  2200. $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
  2201. dbesc(datetime_convert()),
  2202. dbesc($parent_uri),
  2203. intval($importer['uid'])
  2204. );
  2205. $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  2206. intval($allow[0]['data']),
  2207. dbesc(datetime_convert()),
  2208. dbesc($item_id),
  2209. intval($importer['uid'])
  2210. );
  2211. update_thread_uri($item_id, $importer['uid']);
  2212. }
  2213. continue;
  2214. }
  2215. if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
  2216. // one way feed - no remote comment ability
  2217. $datarray['last-child'] = 0;
  2218. }
  2219. $datarray['parent-uri'] = $parent_uri;
  2220. $datarray['uid'] = $importer['uid'];
  2221. $datarray['contact-id'] = $contact['id'];
  2222. if(($datarray['verb'] === ACTIVITY_LIKE)
  2223. || ($datarray['verb'] === ACTIVITY_DISLIKE)
  2224. || ($datarray['verb'] === ACTIVITY_ATTEND)
  2225. || ($datarray['verb'] === ACTIVITY_ATTENDNO)
  2226. || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
  2227. $datarray['type'] = 'activity';
  2228. $datarray['gravity'] = GRAVITY_LIKE;
  2229. // only one like or dislike per person
  2230. // splitted into two queries for performance issues
  2231. $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
  2232. intval($datarray['uid']),
  2233. intval($datarray['contact-id']),
  2234. dbesc($datarray['verb']),
  2235. dbesc($parent_uri)
  2236. );
  2237. if($r && count($r))
  2238. continue;
  2239. $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
  2240. intval($datarray['uid']),
  2241. intval($datarray['contact-id']),
  2242. dbesc($datarray['verb']),
  2243. dbesc($parent_uri)
  2244. );
  2245. if($r && count($r))
  2246. continue;
  2247. }
  2248. if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
  2249. $xo = parse_xml_string($datarray['object'],false);
  2250. $xt = parse_xml_string($datarray['target'],false);
  2251. if($xt->type == ACTIVITY_OBJ_NOTE) {
  2252. $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
  2253. dbesc($xt->id),
  2254. intval($importer['importer_uid'])
  2255. );
  2256. if(! count($r))
  2257. continue;
  2258. // extract tag, if not duplicate, add to parent item
  2259. if($xo->id && $xo->content) {
  2260. $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
  2261. if(! (stristr($r[0]['tag'],$newtag))) {
  2262. q("UPDATE item SET tag = '%s' WHERE id = %d",
  2263. dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
  2264. intval($r[0]['id'])
  2265. );
  2266. create_tags_from_item($r[0]['id']);
  2267. }
  2268. }
  2269. }
  2270. }
  2271. $r = item_store($datarray,$force_parent);
  2272. continue;
  2273. }
  2274. else {
  2275. // Head post of a conversation. Have we seen it? If not, import it.
  2276. $item_id = $item->get_id();
  2277. $datarray = get_atom_elements($feed, $item, $contact);
  2278. if(is_array($contact)) {
  2279. if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
  2280. $datarray['author-name'] = $contact['name'];
  2281. if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
  2282. $datarray['author-link'] = $contact['url'];
  2283. if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
  2284. $datarray['author-avatar'] = $contact['thumb'];
  2285. }
  2286. if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
  2287. logger('consume_feed: no author information! ' . print_r($datarray,true));
  2288. continue;
  2289. }
  2290. // special handling for events
  2291. if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
  2292. $ev = bbtoevent($datarray['body']);
  2293. if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
  2294. $ev['uid'] = $importer['uid'];
  2295. $ev['uri'] = $item_id;
  2296. $ev['edited'] = $datarray['edited'];
  2297. $ev['private'] = $datarray['private'];
  2298. $ev['guid'] = $datarray['guid'];
  2299. if(is_array($contact))
  2300. $ev['cid'] = $contact['id'];
  2301. $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  2302. dbesc($item_id),
  2303. intval($importer['uid'])
  2304. );
  2305. if(count($r))
  2306. $ev['id'] = $r[0]['id'];
  2307. $xyz = event_store($ev);
  2308. continue;
  2309. }
  2310. }
  2311. if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
  2312. if(strlen($datarray['title']))
  2313. unset($datarray['title']);
  2314. $datarray['last-child'] = 1;
  2315. }
  2316. $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  2317. dbesc($item_id),
  2318. intval($importer['uid'])
  2319. );
  2320. // Update content if 'updated' changes
  2321. if(count($r)) {
  2322. if (edited_timestamp_is_newer($r[0], $datarray)) {
  2323. // do not accept (ignore) an earlier edit than one we currently have.
  2324. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
  2325. continue;
  2326. $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  2327. dbesc($datarray['title']),
  2328. dbesc($datarray['body']),
  2329. dbesc($datarray['tag']),
  2330. dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
  2331. dbesc(datetime_convert()),
  2332. dbesc($item_id),
  2333. intval($importer['uid'])
  2334. );
  2335. create_tags_from_itemuri($item_id, $importer['uid']);
  2336. update_thread_uri($item_id, $importer['uid']);
  2337. }
  2338. // update last-child if it changes
  2339. $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
  2340. if($allow && $allow[0]['data'] != $r[0]['last-child']) {
  2341. $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  2342. intval($allow[0]['data']),
  2343. dbesc(datetime_convert()),
  2344. dbesc($item_id),
  2345. intval($importer['uid'])
  2346. );
  2347. update_thread_uri($item_id, $importer['uid']);
  2348. }
  2349. continue;
  2350. }
  2351. if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
  2352. logger('consume-feed: New follower');
  2353. new_follower($importer,$contact,$datarray,$item);
  2354. return;
  2355. }
  2356. if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
  2357. lose_follower($importer,$contact,$datarray,$item);
  2358. return;
  2359. }
  2360. if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
  2361. logger('consume-feed: New friend request');
  2362. new_follower($importer,$contact,$datarray,$item,true);
  2363. return;
  2364. }
  2365. if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
  2366. lose_sharer($importer,$contact,$datarray,$item);
  2367. return;
  2368. }
  2369. if(! is_array($contact))
  2370. return;
  2371. if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
  2372. // one way feed - no remote comment ability
  2373. $datarray['last-child'] = 0;
  2374. }
  2375. if($contact['network'] === NETWORK_FEED)
  2376. $datarray['private'] = 2;
  2377. $datarray['parent-uri'] = $item_id;
  2378. $datarray['uid'] = $importer['uid'];
  2379. $datarray['contact-id'] = $contact['id'];
  2380. if(! link_compare($datarray['owner-link'],$contact['url'])) {
  2381. // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
  2382. // but otherwise there's a possible data mixup on the sender's system.
  2383. // the tgroup delivery code called from item_store will correct it if it's a forum,
  2384. // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
  2385. logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
  2386. $datarray['owner-name'] = $contact['name'];
  2387. $datarray['owner-link'] = $contact['url'];
  2388. $datarray['owner-avatar'] = $contact['thumb'];
  2389. }
  2390. // We've allowed "followers" to reach this point so we can decide if they are
  2391. // posting an @-tag delivery, which followers are allowed to do for certain
  2392. // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
  2393. if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
  2394. continue;
  2395. // This is my contact on another system, but it's really me.
  2396. // Turn this into a wall post.
  2397. $notify = item_is_remote_self($contact, $datarray);
  2398. $r = item_store($datarray, false, $notify);
  2399. logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
  2400. continue;
  2401. }
  2402. }
  2403. }
  2404. }
  2405. function item_is_remote_self($contact, &$datarray) {
  2406. $a = get_app();
  2407. if (!$contact['remote_self'])
  2408. return false;
  2409. // Prevent the forwarding of posts that are forwarded
  2410. if ($datarray["extid"] == NETWORK_DFRN)
  2411. return false;
  2412. // Prevent to forward already forwarded posts
  2413. if ($datarray["app"] == $a->get_hostname())
  2414. return false;
  2415. // Only forward posts
  2416. if ($datarray["verb"] != ACTIVITY_POST)
  2417. return false;
  2418. if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
  2419. return false;
  2420. $datarray2 = $datarray;
  2421. logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
  2422. if ($contact['remote_self'] == 2) {
  2423. $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
  2424. intval($contact['uid']));
  2425. if (count($r)) {
  2426. $datarray['contact-id'] = $r[0]["id"];
  2427. $datarray['owner-name'] = $r[0]["name"];
  2428. $datarray['owner-link'] = $r[0]["url"];
  2429. $datarray['owner-avatar'] = $r[0]["thumb"];
  2430. $datarray['author-name'] = $datarray['owner-name'];
  2431. $datarray['author-link'] = $datarray['owner-link'];
  2432. $datarray['author-avatar'] = $datarray['owner-avatar'];
  2433. }
  2434. if ($contact['network'] != NETWORK_FEED) {
  2435. $datarray["guid"] = get_guid(32);
  2436. unset($datarray["plink"]);
  2437. $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
  2438. $datarray["parent-uri"] = $datarray["uri"];
  2439. $datarray["extid"] = $contact['network'];
  2440. $urlpart = parse_url($datarray2['author-link']);
  2441. $datarray["app"] = $urlpart["host"];
  2442. } else
  2443. $datarray['private'] = 0;
  2444. }
  2445. if ($contact['network'] != NETWORK_FEED) {
  2446. // Store the original post
  2447. $r = item_store($datarray2, false, false);
  2448. logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
  2449. } else
  2450. $datarray["app"] = "Feed";
  2451. return true;
  2452. }
  2453. function local_delivery($importer,$data) {
  2454. $a = get_app();
  2455. logger(__function__, LOGGER_TRACE);
  2456. if($importer['readonly']) {
  2457. // We aren't receiving stuff from this person. But we will quietly ignore them
  2458. // rather than a blatant "go away" message.
  2459. logger('local_delivery: ignoring');
  2460. return 0;
  2461. //NOTREACHED
  2462. }
  2463. // Consume notification feed. This may differ from consuming a public feed in several ways
  2464. // - might contain email or friend suggestions
  2465. // - might contain remote followup to our message
  2466. // - in which case we need to accept it and then notify other conversants
  2467. // - we may need to send various email notifications
  2468. $feed = new SimplePie();
  2469. $feed->set_raw_data($data);
  2470. $feed->enable_order_by_date(false);
  2471. $feed->init();
  2472. if($feed->error())
  2473. logger('local_delivery: Error parsing XML: ' . $feed->error());
  2474. // Check at the feed level for updated contact name and/or photo
  2475. $name_updated = '';
  2476. $new_name = '';
  2477. $photo_timestamp = '';
  2478. $photo_url = '';
  2479. $contact_updated = '';
  2480. $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
  2481. // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
  2482. // if(! $rawtags)
  2483. // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
  2484. if($rawtags) {
  2485. $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
  2486. if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
  2487. $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
  2488. $new_name = $elems['name'][0]['data'];
  2489. // Manually checking for changed contact names
  2490. if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
  2491. $name_updated = date("c");
  2492. $photo_timestamp = date("c");
  2493. }
  2494. }
  2495. if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
  2496. if ($photo_timestamp == "")
  2497. $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
  2498. $photo_url = $elems['link'][0]['attribs']['']['href'];
  2499. }
  2500. }
  2501. if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
  2502. $contact_updated = $photo_timestamp;
  2503. logger('local_delivery: Updating photo for ' . $importer['name']);
  2504. require_once("include/Photo.php");
  2505. $photo_failure = false;
  2506. $have_photo = false;
  2507. $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
  2508. intval($importer['id']),
  2509. intval($importer['importer_uid'])
  2510. );
  2511. if(count($r)) {
  2512. $resource_id = $r[0]['resource-id'];
  2513. $have_photo = true;
  2514. }
  2515. else {
  2516. $resource_id = photo_new_resource();
  2517. }
  2518. $img_str = fetch_url($photo_url,true);
  2519. // guess mimetype from headers or filename
  2520. $type = guess_image_type($photo_url,true);
  2521. $img = new Photo($img_str, $type);
  2522. if($img->is_valid()) {
  2523. if($have_photo) {
  2524. q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
  2525. dbesc($resource_id),
  2526. intval($importer['id']),
  2527. intval($importer['importer_uid'])
  2528. );
  2529. }
  2530. $img->scaleImageSquare(175);
  2531. $hash = $resource_id;
  2532. $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
  2533. $img->scaleImage(80);
  2534. $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
  2535. $img->scaleImage(48);
  2536. $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
  2537. $a = get_app();
  2538. q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
  2539. WHERE `uid` = %d AND `id` = %d",
  2540. dbesc(datetime_convert()),
  2541. dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
  2542. dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
  2543. dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
  2544. intval($importer['importer_uid']),
  2545. intval($importer['id'])
  2546. );
  2547. }
  2548. }
  2549. if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
  2550. if ($name_updated > $contact_updated)
  2551. $contact_updated = $name_updated;
  2552. $r = q("select * from contact where uid = %d and id = %d limit 1",
  2553. intval($importer['importer_uid']),
  2554. intval($importer['id'])
  2555. );
  2556. $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
  2557. dbesc(notags(trim($new_name))),
  2558. dbesc(datetime_convert()),
  2559. intval($importer['importer_uid']),
  2560. intval($importer['id'])
  2561. );
  2562. // do our best to update the name on content items
  2563. if(count($r)) {
  2564. q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
  2565. dbesc(notags(trim($new_name))),
  2566. dbesc($r[0]['name']),
  2567. dbesc($r[0]['url']),
  2568. intval($importer['importer_uid'])
  2569. );
  2570. }
  2571. }
  2572. if ($contact_updated AND $new_name AND $photo_url)
  2573. poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
  2574. // Currently unsupported - needs a lot of work
  2575. $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
  2576. if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
  2577. $base = $reloc[0]['child'][NAMESPACE_DFRN];
  2578. $newloc = array();
  2579. $newloc['uid'] = $importer['importer_uid'];
  2580. $newloc['cid'] = $importer['id'];
  2581. $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
  2582. $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
  2583. $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
  2584. $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
  2585. $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
  2586. $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
  2587. $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
  2588. $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
  2589. $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
  2590. $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
  2591. /** relocated user must have original key pair */
  2592. /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
  2593. $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
  2594. logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
  2595. // update contact
  2596. $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
  2597. intval($importer['id']),
  2598. intval($importer['importer_uid']));
  2599. if ($r === false)
  2600. return 1;
  2601. $old = $r[0];
  2602. $x = q("UPDATE contact SET
  2603. name = '%s',
  2604. photo = '%s',
  2605. thumb = '%s',
  2606. micro = '%s',
  2607. url = '%s',
  2608. nurl = '%s',
  2609. request = '%s',
  2610. confirm = '%s',
  2611. notify = '%s',
  2612. poll = '%s',
  2613. `site-pubkey` = '%s'
  2614. WHERE id=%d AND uid=%d;",
  2615. dbesc($newloc['name']),
  2616. dbesc($newloc['photo']),
  2617. dbesc($newloc['thumb']),
  2618. dbesc($newloc['micro']),
  2619. dbesc($newloc['url']),
  2620. dbesc(normalise_link($newloc['url'])),
  2621. dbesc($newloc['request']),
  2622. dbesc($newloc['confirm']),
  2623. dbesc($newloc['notify']),
  2624. dbesc($newloc['poll']),
  2625. dbesc($newloc['sitepubkey']),
  2626. intval($importer['id']),
  2627. intval($importer['importer_uid']));
  2628. if ($x === false)
  2629. return 1;
  2630. // update items
  2631. $fields = array(
  2632. 'owner-link' => array($old['url'], $newloc['url']),
  2633. 'author-link' => array($old['url'], $newloc['url']),
  2634. 'owner-avatar' => array($old['photo'], $newloc['photo']),
  2635. 'author-avatar' => array($old['photo'], $newloc['photo']),
  2636. );
  2637. foreach ($fields as $n=>$f){
  2638. $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
  2639. $n, dbesc($f[1]),
  2640. $n, dbesc($f[0]),
  2641. intval($importer['importer_uid']));
  2642. if ($x === false)
  2643. return 1;
  2644. }
  2645. // TODO
  2646. // merge with current record, current contents have priority
  2647. // update record, set url-updated
  2648. // update profile photos
  2649. // schedule a scan?
  2650. return 0;
  2651. }
  2652. // handle friend suggestion notification
  2653. $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
  2654. if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
  2655. $base = $sugg[0]['child'][NAMESPACE_DFRN];
  2656. $fsugg = array();
  2657. $fsugg['uid'] = $importer['importer_uid'];
  2658. $fsugg['cid'] = $importer['id'];
  2659. $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
  2660. $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
  2661. $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
  2662. $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
  2663. $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
  2664. // Does our member already have a friend matching this description?
  2665. $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
  2666. dbesc($fsugg['name']),
  2667. dbesc(normalise_link($fsugg['url'])),
  2668. intval($fsugg['uid'])
  2669. );
  2670. if(count($r))
  2671. return 0;
  2672. // Do we already have an fcontact record for this person?
  2673. $fid = 0;
  2674. $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
  2675. dbesc($fsugg['url']),
  2676. dbesc($fsugg['name']),
  2677. dbesc($fsugg['request'])
  2678. );
  2679. if(count($r)) {
  2680. $fid = $r[0]['id'];
  2681. // OK, we do. Do we already have an introduction for this person ?
  2682. $r = q("select id from intro where uid = %d and fid = %d limit 1",
  2683. intval($fsugg['uid']),
  2684. intval($fid)
  2685. );
  2686. if(count($r))
  2687. return 0;
  2688. }
  2689. if(! $fid)
  2690. $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
  2691. dbesc($fsugg['name']),
  2692. dbesc($fsugg['url']),
  2693. dbesc($fsugg['photo']),
  2694. dbesc($fsugg['request'])
  2695. );
  2696. $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
  2697. dbesc($fsugg['url']),
  2698. dbesc($fsugg['name']),
  2699. dbesc($fsugg['request'])
  2700. );
  2701. if(count($r)) {
  2702. $fid = $r[0]['id'];
  2703. }
  2704. // database record did not get created. Quietly give up.
  2705. else
  2706. return 0;
  2707. $hash = random_string();
  2708. $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
  2709. VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
  2710. intval($fsugg['uid']),
  2711. intval($fid),
  2712. intval($fsugg['cid']),
  2713. dbesc($fsugg['body']),
  2714. dbesc($hash),
  2715. dbesc(datetime_convert()),
  2716. intval(0)
  2717. );
  2718. notification(array(
  2719. 'type' => NOTIFY_SUGGEST,
  2720. 'notify_flags' => $importer['notify-flags'],
  2721. 'language' => $importer['language'],
  2722. 'to_name' => $importer['username'],
  2723. 'to_email' => $importer['email'],
  2724. 'uid' => $importer['importer_uid'],
  2725. 'item' => $fsugg,
  2726. 'link' => $a->get_baseurl() . '/notifications/intros',
  2727. 'source_name' => $importer['name'],
  2728. 'source_link' => $importer['url'],
  2729. 'source_photo' => $importer['photo'],
  2730. 'verb' => ACTIVITY_REQ_FRIEND,
  2731. 'otype' => 'intro'
  2732. ));
  2733. return 0;
  2734. }
  2735. $ismail = false;
  2736. $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
  2737. if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
  2738. logger('local_delivery: private message received');
  2739. $ismail = true;
  2740. $base = $rawmail[0]['child'][NAMESPACE_DFRN];
  2741. $msg = array();
  2742. $msg['uid'] = $importer['importer_uid'];
  2743. $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
  2744. $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
  2745. $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
  2746. $msg['contact-id'] = $importer['id'];
  2747. $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
  2748. $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
  2749. $msg['seen'] = 0;
  2750. $msg['replied'] = 0;
  2751. $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
  2752. $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
  2753. $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
  2754. dbesc_array($msg);
  2755. $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
  2756. . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
  2757. // send notifications.
  2758. require_once('include/enotify.php');
  2759. $notif_params = array(
  2760. 'type' => NOTIFY_MAIL,
  2761. 'notify_flags' => $importer['notify-flags'],
  2762. 'language' => $importer['language'],
  2763. 'to_name' => $importer['username'],
  2764. 'to_email' => $importer['email'],
  2765. 'uid' => $importer['importer_uid'],
  2766. 'item' => $msg,
  2767. 'source_name' => $msg['from-name'],
  2768. 'source_link' => $importer['url'],
  2769. 'source_photo' => $importer['thumb'],
  2770. 'verb' => ACTIVITY_POST,
  2771. 'otype' => 'mail'
  2772. );
  2773. notification($notif_params);
  2774. return 0;
  2775. // NOTREACHED
  2776. }
  2777. $community_page = 0;
  2778. $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
  2779. if($rawtags) {
  2780. $community_page = intval($rawtags[0]['data']);
  2781. }
  2782. if(intval($importer['forum']) != $community_page) {
  2783. q("update contact set forum = %d where id = %d",
  2784. intval($community_page),
  2785. intval($importer['id'])
  2786. );
  2787. $importer['forum'] = (string) $community_page;
  2788. }
  2789. logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
  2790. // process any deleted entries
  2791. $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
  2792. if(is_array($del_entries) && count($del_entries)) {
  2793. foreach($del_entries as $dentry) {
  2794. $deleted = false;
  2795. if(isset($dentry['attribs']['']['ref'])) {
  2796. $uri = $dentry['attribs']['']['ref'];
  2797. $deleted = true;
  2798. if(isset($dentry['attribs']['']['when'])) {
  2799. $when = $dentry['attribs']['']['when'];
  2800. $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
  2801. }
  2802. else
  2803. $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
  2804. }
  2805. if($deleted) {
  2806. // check for relayed deletes to our conversation
  2807. $is_reply = false;
  2808. $r = q("select * from item where uri = '%s' and uid = %d limit 1",
  2809. dbesc($uri),
  2810. intval($importer['importer_uid'])
  2811. );
  2812. if(count($r)) {
  2813. $parent_uri = $r[0]['parent-uri'];
  2814. if($r[0]['id'] != $r[0]['parent'])
  2815. $is_reply = true;
  2816. }
  2817. if($is_reply) {
  2818. $community = false;
  2819. if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
  2820. $sql_extra = '';
  2821. $community = true;
  2822. logger('local_delivery: possible community delete');
  2823. }
  2824. else
  2825. $sql_extra = " and contact.self = 1 and item.wall = 1 ";
  2826. // was the top-level post for this reply written by somebody on this site?
  2827. // Specifically, the recipient?
  2828. $is_a_remote_delete = false;
  2829. // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
  2830. $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
  2831. `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
  2832. INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  2833. WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
  2834. AND `item`.`uid` = %d
  2835. $sql_extra
  2836. LIMIT 1",
  2837. dbesc($parent_uri),
  2838. dbesc($parent_uri),
  2839. dbesc($parent_uri),
  2840. intval($importer['importer_uid'])
  2841. );
  2842. if($r && count($r))
  2843. $is_a_remote_delete = true;
  2844. // Does this have the characteristics of a community or private group comment?
  2845. // If it's a reply to a wall post on a community/prvgroup page it's a
  2846. // valid community comment. Also forum_mode makes it valid for sure.
  2847. // If neither, it's not.
  2848. if($is_a_remote_delete && $community) {
  2849. if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
  2850. $is_a_remote_delete = false;
  2851. logger('local_delivery: not a community delete');
  2852. }
  2853. }
  2854. if($is_a_remote_delete) {
  2855. logger('local_delivery: received remote delete');
  2856. }
  2857. }
  2858. $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
  2859. WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
  2860. dbesc($uri),
  2861. intval($importer['importer_uid']),
  2862. intval($importer['id'])
  2863. );
  2864. if(count($r)) {
  2865. $item = $r[0];
  2866. if($item['deleted'])
  2867. continue;
  2868. logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
  2869. if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
  2870. logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
  2871. event_delete($item['event-id']);
  2872. }
  2873. if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
  2874. $xo = parse_xml_string($item['object'],false);
  2875. $xt = parse_xml_string($item['target'],false);
  2876. if($xt->type === ACTIVITY_OBJ_NOTE) {
  2877. $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
  2878. dbesc($xt->id),
  2879. intval($importer['importer_uid'])
  2880. );
  2881. if(count($i)) {
  2882. // For tags, the owner cannot remove the tag on the author's copy of the post.
  2883. $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
  2884. $author_remove = (($item['origin'] && $item['self']) ? true : false);
  2885. $author_copy = (($item['origin']) ? true : false);
  2886. if($owner_remove && $author_copy)
  2887. continue;
  2888. if($author_remove || $owner_remove) {
  2889. $tags = explode(',',$i[0]['tag']);
  2890. $newtags = array();
  2891. if(count($tags)) {
  2892. foreach($tags as $tag)
  2893. if(trim($tag) !== trim($xo->body))
  2894. $newtags[] = trim($tag);
  2895. }
  2896. q("update item set tag = '%s' where id = %d",
  2897. dbesc(implode(',',$newtags)),
  2898. intval($i[0]['id'])
  2899. );
  2900. create_tags_from_item($i[0]['id']);
  2901. }
  2902. }
  2903. }
  2904. }
  2905. if($item['uri'] == $item['parent-uri']) {
  2906. $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
  2907. `body` = '', `title` = ''
  2908. WHERE `parent-uri` = '%s' AND `uid` = %d",
  2909. dbesc($when),
  2910. dbesc(datetime_convert()),
  2911. dbesc($item['uri']),
  2912. intval($importer['importer_uid'])
  2913. );
  2914. create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
  2915. create_files_from_itemuri($item['uri'], $importer['importer_uid']);
  2916. update_thread_uri($item['uri'], $importer['importer_uid']);
  2917. }
  2918. else {
  2919. $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
  2920. `body` = '', `title` = ''
  2921. WHERE `uri` = '%s' AND `uid` = %d",
  2922. dbesc($when),
  2923. dbesc(datetime_convert()),
  2924. dbesc($uri),
  2925. intval($importer['importer_uid'])
  2926. );
  2927. create_tags_from_itemuri($uri, $importer['importer_uid']);
  2928. create_files_from_itemuri($uri, $importer['importer_uid']);
  2929. update_thread_uri($uri, $importer['importer_uid']);
  2930. if($item['last-child']) {
  2931. // ensure that last-child is set in case the comment that had it just got wiped.
  2932. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
  2933. dbesc(datetime_convert()),
  2934. dbesc($item['parent-uri']),
  2935. intval($item['uid'])
  2936. );
  2937. // who is the last child now?
  2938. $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
  2939. ORDER BY `created` DESC LIMIT 1",
  2940. dbesc($item['parent-uri']),
  2941. intval($importer['importer_uid'])
  2942. );
  2943. if(count($r)) {
  2944. q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
  2945. intval($r[0]['id'])
  2946. );
  2947. }
  2948. }
  2949. // if this is a relayed delete, propagate it to other recipients
  2950. if($is_a_remote_delete)
  2951. proc_run('php',"include/notifier.php","drop",$item['id']);
  2952. }
  2953. }
  2954. }
  2955. }
  2956. }
  2957. foreach($feed->get_items() as $item) {
  2958. $is_reply = false;
  2959. $item_id = $item->get_id();
  2960. $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
  2961. if(isset($rawthread[0]['attribs']['']['ref'])) {
  2962. $is_reply = true;
  2963. $parent_uri = $rawthread[0]['attribs']['']['ref'];
  2964. }
  2965. if($is_reply) {
  2966. $community = false;
  2967. if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
  2968. $sql_extra = '';
  2969. $community = true;
  2970. logger('local_delivery: possible community reply');
  2971. }
  2972. else
  2973. $sql_extra = " and contact.self = 1 and item.wall = 1 ";
  2974. // was the top-level post for this reply written by somebody on this site?
  2975. // Specifically, the recipient?
  2976. $is_a_remote_comment = false;
  2977. $top_uri = $parent_uri;
  2978. $r = q("select `item`.`parent-uri` from `item`
  2979. WHERE `item`.`uri` = '%s'
  2980. LIMIT 1",
  2981. dbesc($parent_uri)
  2982. );
  2983. if($r && count($r)) {
  2984. $top_uri = $r[0]['parent-uri'];
  2985. // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
  2986. $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
  2987. `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
  2988. INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  2989. WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
  2990. AND `item`.`uid` = %d
  2991. $sql_extra
  2992. LIMIT 1",
  2993. dbesc($top_uri),
  2994. dbesc($top_uri),
  2995. dbesc($top_uri),
  2996. intval($importer['importer_uid'])
  2997. );
  2998. if($r && count($r))
  2999. $is_a_remote_comment = true;
  3000. }
  3001. // Does this have the characteristics of a community or private group comment?
  3002. // If it's a reply to a wall post on a community/prvgroup page it's a
  3003. // valid community comment. Also forum_mode makes it valid for sure.
  3004. // If neither, it's not.
  3005. if($is_a_remote_comment && $community) {
  3006. if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
  3007. $is_a_remote_comment = false;
  3008. logger('local_delivery: not a community reply');
  3009. }
  3010. }
  3011. if($is_a_remote_comment) {
  3012. logger('local_delivery: received remote comment');
  3013. $is_like = false;
  3014. // remote reply to our post. Import and then notify everybody else.
  3015. $datarray = get_atom_elements($feed, $item);
  3016. $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  3017. dbesc($item_id),
  3018. intval($importer['importer_uid'])
  3019. );
  3020. // Update content if 'updated' changes
  3021. if(count($r)) {
  3022. $iid = $r[0]['id'];
  3023. if (edited_timestamp_is_newer($r[0], $datarray)) {
  3024. // do not accept (ignore) an earlier edit than one we currently have.
  3025. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
  3026. continue;
  3027. logger('received updated comment' , LOGGER_DEBUG);
  3028. $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  3029. dbesc($datarray['title']),
  3030. dbesc($datarray['body']),
  3031. dbesc($datarray['tag']),
  3032. dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
  3033. dbesc(datetime_convert()),
  3034. dbesc($item_id),
  3035. intval($importer['importer_uid'])
  3036. );
  3037. create_tags_from_itemuri($item_id, $importer['importer_uid']);
  3038. proc_run('php',"include/notifier.php","comment-import",$iid);
  3039. }
  3040. continue;
  3041. }
  3042. $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
  3043. intval($importer['importer_uid'])
  3044. );
  3045. $datarray['type'] = 'remote-comment';
  3046. $datarray['wall'] = 1;
  3047. $datarray['parent-uri'] = $parent_uri;
  3048. $datarray['uid'] = $importer['importer_uid'];
  3049. $datarray['owner-name'] = $own[0]['name'];
  3050. $datarray['owner-link'] = $own[0]['url'];
  3051. $datarray['owner-avatar'] = $own[0]['thumb'];
  3052. $datarray['contact-id'] = $importer['id'];
  3053. if(($datarray['verb'] === ACTIVITY_LIKE)
  3054. || ($datarray['verb'] === ACTIVITY_DISLIKE)
  3055. || ($datarray['verb'] === ACTIVITY_ATTEND)
  3056. || ($datarray['verb'] === ACTIVITY_ATTENDNO)
  3057. || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
  3058. $is_like = true;
  3059. $datarray['type'] = 'activity';
  3060. $datarray['gravity'] = GRAVITY_LIKE;
  3061. $datarray['last-child'] = 0;
  3062. // only one like or dislike per person
  3063. // splitted into two queries for performance issues
  3064. $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
  3065. intval($datarray['uid']),
  3066. intval($datarray['contact-id']),
  3067. dbesc($datarray['verb']),
  3068. dbesc($datarray['parent-uri'])
  3069. );
  3070. if($r && count($r))
  3071. continue;
  3072. $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
  3073. intval($datarray['uid']),
  3074. intval($datarray['contact-id']),
  3075. dbesc($datarray['verb']),
  3076. dbesc($datarray['parent-uri'])
  3077. );
  3078. if($r && count($r))
  3079. continue;
  3080. }
  3081. if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
  3082. $xo = parse_xml_string($datarray['object'],false);
  3083. $xt = parse_xml_string($datarray['target'],false);
  3084. if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
  3085. // fetch the parent item
  3086. $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
  3087. dbesc($xt->id),
  3088. intval($importer['importer_uid'])
  3089. );
  3090. if(! count($tagp))
  3091. continue;
  3092. // extract tag, if not duplicate, and this user allows tags, add to parent item
  3093. if($xo->id && $xo->content) {
  3094. $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
  3095. if(! (stristr($tagp[0]['tag'],$newtag))) {
  3096. $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
  3097. intval($importer['importer_uid'])
  3098. );
  3099. if(count($i) && ! intval($i[0]['blocktags'])) {
  3100. q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
  3101. dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
  3102. intval($tagp[0]['id']),
  3103. dbesc(datetime_convert()),
  3104. dbesc(datetime_convert())
  3105. );
  3106. create_tags_from_item($tagp[0]['id']);
  3107. }
  3108. }
  3109. }
  3110. }
  3111. }
  3112. $posted_id = item_store($datarray);
  3113. $parent = 0;
  3114. if($posted_id) {
  3115. $datarray["id"] = $posted_id;
  3116. $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  3117. intval($posted_id),
  3118. intval($importer['importer_uid'])
  3119. );
  3120. if(count($r)) {
  3121. $parent = $r[0]['parent'];
  3122. $parent_uri = $r[0]['parent-uri'];
  3123. }
  3124. if(! $is_like) {
  3125. $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
  3126. dbesc(datetime_convert()),
  3127. intval($importer['importer_uid']),
  3128. intval($r[0]['parent'])
  3129. );
  3130. $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
  3131. dbesc(datetime_convert()),
  3132. intval($importer['importer_uid']),
  3133. intval($posted_id)
  3134. );
  3135. }
  3136. if($posted_id && $parent) {
  3137. proc_run('php',"include/notifier.php","comment-import","$posted_id");
  3138. if((! $is_like) && (! $importer['self'])) {
  3139. require_once('include/enotify.php');
  3140. notification(array(
  3141. 'type' => NOTIFY_COMMENT,
  3142. 'notify_flags' => $importer['notify-flags'],
  3143. 'language' => $importer['language'],
  3144. 'to_name' => $importer['username'],
  3145. 'to_email' => $importer['email'],
  3146. 'uid' => $importer['importer_uid'],
  3147. 'item' => $datarray,
  3148. 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
  3149. 'source_name' => stripslashes($datarray['author-name']),
  3150. 'source_link' => $datarray['author-link'],
  3151. 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
  3152. ? $importer['thumb'] : $datarray['author-avatar']),
  3153. 'verb' => ACTIVITY_POST,
  3154. 'otype' => 'item',
  3155. 'parent' => $parent,
  3156. 'parent_uri' => $parent_uri,
  3157. ));
  3158. }
  3159. }
  3160. return 0;
  3161. // NOTREACHED
  3162. }
  3163. }
  3164. else {
  3165. // regular comment that is part of this total conversation. Have we seen it? If not, import it.
  3166. $item_id = $item->get_id();
  3167. $datarray = get_atom_elements($feed,$item);
  3168. if($importer['rel'] == CONTACT_IS_FOLLOWER)
  3169. continue;
  3170. $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  3171. dbesc($item_id),
  3172. intval($importer['importer_uid'])
  3173. );
  3174. // Update content if 'updated' changes
  3175. if(count($r)) {
  3176. if (edited_timestamp_is_newer($r[0], $datarray)) {
  3177. // do not accept (ignore) an earlier edit than one we currently have.
  3178. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
  3179. continue;
  3180. $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  3181. dbesc($datarray['title']),
  3182. dbesc($datarray['body']),
  3183. dbesc($datarray['tag']),
  3184. dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
  3185. dbesc(datetime_convert()),
  3186. dbesc($item_id),
  3187. intval($importer['importer_uid'])
  3188. );
  3189. create_tags_from_itemuri($item_id, $importer['importer_uid']);
  3190. }
  3191. // update last-child if it changes
  3192. $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
  3193. if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
  3194. $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
  3195. dbesc(datetime_convert()),
  3196. dbesc($parent_uri),
  3197. intval($importer['importer_uid'])
  3198. );
  3199. $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  3200. intval($allow[0]['data']),
  3201. dbesc(datetime_convert()),
  3202. dbesc($item_id),
  3203. intval($importer['importer_uid'])
  3204. );
  3205. }
  3206. continue;
  3207. }
  3208. $datarray['parent-uri'] = $parent_uri;
  3209. $datarray['uid'] = $importer['importer_uid'];
  3210. $datarray['contact-id'] = $importer['id'];
  3211. if(($datarray['verb'] === ACTIVITY_LIKE)
  3212. || ($datarray['verb'] === ACTIVITY_DISLIKE)
  3213. || ($datarray['verb'] === ACTIVITY_ATTEND)
  3214. || ($datarray['verb'] === ACTIVITY_ATTENDNO)
  3215. || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
  3216. $datarray['type'] = 'activity';
  3217. $datarray['gravity'] = GRAVITY_LIKE;
  3218. // only one like or dislike per person
  3219. // splitted into two queries for performance issues
  3220. $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
  3221. intval($datarray['uid']),
  3222. intval($datarray['contact-id']),
  3223. dbesc($datarray['verb']),
  3224. dbesc($parent_uri)
  3225. );
  3226. if($r && count($r))
  3227. continue;
  3228. $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
  3229. intval($datarray['uid']),
  3230. intval($datarray['contact-id']),
  3231. dbesc($datarray['verb']),
  3232. dbesc($parent_uri)
  3233. );
  3234. if($r && count($r))
  3235. continue;
  3236. }
  3237. if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
  3238. $xo = parse_xml_string($datarray['object'],false);
  3239. $xt = parse_xml_string($datarray['target'],false);
  3240. if($xt->type == ACTIVITY_OBJ_NOTE) {
  3241. $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
  3242. dbesc($xt->id),
  3243. intval($importer['importer_uid'])
  3244. );
  3245. if(! count($r))
  3246. continue;
  3247. // extract tag, if not duplicate, add to parent item
  3248. if($xo->content) {
  3249. if(! (stristr($r[0]['tag'],trim($xo->content)))) {
  3250. q("UPDATE item SET tag = '%s' WHERE id = %d",
  3251. dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
  3252. intval($r[0]['id'])
  3253. );
  3254. create_tags_from_item($r[0]['id']);
  3255. }
  3256. }
  3257. }
  3258. }
  3259. $posted_id = item_store($datarray);
  3260. // find out if our user is involved in this conversation and wants to be notified.
  3261. if(!x($datarray['type']) || $datarray['type'] != 'activity') {
  3262. $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
  3263. dbesc($top_uri),
  3264. intval($importer['importer_uid'])
  3265. );
  3266. if(count($myconv)) {
  3267. $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
  3268. // first make sure this isn't our own post coming back to us from a wall-to-wall event
  3269. if(! link_compare($datarray['author-link'],$importer_url)) {
  3270. foreach($myconv as $conv) {
  3271. // now if we find a match, it means we're in this conversation
  3272. if(! link_compare($conv['author-link'],$importer_url))
  3273. continue;
  3274. require_once('include/enotify.php');
  3275. $conv_parent = $conv['parent'];
  3276. notification(array(
  3277. 'type' => NOTIFY_COMMENT,
  3278. 'notify_flags' => $importer['notify-flags'],
  3279. 'language' => $importer['language'],
  3280. 'to_name' => $importer['username'],
  3281. 'to_email' => $importer['email'],
  3282. 'uid' => $importer['importer_uid'],
  3283. 'item' => $datarray,
  3284. 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
  3285. 'source_name' => stripslashes($datarray['author-name']),
  3286. 'source_link' => $datarray['author-link'],
  3287. 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
  3288. ? $importer['thumb'] : $datarray['author-avatar']),
  3289. 'verb' => ACTIVITY_POST,
  3290. 'otype' => 'item',
  3291. 'parent' => $conv_parent,
  3292. 'parent_uri' => $parent_uri
  3293. ));
  3294. // only send one notification
  3295. break;
  3296. }
  3297. }
  3298. }
  3299. }
  3300. continue;
  3301. }
  3302. }
  3303. else {
  3304. // Head post of a conversation. Have we seen it? If not, import it.
  3305. $item_id = $item->get_id();
  3306. $datarray = get_atom_elements($feed,$item);
  3307. if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
  3308. $ev = bbtoevent($datarray['body']);
  3309. if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
  3310. $ev['cid'] = $importer['id'];
  3311. $ev['uid'] = $importer['uid'];
  3312. $ev['uri'] = $item_id;
  3313. $ev['edited'] = $datarray['edited'];
  3314. $ev['private'] = $datarray['private'];
  3315. $ev['guid'] = $datarray['guid'];
  3316. $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  3317. dbesc($item_id),
  3318. intval($importer['uid'])
  3319. );
  3320. if(count($r))
  3321. $ev['id'] = $r[0]['id'];
  3322. $xyz = event_store($ev);
  3323. continue;
  3324. }
  3325. }
  3326. $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
  3327. dbesc($item_id),
  3328. intval($importer['importer_uid'])
  3329. );
  3330. // Update content if 'updated' changes
  3331. if(count($r)) {
  3332. if (edited_timestamp_is_newer($r[0], $datarray)) {
  3333. // do not accept (ignore) an earlier edit than one we currently have.
  3334. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
  3335. continue;
  3336. $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  3337. dbesc($datarray['title']),
  3338. dbesc($datarray['body']),
  3339. dbesc($datarray['tag']),
  3340. dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
  3341. dbesc(datetime_convert()),
  3342. dbesc($item_id),
  3343. intval($importer['importer_uid'])
  3344. );
  3345. create_tags_from_itemuri($item_id, $importer['importer_uid']);
  3346. update_thread_uri($item_id, $importer['importer_uid']);
  3347. }
  3348. // update last-child if it changes
  3349. $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
  3350. if($allow && $allow[0]['data'] != $r[0]['last-child']) {
  3351. $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
  3352. intval($allow[0]['data']),
  3353. dbesc(datetime_convert()),
  3354. dbesc($item_id),
  3355. intval($importer['importer_uid'])
  3356. );
  3357. }
  3358. continue;
  3359. }
  3360. $datarray['parent-uri'] = $item_id;
  3361. $datarray['uid'] = $importer['importer_uid'];
  3362. $datarray['contact-id'] = $importer['id'];
  3363. if(! link_compare($datarray['owner-link'],$importer['url'])) {
  3364. // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
  3365. // but otherwise there's a possible data mixup on the sender's system.
  3366. // the tgroup delivery code called from item_store will correct it if it's a forum,
  3367. // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
  3368. logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
  3369. $datarray['owner-name'] = $importer['senderName'];
  3370. $datarray['owner-link'] = $importer['url'];
  3371. $datarray['owner-avatar'] = $importer['thumb'];
  3372. }
  3373. if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
  3374. continue;
  3375. // This is my contact on another system, but it's really me.
  3376. // Turn this into a wall post.
  3377. $notify = item_is_remote_self($importer, $datarray);
  3378. $posted_id = item_store($datarray, false, $notify);
  3379. if(stristr($datarray['verb'],ACTIVITY_POKE)) {
  3380. $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
  3381. if(! $verb)
  3382. continue;
  3383. $xo = parse_xml_string($datarray['object'],false);
  3384. if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
  3385. // somebody was poked/prodded. Was it me?
  3386. $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
  3387. foreach($links->link as $l) {
  3388. $atts = $l->attributes();
  3389. switch($atts['rel']) {
  3390. case "alternate":
  3391. $Blink = $atts['href'];
  3392. break;
  3393. default:
  3394. break;
  3395. }
  3396. }
  3397. if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
  3398. // send a notification
  3399. require_once('include/enotify.php');
  3400. notification(array(
  3401. 'type' => NOTIFY_POKE,
  3402. 'notify_flags' => $importer['notify-flags'],
  3403. 'language' => $importer['language'],
  3404. 'to_name' => $importer['username'],
  3405. 'to_email' => $importer['email'],
  3406. 'uid' => $importer['importer_uid'],
  3407. 'item' => $datarray,
  3408. 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
  3409. 'source_name' => stripslashes($datarray['author-name']),
  3410. 'source_link' => $datarray['author-link'],
  3411. 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
  3412. ? $importer['thumb'] : $datarray['author-avatar']),
  3413. 'verb' => $datarray['verb'],
  3414. 'otype' => 'person',
  3415. 'activity' => $verb,
  3416. 'parent' => $datarray['parent']
  3417. ));
  3418. }
  3419. }
  3420. }
  3421. continue;
  3422. }
  3423. }
  3424. return 0;
  3425. // NOTREACHED
  3426. }
  3427. function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
  3428. $url = notags(trim($datarray['author-link']));
  3429. $name = notags(trim($datarray['author-name']));
  3430. $photo = notags(trim($datarray['author-avatar']));
  3431. if (is_object($item)) {
  3432. $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
  3433. if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
  3434. $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
  3435. } else
  3436. $nick = $item;
  3437. if(is_array($contact)) {
  3438. if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
  3439. || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
  3440. $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
  3441. intval(CONTACT_IS_FRIEND),
  3442. intval($contact['id']),
  3443. intval($importer['uid'])
  3444. );
  3445. }
  3446. // send email notification to owner?
  3447. }
  3448. else {
  3449. // create contact record
  3450. $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
  3451. `blocked`, `readonly`, `pending`, `writable` )
  3452. VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
  3453. intval($importer['uid']),
  3454. dbesc(datetime_convert()),
  3455. dbesc($url),
  3456. dbesc(normalise_link($url)),
  3457. dbesc($name),
  3458. dbesc($nick),
  3459. dbesc($photo),
  3460. dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
  3461. intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
  3462. );
  3463. $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
  3464. intval($importer['uid']),
  3465. dbesc($url)
  3466. );
  3467. if(count($r))
  3468. $contact_record = $r[0];
  3469. // create notification
  3470. $hash = random_string();
  3471. if(is_array($contact_record)) {
  3472. $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
  3473. VALUES ( %d, %d, 0, 0, '%s', '%s' )",
  3474. intval($importer['uid']),
  3475. intval($contact_record['id']),
  3476. dbesc($hash),
  3477. dbesc(datetime_convert())
  3478. );
  3479. }
  3480. $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
  3481. intval($importer['uid'])
  3482. );
  3483. $a = get_app();
  3484. if(count($r)) {
  3485. if(intval($r[0]['def_gid'])) {
  3486. require_once('include/group.php');
  3487. group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
  3488. }
  3489. if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
  3490. in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
  3491. notification(array(
  3492. 'type' => NOTIFY_INTRO,
  3493. 'notify_flags' => $r[0]['notify-flags'],
  3494. 'language' => $r[0]['language'],
  3495. 'to_name' => $r[0]['username'],
  3496. 'to_email' => $r[0]['email'],
  3497. 'uid' => $r[0]['uid'],
  3498. 'link' => $a->get_baseurl() . '/notifications/intro',
  3499. 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
  3500. 'source_link' => $contact_record['url'],
  3501. 'source_photo' => $contact_record['photo'],
  3502. 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
  3503. 'otype' => 'intro'
  3504. ));
  3505. }
  3506. }
  3507. }
  3508. }
  3509. function lose_follower($importer,$contact,$datarray,$item) {
  3510. if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
  3511. q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
  3512. intval(CONTACT_IS_SHARING),
  3513. intval($contact['id'])
  3514. );
  3515. }
  3516. else {
  3517. contact_remove($contact['id']);
  3518. }
  3519. }
  3520. function lose_sharer($importer,$contact,$datarray,$item) {
  3521. if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
  3522. q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
  3523. intval(CONTACT_IS_FOLLOWER),
  3524. intval($contact['id'])
  3525. );
  3526. }
  3527. else {
  3528. contact_remove($contact['id']);
  3529. }
  3530. }
  3531. function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
  3532. $a = get_app();
  3533. if(is_array($importer)) {
  3534. $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
  3535. intval($importer['uid'])
  3536. );
  3537. }
  3538. // Diaspora has different message-ids in feeds than they do
  3539. // through the direct Diaspora protocol. If we try and use
  3540. // the feed, we'll get duplicates. So don't.
  3541. if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
  3542. return;
  3543. $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
  3544. // Use a single verify token, even if multiple hubs
  3545. $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
  3546. $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
  3547. logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
  3548. if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
  3549. $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
  3550. dbesc($verify_token),
  3551. intval($contact['id'])
  3552. );
  3553. }
  3554. post_url($url,$params);
  3555. logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
  3556. return;
  3557. }
  3558. function atom_author($tag,$name,$uri,$h,$w,$photo) {
  3559. $o = '';
  3560. if(! $tag)
  3561. return $o;
  3562. $name = xmlify($name);
  3563. $uri = xmlify($uri);
  3564. $h = intval($h);
  3565. $w = intval($w);
  3566. $photo = xmlify($photo);
  3567. $o .= "<$tag>\r\n";
  3568. $o .= "\t<name>$name</name>\r\n";
  3569. $o .= "\t<uri>$uri</uri>\r\n";
  3570. $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
  3571. $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
  3572. if ($tag == "author") {
  3573. $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
  3574. `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
  3575. `profile`.`homepage`,`contact`.`nick` FROM `profile`
  3576. INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
  3577. INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
  3578. WHERE `profile`.`is-default` AND `contact`.`self` AND
  3579. NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
  3580. dbesc(normalise_link($uri)));
  3581. if ($r) {
  3582. $location = '';
  3583. if($r[0]['locality'])
  3584. $location .= $r[0]['locality'];
  3585. if($r[0]['region']) {
  3586. if($location)
  3587. $location .= ', ';
  3588. $location .= $r[0]['region'];
  3589. }
  3590. if($r[0]['country-name']) {
  3591. if($location)
  3592. $location .= ', ';
  3593. $location .= $r[0]['country-name'];
  3594. }
  3595. $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
  3596. $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
  3597. $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
  3598. $o .= "\t<poco:address>\r\n";
  3599. $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
  3600. $o .= "\t</poco:address>\r\n";
  3601. $o .= "\t<poco:urls>\r\n";
  3602. $o .= "\t<poco:type>homepage</poco:type>\r\n";
  3603. $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
  3604. $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
  3605. $o .= "\t</poco:urls>\r\n";
  3606. }
  3607. }
  3608. call_hooks('atom_author', $o);
  3609. $o .= "</$tag>\r\n";
  3610. return $o;
  3611. }
  3612. function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
  3613. $a = get_app();
  3614. if(! $item['parent'])
  3615. return;
  3616. if($item['deleted'])
  3617. return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
  3618. if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
  3619. $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
  3620. else
  3621. $body = $item['body'];
  3622. $o = "\r\n\r\n<entry>\r\n";
  3623. if(is_array($author))
  3624. $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
  3625. else
  3626. $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb']));
  3627. if(strlen($item['owner-name']))
  3628. $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
  3629. if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
  3630. $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
  3631. $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
  3632. $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
  3633. }
  3634. $htmlbody = $body;
  3635. if ($item['title'] != "")
  3636. $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
  3637. $htmlbody = bbcode($htmlbody, false, false, 7);
  3638. $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
  3639. $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
  3640. $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
  3641. $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
  3642. $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
  3643. $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
  3644. $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
  3645. $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
  3646. if($comment)
  3647. $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
  3648. if($item['location']) {
  3649. $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
  3650. $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
  3651. }
  3652. if($item['coord'])
  3653. $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
  3654. if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
  3655. $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
  3656. if($item['extid'])
  3657. $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
  3658. if($item['bookmark'])
  3659. $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
  3660. if($item['app'])
  3661. $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
  3662. if($item['guid'])
  3663. $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
  3664. if($item['signed_text']) {
  3665. $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
  3666. $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
  3667. }
  3668. $verb = construct_verb($item);
  3669. $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
  3670. $actobj = construct_activity_object($item);
  3671. if(strlen($actobj))
  3672. $o .= $actobj;
  3673. $actarg = construct_activity_target($item);
  3674. if(strlen($actarg))
  3675. $o .= $actarg;
  3676. $tags = item_getfeedtags($item);
  3677. if(count($tags)) {
  3678. foreach($tags as $t)
  3679. if (($type != 'html') OR ($t[0] != "@"))
  3680. $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
  3681. }
  3682. // To-Do:
  3683. // To support these elements, the API needs to be enhanced
  3684. //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
  3685. //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
  3686. //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
  3687. $o .= item_get_attachment($item);
  3688. $o .= item_getfeedattach($item);
  3689. $mentioned = get_mentions($item);
  3690. if($mentioned)
  3691. $o .= $mentioned;
  3692. call_hooks('atom_entry', $o);
  3693. $o .= '</entry>' . "\r\n";
  3694. return $o;
  3695. }
  3696. function fix_private_photos($s, $uid, $item = null, $cid = 0) {
  3697. if(get_config('system','disable_embedded'))
  3698. return $s;
  3699. $a = get_app();
  3700. logger('fix_private_photos: check for photos', LOGGER_DEBUG);
  3701. $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
  3702. $orig_body = $s;
  3703. $new_body = '';
  3704. $img_start = strpos($orig_body, '[img');
  3705. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  3706. $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
  3707. while( ($img_st_close !== false) && ($img_len !== false) ) {
  3708. $img_st_close++; // make it point to AFTER the closing bracket
  3709. $image = substr($orig_body, $img_start + $img_st_close, $img_len);
  3710. logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
  3711. if(stristr($image , $site . '/photo/')) {
  3712. // Only embed locally hosted photos
  3713. $replace = false;
  3714. $i = basename($image);
  3715. $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
  3716. $x = strpos($i,'-');
  3717. if($x) {
  3718. $res = substr($i,$x+1);
  3719. $i = substr($i,0,$x);
  3720. $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
  3721. dbesc($i),
  3722. intval($res),
  3723. intval($uid)
  3724. );
  3725. if($r) {
  3726. // Check to see if we should replace this photo link with an embedded image
  3727. // 1. No need to do so if the photo is public
  3728. // 2. If there's a contact-id provided, see if they're in the access list
  3729. // for the photo. If so, embed it.
  3730. // 3. Otherwise, if we have an item, see if the item permissions match the photo
  3731. // permissions, regardless of order but first check to see if they're an exact
  3732. // match to save some processing overhead.
  3733. if(has_permissions($r[0])) {
  3734. if($cid) {
  3735. $recips = enumerate_permissions($r[0]);
  3736. if(in_array($cid, $recips)) {
  3737. $replace = true;
  3738. }
  3739. }
  3740. elseif($item) {
  3741. if(compare_permissions($item,$r[0]))
  3742. $replace = true;
  3743. }
  3744. }
  3745. if($replace) {
  3746. $data = $r[0]['data'];
  3747. $type = $r[0]['type'];
  3748. // If a custom width and height were specified, apply before embedding
  3749. if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
  3750. logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
  3751. $width = intval($match[1]);
  3752. $height = intval($match[2]);
  3753. $ph = new Photo($data, $type);
  3754. if($ph->is_valid()) {
  3755. $ph->scaleImage(max($width, $height));
  3756. $data = $ph->imageString();
  3757. $type = $ph->getType();
  3758. }
  3759. }
  3760. logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
  3761. $image = 'data:' . $type . ';base64,' . base64_encode($data);
  3762. logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
  3763. }
  3764. }
  3765. }
  3766. }
  3767. $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
  3768. $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
  3769. if($orig_body === false)
  3770. $orig_body = '';
  3771. $img_start = strpos($orig_body, '[img');
  3772. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  3773. $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
  3774. }
  3775. $new_body = $new_body . $orig_body;
  3776. return($new_body);
  3777. }
  3778. function has_permissions($obj) {
  3779. if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
  3780. return true;
  3781. return false;
  3782. }
  3783. function compare_permissions($obj1,$obj2) {
  3784. // first part is easy. Check that these are exactly the same.
  3785. if(($obj1['allow_cid'] == $obj2['allow_cid'])
  3786. && ($obj1['allow_gid'] == $obj2['allow_gid'])
  3787. && ($obj1['deny_cid'] == $obj2['deny_cid'])
  3788. && ($obj1['deny_gid'] == $obj2['deny_gid']))
  3789. return true;
  3790. // This is harder. Parse all the permissions and compare the resulting set.
  3791. $recipients1 = enumerate_permissions($obj1);
  3792. $recipients2 = enumerate_permissions($obj2);
  3793. sort($recipients1);
  3794. sort($recipients2);
  3795. if($recipients1 == $recipients2)
  3796. return true;
  3797. return false;
  3798. }
  3799. // returns an array of contact-ids that are allowed to see this object
  3800. function enumerate_permissions($obj) {
  3801. require_once('include/group.php');
  3802. $allow_people = expand_acl($obj['allow_cid']);
  3803. $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
  3804. $deny_people = expand_acl($obj['deny_cid']);
  3805. $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
  3806. $recipients = array_unique(array_merge($allow_people,$allow_groups));
  3807. $deny = array_unique(array_merge($deny_people,$deny_groups));
  3808. $recipients = array_diff($recipients,$deny);
  3809. return $recipients;
  3810. }
  3811. function item_getfeedtags($item) {
  3812. $ret = array();
  3813. $matches = false;
  3814. $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
  3815. if($cnt) {
  3816. for($x = 0; $x < $cnt; $x ++) {
  3817. if($matches[1][$x])
  3818. $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
  3819. }
  3820. }
  3821. $matches = false;
  3822. $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
  3823. if($cnt) {
  3824. for($x = 0; $x < $cnt; $x ++) {
  3825. if($matches[1][$x])
  3826. $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
  3827. }
  3828. }
  3829. return $ret;
  3830. }
  3831. function item_get_attachment($item) {
  3832. $o = "";
  3833. $siteinfo = get_attached_data($item["body"]);
  3834. switch($siteinfo["type"]) {
  3835. case 'link':
  3836. $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
  3837. break;
  3838. case 'photo':
  3839. $imgdata = get_photo_info($siteinfo["image"]);
  3840. $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
  3841. break;
  3842. case 'video':
  3843. $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
  3844. break;
  3845. default:
  3846. break;
  3847. }
  3848. return $o;
  3849. }
  3850. function item_getfeedattach($item) {
  3851. $ret = '';
  3852. $arr = explode('[/attach],',$item['attach']);
  3853. if(count($arr)) {
  3854. foreach($arr as $r) {
  3855. $matches = false;
  3856. $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
  3857. if($cnt) {
  3858. $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
  3859. if(intval($matches[2]))
  3860. $ret .= 'length="' . intval($matches[2]) . '" ';
  3861. if($matches[4] !== ' ')
  3862. $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
  3863. $ret .= ' />' . "\r\n";
  3864. }
  3865. }
  3866. }
  3867. return $ret;
  3868. }
  3869. function item_expire($uid, $days, $network = "", $force = false) {
  3870. if((! $uid) || ($days < 1))
  3871. return;
  3872. // $expire_network_only = save your own wall posts
  3873. // and just expire conversations started by others
  3874. $expire_network_only = get_pconfig($uid,'expire','network_only');
  3875. $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
  3876. if ($network != "") {
  3877. $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
  3878. // There is an index "uid_network_received" but not "uid_network_created"
  3879. // This avoids the creation of another index just for one purpose.
  3880. // And it doesn't really matter wether to look at "received" or "created"
  3881. $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
  3882. } else
  3883. $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
  3884. $r = q("SELECT * FROM `item`
  3885. WHERE `uid` = %d $range
  3886. AND `id` = `parent`
  3887. $sql_extra
  3888. AND `deleted` = 0",
  3889. intval($uid),
  3890. intval($days)
  3891. );
  3892. if(! count($r))
  3893. return;
  3894. $expire_items = get_pconfig($uid, 'expire','items');
  3895. $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
  3896. // Forcing expiring of items - but not notes and marked items
  3897. if ($force)
  3898. $expire_items = true;
  3899. $expire_notes = get_pconfig($uid, 'expire','notes');
  3900. $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
  3901. $expire_starred = get_pconfig($uid, 'expire','starred');
  3902. $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
  3903. $expire_photos = get_pconfig($uid, 'expire','photos');
  3904. $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
  3905. logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
  3906. foreach($r as $item) {
  3907. // don't expire filed items
  3908. if(strpos($item['file'],'[') !== false)
  3909. continue;
  3910. // Only expire posts, not photos and photo comments
  3911. if($expire_photos==0 && strlen($item['resource-id']))
  3912. continue;
  3913. if($expire_starred==0 && intval($item['starred']))
  3914. continue;
  3915. if($expire_notes==0 && $item['type']=='note')
  3916. continue;
  3917. if($expire_items==0 && $item['type']!='note')
  3918. continue;
  3919. drop_item($item['id'],false);
  3920. }
  3921. proc_run('php',"include/notifier.php","expire","$uid");
  3922. }
  3923. function drop_items($items) {
  3924. $uid = 0;
  3925. if(! local_user() && ! remote_user())
  3926. return;
  3927. if(count($items)) {
  3928. foreach($items as $item) {
  3929. $owner = drop_item($item,false);
  3930. if($owner && ! $uid)
  3931. $uid = $owner;
  3932. }
  3933. }
  3934. // multiple threads may have been deleted, send an expire notification
  3935. if($uid)
  3936. proc_run('php',"include/notifier.php","expire","$uid");
  3937. }
  3938. function drop_item($id,$interactive = true) {
  3939. $a = get_app();
  3940. // locate item to be deleted
  3941. $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
  3942. intval($id)
  3943. );
  3944. if(! count($r)) {
  3945. if(! $interactive)
  3946. return 0;
  3947. notice( t('Item not found.') . EOL);
  3948. goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
  3949. }
  3950. $item = $r[0];
  3951. $owner = $item['uid'];
  3952. $cid = 0;
  3953. // check if logged in user is either the author or owner of this item
  3954. if(is_array($_SESSION['remote'])) {
  3955. foreach($_SESSION['remote'] as $visitor) {
  3956. if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
  3957. $cid = $visitor['cid'];
  3958. break;
  3959. }
  3960. }
  3961. }
  3962. if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
  3963. // Check if we should do HTML-based delete confirmation
  3964. if($_REQUEST['confirm']) {
  3965. // <form> can't take arguments in its "action" parameter
  3966. // so add any arguments as hidden inputs
  3967. $query = explode_querystring($a->query_string);
  3968. $inputs = array();
  3969. foreach($query['args'] as $arg) {
  3970. if(strpos($arg, 'confirm=') === false) {
  3971. $arg_parts = explode('=', $arg);
  3972. $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
  3973. }
  3974. }
  3975. return replace_macros(get_markup_template('confirm.tpl'), array(
  3976. '$method' => 'get',
  3977. '$message' => t('Do you really want to delete this item?'),
  3978. '$extra_inputs' => $inputs,
  3979. '$confirm' => t('Yes'),
  3980. '$confirm_url' => $query['base'],
  3981. '$confirm_name' => 'confirmed',
  3982. '$cancel' => t('Cancel'),
  3983. ));
  3984. }
  3985. // Now check how the user responded to the confirmation query
  3986. if($_REQUEST['canceled']) {
  3987. goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
  3988. }
  3989. logger('delete item: ' . $item['id'], LOGGER_DEBUG);
  3990. // delete the item
  3991. $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
  3992. dbesc(datetime_convert()),
  3993. dbesc(datetime_convert()),
  3994. intval($item['id'])
  3995. );
  3996. create_tags_from_item($item['id']);
  3997. create_files_from_item($item['id']);
  3998. delete_thread($item['id'], $item['parent-uri']);
  3999. // clean up categories and tags so they don't end up as orphans
  4000. $matches = false;
  4001. $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
  4002. if($cnt) {
  4003. foreach($matches as $mtch) {
  4004. file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
  4005. }
  4006. }
  4007. $matches = false;
  4008. $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
  4009. if($cnt) {
  4010. foreach($matches as $mtch) {
  4011. file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
  4012. }
  4013. }
  4014. // If item is a link to a photo resource, nuke all the associated photos
  4015. // (visitors will not have photo resources)
  4016. // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
  4017. // generate a resource-id and therefore aren't intimately linked to the item.
  4018. if(strlen($item['resource-id'])) {
  4019. q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
  4020. dbesc($item['resource-id']),
  4021. intval($item['uid'])
  4022. );
  4023. // ignore the result
  4024. }
  4025. // If item is a link to an event, nuke the event record.
  4026. if(intval($item['event-id'])) {
  4027. q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
  4028. intval($item['event-id']),
  4029. intval($item['uid'])
  4030. );
  4031. // ignore the result
  4032. }
  4033. // If item has attachments, drop them
  4034. foreach(explode(",",$item['attach']) as $attach){
  4035. preg_match("|attach/(\d+)|", $attach, $matches);
  4036. q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
  4037. intval($matches[1]),
  4038. local_user()
  4039. );
  4040. // ignore the result
  4041. }
  4042. // clean up item_id and sign meta-data tables
  4043. /*
  4044. // Old code - caused very long queries and warning entries in the mysql logfiles:
  4045. $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
  4046. intval($item['id']),
  4047. intval($item['uid'])
  4048. );
  4049. $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
  4050. intval($item['id']),
  4051. intval($item['uid'])
  4052. );
  4053. */
  4054. // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
  4055. // Creating list of parents
  4056. $r = q("select id from item where parent = %d and uid = %d",
  4057. intval($item['id']),
  4058. intval($item['uid'])
  4059. );
  4060. $parentid = "";
  4061. foreach ($r AS $row) {
  4062. if ($parentid != "")
  4063. $parentid .= ", ";
  4064. $parentid .= $row["id"];
  4065. }
  4066. // Now delete them
  4067. if ($parentid != "") {
  4068. $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
  4069. $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
  4070. }
  4071. // If it's the parent of a comment thread, kill all the kids
  4072. if($item['uri'] == $item['parent-uri']) {
  4073. $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
  4074. WHERE `parent-uri` = '%s' AND `uid` = %d ",
  4075. dbesc(datetime_convert()),
  4076. dbesc(datetime_convert()),
  4077. dbesc($item['parent-uri']),
  4078. intval($item['uid'])
  4079. );
  4080. create_tags_from_itemuri($item['parent-uri'], $item['uid']);
  4081. create_files_from_itemuri($item['parent-uri'], $item['uid']);
  4082. delete_thread_uri($item['parent-uri'], $item['uid']);
  4083. // ignore the result
  4084. }
  4085. else {
  4086. // ensure that last-child is set in case the comment that had it just got wiped.
  4087. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
  4088. dbesc(datetime_convert()),
  4089. dbesc($item['parent-uri']),
  4090. intval($item['uid'])
  4091. );
  4092. // who is the last child now?
  4093. $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d ORDER BY `edited` DESC LIMIT 1",
  4094. dbesc($item['parent-uri']),
  4095. intval($item['uid'])
  4096. );
  4097. if(count($r)) {
  4098. q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
  4099. intval($r[0]['id'])
  4100. );
  4101. }
  4102. // Add a relayable_retraction signature for Diaspora.
  4103. store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
  4104. }
  4105. $drop_id = intval($item['id']);
  4106. // send the notification upstream/downstream as the case may be
  4107. proc_run('php',"include/notifier.php","drop","$drop_id");
  4108. if(! $interactive)
  4109. return $owner;
  4110. goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
  4111. //NOTREACHED
  4112. }
  4113. else {
  4114. if(! $interactive)
  4115. return 0;
  4116. notice( t('Permission denied.') . EOL);
  4117. goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
  4118. //NOTREACHED
  4119. }
  4120. }
  4121. function first_post_date($uid,$wall = false) {
  4122. $r = q("select id, created from item
  4123. where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
  4124. and id = parent
  4125. order by created asc limit 1",
  4126. intval($uid),
  4127. intval($wall ? 1 : 0)
  4128. );
  4129. if(count($r)) {
  4130. // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
  4131. return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
  4132. }
  4133. return false;
  4134. }
  4135. /* modified posted_dates() {below} to arrange the list in years */
  4136. function list_post_dates($uid, $wall) {
  4137. $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
  4138. $dthen = first_post_date($uid, $wall);
  4139. if(! $dthen)
  4140. return array();
  4141. // Set the start and end date to the beginning of the month
  4142. $dnow = substr($dnow,0,8).'01';
  4143. $dthen = substr($dthen,0,8).'01';
  4144. $ret = array();
  4145. // Starting with the current month, get the first and last days of every
  4146. // month down to and including the month of the first post
  4147. while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
  4148. $dyear = intval(substr($dnow,0,4));
  4149. $dstart = substr($dnow,0,8) . '01';
  4150. $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
  4151. $start_month = datetime_convert('','',$dstart,'Y-m-d');
  4152. $end_month = datetime_convert('','',$dend,'Y-m-d');
  4153. $str = day_translate(datetime_convert('','',$dnow,'F'));
  4154. if(! $ret[$dyear])
  4155. $ret[$dyear] = array();
  4156. $ret[$dyear][] = array($str,$end_month,$start_month);
  4157. $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
  4158. }
  4159. return $ret;
  4160. }
  4161. function posted_dates($uid,$wall) {
  4162. $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
  4163. $dthen = first_post_date($uid,$wall);
  4164. if(! $dthen)
  4165. return array();
  4166. // Set the start and end date to the beginning of the month
  4167. $dnow = substr($dnow,0,8).'01';
  4168. $dthen = substr($dthen,0,8).'01';
  4169. $ret = array();
  4170. // Starting with the current month, get the first and last days of every
  4171. // month down to and including the month of the first post
  4172. while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
  4173. $dstart = substr($dnow,0,8) . '01';
  4174. $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
  4175. $start_month = datetime_convert('','',$dstart,'Y-m-d');
  4176. $end_month = datetime_convert('','',$dend,'Y-m-d');
  4177. $str = day_translate(datetime_convert('','',$dnow,'F Y'));
  4178. $ret[] = array($str,$end_month,$start_month);
  4179. $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
  4180. }
  4181. return $ret;
  4182. }
  4183. function posted_date_widget($url,$uid,$wall) {
  4184. $o = '';
  4185. if(! feature_enabled($uid,'archives'))
  4186. return $o;
  4187. // For former Facebook folks that left because of "timeline"
  4188. /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
  4189. return $o;*/
  4190. $visible_years = get_pconfig($uid,'system','archive_visible_years');
  4191. if(! $visible_years)
  4192. $visible_years = 5;
  4193. $ret = list_post_dates($uid,$wall);
  4194. if(! count($ret))
  4195. return $o;
  4196. $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
  4197. $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
  4198. $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
  4199. '$title' => t('Archives'),
  4200. '$size' => $visible_years,
  4201. '$cutoff_year' => $cutoff_year,
  4202. '$cutoff' => $cutoff,
  4203. '$url' => $url,
  4204. '$dates' => $ret,
  4205. '$showmore' => t('show more')
  4206. ));
  4207. return $o;
  4208. }
  4209. function store_diaspora_retract_sig($item, $user, $baseurl) {
  4210. // Note that we can't add a target_author_signature
  4211. // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
  4212. // the comment, that means we're the home of the post, and Diaspora will only
  4213. // check the parent_author_signature of retractions that it doesn't have to relay further
  4214. //
  4215. // I don't think this function gets called for an "unlike," but I'll check anyway
  4216. $enabled = intval(get_config('system','diaspora_enabled'));
  4217. if(! $enabled) {
  4218. logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
  4219. return;
  4220. }
  4221. logger('drop_item: storing diaspora retraction signature');
  4222. $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
  4223. if(local_user() == $item['uid']) {
  4224. $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
  4225. $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
  4226. }
  4227. else {
  4228. $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
  4229. $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
  4230. );
  4231. if(count($r)) {
  4232. // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
  4233. // only handles DFRN deletes
  4234. $handle_baseurl_start = strpos($r['url'],'://') + 3;
  4235. $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
  4236. $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
  4237. $authorsig = '';
  4238. }
  4239. }
  4240. if(isset($handle))
  4241. q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
  4242. intval($item['id']),
  4243. dbesc($signed_text),
  4244. dbesc($authorsig),
  4245. dbesc($handle)
  4246. );
  4247. return;
  4248. }