3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15 require_once('mod/share.php');
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
20 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21 $public_feed = (($dfrn_id) ? false : true);
22 $starred = false; // not yet implemented, possible security issues
25 if($public_feed && $a->argc > 2) {
26 for($x = 2; $x < $a->argc; $x++) {
27 if($a->argv[$x] == 'converse')
29 if($a->argv[$x] == 'starred')
31 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32 $category = $a->argv[$x+1];
38 // default permissions - anonymous user
40 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
42 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
52 $owner_id = $owner['user_uid'];
53 $owner_nick = $owner['nickname'];
55 $birthday = feed_birthday($owner_id,$owner['timezone']);
64 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
68 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '1:' . $dfrn_id;
72 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
73 $my_id = '0:' . $dfrn_id;
80 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
88 require_once('include/security.php');
89 $groups = init_groups_visitor($contact['id']);
92 for($x = 0; $x < count($groups); $x ++)
93 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
94 $gs = implode('|', $groups);
97 $gs = '<<>>' ; // Impossible to match
99 $sql_extra = sprintf("
100 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
101 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
102 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
103 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
105 intval($contact['id']),
106 intval($contact['id']),
117 if(! strlen($last_update))
118 $last_update = 'now -30 days';
120 if(isset($category)) {
121 $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` ",
122 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
123 //$sql_extra .= file_tag_file_query('item',$category,'category');
128 $sql_extra .= " AND `contact`.`self` = 1 ";
131 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
133 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
134 // dbesc($check_date),
136 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
137 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
138 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
139 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
140 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
141 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
142 FROM `item` $sql_post_table
143 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
144 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
145 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
146 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
147 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
149 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
155 // Will check further below if this actually returned results.
156 // We will provide an empty feed if that is the case.
160 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
164 $hubxml = feed_hublinks();
166 $salmon = feed_salmonlinks($owner_nick);
168 $alternatelink = $owner['url'];
171 $alternatelink .= "/category/".$category;
173 $atom .= replace_macros($feed_template, array(
174 '$version' => xmlify(FRIENDICA_VERSION),
175 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
176 '$feed_title' => xmlify($owner['name']),
177 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
179 '$salmon' => $salmon,
180 '$alternatelink' => xmlify($alternatelink),
181 '$name' => xmlify($owner['name']),
182 '$profile_page' => xmlify($owner['url']),
183 '$photo' => xmlify($owner['photo']),
184 '$thumb' => xmlify($owner['thumb']),
185 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
186 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
187 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
188 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
189 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
192 call_hooks('atom_feed', $atom);
194 if(! count($items)) {
196 call_hooks('atom_feed_end', $atom);
198 $atom .= '</feed>' . "\r\n";
202 foreach($items as $item) {
204 // prevent private email from leaking.
205 if($item['network'] === NETWORK_MAIL)
208 // public feeds get html, our own nodes use bbcode
212 // catch any email that's in a public conversation and make sure it doesn't leak
220 $atom .= atom_entry($item,$type,null,$owner,true);
223 call_hooks('atom_feed_end', $atom);
225 $atom .= '</feed>' . "\r\n";
231 function construct_verb($item) {
233 return $item['verb'];
234 return ACTIVITY_POST;
237 function construct_activity_object($item) {
239 if($item['object']) {
240 $o = '<as:object>' . "\r\n";
241 $r = parse_xml_string($item['object'],false);
247 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
249 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
251 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
253 if(substr($r->link,0,1) === '<') {
254 // patch up some facebook "like" activity objects that got stored incorrectly
255 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
256 // we can probably remove this hack here and in the following function in a few months time.
257 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
258 $r->link = str_replace('&','&', $r->link);
259 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
263 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
266 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
267 $o .= '</as:object>' . "\r\n";
274 function construct_activity_target($item) {
276 if($item['target']) {
277 $o = '<as:target>' . "\r\n";
278 $r = parse_xml_string($item['target'],false);
282 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
284 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
286 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
288 if(substr($r->link,0,1) === '<') {
289 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
290 $r->link = str_replace('&','&', $r->link);
291 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
295 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
298 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
299 $o .= '</as:target>' . "\r\n";
308 * The purpose of this function is to apply system message length limits to
309 * imported messages without including any embedded photos in the length
311 if(! function_exists('limit_body_size')) {
312 function limit_body_size($body) {
314 // logger('limit_body_size: start', LOGGER_DEBUG);
316 $maxlen = get_max_import_size();
318 // If the length of the body, including the embedded images, is smaller
319 // than the maximum, then don't waste time looking for the images
320 if($maxlen && (strlen($body) > $maxlen)) {
322 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
329 $img_start = strpos($orig_body, '[img');
330 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
331 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
332 while(($img_st_close !== false) && ($img_end !== false)) {
334 $img_st_close++; // make it point to AFTER the closing bracket
335 $img_end += $img_start;
336 $img_end += strlen('[/img]');
338 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
339 // This is an embedded image
341 if( ($textlen + $img_start) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_start);
350 $textlen += $img_start;
353 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
357 if( ($textlen + $img_end) > $maxlen ) {
358 if($textlen < $maxlen) {
359 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
360 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
365 $new_body = $new_body . substr($orig_body, 0, $img_end);
366 $textlen += $img_end;
369 $orig_body = substr($orig_body, $img_end);
371 if($orig_body === false) // in case the body ends on a closing image tag
374 $img_start = strpos($orig_body, '[img');
375 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
376 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
379 if( ($textlen + strlen($orig_body)) > $maxlen) {
380 if($textlen < $maxlen) {
381 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
382 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
387 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
388 $new_body = $new_body . $orig_body;
389 $textlen += strlen($orig_body);
398 function title_is_body($title, $body) {
400 $title = strip_tags($title);
401 $title = trim($title);
402 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
403 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
405 $body = strip_tags($body);
407 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
408 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
410 if (strlen($title) < strlen($body))
411 $body = substr($body, 0, strlen($title));
413 if (($title != $body) and (substr($title, -3) == "...")) {
414 $pos = strrpos($title, "...");
416 $title = substr($title, 0, $pos);
417 $body = substr($body, 0, $pos);
421 return($title == $body);
426 function get_atom_elements($feed, $item, $contact = array()) {
428 require_once('library/HTMLPurifier.auto.php');
429 require_once('include/html2bbcode.php');
431 $best_photo = array();
435 $author = $item->get_author();
437 $res['author-name'] = unxmlify($author->get_name());
438 $res['author-link'] = unxmlify($author->get_link());
441 $res['author-name'] = unxmlify($feed->get_title());
442 $res['author-link'] = unxmlify($feed->get_permalink());
444 $res['uri'] = unxmlify($item->get_id());
445 $res['title'] = unxmlify($item->get_title());
446 $res['body'] = unxmlify($item->get_content());
447 $res['plink'] = unxmlify($item->get_link(0));
449 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
450 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
452 $res['body'] = nl2br($res['body']);
455 // removing the content of the title if its identically to the body
456 // This helps with auto generated titles e.g. from tumblr
457 if (title_is_body($res["title"], $res["body"]))
461 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
465 // look for a photo. We should check media size and find the best one,
466 // but for now let's just find any author photo
468 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
470 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
471 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
472 foreach($base as $link) {
473 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
474 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
475 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
480 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
482 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
483 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 if($base && count($base)) {
485 foreach($base as $link) {
486 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
487 $res['author-link'] = unxmlify($link['attribs']['']['href']);
488 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
489 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
490 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
496 // No photo/profile-link on the item - look at the feed level
498 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
499 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(! $res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(! (x($res,'author-avatar'))) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
530 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
531 if($apps && $apps[0]['attribs']['']['source']) {
532 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
533 if($res['app'] === 'web')
534 $res['app'] = 'OStatus';
537 // base64 encoded json structure representing Diaspora signature
539 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
541 $res['dsprsig'] = unxmlify($dsig[0]['data']);
544 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
546 $res['guid'] = unxmlify($dguid[0]['data']);
548 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
550 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
554 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
557 $have_real_body = false;
559 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
561 $have_real_body = true;
562 $res['body'] = $rawenv[0]['data'];
563 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
564 // make sure nobody is trying to sneak some html tags by us
565 $res['body'] = notags(base64url_decode($res['body']));
569 $res['body'] = limit_body_size($res['body']);
571 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
572 // the content type. Our own network only emits text normally, though it might have been converted to
573 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
574 // have to assume it is all html and needs to be purified.
576 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
577 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
578 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
581 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
583 $res['body'] = reltoabs($res['body'],$base_url);
585 $res['body'] = html2bb_video($res['body']);
587 $res['body'] = oembed_html2bbcode($res['body']);
589 $config = HTMLPurifier_Config::createDefault();
590 $config->set('Cache.DefinitionImpl', null);
592 // we shouldn't need a whitelist, because the bbcode converter
593 // will strip out any unsupported tags.
595 $purifier = new HTMLPurifier($config);
596 $res['body'] = $purifier->purify($res['body']);
598 $res['body'] = @html2bbcode($res['body']);
602 elseif(! $have_real_body) {
604 // it's not one of our messages and it has no tags
605 // so it's probably just text. We'll escape it just to be safe.
607 $res['body'] = escape_tags($res['body']);
611 // this tag is obsolete but we keep it for really old sites
613 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
614 if($allow && $allow[0]['data'] == 1)
615 $res['last-child'] = 1;
617 $res['last-child'] = 0;
619 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
620 if($private && intval($private[0]['data']) > 0)
621 $res['private'] = intval($private[0]['data']);
625 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
626 if($extid && $extid[0]['data'])
627 $res['extid'] = $extid[0]['data'];
629 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
631 $res['location'] = unxmlify($rawlocation[0]['data']);
634 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
636 $res['created'] = unxmlify($rawcreated[0]['data']);
639 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
641 $res['edited'] = unxmlify($rawedited[0]['data']);
643 if((x($res,'edited')) && (! (x($res,'created'))))
644 $res['created'] = $res['edited'];
646 if(! $res['created'])
647 $res['created'] = $item->get_date('c');
650 $res['edited'] = $item->get_date('c');
653 // Disallow time travelling posts
655 $d1 = strtotime($res['created']);
656 $d2 = strtotime($res['edited']);
657 $d3 = strtotime('now');
660 $res['created'] = datetime_convert();
662 $res['edited'] = datetime_convert();
664 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
665 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
667 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
668 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
669 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
671 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
672 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
674 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
675 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
677 foreach($base as $link) {
678 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
679 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
680 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
685 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
687 $res['coord'] = unxmlify($rawgeo[0]['data']);
689 if ($contact["network"] == NETWORK_FEED) {
690 $res['verb'] = ACTIVITY_POST;
691 $res['object-type'] = ACTIVITY_OBJ_NOTE;
694 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
696 // select between supported verbs
699 $res['verb'] = unxmlify($rawverb[0]['data']);
702 // translate OStatus unfollow to activity streams if it happened to get selected
704 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
705 $res['verb'] = ACTIVITY_UNFOLLOW;
707 $cats = $item->get_categories();
710 foreach($cats as $cat) {
711 $term = $cat->get_term();
713 $term = $cat->get_label();
714 $scheme = $cat->get_scheme();
715 if($scheme && $term && stristr($scheme,'X-DFRN:'))
716 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
718 $tag_arr[] = notags(trim($term));
720 $res['tag'] = implode(',', $tag_arr);
723 $attach = $item->get_enclosures();
726 foreach($attach as $att) {
727 $len = intval($att->get_length());
728 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
729 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
730 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
731 if(strpos($type,';'))
732 $type = substr($type,0,strpos($type,';'));
733 if((! $link) || (strpos($link,'http') !== 0))
739 $type = 'application/octet-stream';
741 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
743 $res['attach'] = implode(',', $att_arr);
746 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
749 $res['object'] = '<object>' . "\n";
750 $child = $rawobj[0]['child'];
751 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
752 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
753 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
756 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
758 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
760 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
761 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
764 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
765 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
766 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
767 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
769 $body = html2bb_video($body);
771 $config = HTMLPurifier_Config::createDefault();
772 $config->set('Cache.DefinitionImpl', null);
774 $purifier = new HTMLPurifier($config);
775 $body = $purifier->purify($body);
776 $body = html2bbcode($body);
779 $res['object'] .= '<content>' . $body . '</content>' . "\n";
782 $res['object'] .= '</object>' . "\n";
785 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
788 $res['target'] = '<target>' . "\n";
789 $child = $rawobj[0]['child'];
790 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
791 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
794 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
796 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
798 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
799 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
802 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
803 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
804 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
805 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
807 $body = html2bb_video($body);
809 $config = HTMLPurifier_Config::createDefault();
810 $config->set('Cache.DefinitionImpl', null);
812 $purifier = new HTMLPurifier($config);
813 $body = $purifier->purify($body);
814 $body = html2bbcode($body);
817 $res['target'] .= '<content>' . $body . '</content>' . "\n";
820 $res['target'] .= '</target>' . "\n";
823 // This is some experimental stuff. By now retweets are shown with "RT:"
824 // But: There is data so that the message could be shown similar to native retweets
825 // There is some better way to parse this array - but it didn't worked for me.
826 $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"];
827 if (is_array($child)) {
828 logger('get_atom_elements: Looking for status.net repeated message');
830 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
831 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
832 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
833 $uri = $author["uri"][0]["data"];
834 $name = $author["name"][0]["data"];
835 $avatar = @array_shift($author["link"][2]["attribs"]);
836 $avatar = $avatar["href"];
838 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
839 logger('get_atom_elements: fixing sender of repeated message.');
841 if (!intval(get_config('system','wall-to-wall_share'))) {
842 logger("Repeated data: ".print_r($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10], true), LOGGER_DEBUG);
843 $prefix = share_header($name, $uri, $avatar, "", "", $orig_uri);
845 $res["body"] = $prefix.html2bbcode($message)."[/share]";
847 $res["owner-name"] = $res["author-name"];
848 $res["owner-link"] = $res["author-link"];
849 $res["owner-avatar"] = $res["author-avatar"];
851 $res["author-name"] = $name;
852 $res["author-link"] = $uri;
853 $res["author-avatar"] = $avatar;
855 $res["body"] = html2bbcode($message);
860 // Search for ostatus conversation url
861 $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"];
863 if (is_array($links)) {
864 foreach ($links as $link) {
865 $conversation = array_shift($link["attribs"]);
867 if ($conversation["rel"] == "ostatus:conversation") {
868 $res["ostatus_conversation"] = $conversation["href"];
869 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
874 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
877 // Handle enclosures and treat them as preview picture
879 foreach ($attach AS $attachment)
880 if ($attachment->type == "image/jpeg")
881 $preview = $attachment->link;
883 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
884 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
886 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
887 unset($res["attach"]);
888 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
889 $res["body"] = add_page_info_to_body($res["body"]);
890 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
891 $res["body"] = add_page_info_to_body($res["body"]);
894 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
896 call_hooks('parse_atom', $arr);
901 function add_page_info_data($data) {
902 call_hooks('page_info_data', $data);
904 // It maybe is a rich content, but if it does have everything that a link has,
905 // then treat it that way
906 if (($data["type"] == "rich") AND is_string($data["title"]) AND
907 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
908 $data["type"] = "link";
910 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
913 if ($no_photos AND ($data["type"] == "photo"))
916 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
917 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
918 require_once("include/network.php");
919 $data["url"] = short_link($data["url"]);
922 if (($data["type"] != "photo") AND is_string($data["title"]))
923 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
925 if (($data["type"] != "video") AND ($photo != ""))
926 $text .= '[img]'.$photo.'[/img]';
927 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
928 $imagedata = $data["images"][0];
929 $text .= '[img]'.$imagedata["src"].'[/img]';
932 if (($data["type"] != "photo") AND is_string($data["text"]))
933 $text .= "[quote]".$data["text"]."[/quote]";
936 if (isset($data["keywords"]) AND count($data["keywords"])) {
939 foreach ($data["keywords"] AS $keyword) {
940 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
941 array("","", "", "", "", ""), $keyword);
942 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
946 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
949 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
950 require_once("mod/parse_url.php");
952 $data = Cache::get("parse_url:".$url);
954 $data = parseurl_getsiteinfo($url, true);
955 Cache::set("parse_url:".$url,serialize($data));
957 $data = unserialize($data);
960 $data["images"][0]["src"] = $photo;
962 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
964 if (!$keywords AND isset($data["keywords"]))
965 unset($data["keywords"]);
967 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
968 $list = explode(",", $keyword_blacklist);
969 foreach ($list AS $keyword) {
970 $keyword = trim($keyword);
971 $index = array_search($keyword, $data["keywords"]);
972 if ($index !== false)
973 unset($data["keywords"][$index]);
980 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
981 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
984 if (isset($data["keywords"]) AND count($data["keywords"])) {
986 foreach ($data["keywords"] AS $keyword) {
987 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
988 array("","", "", "", "", ""), $keyword);
993 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1000 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1001 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1003 $text = add_page_info_data($data);
1008 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1010 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1012 $URLSearchString = "^\[\]";
1014 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1015 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1018 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1020 // Convert urls without bbcode elements
1021 if (!$matches AND $texturl) {
1022 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1024 // Yeah, a hack. I really hate regular expressions :)
1026 $matches[1] = $matches[2];
1030 $footer = add_page_info($matches[1], $no_photos);
1032 // Remove the link from the body if the link is attached at the end of the post
1033 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1034 $removedlink = trim(str_replace($matches[1], "", $body));
1035 if (($removedlink == "") OR strstr($body, $removedlink))
1036 $body = $removedlink;
1038 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1039 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1040 if (($removedlink == "") OR strstr($body, $removedlink))
1041 $body = $removedlink;
1044 // Add the page information to the bottom
1045 if (isset($footer) AND (trim($footer) != ""))
1051 function encode_rel_links($links) {
1053 if(! ((is_array($links)) && (count($links))))
1055 foreach($links as $link) {
1057 if($link['attribs']['']['rel'])
1058 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1059 if($link['attribs']['']['type'])
1060 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1061 if($link['attribs']['']['href'])
1062 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1063 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1064 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1065 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1066 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1067 $o .= ' />' . "\n" ;
1074 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1076 // If it is a posting where users should get notifications, then define it as wall posting
1079 $arr['type'] = 'wall';
1081 $arr['last-child'] = 1;
1082 $arr['network'] = NETWORK_DFRN;
1085 // If a Diaspora signature structure was passed in, pull it out of the
1086 // item array and set it aside for later storage.
1089 if(x($arr,'dsprsig')) {
1090 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1091 unset($arr['dsprsig']);
1094 // if an OStatus conversation url was passed in, it is stored and then
1095 // removed from the array.
1096 $ostatus_conversation = null;
1098 if (isset($arr["ostatus_conversation"])) {
1099 $ostatus_conversation = $arr["ostatus_conversation"];
1100 unset($arr["ostatus_conversation"]);
1103 if(x($arr, 'gravity'))
1104 $arr['gravity'] = intval($arr['gravity']);
1105 elseif($arr['parent-uri'] === $arr['uri'])
1106 $arr['gravity'] = 0;
1107 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1108 $arr['gravity'] = 6;
1110 $arr['gravity'] = 6; // extensible catchall
1112 if(! x($arr,'type'))
1113 $arr['type'] = 'remote';
1117 /* check for create date and expire time */
1118 $uid = intval($arr['uid']);
1119 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1121 $expire_interval = $r[0]['expire'];
1122 if ($expire_interval>0) {
1123 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1124 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1125 if ($created_date < $expire_date) {
1126 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1132 // If there is no guid then take the same guid that was taken before for the same uri
1133 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1134 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1135 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1136 dbesc(trim($arr['uri']))
1140 $arr['guid'] = $r[0]["guid"];
1141 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1145 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1146 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1147 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1148 // $arr['body'] = strip_tags($arr['body']);
1151 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1152 require_once('library/langdet/Text/LanguageDetect.php');
1153 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1154 $l = new Text_LanguageDetect;
1155 //$lng = $l->detectConfidence($naked_body);
1156 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1157 $lng = $l->detect($naked_body, 3);
1159 if (sizeof($lng) > 0) {
1162 foreach ($lng as $language => $score) {
1163 if ($postopts == "")
1164 $postopts = "lang=";
1168 $postopts .= $language.";".$score;
1170 $arr['postopts'] = $postopts;
1174 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1175 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1176 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1177 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1178 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1179 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1180 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1181 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1182 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1183 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1184 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1185 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1186 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1187 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1188 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1189 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1190 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1191 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1192 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1193 $arr['deleted'] = 0;
1194 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1195 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1196 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1197 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1198 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1199 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1200 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1201 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1202 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1203 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1204 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1205 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1206 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1207 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1208 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1209 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1210 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1211 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1212 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1213 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1214 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1215 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1216 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1217 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1218 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1220 if ($arr['plink'] == "") {
1222 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1225 if ($arr['network'] == "") {
1226 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1227 intval($arr['contact-id']),
1232 $arr['network'] = $r[0]["network"];
1234 // Fallback to friendica (why is it empty in some cases?)
1235 if ($arr['network'] == "")
1236 $arr['network'] = NETWORK_DFRN;
1238 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1241 // Check for hashtags in the body and repair or add hashtag links
1242 item_body_set_hashtags($arr);
1244 $arr['thr-parent'] = $arr['parent-uri'];
1245 if($arr['parent-uri'] === $arr['uri']) {
1247 $parent_deleted = 0;
1248 $allow_cid = $arr['allow_cid'];
1249 $allow_gid = $arr['allow_gid'];
1250 $deny_cid = $arr['deny_cid'];
1251 $deny_gid = $arr['deny_gid'];
1252 $notify_type = 'wall-new';
1256 // find the parent and snarf the item id and ACLs
1257 // and anything else we need to inherit
1259 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1260 dbesc($arr['parent-uri']),
1266 // is the new message multi-level threaded?
1267 // even though we don't support it now, preserve the info
1268 // and re-attach to the conversation parent.
1270 if($r[0]['uri'] != $r[0]['parent-uri']) {
1271 $arr['parent-uri'] = $r[0]['parent-uri'];
1272 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1273 ORDER BY `id` ASC LIMIT 1",
1274 dbesc($r[0]['parent-uri']),
1275 dbesc($r[0]['parent-uri']),
1282 $parent_id = $r[0]['id'];
1283 $parent_deleted = $r[0]['deleted'];
1284 $allow_cid = $r[0]['allow_cid'];
1285 $allow_gid = $r[0]['allow_gid'];
1286 $deny_cid = $r[0]['deny_cid'];
1287 $deny_gid = $r[0]['deny_gid'];
1288 $arr['wall'] = $r[0]['wall'];
1289 $notify_type = 'comment-new';
1291 // if the parent is private, force privacy for the entire conversation
1292 // This differs from the above settings as it subtly allows comments from
1293 // email correspondents to be private even if the overall thread is not.
1295 if($r[0]['private'])
1296 $arr['private'] = $r[0]['private'];
1298 // Edge case. We host a public forum that was originally posted to privately.
1299 // The original author commented, but as this is a comment, the permissions
1300 // weren't fixed up so it will still show the comment as private unless we fix it here.
1302 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1303 $arr['private'] = 0;
1306 // If its a post from myself then tag the thread as "mention"
1307 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1308 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1311 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1312 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1313 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1314 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1315 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1321 // Allow one to see reply tweets from status.net even when
1322 // we don't have or can't see the original post.
1325 logger('item_store: $force_parent=true, reply converted to top-level post.');
1327 $arr['parent-uri'] = $arr['uri'];
1328 $arr['gravity'] = 0;
1331 logger('item_store: item parent was not found - ignoring item');
1335 $parent_deleted = 0;
1339 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1343 if($r && count($r)) {
1344 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1348 // Is this item available in the global items (with uid=0)?
1349 if ($arr["uid"] == 0) {
1350 $arr["global"] = true;
1352 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1354 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1356 $arr["global"] = (count($isglobal) > 0);
1359 // Fill the cache field
1360 put_item_in_cache($arr);
1362 call_hooks('post_remote',$arr);
1364 if(x($arr,'cancel')) {
1365 logger('item_store: post cancelled by plugin.');
1369 // Store the unescaped version
1374 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1376 $r = dbq("INSERT INTO `item` (`"
1377 . implode("`, `", array_keys($arr))
1379 . implode("', '", array_values($arr))
1385 // find the item we just created
1386 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1392 $current_post = $r[0]['id'];
1393 logger('item_store: created item ' . $current_post);
1395 // Set "success_update" to the date of the last time we heard from this contact
1396 // This can be used to filter for inactive contacts and poco.
1397 // Only do this for public postings to avoid privacy problems, since poco data is public.
1398 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1399 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1400 q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1401 dbesc($arr['received']),
1402 intval($arr['contact-id'])
1405 logger('item_store: could not locate created item');
1409 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1410 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1412 intval($arr['uid']),
1413 intval($current_post)
1417 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1418 $parent_id = $current_post;
1420 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1423 $private = $arr['private'];
1425 // Set parent id - and also make sure to inherit the parent's ACLs.
1427 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1428 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1435 intval($parent_deleted),
1436 intval($current_post)
1439 // Complete ostatus threads
1440 if ($ostatus_conversation)
1441 complete_conversation($current_post, $ostatus_conversation);
1443 $arr['id'] = $current_post;
1444 $arr['parent'] = $parent_id;
1445 $arr['allow_cid'] = $allow_cid;
1446 $arr['allow_gid'] = $allow_gid;
1447 $arr['deny_cid'] = $deny_cid;
1448 $arr['deny_gid'] = $deny_gid;
1449 $arr['private'] = $private;
1450 $arr['deleted'] = $parent_deleted;
1452 // update the commented timestamp on the parent
1453 // Only update "commented" if it is really a comment
1454 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1455 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1456 dbesc(datetime_convert()),
1457 dbesc(datetime_convert()),
1461 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1462 dbesc(datetime_convert()),
1467 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1468 intval($current_post),
1469 dbesc($dsprsig->signed_text),
1470 dbesc($dsprsig->signature),
1471 dbesc($dsprsig->signer)
1477 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1480 if($arr['last-child']) {
1481 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1483 intval($arr['uid']),
1484 intval($current_post)
1488 $deleted = tag_deliver($arr['uid'],$current_post);
1490 // current post can be deleted if is for a community page and no mention are
1492 if (!$deleted AND !$dontcache) {
1494 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1495 if (count($r) == 1) {
1496 call_hooks('post_remote_end', $r[0]);
1498 logger('item_store: new item not found in DB, id ' . $current_post);
1501 // Add every contact of the post to the global contact table
1504 create_tags_from_item($current_post);
1505 create_files_from_item($current_post);
1507 // Only check for notifications on start posts
1508 if ($arr['parent-uri'] === $arr['uri']) {
1509 add_thread($current_post);
1510 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1512 // Send a notification for every new post?
1513 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1514 intval($arr['contact-id']),
1517 $send_notification = count($r);
1519 if (!$send_notification) {
1520 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1521 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1524 foreach ($tags AS $tag) {
1525 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1526 normalise_link($tag["url"]), intval($arr['uid']));
1528 $send_notification = true;
1533 if ($send_notification) {
1534 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1535 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1536 intval($arr['uid']));
1538 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1539 intval($current_post),
1545 require_once('include/enotify.php');
1547 'type' => NOTIFY_SHARE,
1548 'notify_flags' => $u[0]['notify-flags'],
1549 'language' => $u[0]['language'],
1550 'to_name' => $u[0]['username'],
1551 'to_email' => $u[0]['email'],
1552 'uid' => $u[0]['uid'],
1554 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1555 'source_name' => $item[0]['author-name'],
1556 'source_link' => $item[0]['author-link'],
1557 'source_photo' => $item[0]['author-avatar'],
1558 'verb' => ACTIVITY_TAG,
1561 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1564 update_thread($parent_id);
1565 add_shadow_entry($arr);
1569 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1571 return $current_post;
1574 function item_body_set_hashtags(&$item) {
1576 $tags = get_tags($item["body"]);
1582 // This sorting is important when there are hashtags that are part of other hashtags
1583 // Otherwise there could be problems with hashtags like #test and #test2
1588 $URLSearchString = "^\[\]";
1590 // All hashtags should point to the home server
1591 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1592 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1594 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1595 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1597 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1598 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1600 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1603 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1605 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1608 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1610 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1613 // Repair recursive urls
1614 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1615 "#$2", $item["body"]);
1617 foreach($tags as $tag) {
1618 if(strpos($tag,'#') !== 0)
1621 if(strpos($tag,'[url='))
1624 $basetag = str_replace('_',' ',substr($tag,1));
1626 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1628 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1630 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1631 if(strlen($item["tag"]))
1632 $item["tag"] = ','.$item["tag"];
1633 $item["tag"] = $newtag.$item["tag"];
1637 // Convert back the masked hashtags
1638 $item["body"] = str_replace("#", "#", $item["body"]);
1641 function get_item_guid($id) {
1642 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1644 return($r[0]["guid"]);
1649 function get_item_id($guid, $uid = 0) {
1655 $uid == local_user();
1657 // Does the given user have this item?
1659 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1660 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1661 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1664 $nick = $r[0]["nickname"];
1668 // Or is it anywhere on the server?
1670 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1671 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1672 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1673 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1674 AND `item`.`private` = 0 AND `item`.`wall` = 1
1675 AND `item`.`guid` = '%s'", dbesc($guid));
1678 $nick = $r[0]["nickname"];
1681 return(array("nick" => $nick, "id" => $id));
1685 function get_item_contact($item,$contacts) {
1686 if(! count($contacts) || (! is_array($item)))
1688 foreach($contacts as $contact) {
1689 if($contact['id'] == $item['contact-id']) {
1691 break; // NOTREACHED
1698 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1700 * @param int $item_id
1701 * @return bool true if item was deleted, else false
1703 function tag_deliver($uid,$item_id) {
1711 $u = q("select * from user where uid = %d limit 1",
1717 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1718 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1721 $i = q("select * from item where id = %d and uid = %d limit 1",
1730 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1732 // Diaspora uses their own hardwired link URL in @-tags
1733 // instead of the one we supply with webfinger
1735 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1737 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1739 foreach($matches as $mtch) {
1740 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1742 logger('tag_deliver: mention found: ' . $mtch[2]);
1748 if ( ($community_page || $prvgroup) &&
1749 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1750 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1752 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1753 q("DELETE FROM item WHERE id = %d and uid = %d",
1763 // send a notification
1765 // use a local photo if we have one
1767 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1768 intval($u[0]['uid']),
1769 dbesc(normalise_link($item['author-link']))
1771 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1774 require_once('include/enotify.php');
1776 'type' => NOTIFY_TAGSELF,
1777 'notify_flags' => $u[0]['notify-flags'],
1778 'language' => $u[0]['language'],
1779 'to_name' => $u[0]['username'],
1780 'to_email' => $u[0]['email'],
1781 'uid' => $u[0]['uid'],
1783 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1784 'source_name' => $item['author-name'],
1785 'source_link' => $item['author-link'],
1786 'source_photo' => $photo,
1787 'verb' => ACTIVITY_TAG,
1789 'parent' => $item['parent']
1793 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1795 call_hooks('tagged', $arr);
1797 if((! $community_page) && (! $prvgroup))
1801 // tgroup delivery - setup a second delivery chain
1802 // prevent delivery looping - only proceed
1803 // if the message originated elsewhere and is a top-level post
1805 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1808 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1811 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1812 intval($u[0]['uid'])
1817 // also reset all the privacy bits to the forum default permissions
1819 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1821 $forum_mode = (($prvgroup) ? 2 : 1);
1823 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1824 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1825 intval($forum_mode),
1826 dbesc($c[0]['name']),
1827 dbesc($c[0]['url']),
1828 dbesc($c[0]['thumb']),
1830 dbesc($u[0]['allow_cid']),
1831 dbesc($u[0]['allow_gid']),
1832 dbesc($u[0]['deny_cid']),
1833 dbesc($u[0]['deny_gid']),
1836 update_thread($item_id);
1838 proc_run('php','include/notifier.php','tgroup',$item_id);
1844 function tgroup_check($uid,$item) {
1850 // check that the message originated elsewhere and is a top-level post
1852 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1856 $u = q("select * from user where uid = %d limit 1",
1862 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1863 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1866 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1868 // Diaspora uses their own hardwired link URL in @-tags
1869 // instead of the one we supply with webfinger
1871 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1873 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1875 foreach($matches as $mtch) {
1876 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1878 logger('tgroup_check: mention found: ' . $mtch[2]);
1886 if((! $community_page) && (! $prvgroup))
1900 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1904 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1906 if($contact['duplex'] && $contact['dfrn-id'])
1907 $idtosend = '0:' . $orig_id;
1908 if($contact['duplex'] && $contact['issued-id'])
1909 $idtosend = '1:' . $orig_id;
1911 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1913 $rino_enable = get_config('system','rino_encrypt');
1918 $ssl_val = intval(get_config('system','ssl_policy'));
1922 case SSL_POLICY_FULL:
1923 $ssl_policy = 'full';
1925 case SSL_POLICY_SELFSIGN:
1926 $ssl_policy = 'self';
1928 case SSL_POLICY_NONE:
1930 $ssl_policy = 'none';
1934 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1936 logger('dfrn_deliver: ' . $url);
1938 $xml = fetch_url($url);
1940 $curl_stat = $a->get_curl_code();
1942 return(-1); // timed out
1944 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1949 if(strpos($xml,'<?xml') === false) {
1950 logger('dfrn_deliver: no valid XML returned');
1951 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1955 $res = parse_xml_string($xml);
1957 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1958 return (($res->status) ? $res->status : 3);
1960 $postvars = array();
1961 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1962 $challenge = hex2bin((string) $res->challenge);
1963 $perm = (($res->perm) ? $res->perm : null);
1964 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1965 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1966 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1968 if($owner['page-flags'] == PAGE_PRVGROUP)
1971 $final_dfrn_id = '';
1974 if((($perm == 'rw') && (! intval($contact['writable'])))
1975 || (($perm == 'r') && (intval($contact['writable'])))) {
1976 q("update contact set writable = %d where id = %d",
1977 intval(($perm == 'rw') ? 1 : 0),
1978 intval($contact['id'])
1980 $contact['writable'] = (string) 1 - intval($contact['writable']);
1984 if(($contact['duplex'] && strlen($contact['pubkey']))
1985 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1986 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1987 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1988 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1991 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1992 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1995 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1997 if(strpos($final_dfrn_id,':') == 1)
1998 $final_dfrn_id = substr($final_dfrn_id,2);
2000 if($final_dfrn_id != $orig_id) {
2001 logger('dfrn_deliver: wrong dfrn_id.');
2002 // did not decode properly - cannot trust this site
2006 $postvars['dfrn_id'] = $idtosend;
2007 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2009 $postvars['dissolve'] = '1';
2012 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2013 $postvars['data'] = $atom;
2014 $postvars['perm'] = 'rw';
2017 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2018 $postvars['perm'] = 'r';
2021 $postvars['ssl_policy'] = $ssl_policy;
2024 $postvars['page'] = $page;
2026 if($rino && $rino_allowed && (! $dissolve)) {
2027 $key = substr(random_string(),0,16);
2028 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2029 $postvars['data'] = $data;
2030 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2033 if($dfrn_version >= 2.1) {
2034 if(($contact['duplex'] && strlen($contact['pubkey']))
2035 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2036 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2038 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2041 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2045 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2046 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2049 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2053 logger('md5 rawkey ' . md5($postvars['key']));
2055 $postvars['key'] = bin2hex($postvars['key']);
2058 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2060 $xml = post_url($contact['notify'],$postvars);
2062 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2064 $curl_stat = $a->get_curl_code();
2065 if((! $curl_stat) || (! strlen($xml)))
2066 return(-1); // timed out
2068 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2071 if(strpos($xml,'<?xml') === false) {
2072 logger('dfrn_deliver: phase 2: no valid XML returned');
2073 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2077 if($contact['term-date'] != '0000-00-00 00:00:00') {
2078 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2079 require_once('include/Contact.php');
2080 unmark_for_death($contact);
2083 $res = parse_xml_string($xml);
2085 return $res->status;
2090 This function returns true if $update has an edited timestamp newer
2091 than $existing, i.e. $update contains new data which should override
2092 what's already there. If there is no timestamp yet, the update is
2093 assumed to be newer. If the update has no timestamp, the existing
2094 item is assumed to be up-to-date. If the timestamps are equal it
2095 assumes the update has been seen before and should be ignored.
2097 function edited_timestamp_is_newer($existing, $update) {
2098 if (!x($existing,'edited') || !$existing['edited']) {
2101 if (!x($update,'edited') || !$update['edited']) {
2104 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2105 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2106 return (strcmp($existing_edited, $update_edited) < 0);
2111 * consume_feed - process atom feed and update anything/everything we might need to update
2113 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2115 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2116 * It is this person's stuff that is going to be updated.
2117 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2118 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2119 * have a contact record.
2120 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2121 * might not) try and subscribe to it.
2122 * $datedir sorts in reverse order
2123 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2124 * imported prior to its children being seen in the stream unless we are certain
2125 * of how the feed is arranged/ordered.
2126 * With $pass = 1, we only pull parent items out of the stream.
2127 * With $pass = 2, we only pull children (comments/likes).
2129 * So running this twice, first with pass 1 and then with pass 2 will do the right
2130 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2131 * model where comments can have sub-threads. That would require some massive sorting
2132 * to get all the feed items into a mostly linear ordering, and might still require
2136 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2138 require_once('library/simplepie/simplepie.inc');
2139 require_once('include/contact_selectors.php');
2141 if(! strlen($xml)) {
2142 logger('consume_feed: empty input');
2146 $feed = new SimplePie();
2147 $feed->set_raw_data($xml);
2149 $feed->enable_order_by_date(true);
2151 $feed->enable_order_by_date(false);
2155 logger('consume_feed: Error parsing XML: ' . $feed->error());
2157 $permalink = $feed->get_permalink();
2159 // Check at the feed level for updated contact name and/or photo
2163 $photo_timestamp = '';
2166 $contact_updated = '';
2168 $hubs = $feed->get_links('hub');
2169 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2172 $hub = implode(',', $hubs);
2174 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2176 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2178 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2179 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2180 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2181 $new_name = $elems['name'][0]['data'];
2183 // Manually checking for changed contact names
2184 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2185 $name_updated = date("c");
2186 $photo_timestamp = date("c");
2189 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2190 if ($photo_timestamp == "")
2191 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2192 $photo_url = $elems['link'][0]['attribs']['']['href'];
2195 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2196 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2200 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2201 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2203 $contact_updated = $photo_timestamp;
2205 require_once("include/Photo.php");
2206 $photo_failure = false;
2207 $have_photo = false;
2209 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2210 intval($contact['id']),
2211 intval($contact['uid'])
2214 $resource_id = $r[0]['resource-id'];
2218 $resource_id = photo_new_resource();
2221 $img_str = fetch_url($photo_url,true);
2222 // guess mimetype from headers or filename
2223 $type = guess_image_type($photo_url,true);
2226 $img = new Photo($img_str, $type);
2227 if($img->is_valid()) {
2229 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2230 dbesc($resource_id),
2231 intval($contact['id']),
2232 intval($contact['uid'])
2236 $img->scaleImageSquare(175);
2238 $hash = $resource_id;
2239 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2241 $img->scaleImage(80);
2242 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2244 $img->scaleImage(48);
2245 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2249 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2250 WHERE `uid` = %d AND `id` = %d",
2251 dbesc(datetime_convert()),
2252 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2253 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2254 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2255 intval($contact['uid']),
2256 intval($contact['id'])
2261 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2262 if ($name_updated > $contact_updated)
2263 $contact_updated = $name_updated;
2265 $r = q("select * from contact where uid = %d and id = %d limit 1",
2266 intval($contact['uid']),
2267 intval($contact['id'])
2270 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2271 dbesc(notags(trim($new_name))),
2272 dbesc(datetime_convert()),
2273 intval($contact['uid']),
2274 intval($contact['id'])
2277 // do our best to update the name on content items
2280 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2281 dbesc(notags(trim($new_name))),
2282 dbesc($r[0]['name']),
2283 dbesc($r[0]['url']),
2284 intval($contact['uid'])
2289 if ($contact_updated AND $new_name AND $photo_url)
2290 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2292 if(strlen($birthday)) {
2293 if(substr($birthday,0,4) != $contact['bdyear']) {
2294 logger('consume_feed: updating birthday: ' . $birthday);
2298 * Add new birthday event for this person
2300 * $bdtext is just a readable placeholder in case the event is shared
2301 * with others. We will replace it during presentation to our $importer
2302 * to contain a sparkle link and perhaps a photo.
2306 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2307 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2310 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2311 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2312 intval($contact['uid']),
2313 intval($contact['id']),
2314 dbesc(datetime_convert()),
2315 dbesc(datetime_convert()),
2316 dbesc(datetime_convert('UTC','UTC', $birthday)),
2317 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2326 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2327 dbesc(substr($birthday,0,4)),
2328 intval($contact['uid']),
2329 intval($contact['id'])
2332 // This function is called twice without reloading the contact
2333 // Make sure we only create one event. This is why &$contact
2334 // is a reference var in this function
2336 $contact['bdyear'] = substr($birthday,0,4);
2340 $community_page = 0;
2341 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2343 $community_page = intval($rawtags[0]['data']);
2345 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2346 q("update contact set forum = %d where id = %d",
2347 intval($community_page),
2348 intval($contact['id'])
2350 $contact['forum'] = (string) $community_page;
2354 // process any deleted entries
2356 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2357 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2358 foreach($del_entries as $dentry) {
2360 if(isset($dentry['attribs']['']['ref'])) {
2361 $uri = $dentry['attribs']['']['ref'];
2363 if(isset($dentry['attribs']['']['when'])) {
2364 $when = $dentry['attribs']['']['when'];
2365 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2368 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2370 if($deleted && is_array($contact)) {
2371 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2372 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2374 intval($importer['uid']),
2375 intval($contact['id'])
2380 if(! $item['deleted'])
2381 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2383 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2384 $xo = parse_xml_string($item['object'],false);
2385 $xt = parse_xml_string($item['target'],false);
2386 if($xt->type === ACTIVITY_OBJ_NOTE) {
2387 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2389 intval($importer['importer_uid'])
2393 // For tags, the owner cannot remove the tag on the author's copy of the post.
2395 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2396 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2397 $author_copy = (($item['origin']) ? true : false);
2399 if($owner_remove && $author_copy)
2401 if($author_remove || $owner_remove) {
2402 $tags = explode(',',$i[0]['tag']);
2405 foreach($tags as $tag)
2406 if(trim($tag) !== trim($xo->body))
2407 $newtags[] = trim($tag);
2409 q("update item set tag = '%s' where id = %d",
2410 dbesc(implode(',',$newtags)),
2413 create_tags_from_item($i[0]['id']);
2419 if($item['uri'] == $item['parent-uri']) {
2420 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2421 `body` = '', `title` = ''
2422 WHERE `parent-uri` = '%s' AND `uid` = %d",
2424 dbesc(datetime_convert()),
2425 dbesc($item['uri']),
2426 intval($importer['uid'])
2428 create_tags_from_itemuri($item['uri'], $importer['uid']);
2429 create_files_from_itemuri($item['uri'], $importer['uid']);
2430 update_thread_uri($item['uri'], $importer['uid']);
2433 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2434 `body` = '', `title` = ''
2435 WHERE `uri` = '%s' AND `uid` = %d",
2437 dbesc(datetime_convert()),
2439 intval($importer['uid'])
2441 create_tags_from_itemuri($uri, $importer['uid']);
2442 create_files_from_itemuri($uri, $importer['uid']);
2443 if($item['last-child']) {
2444 // ensure that last-child is set in case the comment that had it just got wiped.
2445 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2446 dbesc(datetime_convert()),
2447 dbesc($item['parent-uri']),
2448 intval($item['uid'])
2450 // who is the last child now?
2451 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2452 ORDER BY `created` DESC LIMIT 1",
2453 dbesc($item['parent-uri']),
2454 intval($importer['uid'])
2457 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2468 // Now process the feed
2470 if($feed->get_item_quantity()) {
2472 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2474 // in inverse date order
2476 $items = array_reverse($feed->get_items());
2478 $items = $feed->get_items();
2481 foreach($items as $item) {
2484 $item_id = $item->get_id();
2485 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2486 if(isset($rawthread[0]['attribs']['']['ref'])) {
2488 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2491 if(($is_reply) && is_array($contact)) {
2496 // not allowed to post
2498 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2502 // Have we seen it? If not, import it.
2504 $item_id = $item->get_id();
2505 $datarray = get_atom_elements($feed, $item, $contact);
2507 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2508 $datarray['author-name'] = $contact['name'];
2509 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2510 $datarray['author-link'] = $contact['url'];
2511 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2512 $datarray['author-avatar'] = $contact['thumb'];
2514 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2515 logger('consume_feed: no author information! ' . print_r($datarray,true));
2519 $force_parent = false;
2520 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2521 if($contact['network'] === NETWORK_OSTATUS)
2522 $force_parent = true;
2523 if(strlen($datarray['title']))
2524 unset($datarray['title']);
2525 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2526 dbesc(datetime_convert()),
2528 intval($importer['uid'])
2530 $datarray['last-child'] = 1;
2531 update_thread_uri($parent_uri, $importer['uid']);
2535 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2537 intval($importer['uid'])
2540 // Update content if 'updated' changes
2543 if (edited_timestamp_is_newer($r[0], $datarray)) {
2545 // do not accept (ignore) an earlier edit than one we currently have.
2546 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2549 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2550 dbesc($datarray['title']),
2551 dbesc($datarray['body']),
2552 dbesc($datarray['tag']),
2553 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2554 dbesc(datetime_convert()),
2556 intval($importer['uid'])
2558 create_tags_from_itemuri($item_id, $importer['uid']);
2559 update_thread_uri($item_id, $importer['uid']);
2562 // update last-child if it changes
2564 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2565 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2566 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2567 dbesc(datetime_convert()),
2569 intval($importer['uid'])
2571 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2572 intval($allow[0]['data']),
2573 dbesc(datetime_convert()),
2575 intval($importer['uid'])
2577 update_thread_uri($item_id, $importer['uid']);
2583 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2584 // one way feed - no remote comment ability
2585 $datarray['last-child'] = 0;
2587 $datarray['parent-uri'] = $parent_uri;
2588 $datarray['uid'] = $importer['uid'];
2589 $datarray['contact-id'] = $contact['id'];
2590 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2591 $datarray['type'] = 'activity';
2592 $datarray['gravity'] = GRAVITY_LIKE;
2593 // only one like or dislike per person
2594 // splitted into two queries for performance issues
2595 $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",
2596 intval($datarray['uid']),
2597 intval($datarray['contact-id']),
2598 dbesc($datarray['verb']),
2604 $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",
2605 intval($datarray['uid']),
2606 intval($datarray['contact-id']),
2607 dbesc($datarray['verb']),
2614 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2615 $xo = parse_xml_string($datarray['object'],false);
2616 $xt = parse_xml_string($datarray['target'],false);
2618 if($xt->type == ACTIVITY_OBJ_NOTE) {
2619 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2621 intval($importer['importer_uid'])
2626 // extract tag, if not duplicate, add to parent item
2627 if($xo->id && $xo->content) {
2628 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2629 if(! (stristr($r[0]['tag'],$newtag))) {
2630 q("UPDATE item SET tag = '%s' WHERE id = %d",
2631 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2634 create_tags_from_item($r[0]['id']);
2640 $r = item_store($datarray,$force_parent);
2646 // Head post of a conversation. Have we seen it? If not, import it.
2648 $item_id = $item->get_id();
2650 $datarray = get_atom_elements($feed, $item, $contact);
2652 if(is_array($contact)) {
2653 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2654 $datarray['author-name'] = $contact['name'];
2655 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2656 $datarray['author-link'] = $contact['url'];
2657 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2658 $datarray['author-avatar'] = $contact['thumb'];
2661 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2662 logger('consume_feed: no author information! ' . print_r($datarray,true));
2666 // special handling for events
2668 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2669 $ev = bbtoevent($datarray['body']);
2670 if(x($ev,'desc') && x($ev,'start')) {
2671 $ev['uid'] = $importer['uid'];
2672 $ev['uri'] = $item_id;
2673 $ev['edited'] = $datarray['edited'];
2674 $ev['private'] = $datarray['private'];
2676 if(is_array($contact))
2677 $ev['cid'] = $contact['id'];
2678 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2680 intval($importer['uid'])
2683 $ev['id'] = $r[0]['id'];
2684 $xyz = event_store($ev);
2689 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2690 if(strlen($datarray['title']))
2691 unset($datarray['title']);
2692 $datarray['last-child'] = 1;
2696 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2698 intval($importer['uid'])
2701 // Update content if 'updated' changes
2704 if (edited_timestamp_is_newer($r[0], $datarray)) {
2706 // do not accept (ignore) an earlier edit than one we currently have.
2707 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2710 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2711 dbesc($datarray['title']),
2712 dbesc($datarray['body']),
2713 dbesc($datarray['tag']),
2714 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2715 dbesc(datetime_convert()),
2717 intval($importer['uid'])
2719 create_tags_from_itemuri($item_id, $importer['uid']);
2720 update_thread_uri($item_id, $importer['uid']);
2723 // update last-child if it changes
2725 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2726 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2727 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2728 intval($allow[0]['data']),
2729 dbesc(datetime_convert()),
2731 intval($importer['uid'])
2733 update_thread_uri($item_id, $importer['uid']);
2738 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2739 logger('consume-feed: New follower');
2740 new_follower($importer,$contact,$datarray,$item);
2743 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2744 lose_follower($importer,$contact,$datarray,$item);
2748 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2749 logger('consume-feed: New friend request');
2750 new_follower($importer,$contact,$datarray,$item,true);
2753 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2754 lose_sharer($importer,$contact,$datarray,$item);
2759 if(! is_array($contact))
2763 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2764 // one way feed - no remote comment ability
2765 $datarray['last-child'] = 0;
2767 if($contact['network'] === NETWORK_FEED)
2768 $datarray['private'] = 2;
2770 $datarray['parent-uri'] = $item_id;
2771 $datarray['uid'] = $importer['uid'];
2772 $datarray['contact-id'] = $contact['id'];
2774 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2775 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2776 // but otherwise there's a possible data mixup on the sender's system.
2777 // the tgroup delivery code called from item_store will correct it if it's a forum,
2778 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2779 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2780 $datarray['owner-name'] = $contact['name'];
2781 $datarray['owner-link'] = $contact['url'];
2782 $datarray['owner-avatar'] = $contact['thumb'];
2785 // We've allowed "followers" to reach this point so we can decide if they are
2786 // posting an @-tag delivery, which followers are allowed to do for certain
2787 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2789 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2792 // This is my contact on another system, but it's really me.
2793 // Turn this into a wall post.
2794 $notify = item_is_remote_self($contact, $datarray);
2796 $r = item_store($datarray, false, $notify);
2797 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2805 function item_is_remote_self($contact, &$datarray) {
2808 if (!$contact['remote_self'])
2811 // Prevent the forwarding of posts that are forwarded
2812 if ($datarray["extid"] == NETWORK_DFRN)
2815 // Prevent to forward already forwarded posts
2816 if ($datarray["app"] == $a->get_hostname())
2819 // Only forward posts
2820 if ($datarray["verb"] != ACTIVITY_POST)
2823 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2826 $datarray2 = $datarray;
2827 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2828 if ($contact['remote_self'] == 2) {
2829 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2830 intval($contact['uid']));
2832 $datarray['contact-id'] = $r[0]["id"];
2834 $datarray['owner-name'] = $r[0]["name"];
2835 $datarray['owner-link'] = $r[0]["url"];
2836 $datarray['owner-avatar'] = $r[0]["thumb"];
2838 $datarray['author-name'] = $datarray['owner-name'];
2839 $datarray['author-link'] = $datarray['owner-link'];
2840 $datarray['author-avatar'] = $datarray['owner-avatar'];
2843 if ($contact['network'] != NETWORK_FEED) {
2844 $datarray["guid"] = get_guid(32);
2845 unset($datarray["plink"]);
2846 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2847 $datarray["parent-uri"] = $datarray["uri"];
2848 $datarray["extid"] = $contact['network'];
2849 $urlpart = parse_url($datarray2['author-link']);
2850 $datarray["app"] = $urlpart["host"];
2852 $datarray['private'] = 0;
2855 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2856 // $datarray["app"] = network_to_name($contact['network']);
2858 if ($contact['network'] != NETWORK_FEED) {
2859 // Store the original post
2860 $r = item_store($datarray2, false, false);
2861 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2863 $datarray["app"] = "Feed";
2868 function local_delivery($importer,$data) {
2871 logger(__function__, LOGGER_TRACE);
2873 if($importer['readonly']) {
2874 // We aren't receiving stuff from this person. But we will quietly ignore them
2875 // rather than a blatant "go away" message.
2876 logger('local_delivery: ignoring');
2881 // Consume notification feed. This may differ from consuming a public feed in several ways
2882 // - might contain email or friend suggestions
2883 // - might contain remote followup to our message
2884 // - in which case we need to accept it and then notify other conversants
2885 // - we may need to send various email notifications
2887 $feed = new SimplePie();
2888 $feed->set_raw_data($data);
2889 $feed->enable_order_by_date(false);
2894 logger('local_delivery: Error parsing XML: ' . $feed->error());
2897 // Check at the feed level for updated contact name and/or photo
2901 $photo_timestamp = '';
2903 $contact_updated = '';
2906 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2908 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2910 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2913 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2914 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2915 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2916 $new_name = $elems['name'][0]['data'];
2918 // Manually checking for changed contact names
2919 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2920 $name_updated = date("c");
2921 $photo_timestamp = date("c");
2924 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2925 if ($photo_timestamp == "")
2926 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2927 $photo_url = $elems['link'][0]['attribs']['']['href'];
2931 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2933 $contact_updated = $photo_timestamp;
2935 logger('local_delivery: Updating photo for ' . $importer['name']);
2936 require_once("include/Photo.php");
2937 $photo_failure = false;
2938 $have_photo = false;
2940 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2941 intval($importer['id']),
2942 intval($importer['importer_uid'])
2945 $resource_id = $r[0]['resource-id'];
2949 $resource_id = photo_new_resource();
2952 $img_str = fetch_url($photo_url,true);
2953 // guess mimetype from headers or filename
2954 $type = guess_image_type($photo_url,true);
2957 $img = new Photo($img_str, $type);
2958 if($img->is_valid()) {
2960 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2961 dbesc($resource_id),
2962 intval($importer['id']),
2963 intval($importer['importer_uid'])
2967 $img->scaleImageSquare(175);
2969 $hash = $resource_id;
2970 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2972 $img->scaleImage(80);
2973 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2975 $img->scaleImage(48);
2976 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2980 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2981 WHERE `uid` = %d AND `id` = %d",
2982 dbesc(datetime_convert()),
2983 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2984 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2985 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2986 intval($importer['importer_uid']),
2987 intval($importer['id'])
2992 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2993 if ($name_updated > $contact_updated)
2994 $contact_updated = $name_updated;
2996 $r = q("select * from contact where uid = %d and id = %d limit 1",
2997 intval($importer['importer_uid']),
2998 intval($importer['id'])
3001 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3002 dbesc(notags(trim($new_name))),
3003 dbesc(datetime_convert()),
3004 intval($importer['importer_uid']),
3005 intval($importer['id'])
3008 // do our best to update the name on content items
3011 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3012 dbesc(notags(trim($new_name))),
3013 dbesc($r[0]['name']),
3014 dbesc($r[0]['url']),
3015 intval($importer['importer_uid'])
3020 if ($contact_updated AND $new_name AND $photo_url)
3021 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3023 // Currently unsupported - needs a lot of work
3024 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3025 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3026 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3028 $newloc['uid'] = $importer['importer_uid'];
3029 $newloc['cid'] = $importer['id'];
3030 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3031 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3032 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3033 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3034 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3035 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3036 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3037 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3038 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3039 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3040 /** relocated user must have original key pair */
3041 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3042 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3044 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3047 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3048 intval($importer['id']),
3049 intval($importer['importer_uid']));
3054 $x = q("UPDATE contact SET
3065 `site-pubkey` = '%s'
3066 WHERE id=%d AND uid=%d;",
3067 dbesc($newloc['name']),
3068 dbesc($newloc['photo']),
3069 dbesc($newloc['thumb']),
3070 dbesc($newloc['micro']),
3071 dbesc($newloc['url']),
3072 dbesc(normalise_link($newloc['url'])),
3073 dbesc($newloc['request']),
3074 dbesc($newloc['confirm']),
3075 dbesc($newloc['notify']),
3076 dbesc($newloc['poll']),
3077 dbesc($newloc['sitepubkey']),
3078 intval($importer['id']),
3079 intval($importer['importer_uid']));
3085 'owner-link' => array($old['url'], $newloc['url']),
3086 'author-link' => array($old['url'], $newloc['url']),
3087 'owner-avatar' => array($old['photo'], $newloc['photo']),
3088 'author-avatar' => array($old['photo'], $newloc['photo']),
3090 foreach ($fields as $n=>$f){
3091 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3094 intval($importer['importer_uid']));
3100 // merge with current record, current contents have priority
3101 // update record, set url-updated
3102 // update profile photos
3108 // handle friend suggestion notification
3110 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3111 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3112 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3114 $fsugg['uid'] = $importer['importer_uid'];
3115 $fsugg['cid'] = $importer['id'];
3116 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3117 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3118 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3119 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3120 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3122 // Does our member already have a friend matching this description?
3124 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3125 dbesc($fsugg['name']),
3126 dbesc(normalise_link($fsugg['url'])),
3127 intval($fsugg['uid'])
3132 // Do we already have an fcontact record for this person?
3135 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3136 dbesc($fsugg['url']),
3137 dbesc($fsugg['name']),
3138 dbesc($fsugg['request'])
3143 // OK, we do. Do we already have an introduction for this person ?
3144 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3145 intval($fsugg['uid']),
3152 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3153 dbesc($fsugg['name']),
3154 dbesc($fsugg['url']),
3155 dbesc($fsugg['photo']),
3156 dbesc($fsugg['request'])
3158 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3159 dbesc($fsugg['url']),
3160 dbesc($fsugg['name']),
3161 dbesc($fsugg['request'])
3166 // database record did not get created. Quietly give up.
3171 $hash = random_string();
3173 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3174 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3175 intval($fsugg['uid']),
3177 intval($fsugg['cid']),
3178 dbesc($fsugg['body']),
3180 dbesc(datetime_convert()),
3185 'type' => NOTIFY_SUGGEST,
3186 'notify_flags' => $importer['notify-flags'],
3187 'language' => $importer['language'],
3188 'to_name' => $importer['username'],
3189 'to_email' => $importer['email'],
3190 'uid' => $importer['importer_uid'],
3192 'link' => $a->get_baseurl() . '/notifications/intros',
3193 'source_name' => $importer['name'],
3194 'source_link' => $importer['url'],
3195 'source_photo' => $importer['photo'],
3196 'verb' => ACTIVITY_REQ_FRIEND,
3205 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3206 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3208 logger('local_delivery: private message received');
3211 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3214 $msg['uid'] = $importer['importer_uid'];
3215 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3216 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3217 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3218 $msg['contact-id'] = $importer['id'];
3219 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3220 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3222 $msg['replied'] = 0;
3223 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3224 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3225 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3229 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3230 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3232 // send notifications.
3234 require_once('include/enotify.php');
3236 $notif_params = array(
3237 'type' => NOTIFY_MAIL,
3238 'notify_flags' => $importer['notify-flags'],
3239 'language' => $importer['language'],
3240 'to_name' => $importer['username'],
3241 'to_email' => $importer['email'],
3242 'uid' => $importer['importer_uid'],
3244 'source_name' => $msg['from-name'],
3245 'source_link' => $importer['url'],
3246 'source_photo' => $importer['thumb'],
3247 'verb' => ACTIVITY_POST,
3251 notification($notif_params);
3257 $community_page = 0;
3258 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3260 $community_page = intval($rawtags[0]['data']);
3262 if(intval($importer['forum']) != $community_page) {
3263 q("update contact set forum = %d where id = %d",
3264 intval($community_page),
3265 intval($importer['id'])
3267 $importer['forum'] = (string) $community_page;
3270 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3272 // process any deleted entries
3274 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3275 if(is_array($del_entries) && count($del_entries)) {
3276 foreach($del_entries as $dentry) {
3278 if(isset($dentry['attribs']['']['ref'])) {
3279 $uri = $dentry['attribs']['']['ref'];
3281 if(isset($dentry['attribs']['']['when'])) {
3282 $when = $dentry['attribs']['']['when'];
3283 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3286 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3290 // check for relayed deletes to our conversation
3293 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3295 intval($importer['importer_uid'])
3298 $parent_uri = $r[0]['parent-uri'];
3299 if($r[0]['id'] != $r[0]['parent'])
3306 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3309 logger('local_delivery: possible community delete');
3312 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3314 // was the top-level post for this reply written by somebody on this site?
3315 // Specifically, the recipient?
3317 $is_a_remote_delete = false;
3319 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3320 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3321 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3322 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3323 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3324 AND `item`.`uid` = %d
3330 intval($importer['importer_uid'])
3333 $is_a_remote_delete = true;
3335 // Does this have the characteristics of a community or private group comment?
3336 // If it's a reply to a wall post on a community/prvgroup page it's a
3337 // valid community comment. Also forum_mode makes it valid for sure.
3338 // If neither, it's not.
3340 if($is_a_remote_delete && $community) {
3341 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3342 $is_a_remote_delete = false;
3343 logger('local_delivery: not a community delete');
3347 if($is_a_remote_delete) {
3348 logger('local_delivery: received remote delete');
3352 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3353 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3355 intval($importer['importer_uid']),
3356 intval($importer['id'])
3362 if($item['deleted'])
3365 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3367 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3368 $xo = parse_xml_string($item['object'],false);
3369 $xt = parse_xml_string($item['target'],false);
3371 if($xt->type === ACTIVITY_OBJ_NOTE) {
3372 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3374 intval($importer['importer_uid'])
3378 // For tags, the owner cannot remove the tag on the author's copy of the post.
3380 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3381 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3382 $author_copy = (($item['origin']) ? true : false);
3384 if($owner_remove && $author_copy)
3386 if($author_remove || $owner_remove) {
3387 $tags = explode(',',$i[0]['tag']);
3390 foreach($tags as $tag)
3391 if(trim($tag) !== trim($xo->body))
3392 $newtags[] = trim($tag);
3394 q("update item set tag = '%s' where id = %d",
3395 dbesc(implode(',',$newtags)),
3398 create_tags_from_item($i[0]['id']);
3404 if($item['uri'] == $item['parent-uri']) {
3405 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3406 `body` = '', `title` = ''
3407 WHERE `parent-uri` = '%s' AND `uid` = %d",
3409 dbesc(datetime_convert()),
3410 dbesc($item['uri']),
3411 intval($importer['importer_uid'])
3413 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3414 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3415 update_thread_uri($item['uri'], $importer['importer_uid']);
3418 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3419 `body` = '', `title` = ''
3420 WHERE `uri` = '%s' AND `uid` = %d",
3422 dbesc(datetime_convert()),
3424 intval($importer['importer_uid'])
3426 create_tags_from_itemuri($uri, $importer['importer_uid']);
3427 create_files_from_itemuri($uri, $importer['importer_uid']);
3428 update_thread_uri($uri, $importer['importer_uid']);
3429 if($item['last-child']) {
3430 // ensure that last-child is set in case the comment that had it just got wiped.
3431 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3432 dbesc(datetime_convert()),
3433 dbesc($item['parent-uri']),
3434 intval($item['uid'])
3436 // who is the last child now?
3437 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3438 ORDER BY `created` DESC LIMIT 1",
3439 dbesc($item['parent-uri']),
3440 intval($importer['importer_uid'])
3443 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3448 // if this is a relayed delete, propagate it to other recipients
3450 if($is_a_remote_delete)
3451 proc_run('php',"include/notifier.php","drop",$item['id']);
3459 foreach($feed->get_items() as $item) {
3462 $item_id = $item->get_id();
3463 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3464 if(isset($rawthread[0]['attribs']['']['ref'])) {
3466 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3472 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3475 logger('local_delivery: possible community reply');
3478 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3480 // was the top-level post for this reply written by somebody on this site?
3481 // Specifically, the recipient?
3483 $is_a_remote_comment = false;
3484 $top_uri = $parent_uri;
3486 $r = q("select `item`.`parent-uri` from `item`
3487 WHERE `item`.`uri` = '%s'
3491 if($r && count($r)) {
3492 $top_uri = $r[0]['parent-uri'];
3494 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3495 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3496 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3497 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3498 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3499 AND `item`.`uid` = %d
3505 intval($importer['importer_uid'])
3508 $is_a_remote_comment = true;
3511 // Does this have the characteristics of a community or private group comment?
3512 // If it's a reply to a wall post on a community/prvgroup page it's a
3513 // valid community comment. Also forum_mode makes it valid for sure.
3514 // If neither, it's not.
3516 if($is_a_remote_comment && $community) {
3517 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3518 $is_a_remote_comment = false;
3519 logger('local_delivery: not a community reply');
3523 if($is_a_remote_comment) {
3524 logger('local_delivery: received remote comment');
3526 // remote reply to our post. Import and then notify everybody else.
3528 $datarray = get_atom_elements($feed, $item);
3530 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3532 intval($importer['importer_uid'])
3535 // Update content if 'updated' changes
3539 if (edited_timestamp_is_newer($r[0], $datarray)) {
3541 // do not accept (ignore) an earlier edit than one we currently have.
3542 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3545 logger('received updated comment' , LOGGER_DEBUG);
3546 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3547 dbesc($datarray['title']),
3548 dbesc($datarray['body']),
3549 dbesc($datarray['tag']),
3550 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3551 dbesc(datetime_convert()),
3553 intval($importer['importer_uid'])
3555 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3557 proc_run('php',"include/notifier.php","comment-import",$iid);
3566 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3567 intval($importer['importer_uid'])
3571 $datarray['type'] = 'remote-comment';
3572 $datarray['wall'] = 1;
3573 $datarray['parent-uri'] = $parent_uri;
3574 $datarray['uid'] = $importer['importer_uid'];
3575 $datarray['owner-name'] = $own[0]['name'];
3576 $datarray['owner-link'] = $own[0]['url'];
3577 $datarray['owner-avatar'] = $own[0]['thumb'];
3578 $datarray['contact-id'] = $importer['id'];
3580 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3582 $datarray['type'] = 'activity';
3583 $datarray['gravity'] = GRAVITY_LIKE;
3584 $datarray['last-child'] = 0;
3585 // only one like or dislike per person
3586 // splitted into two queries for performance issues
3587 $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",
3588 intval($datarray['uid']),
3589 intval($datarray['contact-id']),
3590 dbesc($datarray['verb']),
3591 dbesc($datarray['parent-uri'])
3597 $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",
3598 intval($datarray['uid']),
3599 intval($datarray['contact-id']),
3600 dbesc($datarray['verb']),
3601 dbesc($datarray['parent-uri'])
3608 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3610 $xo = parse_xml_string($datarray['object'],false);
3611 $xt = parse_xml_string($datarray['target'],false);
3613 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3615 // fetch the parent item
3617 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3619 intval($importer['importer_uid'])
3624 // extract tag, if not duplicate, and this user allows tags, add to parent item
3626 if($xo->id && $xo->content) {
3627 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3628 if(! (stristr($tagp[0]['tag'],$newtag))) {
3629 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3630 intval($importer['importer_uid'])
3632 if(count($i) && ! intval($i[0]['blocktags'])) {
3633 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3634 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3635 intval($tagp[0]['id']),
3636 dbesc(datetime_convert()),
3637 dbesc(datetime_convert())
3639 create_tags_from_item($tagp[0]['id']);
3647 $posted_id = item_store($datarray);
3651 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3653 intval($importer['importer_uid'])
3656 $parent = $r[0]['parent'];
3657 $parent_uri = $r[0]['parent-uri'];
3661 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3662 dbesc(datetime_convert()),
3663 intval($importer['importer_uid']),
3664 intval($r[0]['parent'])
3667 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3668 dbesc(datetime_convert()),
3669 intval($importer['importer_uid']),
3674 if($posted_id && $parent) {
3676 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3678 if((! $is_like) && (! $importer['self'])) {
3680 require_once('include/enotify.php');
3683 'type' => NOTIFY_COMMENT,
3684 'notify_flags' => $importer['notify-flags'],
3685 'language' => $importer['language'],
3686 'to_name' => $importer['username'],
3687 'to_email' => $importer['email'],
3688 'uid' => $importer['importer_uid'],
3689 'item' => $datarray,
3690 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3691 'source_name' => stripslashes($datarray['author-name']),
3692 'source_link' => $datarray['author-link'],
3693 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3694 ? $importer['thumb'] : $datarray['author-avatar']),
3695 'verb' => ACTIVITY_POST,
3697 'parent' => $parent,
3698 'parent_uri' => $parent_uri,
3710 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3712 $item_id = $item->get_id();
3713 $datarray = get_atom_elements($feed,$item);
3715 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3718 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3720 intval($importer['importer_uid'])
3723 // Update content if 'updated' changes
3726 if (edited_timestamp_is_newer($r[0], $datarray)) {
3728 // do not accept (ignore) an earlier edit than one we currently have.
3729 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3732 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3733 dbesc($datarray['title']),
3734 dbesc($datarray['body']),
3735 dbesc($datarray['tag']),
3736 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3737 dbesc(datetime_convert()),
3739 intval($importer['importer_uid'])
3741 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3744 // update last-child if it changes
3746 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3747 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3748 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3749 dbesc(datetime_convert()),
3751 intval($importer['importer_uid'])
3753 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3754 intval($allow[0]['data']),
3755 dbesc(datetime_convert()),
3757 intval($importer['importer_uid'])
3763 $datarray['parent-uri'] = $parent_uri;
3764 $datarray['uid'] = $importer['importer_uid'];
3765 $datarray['contact-id'] = $importer['id'];
3766 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3767 $datarray['type'] = 'activity';
3768 $datarray['gravity'] = GRAVITY_LIKE;
3769 // only one like or dislike per person
3770 // splitted into two queries for performance issues
3771 $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",
3772 intval($datarray['uid']),
3773 intval($datarray['contact-id']),
3774 dbesc($datarray['verb']),
3780 $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",
3781 intval($datarray['uid']),
3782 intval($datarray['contact-id']),
3783 dbesc($datarray['verb']),
3791 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3793 $xo = parse_xml_string($datarray['object'],false);
3794 $xt = parse_xml_string($datarray['target'],false);
3796 if($xt->type == ACTIVITY_OBJ_NOTE) {
3797 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3799 intval($importer['importer_uid'])
3804 // extract tag, if not duplicate, add to parent item
3806 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3807 q("UPDATE item SET tag = '%s' WHERE id = %d",
3808 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3811 create_tags_from_item($r[0]['id']);
3817 $posted_id = item_store($datarray);
3819 // find out if our user is involved in this conversation and wants to be notified.
3821 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3823 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3825 intval($importer['importer_uid'])
3828 if(count($myconv)) {
3829 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3831 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3832 if(! link_compare($datarray['author-link'],$importer_url)) {
3835 foreach($myconv as $conv) {
3837 // now if we find a match, it means we're in this conversation
3839 if(! link_compare($conv['author-link'],$importer_url))
3842 require_once('include/enotify.php');
3844 $conv_parent = $conv['parent'];
3847 'type' => NOTIFY_COMMENT,
3848 'notify_flags' => $importer['notify-flags'],
3849 'language' => $importer['language'],
3850 'to_name' => $importer['username'],
3851 'to_email' => $importer['email'],
3852 'uid' => $importer['importer_uid'],
3853 'item' => $datarray,
3854 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3855 'source_name' => stripslashes($datarray['author-name']),
3856 'source_link' => $datarray['author-link'],
3857 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3858 ? $importer['thumb'] : $datarray['author-avatar']),
3859 'verb' => ACTIVITY_POST,
3861 'parent' => $conv_parent,
3862 'parent_uri' => $parent_uri
3866 // only send one notification
3878 // Head post of a conversation. Have we seen it? If not, import it.
3881 $item_id = $item->get_id();
3882 $datarray = get_atom_elements($feed,$item);
3884 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3885 $ev = bbtoevent($datarray['body']);
3886 if(x($ev,'desc') && x($ev,'start')) {
3887 $ev['cid'] = $importer['id'];
3888 $ev['uid'] = $importer['uid'];
3889 $ev['uri'] = $item_id;
3890 $ev['edited'] = $datarray['edited'];
3891 $ev['private'] = $datarray['private'];
3893 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3895 intval($importer['uid'])
3898 $ev['id'] = $r[0]['id'];
3899 $xyz = event_store($ev);
3904 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3906 intval($importer['importer_uid'])
3909 // Update content if 'updated' changes
3912 if (edited_timestamp_is_newer($r[0], $datarray)) {
3914 // do not accept (ignore) an earlier edit than one we currently have.
3915 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3918 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3919 dbesc($datarray['title']),
3920 dbesc($datarray['body']),
3921 dbesc($datarray['tag']),
3922 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3923 dbesc(datetime_convert()),
3925 intval($importer['importer_uid'])
3927 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3928 update_thread_uri($item_id, $importer['importer_uid']);
3931 // update last-child if it changes
3933 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3934 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3935 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3936 intval($allow[0]['data']),
3937 dbesc(datetime_convert()),
3939 intval($importer['importer_uid'])
3945 $datarray['parent-uri'] = $item_id;
3946 $datarray['uid'] = $importer['importer_uid'];
3947 $datarray['contact-id'] = $importer['id'];
3950 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3951 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3952 // but otherwise there's a possible data mixup on the sender's system.
3953 // the tgroup delivery code called from item_store will correct it if it's a forum,
3954 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3955 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3956 $datarray['owner-name'] = $importer['senderName'];
3957 $datarray['owner-link'] = $importer['url'];
3958 $datarray['owner-avatar'] = $importer['thumb'];
3961 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3964 // This is my contact on another system, but it's really me.
3965 // Turn this into a wall post.
3966 $notify = item_is_remote_self($importer, $datarray);
3968 $posted_id = item_store($datarray, false, $notify);
3970 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3971 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3974 $xo = parse_xml_string($datarray['object'],false);
3976 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3978 // somebody was poked/prodded. Was it me?
3980 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3982 foreach($links->link as $l) {
3983 $atts = $l->attributes();
3984 switch($atts['rel']) {
3986 $Blink = $atts['href'];
3992 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3994 // send a notification
3995 require_once('include/enotify.php');
3998 'type' => NOTIFY_POKE,
3999 'notify_flags' => $importer['notify-flags'],
4000 'language' => $importer['language'],
4001 'to_name' => $importer['username'],
4002 'to_email' => $importer['email'],
4003 'uid' => $importer['importer_uid'],
4004 'item' => $datarray,
4005 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4006 'source_name' => stripslashes($datarray['author-name']),
4007 'source_link' => $datarray['author-link'],
4008 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4009 ? $importer['thumb'] : $datarray['author-avatar']),
4010 'verb' => $datarray['verb'],
4011 'otype' => 'person',
4012 'activity' => $verb,
4029 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4030 $url = notags(trim($datarray['author-link']));
4031 $name = notags(trim($datarray['author-name']));
4032 $photo = notags(trim($datarray['author-avatar']));
4034 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4035 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4036 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4038 if(is_array($contact)) {
4039 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4040 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4041 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4042 intval(CONTACT_IS_FRIEND),
4043 intval($contact['id']),
4044 intval($importer['uid'])
4047 // send email notification to owner?
4051 // create contact record
4053 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4054 `blocked`, `readonly`, `pending`, `writable` )
4055 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4056 intval($importer['uid']),
4057 dbesc(datetime_convert()),
4059 dbesc(normalise_link($url)),
4063 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4064 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4066 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4067 intval($importer['uid']),
4071 $contact_record = $r[0];
4073 // create notification
4074 $hash = random_string();
4076 if(is_array($contact_record)) {
4077 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4078 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4079 intval($importer['uid']),
4080 intval($contact_record['id']),
4082 dbesc(datetime_convert())
4086 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4087 intval($importer['uid'])
4092 if(intval($r[0]['def_gid'])) {
4093 require_once('include/group.php');
4094 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4097 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4098 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4103 'type' => NOTIFY_INTRO,
4104 'notify_flags' => $r[0]['notify-flags'],
4105 'language' => $r[0]['language'],
4106 'to_name' => $r[0]['username'],
4107 'to_email' => $r[0]['email'],
4108 'uid' => $r[0]['uid'],
4109 'link' => $a->get_baseurl() . '/notifications/intro',
4110 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4111 'source_link' => $contact_record['url'],
4112 'source_photo' => $contact_record['photo'],
4113 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4123 function lose_follower($importer,$contact,$datarray,$item) {
4125 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4126 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4127 intval(CONTACT_IS_SHARING),
4128 intval($contact['id'])
4132 contact_remove($contact['id']);
4136 function lose_sharer($importer,$contact,$datarray,$item) {
4138 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4139 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4140 intval(CONTACT_IS_FOLLOWER),
4141 intval($contact['id'])
4145 contact_remove($contact['id']);
4150 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4154 if(is_array($importer)) {
4155 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4156 intval($importer['uid'])
4160 // Diaspora has different message-ids in feeds than they do
4161 // through the direct Diaspora protocol. If we try and use
4162 // the feed, we'll get duplicates. So don't.
4164 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4167 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4169 // Use a single verify token, even if multiple hubs
4171 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4173 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4175 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4177 if(! strlen($contact['hub-verify'])) {
4178 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4179 dbesc($verify_token),
4180 intval($contact['id'])
4184 post_url($url,$params);
4186 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4193 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4197 $name = xmlify($name);
4198 $uri = xmlify($uri);
4201 $photo = xmlify($photo);
4205 $o .= "<name>$name</name>\r\n";
4206 $o .= "<uri>$uri</uri>\r\n";
4207 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4208 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4210 call_hooks('atom_author', $o);
4212 $o .= "</$tag>\r\n";
4216 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4220 if(! $item['parent'])
4223 if($item['deleted'])
4224 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4227 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4228 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4230 $body = $item['body'];
4233 $o = "\r\n\r\n<entry>\r\n";
4235 if(is_array($author))
4236 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4238 $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']));
4239 if(strlen($item['owner-name']))
4240 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4242 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4243 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4244 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4249 if ($item['title'] != "")
4250 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4252 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4254 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4255 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4256 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4257 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4258 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4259 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4260 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4264 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4266 if($item['location']) {
4267 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4268 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4272 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4274 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4275 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4278 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4279 if($item['bookmark'])
4280 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4283 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4286 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4288 if($item['signed_text']) {
4289 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4290 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4293 $verb = construct_verb($item);
4294 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4295 $actobj = construct_activity_object($item);
4298 $actarg = construct_activity_target($item);
4302 $tags = item_getfeedtags($item);
4304 foreach($tags as $t) {
4305 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4309 $o .= item_getfeedattach($item);
4311 $mentioned = get_mentions($item);
4315 call_hooks('atom_entry', $o);
4317 $o .= '</entry>' . "\r\n";
4322 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4324 if(get_config('system','disable_embedded'))
4329 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4330 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4335 $img_start = strpos($orig_body, '[img');
4336 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4337 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4338 while( ($img_st_close !== false) && ($img_len !== false) ) {
4340 $img_st_close++; // make it point to AFTER the closing bracket
4341 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4343 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4346 if(stristr($image , $site . '/photo/')) {
4347 // Only embed locally hosted photos
4349 $i = basename($image);
4350 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4351 $x = strpos($i,'-');
4354 $res = substr($i,$x+1);
4355 $i = substr($i,0,$x);
4356 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4363 // Check to see if we should replace this photo link with an embedded image
4364 // 1. No need to do so if the photo is public
4365 // 2. If there's a contact-id provided, see if they're in the access list
4366 // for the photo. If so, embed it.
4367 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4368 // permissions, regardless of order but first check to see if they're an exact
4369 // match to save some processing overhead.
4371 if(has_permissions($r[0])) {
4373 $recips = enumerate_permissions($r[0]);
4374 if(in_array($cid, $recips)) {
4379 if(compare_permissions($item,$r[0]))
4384 $data = $r[0]['data'];
4385 $type = $r[0]['type'];
4387 // If a custom width and height were specified, apply before embedding
4388 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4389 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4391 $width = intval($match[1]);
4392 $height = intval($match[2]);
4394 $ph = new Photo($data, $type);
4395 if($ph->is_valid()) {
4396 $ph->scaleImage(max($width, $height));
4397 $data = $ph->imageString();
4398 $type = $ph->getType();
4402 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4403 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4404 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4410 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4411 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4412 if($orig_body === false)
4415 $img_start = strpos($orig_body, '[img');
4416 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4417 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4420 $new_body = $new_body . $orig_body;
4426 function has_permissions($obj) {
4427 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4432 function compare_permissions($obj1,$obj2) {
4433 // first part is easy. Check that these are exactly the same.
4434 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4435 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4436 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4437 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4440 // This is harder. Parse all the permissions and compare the resulting set.
4442 $recipients1 = enumerate_permissions($obj1);
4443 $recipients2 = enumerate_permissions($obj2);
4446 if($recipients1 == $recipients2)
4451 // returns an array of contact-ids that are allowed to see this object
4453 function enumerate_permissions($obj) {
4454 require_once('include/group.php');
4455 $allow_people = expand_acl($obj['allow_cid']);
4456 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4457 $deny_people = expand_acl($obj['deny_cid']);
4458 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4459 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4460 $deny = array_unique(array_merge($deny_people,$deny_groups));
4461 $recipients = array_diff($recipients,$deny);
4465 function item_getfeedtags($item) {
4468 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4470 for($x = 0; $x < $cnt; $x ++) {
4472 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4476 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4478 for($x = 0; $x < $cnt; $x ++) {
4480 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4486 function item_getfeedattach($item) {
4488 $arr = explode('[/attach],',$item['attach']);
4490 foreach($arr as $r) {
4492 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4494 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4495 if(intval($matches[2]))
4496 $ret .= 'length="' . intval($matches[2]) . '" ';
4497 if($matches[4] !== ' ')
4498 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4499 $ret .= ' />' . "\r\n";
4508 function item_expire($uid, $days, $network = "", $force = false) {
4510 if((! $uid) || ($days < 1))
4513 // $expire_network_only = save your own wall posts
4514 // and just expire conversations started by others
4516 $expire_network_only = get_pconfig($uid,'expire','network_only');
4517 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4519 if ($network != "") {
4520 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4521 // There is an index "uid_network_received" but not "uid_network_created"
4522 // This avoids the creation of another index just for one purpose.
4523 // And it doesn't really matter wether to look at "received" or "created"
4524 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4526 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4528 $r = q("SELECT * FROM `item`
4529 WHERE `uid` = %d $range
4540 $expire_items = get_pconfig($uid, 'expire','items');
4541 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4543 // Forcing expiring of items - but not notes and marked items
4545 $expire_items = true;
4547 $expire_notes = get_pconfig($uid, 'expire','notes');
4548 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4550 $expire_starred = get_pconfig($uid, 'expire','starred');
4551 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4553 $expire_photos = get_pconfig($uid, 'expire','photos');
4554 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4556 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4558 foreach($r as $item) {
4560 // don't expire filed items
4562 if(strpos($item['file'],'[') !== false)
4565 // Only expire posts, not photos and photo comments
4567 if($expire_photos==0 && strlen($item['resource-id']))
4569 if($expire_starred==0 && intval($item['starred']))
4571 if($expire_notes==0 && $item['type']=='note')
4573 if($expire_items==0 && $item['type']!='note')
4576 drop_item($item['id'],false);
4579 proc_run('php',"include/notifier.php","expire","$uid");
4584 function drop_items($items) {
4587 if(! local_user() && ! remote_user())
4591 foreach($items as $item) {
4592 $owner = drop_item($item,false);
4593 if($owner && ! $uid)
4598 // multiple threads may have been deleted, send an expire notification
4601 proc_run('php',"include/notifier.php","expire","$uid");
4605 function drop_item($id,$interactive = true) {
4609 // locate item to be deleted
4611 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4618 notice( t('Item not found.') . EOL);
4619 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4624 $owner = $item['uid'];
4628 // check if logged in user is either the author or owner of this item
4630 if(is_array($_SESSION['remote'])) {
4631 foreach($_SESSION['remote'] as $visitor) {
4632 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4633 $cid = $visitor['cid'];
4640 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4642 // Check if we should do HTML-based delete confirmation
4643 if($_REQUEST['confirm']) {
4644 // <form> can't take arguments in its "action" parameter
4645 // so add any arguments as hidden inputs
4646 $query = explode_querystring($a->query_string);
4648 foreach($query['args'] as $arg) {
4649 if(strpos($arg, 'confirm=') === false) {
4650 $arg_parts = explode('=', $arg);
4651 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4655 return replace_macros(get_markup_template('confirm.tpl'), array(
4657 '$message' => t('Do you really want to delete this item?'),
4658 '$extra_inputs' => $inputs,
4659 '$confirm' => t('Yes'),
4660 '$confirm_url' => $query['base'],
4661 '$confirm_name' => 'confirmed',
4662 '$cancel' => t('Cancel'),
4665 // Now check how the user responded to the confirmation query
4666 if($_REQUEST['canceled']) {
4667 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4670 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4673 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4674 dbesc(datetime_convert()),
4675 dbesc(datetime_convert()),
4678 create_tags_from_item($item['id']);
4679 create_files_from_item($item['id']);
4680 delete_thread($item['id'], $item['parent-uri']);
4682 // clean up categories and tags so they don't end up as orphans
4685 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4687 foreach($matches as $mtch) {
4688 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4694 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4696 foreach($matches as $mtch) {
4697 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4701 // If item is a link to a photo resource, nuke all the associated photos
4702 // (visitors will not have photo resources)
4703 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4704 // generate a resource-id and therefore aren't intimately linked to the item.
4706 if(strlen($item['resource-id'])) {
4707 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4708 dbesc($item['resource-id']),
4709 intval($item['uid'])
4711 // ignore the result
4714 // If item is a link to an event, nuke the event record.
4716 if(intval($item['event-id'])) {
4717 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4718 intval($item['event-id']),
4719 intval($item['uid'])
4721 // ignore the result
4724 // clean up item_id and sign meta-data tables
4727 // Old code - caused very long queries and warning entries in the mysql logfiles:
4729 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4730 intval($item['id']),
4731 intval($item['uid'])
4734 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4735 intval($item['id']),
4736 intval($item['uid'])
4740 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4742 // Creating list of parents
4743 $r = q("select id from item where parent = %d and uid = %d",
4744 intval($item['id']),
4745 intval($item['uid'])
4750 foreach ($r AS $row) {
4751 if ($parentid != "")
4754 $parentid .= $row["id"];
4758 if ($parentid != "") {
4759 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4761 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4764 // If it's the parent of a comment thread, kill all the kids
4766 if($item['uri'] == $item['parent-uri']) {
4767 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4768 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4769 dbesc(datetime_convert()),
4770 dbesc(datetime_convert()),
4771 dbesc($item['parent-uri']),
4772 intval($item['uid'])
4774 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4775 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4776 delete_thread_uri($item['parent-uri'], $item['uid']);
4777 // ignore the result
4780 // ensure that last-child is set in case the comment that had it just got wiped.
4781 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4782 dbesc(datetime_convert()),
4783 dbesc($item['parent-uri']),
4784 intval($item['uid'])
4786 // who is the last child now?
4787 $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",
4788 dbesc($item['parent-uri']),
4789 intval($item['uid'])
4792 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4797 // Add a relayable_retraction signature for Diaspora.
4798 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4800 $drop_id = intval($item['id']);
4802 // send the notification upstream/downstream as the case may be
4804 proc_run('php',"include/notifier.php","drop","$drop_id");
4808 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4814 notice( t('Permission denied.') . EOL);
4815 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4822 function first_post_date($uid,$wall = false) {
4823 $r = q("select id, created from item
4824 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4826 order by created asc limit 1",
4828 intval($wall ? 1 : 0)
4831 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4832 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4837 function posted_dates($uid,$wall) {
4838 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4840 $dthen = first_post_date($uid,$wall);
4844 // Set the start and end date to the beginning of the month
4845 $dnow = substr($dnow,0,8).'01';
4846 $dthen = substr($dthen,0,8).'01';
4849 // Starting with the current month, get the first and last days of every
4850 // month down to and including the month of the first post
4851 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4852 $dstart = substr($dnow,0,8) . '01';
4853 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4854 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4855 $end_month = datetime_convert('','',$dend,'Y-m-d');
4856 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4857 $ret[] = array($str,$end_month,$start_month);
4858 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4864 function posted_date_widget($url,$uid,$wall) {
4867 if(! feature_enabled($uid,'archives'))
4870 // For former Facebook folks that left because of "timeline"
4872 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4875 $ret = posted_dates($uid,$wall);
4879 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4880 '$title' => t('Archives'),
4881 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4888 function store_diaspora_retract_sig($item, $user, $baseurl) {
4889 // Note that we can't add a target_author_signature
4890 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4891 // the comment, that means we're the home of the post, and Diaspora will only
4892 // check the parent_author_signature of retractions that it doesn't have to relay further
4894 // I don't think this function gets called for an "unlike," but I'll check anyway
4896 $enabled = intval(get_config('system','diaspora_enabled'));
4898 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4902 logger('drop_item: storing diaspora retraction signature');
4904 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4906 if(local_user() == $item['uid']) {
4908 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4909 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4912 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4913 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4916 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4917 // only handles DFRN deletes
4918 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4919 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4920 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4926 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4927 intval($item['id']),
4928 dbesc($signed_text),