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.
 
 
 
 
 
 

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