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');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $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` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $alternatelink = $owner['url'];
169 $alternatelink .= "/category/".$category;
171 $atom .= replace_macros($feed_template, array(
172 '$version' => xmlify(FRIENDICA_VERSION),
173 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
174 '$feed_title' => xmlify($owner['name']),
175 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
177 '$salmon' => $salmon,
178 '$alternatelink' => xmlify($alternatelink),
179 '$name' => xmlify($owner['name']),
180 '$profile_page' => xmlify($owner['url']),
181 '$photo' => xmlify($owner['photo']),
182 '$thumb' => xmlify($owner['thumb']),
183 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
184 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
185 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
186 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
187 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
190 call_hooks('atom_feed', $atom);
192 if(! count($items)) {
194 call_hooks('atom_feed_end', $atom);
196 $atom .= '</feed>' . "\r\n";
200 foreach($items as $item) {
202 // prevent private email from leaking.
203 if($item['network'] === NETWORK_MAIL)
206 // public feeds get html, our own nodes use bbcode
210 // catch any email that's in a public conversation and make sure it doesn't leak
218 $atom .= atom_entry($item,$type,null,$owner,true);
221 call_hooks('atom_feed_end', $atom);
223 $atom .= '</feed>' . "\r\n";
229 function construct_verb($item) {
231 return $item['verb'];
232 return ACTIVITY_POST;
235 function construct_activity_object($item) {
237 if($item['object']) {
238 $o = '<as:object>' . "\r\n";
239 $r = parse_xml_string($item['object'],false);
245 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
247 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
249 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
251 if(substr($r->link,0,1) === '<') {
252 // patch up some facebook "like" activity objects that got stored incorrectly
253 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
254 // we can probably remove this hack here and in the following function in a few months time.
255 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
256 $r->link = str_replace('&','&', $r->link);
257 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
261 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
264 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
265 $o .= '</as:object>' . "\r\n";
272 function construct_activity_target($item) {
274 if($item['target']) {
275 $o = '<as:target>' . "\r\n";
276 $r = parse_xml_string($item['target'],false);
280 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
282 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
284 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
286 if(substr($r->link,0,1) === '<') {
287 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
288 $r->link = str_replace('&','&', $r->link);
289 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
293 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
296 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
297 $o .= '</as:target>' . "\r\n";
306 * The purpose of this function is to apply system message length limits to
307 * imported messages without including any embedded photos in the length
309 if(! function_exists('limit_body_size')) {
310 function limit_body_size($body) {
312 // logger('limit_body_size: start', LOGGER_DEBUG);
314 $maxlen = get_max_import_size();
316 // If the length of the body, including the embedded images, is smaller
317 // than the maximum, then don't waste time looking for the images
318 if($maxlen && (strlen($body) > $maxlen)) {
320 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
327 $img_start = strpos($orig_body, '[img');
328 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
329 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
330 while(($img_st_close !== false) && ($img_end !== false)) {
332 $img_st_close++; // make it point to AFTER the closing bracket
333 $img_end += $img_start;
334 $img_end += strlen('[/img]');
336 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
337 // This is an embedded image
339 if( ($textlen + $img_start) > $maxlen ) {
340 if($textlen < $maxlen) {
341 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
342 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
347 $new_body = $new_body . substr($orig_body, 0, $img_start);
348 $textlen += $img_start;
351 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
355 if( ($textlen + $img_end) > $maxlen ) {
356 if($textlen < $maxlen) {
357 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
358 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
363 $new_body = $new_body . substr($orig_body, 0, $img_end);
364 $textlen += $img_end;
367 $orig_body = substr($orig_body, $img_end);
369 if($orig_body === false) // in case the body ends on a closing image tag
372 $img_start = strpos($orig_body, '[img');
373 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
374 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
377 if( ($textlen + strlen($orig_body)) > $maxlen) {
378 if($textlen < $maxlen) {
379 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
380 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
385 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
386 $new_body = $new_body . $orig_body;
387 $textlen += strlen($orig_body);
396 function title_is_body($title, $body) {
398 $title = strip_tags($title);
399 $title = trim($title);
400 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
401 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
403 $body = strip_tags($body);
405 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
406 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
408 if (strlen($title) < strlen($body))
409 $body = substr($body, 0, strlen($title));
411 if (($title != $body) and (substr($title, -3) == "...")) {
412 $pos = strrpos($title, "...");
414 $title = substr($title, 0, $pos);
415 $body = substr($body, 0, $pos);
419 return($title == $body);
424 function get_atom_elements($feed, $item, $contact = array()) {
426 require_once('library/HTMLPurifier.auto.php');
427 require_once('include/html2bbcode.php');
429 $best_photo = array();
433 $author = $item->get_author();
435 $res['author-name'] = unxmlify($author->get_name());
436 $res['author-link'] = unxmlify($author->get_link());
439 $res['author-name'] = unxmlify($feed->get_title());
440 $res['author-link'] = unxmlify($feed->get_permalink());
442 $res['uri'] = unxmlify($item->get_id());
443 $res['title'] = unxmlify($item->get_title());
444 $res['body'] = unxmlify($item->get_content());
445 $res['plink'] = unxmlify($item->get_link(0));
447 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
448 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
450 $res['body'] = nl2br($res['body']);
453 // removing the content of the title if its identically to the body
454 // This helps with auto generated titles e.g. from tumblr
455 if (title_is_body($res["title"], $res["body"]))
459 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
463 // look for a photo. We should check media size and find the best one,
464 // but for now let's just find any author photo
466 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
468 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
469 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
470 foreach($base as $link) {
471 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
472 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
473 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
478 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
480 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
481 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
482 if($base && count($base)) {
483 foreach($base as $link) {
484 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
485 $res['author-link'] = unxmlify($link['attribs']['']['href']);
486 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
487 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
488 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
494 // No photo/profile-link on the item - look at the feed level
496 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
497 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
498 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
499 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
503 if(! $res['author-avatar']) {
504 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
505 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
510 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
512 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
513 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
515 if($base && count($base)) {
516 foreach($base as $link) {
517 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
518 $res['author-link'] = unxmlify($link['attribs']['']['href']);
519 if(! (x($res,'author-avatar'))) {
520 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
521 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
528 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
529 if($apps && $apps[0]['attribs']['']['source']) {
530 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
531 if($res['app'] === 'web')
532 $res['app'] = 'OStatus';
535 // base64 encoded json structure representing Diaspora signature
537 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
539 $res['dsprsig'] = unxmlify($dsig[0]['data']);
542 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
544 $res['guid'] = unxmlify($dguid[0]['data']);
546 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
548 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
552 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
555 $have_real_body = false;
557 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
559 $have_real_body = true;
560 $res['body'] = $rawenv[0]['data'];
561 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
562 // make sure nobody is trying to sneak some html tags by us
563 $res['body'] = notags(base64url_decode($res['body']));
567 $res['body'] = limit_body_size($res['body']);
569 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
570 // the content type. Our own network only emits text normally, though it might have been converted to
571 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
572 // have to assume it is all html and needs to be purified.
574 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
575 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
576 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
579 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
581 $res['body'] = reltoabs($res['body'],$base_url);
583 $res['body'] = html2bb_video($res['body']);
585 $res['body'] = oembed_html2bbcode($res['body']);
587 $config = HTMLPurifier_Config::createDefault();
588 $config->set('Cache.DefinitionImpl', null);
590 // we shouldn't need a whitelist, because the bbcode converter
591 // will strip out any unsupported tags.
593 $purifier = new HTMLPurifier($config);
594 $res['body'] = $purifier->purify($res['body']);
596 $res['body'] = @html2bbcode($res['body']);
600 elseif(! $have_real_body) {
602 // it's not one of our messages and it has no tags
603 // so it's probably just text. We'll escape it just to be safe.
605 $res['body'] = escape_tags($res['body']);
609 // this tag is obsolete but we keep it for really old sites
611 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
612 if($allow && $allow[0]['data'] == 1)
613 $res['last-child'] = 1;
615 $res['last-child'] = 0;
617 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
618 if($private && intval($private[0]['data']) > 0)
619 $res['private'] = intval($private[0]['data']);
623 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
624 if($extid && $extid[0]['data'])
625 $res['extid'] = $extid[0]['data'];
627 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
629 $res['location'] = unxmlify($rawlocation[0]['data']);
632 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
634 $res['created'] = unxmlify($rawcreated[0]['data']);
637 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
639 $res['edited'] = unxmlify($rawedited[0]['data']);
641 if((x($res,'edited')) && (! (x($res,'created'))))
642 $res['created'] = $res['edited'];
644 if(! $res['created'])
645 $res['created'] = $item->get_date('c');
648 $res['edited'] = $item->get_date('c');
651 // Disallow time travelling posts
653 $d1 = strtotime($res['created']);
654 $d2 = strtotime($res['edited']);
655 $d3 = strtotime('now');
658 $res['created'] = datetime_convert();
660 $res['edited'] = datetime_convert();
662 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
663 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
664 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
665 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
667 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
668 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
669 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
672 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
673 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
675 foreach($base as $link) {
676 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
677 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
678 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
683 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
685 $res['coord'] = unxmlify($rawgeo[0]['data']);
687 if ($contact["network"] == NETWORK_FEED) {
688 $res['verb'] = ACTIVITY_POST;
689 $res['object-type'] = ACTIVITY_OBJ_NOTE;
692 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
694 // select between supported verbs
697 $res['verb'] = unxmlify($rawverb[0]['data']);
700 // translate OStatus unfollow to activity streams if it happened to get selected
702 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
703 $res['verb'] = ACTIVITY_UNFOLLOW;
705 $cats = $item->get_categories();
708 foreach($cats as $cat) {
709 $term = $cat->get_term();
711 $term = $cat->get_label();
712 $scheme = $cat->get_scheme();
713 if($scheme && $term && stristr($scheme,'X-DFRN:'))
714 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
716 $tag_arr[] = notags(trim($term));
718 $res['tag'] = implode(',', $tag_arr);
721 $attach = $item->get_enclosures();
724 foreach($attach as $att) {
725 $len = intval($att->get_length());
726 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
727 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
728 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
729 if(strpos($type,';'))
730 $type = substr($type,0,strpos($type,';'));
731 if((! $link) || (strpos($link,'http') !== 0))
737 $type = 'application/octet-stream';
739 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
741 $res['attach'] = implode(',', $att_arr);
744 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
747 $res['object'] = '<object>' . "\n";
748 $child = $rawobj[0]['child'];
749 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
750 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
751 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
754 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
756 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
758 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
760 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
763 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
764 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
765 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
767 $body = html2bb_video($body);
769 $config = HTMLPurifier_Config::createDefault();
770 $config->set('Cache.DefinitionImpl', null);
772 $purifier = new HTMLPurifier($config);
773 $body = $purifier->purify($body);
774 $body = html2bbcode($body);
777 $res['object'] .= '<content>' . $body . '</content>' . "\n";
780 $res['object'] .= '</object>' . "\n";
783 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
786 $res['target'] = '<target>' . "\n";
787 $child = $rawobj[0]['child'];
788 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
789 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
792 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
794 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
796 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
798 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
801 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
802 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
803 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
805 $body = html2bb_video($body);
807 $config = HTMLPurifier_Config::createDefault();
808 $config->set('Cache.DefinitionImpl', null);
810 $purifier = new HTMLPurifier($config);
811 $body = $purifier->purify($body);
812 $body = html2bbcode($body);
815 $res['target'] .= '<content>' . $body . '</content>' . "\n";
818 $res['target'] .= '</target>' . "\n";
821 // This is some experimental stuff. By now retweets are shown with "RT:"
822 // But: There is data so that the message could be shown similar to native retweets
823 // There is some better way to parse this array - but it didn't worked for me.
824 $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"];
825 if (is_array($child)) {
826 logger('get_atom_elements: Looking for status.net repeated message');
828 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
829 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
830 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
831 $uri = $author["uri"][0]["data"];
832 $name = $author["name"][0]["data"];
833 $avatar = @array_shift($author["link"][2]["attribs"]);
834 $avatar = $avatar["href"];
836 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
837 logger('get_atom_elements: fixing sender of repeated message.');
839 if (!intval(get_config('system','wall-to-wall_share'))) {
840 $prefix = "[share author='".str_replace("'", "'",$name).
842 "' avatar='".$avatar.
843 "' link='".$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']);
885 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
886 unset($res["attach"]);
887 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
888 $res["body"] = add_page_info_to_body($res["body"]);
889 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
890 $res["body"] = add_page_info_to_body($res["body"]);
893 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
895 call_hooks('parse_atom', $arr);
900 function add_page_info_data($data) {
901 call_hooks('page_info_data', $data);
903 // It maybe is a rich content, but if it does have everything that a link has,
904 // then treat it that way
905 if (($data["type"] == "rich") AND is_string($data["title"]) AND
906 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
907 $data["type"] = "link";
909 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
912 if ($no_photos AND ($data["type"] == "photo"))
915 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
916 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
917 require_once("include/network.php");
918 $data["url"] = short_link($data["url"]);
921 if (($data["type"] != "photo") AND is_string($data["title"]))
922 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
924 if (($data["type"] != "video") AND ($photo != ""))
925 $text .= '[img]'.$photo.'[/img]';
926 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
927 $imagedata = $data["images"][0];
928 $text .= '[img]'.$imagedata["src"].'[/img]';
931 if (($data["type"] != "photo") AND is_string($data["text"]))
932 $text .= "[quote]".$data["text"]."[/quote]";
935 if (isset($data["keywords"]) AND count($data["keywords"])) {
938 foreach ($data["keywords"] AS $keyword) {
939 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
940 array("","", "", "", "", ""), $keyword);
941 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
945 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
948 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
949 require_once("mod/parse_url.php");
951 $data = parseurl_getsiteinfo($url, true);
954 $data["images"][0]["src"] = $photo;
956 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
958 if (!$keywords AND isset($data["keywords"]))
959 unset($data["keywords"]);
961 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
962 $list = explode(",", $keyword_blacklist);
963 foreach ($list AS $keyword) {
964 $keyword = trim($keyword);
965 $index = array_search($keyword, $data["keywords"]);
966 if ($index !== false)
967 unset($data["keywords"][$index]);
971 $text = add_page_info_data($data);
976 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
978 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
980 $URLSearchString = "^\[\]";
982 // Adding these spaces is a quick hack due to my problems with regular expressions :)
983 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
986 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
988 // Convert urls without bbcode elements
989 if (!$matches AND $texturl) {
990 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
992 // Yeah, a hack. I really hate regular expressions :)
994 $matches[1] = $matches[2];
998 $footer = add_page_info($matches[1], $no_photos);
1000 // Remove the link from the body if the link is attached at the end of the post
1001 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1002 $removedlink = trim(str_replace($matches[1], "", $body));
1003 if (($removedlink == "") OR strstr($body, $removedlink))
1004 $body = $removedlink;
1006 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1007 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1008 if (($removedlink == "") OR strstr($body, $removedlink))
1009 $body = $removedlink;
1012 // Add the page information to the bottom
1013 if (isset($footer) AND (trim($footer) != ""))
1019 function encode_rel_links($links) {
1021 if(! ((is_array($links)) && (count($links))))
1023 foreach($links as $link) {
1025 if($link['attribs']['']['rel'])
1026 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1027 if($link['attribs']['']['type'])
1028 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1029 if($link['attribs']['']['href'])
1030 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1031 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1032 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1033 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1034 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1035 $o .= ' />' . "\n" ;
1042 function item_store($arr,$force_parent = false, $notify = false) {
1044 // If it is a posting where users should get notifications, then define it as wall posting
1047 $arr['type'] = 'wall';
1049 $arr['last-child'] = 1;
1050 $arr['network'] = NETWORK_DFRN;
1053 // If a Diaspora signature structure was passed in, pull it out of the
1054 // item array and set it aside for later storage.
1057 if(x($arr,'dsprsig')) {
1058 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1059 unset($arr['dsprsig']);
1062 // if an OStatus conversation url was passed in, it is stored and then
1063 // removed from the array.
1064 $ostatus_conversation = null;
1066 if (isset($arr["ostatus_conversation"])) {
1067 $ostatus_conversation = $arr["ostatus_conversation"];
1068 unset($arr["ostatus_conversation"]);
1071 if(x($arr, 'gravity'))
1072 $arr['gravity'] = intval($arr['gravity']);
1073 elseif($arr['parent-uri'] === $arr['uri'])
1074 $arr['gravity'] = 0;
1075 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1076 $arr['gravity'] = 6;
1078 $arr['gravity'] = 6; // extensible catchall
1080 if(! x($arr,'type'))
1081 $arr['type'] = 'remote';
1085 /* check for create date and expire time */
1086 $uid = intval($arr['uid']);
1087 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1089 $expire_interval = $r[0]['expire'];
1090 if ($expire_interval>0) {
1091 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1092 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1093 if ($created_date < $expire_date) {
1094 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1100 // If there is no guid then take the same guid that was taken before for the same uri
1101 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1102 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1103 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1104 dbesc(trim($arr['uri']))
1108 $arr['guid'] = $r[0]["guid"];
1109 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1113 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1114 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1115 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1116 // $arr['body'] = strip_tags($arr['body']);
1119 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1120 require_once('library/langdet/Text/LanguageDetect.php');
1121 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1122 $l = new Text_LanguageDetect;
1123 //$lng = $l->detectConfidence($naked_body);
1124 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1125 $lng = $l->detect($naked_body, 3);
1127 if (sizeof($lng) > 0) {
1130 foreach ($lng as $language => $score) {
1131 if ($postopts == "")
1132 $postopts = "lang=";
1136 $postopts .= $language.";".$score;
1138 $arr['postopts'] = $postopts;
1142 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1143 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1144 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1145 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1146 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1147 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1148 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1149 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1150 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1151 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1152 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1153 $arr['commented'] = datetime_convert();
1154 $arr['received'] = datetime_convert();
1155 $arr['changed'] = datetime_convert();
1156 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1157 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1158 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1159 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1160 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1161 $arr['deleted'] = 0;
1162 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1163 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1164 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1165 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1166 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1167 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1168 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1169 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1170 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1171 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1172 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1173 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1174 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1175 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1176 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1177 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1178 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1179 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1180 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1181 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1182 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1183 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1184 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1185 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1186 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1188 if ($arr['plink'] == "") {
1190 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1193 if ($arr['network'] == "") {
1194 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1195 intval($arr['contact-id']),
1200 $arr['network'] = $r[0]["network"];
1202 // Fallback to friendica (why is it empty in some cases?)
1203 if ($arr['network'] == "")
1204 $arr['network'] = NETWORK_DFRN;
1206 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1209 $arr['thr-parent'] = $arr['parent-uri'];
1210 if($arr['parent-uri'] === $arr['uri']) {
1212 $parent_deleted = 0;
1213 $allow_cid = $arr['allow_cid'];
1214 $allow_gid = $arr['allow_gid'];
1215 $deny_cid = $arr['deny_cid'];
1216 $deny_gid = $arr['deny_gid'];
1217 $notify_type = 'wall-new';
1221 // find the parent and snarf the item id and ACLs
1222 // and anything else we need to inherit
1224 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1225 dbesc($arr['parent-uri']),
1231 // is the new message multi-level threaded?
1232 // even though we don't support it now, preserve the info
1233 // and re-attach to the conversation parent.
1235 if($r[0]['uri'] != $r[0]['parent-uri']) {
1236 $arr['parent-uri'] = $r[0]['parent-uri'];
1237 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1238 ORDER BY `id` ASC LIMIT 1",
1239 dbesc($r[0]['parent-uri']),
1240 dbesc($r[0]['parent-uri']),
1247 $parent_id = $r[0]['id'];
1248 $parent_deleted = $r[0]['deleted'];
1249 $allow_cid = $r[0]['allow_cid'];
1250 $allow_gid = $r[0]['allow_gid'];
1251 $deny_cid = $r[0]['deny_cid'];
1252 $deny_gid = $r[0]['deny_gid'];
1253 $arr['wall'] = $r[0]['wall'];
1254 $notify_type = 'comment-new';
1256 // if the parent is private, force privacy for the entire conversation
1257 // This differs from the above settings as it subtly allows comments from
1258 // email correspondents to be private even if the overall thread is not.
1260 if($r[0]['private'])
1261 $arr['private'] = $r[0]['private'];
1263 // Edge case. We host a public forum that was originally posted to privately.
1264 // The original author commented, but as this is a comment, the permissions
1265 // weren't fixed up so it will still show the comment as private unless we fix it here.
1267 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1268 $arr['private'] = 0;
1271 // If its a post from myself then tag the thread as "mention"
1272 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1273 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1276 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1277 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1278 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1279 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1280 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1286 // Allow one to see reply tweets from status.net even when
1287 // we don't have or can't see the original post.
1290 logger('item_store: $force_parent=true, reply converted to top-level post.');
1292 $arr['parent-uri'] = $arr['uri'];
1293 $arr['gravity'] = 0;
1296 logger('item_store: item parent was not found - ignoring item');
1300 $parent_deleted = 0;
1304 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1308 if($r && count($r)) {
1309 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1313 call_hooks('post_remote',$arr);
1315 if(x($arr,'cancel')) {
1316 logger('item_store: post cancelled by plugin.');
1322 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1324 $r = dbq("INSERT INTO `item` (`"
1325 . implode("`, `", array_keys($arr))
1327 . implode("', '", array_values($arr))
1330 // find the item we just created
1332 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1333 $arr['uri'], // already dbesc'd
1338 $current_post = $r[0]['id'];
1339 logger('item_store: created item ' . $current_post);
1341 // Only check for notifications on start posts
1342 if ($arr['parent-uri'] === $arr['uri']) {
1343 add_thread($r[0]['id']);
1344 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1346 // Send a notification for every new post?
1347 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1348 intval($arr['contact-id']),
1353 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1354 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1355 intval($arr['uid']));
1357 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1358 intval($current_post),
1364 require_once('include/enotify.php');
1366 'type' => NOTIFY_SHARE,
1367 'notify_flags' => $u[0]['notify-flags'],
1368 'language' => $u[0]['language'],
1369 'to_name' => $u[0]['username'],
1370 'to_email' => $u[0]['email'],
1371 'uid' => $u[0]['uid'],
1373 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1374 'source_name' => $item[0]['author-name'],
1375 'source_link' => $item[0]['author-link'],
1376 'source_photo' => $item[0]['author-avatar'],
1377 'verb' => ACTIVITY_TAG,
1380 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1385 logger('item_store: could not locate created item');
1389 logger('item_store: duplicated post occurred. Removing duplicates.');
1390 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1392 intval($arr['uid']),
1393 intval($current_post)
1397 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1398 $parent_id = $current_post;
1400 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1403 $private = $arr['private'];
1405 // Set parent id - and also make sure to inherit the parent's ACLs.
1407 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1408 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1415 intval($parent_deleted),
1416 intval($current_post)
1419 // Complete ostatus threads
1420 if ($ostatus_conversation)
1421 complete_conversation($current_post, $ostatus_conversation);
1423 $arr['id'] = $current_post;
1424 $arr['parent'] = $parent_id;
1425 $arr['allow_cid'] = $allow_cid;
1426 $arr['allow_gid'] = $allow_gid;
1427 $arr['deny_cid'] = $deny_cid;
1428 $arr['deny_gid'] = $deny_gid;
1429 $arr['private'] = $private;
1430 $arr['deleted'] = $parent_deleted;
1432 // update the commented timestamp on the parent
1434 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1435 dbesc(datetime_convert()),
1436 dbesc(datetime_convert()),
1439 update_thread($parent_id);
1442 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1443 intval($current_post),
1444 dbesc($dsprsig->signed_text),
1445 dbesc($dsprsig->signature),
1446 dbesc($dsprsig->signer)
1452 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1455 if($arr['last-child']) {
1456 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1458 intval($arr['uid']),
1459 intval($current_post)
1463 $deleted = tag_deliver($arr['uid'],$current_post);
1465 // current post can be deleted if is for a communuty page and no mention are
1469 // Store the fresh generated item into the cache
1470 $cachefile = get_cachefile(urlencode($arr["guid"])."-".hash("md5", $arr['body']));
1472 if (($cachefile != '') AND !file_exists($cachefile)) {
1473 $s = prepare_text($arr['body']);
1475 $stamp1 = microtime(true);
1476 file_put_contents($cachefile, $s);
1477 $a->save_timestamp($stamp1, "file");
1478 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1481 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1482 if (count($r) == 1) {
1483 call_hooks('post_remote_end', $r[0]);
1485 logger('item_store: new item not found in DB, id ' . $current_post);
1489 create_tags_from_item($current_post);
1490 create_files_from_item($current_post);
1493 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1495 return $current_post;
1498 function get_item_guid($id) {
1499 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1501 return($r[0]["guid"]);
1506 function get_item_id($guid, $uid = 0) {
1512 $uid == local_user();
1514 // Does the given user have this item?
1516 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1517 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1518 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1521 $nick = $r[0]["nickname"];
1525 // Or is it anywhere on the server?
1527 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1528 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1529 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1530 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1531 AND `item`.`private` = 0 AND `item`.`wall` = 1
1532 AND `item`.`guid` = '%s'", dbesc($guid));
1535 $nick = $r[0]["nickname"];
1538 return(array("nick" => $nick, "id" => $id));
1542 function get_item_contact($item,$contacts) {
1543 if(! count($contacts) || (! is_array($item)))
1545 foreach($contacts as $contact) {
1546 if($contact['id'] == $item['contact-id']) {
1548 break; // NOTREACHED
1555 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1557 * @param int $item_id
1558 * @return bool true if item was deleted, else false
1560 function tag_deliver($uid,$item_id) {
1568 $u = q("select * from user where uid = %d limit 1",
1574 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1575 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1578 $i = q("select * from item where id = %d and uid = %d limit 1",
1587 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1589 // Diaspora uses their own hardwired link URL in @-tags
1590 // instead of the one we supply with webfinger
1592 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1594 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1596 foreach($matches as $mtch) {
1597 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1599 logger('tag_deliver: mention found: ' . $mtch[2]);
1605 if ( ($community_page || $prvgroup) &&
1606 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1607 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1609 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1610 q("DELETE FROM item WHERE id = %d and uid = %d",
1620 // send a notification
1622 // use a local photo if we have one
1624 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1625 intval($u[0]['uid']),
1626 dbesc(normalise_link($item['author-link']))
1628 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1631 require_once('include/enotify.php');
1633 'type' => NOTIFY_TAGSELF,
1634 'notify_flags' => $u[0]['notify-flags'],
1635 'language' => $u[0]['language'],
1636 'to_name' => $u[0]['username'],
1637 'to_email' => $u[0]['email'],
1638 'uid' => $u[0]['uid'],
1640 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1641 'source_name' => $item['author-name'],
1642 'source_link' => $item['author-link'],
1643 'source_photo' => $photo,
1644 'verb' => ACTIVITY_TAG,
1646 'parent' => $item['parent']
1650 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1652 call_hooks('tagged', $arr);
1654 if((! $community_page) && (! $prvgroup))
1658 // tgroup delivery - setup a second delivery chain
1659 // prevent delivery looping - only proceed
1660 // if the message originated elsewhere and is a top-level post
1662 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1665 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1668 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1669 intval($u[0]['uid'])
1674 // also reset all the privacy bits to the forum default permissions
1676 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1678 $forum_mode = (($prvgroup) ? 2 : 1);
1680 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1681 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1682 intval($forum_mode),
1683 dbesc($c[0]['name']),
1684 dbesc($c[0]['url']),
1685 dbesc($c[0]['thumb']),
1687 dbesc($u[0]['allow_cid']),
1688 dbesc($u[0]['allow_gid']),
1689 dbesc($u[0]['deny_cid']),
1690 dbesc($u[0]['deny_gid']),
1693 update_thread($item_id);
1695 proc_run('php','include/notifier.php','tgroup',$item_id);
1701 function tgroup_check($uid,$item) {
1707 // check that the message originated elsewhere and is a top-level post
1709 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1713 $u = q("select * from user where uid = %d limit 1",
1719 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1720 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1723 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1725 // Diaspora uses their own hardwired link URL in @-tags
1726 // instead of the one we supply with webfinger
1728 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1730 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1732 foreach($matches as $mtch) {
1733 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1735 logger('tgroup_check: mention found: ' . $mtch[2]);
1743 if((! $community_page) && (! $prvgroup))
1757 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1761 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1763 if($contact['duplex'] && $contact['dfrn-id'])
1764 $idtosend = '0:' . $orig_id;
1765 if($contact['duplex'] && $contact['issued-id'])
1766 $idtosend = '1:' . $orig_id;
1768 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1770 $rino_enable = get_config('system','rino_encrypt');
1775 $ssl_val = intval(get_config('system','ssl_policy'));
1779 case SSL_POLICY_FULL:
1780 $ssl_policy = 'full';
1782 case SSL_POLICY_SELFSIGN:
1783 $ssl_policy = 'self';
1785 case SSL_POLICY_NONE:
1787 $ssl_policy = 'none';
1791 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1793 logger('dfrn_deliver: ' . $url);
1795 $xml = fetch_url($url);
1797 $curl_stat = $a->get_curl_code();
1799 return(-1); // timed out
1801 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1806 if(strpos($xml,'<?xml') === false) {
1807 logger('dfrn_deliver: no valid XML returned');
1808 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1812 $res = parse_xml_string($xml);
1814 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1815 return (($res->status) ? $res->status : 3);
1817 $postvars = array();
1818 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1819 $challenge = hex2bin((string) $res->challenge);
1820 $perm = (($res->perm) ? $res->perm : null);
1821 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1822 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1823 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1825 if($owner['page-flags'] == PAGE_PRVGROUP)
1828 $final_dfrn_id = '';
1831 if((($perm == 'rw') && (! intval($contact['writable'])))
1832 || (($perm == 'r') && (intval($contact['writable'])))) {
1833 q("update contact set writable = %d where id = %d",
1834 intval(($perm == 'rw') ? 1 : 0),
1835 intval($contact['id'])
1837 $contact['writable'] = (string) 1 - intval($contact['writable']);
1841 if(($contact['duplex'] && strlen($contact['pubkey']))
1842 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1843 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1844 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1845 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1848 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1849 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1852 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1854 if(strpos($final_dfrn_id,':') == 1)
1855 $final_dfrn_id = substr($final_dfrn_id,2);
1857 if($final_dfrn_id != $orig_id) {
1858 logger('dfrn_deliver: wrong dfrn_id.');
1859 // did not decode properly - cannot trust this site
1863 $postvars['dfrn_id'] = $idtosend;
1864 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1866 $postvars['dissolve'] = '1';
1869 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1870 $postvars['data'] = $atom;
1871 $postvars['perm'] = 'rw';
1874 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1875 $postvars['perm'] = 'r';
1878 $postvars['ssl_policy'] = $ssl_policy;
1881 $postvars['page'] = $page;
1883 if($rino && $rino_allowed && (! $dissolve)) {
1884 $key = substr(random_string(),0,16);
1885 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1886 $postvars['data'] = $data;
1887 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1890 if($dfrn_version >= 2.1) {
1891 if(($contact['duplex'] && strlen($contact['pubkey']))
1892 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1893 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1895 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1898 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1902 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1903 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1906 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1910 logger('md5 rawkey ' . md5($postvars['key']));
1912 $postvars['key'] = bin2hex($postvars['key']);
1915 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1917 $xml = post_url($contact['notify'],$postvars);
1919 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1921 $curl_stat = $a->get_curl_code();
1922 if((! $curl_stat) || (! strlen($xml)))
1923 return(-1); // timed out
1925 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1928 if(strpos($xml,'<?xml') === false) {
1929 logger('dfrn_deliver: phase 2: no valid XML returned');
1930 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1934 if($contact['term-date'] != '0000-00-00 00:00:00') {
1935 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1936 require_once('include/Contact.php');
1937 unmark_for_death($contact);
1940 $res = parse_xml_string($xml);
1942 return $res->status;
1947 This function returns true if $update has an edited timestamp newer
1948 than $existing, i.e. $update contains new data which should override
1949 what's already there. If there is no timestamp yet, the update is
1950 assumed to be newer. If the update has no timestamp, the existing
1951 item is assumed to be up-to-date. If the timestamps are equal it
1952 assumes the update has been seen before and should be ignored.
1954 function edited_timestamp_is_newer($existing, $update) {
1955 if (!x($existing,'edited') || !$existing['edited']) {
1958 if (!x($update,'edited') || !$update['edited']) {
1961 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1962 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1963 return (strcmp($existing_edited, $update_edited) < 0);
1968 * consume_feed - process atom feed and update anything/everything we might need to update
1970 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1972 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1973 * It is this person's stuff that is going to be updated.
1974 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1975 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1976 * have a contact record.
1977 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1978 * might not) try and subscribe to it.
1979 * $datedir sorts in reverse order
1980 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1981 * imported prior to its children being seen in the stream unless we are certain
1982 * of how the feed is arranged/ordered.
1983 * With $pass = 1, we only pull parent items out of the stream.
1984 * With $pass = 2, we only pull children (comments/likes).
1986 * So running this twice, first with pass 1 and then with pass 2 will do the right
1987 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1988 * model where comments can have sub-threads. That would require some massive sorting
1989 * to get all the feed items into a mostly linear ordering, and might still require
1993 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1995 require_once('library/simplepie/simplepie.inc');
1996 require_once('include/contact_selectors.php');
1998 if(! strlen($xml)) {
1999 logger('consume_feed: empty input');
2003 $feed = new SimplePie();
2004 $feed->set_raw_data($xml);
2006 $feed->enable_order_by_date(true);
2008 $feed->enable_order_by_date(false);
2012 logger('consume_feed: Error parsing XML: ' . $feed->error());
2014 $permalink = $feed->get_permalink();
2016 // Check at the feed level for updated contact name and/or photo
2020 $photo_timestamp = '';
2024 $hubs = $feed->get_links('hub');
2025 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2028 $hub = implode(',', $hubs);
2030 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2032 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2034 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2035 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2036 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2037 $new_name = $elems['name'][0]['data'];
2039 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2040 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2041 $photo_url = $elems['link'][0]['attribs']['']['href'];
2044 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2045 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2049 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2050 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2051 require_once("include/Photo.php");
2052 $photo_failure = false;
2053 $have_photo = false;
2055 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2056 intval($contact['id']),
2057 intval($contact['uid'])
2060 $resource_id = $r[0]['resource-id'];
2064 $resource_id = photo_new_resource();
2067 $img_str = fetch_url($photo_url,true);
2068 // guess mimetype from headers or filename
2069 $type = guess_image_type($photo_url,true);
2072 $img = new Photo($img_str, $type);
2073 if($img->is_valid()) {
2075 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2076 dbesc($resource_id),
2077 intval($contact['id']),
2078 intval($contact['uid'])
2082 $img->scaleImageSquare(175);
2084 $hash = $resource_id;
2085 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2087 $img->scaleImage(80);
2088 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2090 $img->scaleImage(48);
2091 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2095 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2096 WHERE `uid` = %d AND `id` = %d",
2097 dbesc(datetime_convert()),
2098 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2099 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2100 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2101 intval($contact['uid']),
2102 intval($contact['id'])
2107 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2108 $r = q("select * from contact where uid = %d and id = %d limit 1",
2109 intval($contact['uid']),
2110 intval($contact['id'])
2113 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2114 dbesc(notags(trim($new_name))),
2115 dbesc(datetime_convert()),
2116 intval($contact['uid']),
2117 intval($contact['id'])
2120 // do our best to update the name on content items
2123 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2124 dbesc(notags(trim($new_name))),
2125 dbesc($r[0]['name']),
2126 dbesc($r[0]['url']),
2127 intval($contact['uid'])
2132 if(strlen($birthday)) {
2133 if(substr($birthday,0,4) != $contact['bdyear']) {
2134 logger('consume_feed: updating birthday: ' . $birthday);
2138 * Add new birthday event for this person
2140 * $bdtext is just a readable placeholder in case the event is shared
2141 * with others. We will replace it during presentation to our $importer
2142 * to contain a sparkle link and perhaps a photo.
2146 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2147 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2150 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2151 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2152 intval($contact['uid']),
2153 intval($contact['id']),
2154 dbesc(datetime_convert()),
2155 dbesc(datetime_convert()),
2156 dbesc(datetime_convert('UTC','UTC', $birthday)),
2157 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2166 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2167 dbesc(substr($birthday,0,4)),
2168 intval($contact['uid']),
2169 intval($contact['id'])
2172 // This function is called twice without reloading the contact
2173 // Make sure we only create one event. This is why &$contact
2174 // is a reference var in this function
2176 $contact['bdyear'] = substr($birthday,0,4);
2181 $community_page = 0;
2182 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2184 $community_page = intval($rawtags[0]['data']);
2186 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2187 q("update contact set forum = %d where id = %d",
2188 intval($community_page),
2189 intval($contact['id'])
2191 $contact['forum'] = (string) $community_page;
2195 // process any deleted entries
2197 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2198 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2199 foreach($del_entries as $dentry) {
2201 if(isset($dentry['attribs']['']['ref'])) {
2202 $uri = $dentry['attribs']['']['ref'];
2204 if(isset($dentry['attribs']['']['when'])) {
2205 $when = $dentry['attribs']['']['when'];
2206 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2209 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2211 if($deleted && is_array($contact)) {
2212 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2213 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2215 intval($importer['uid']),
2216 intval($contact['id'])
2221 if(! $item['deleted'])
2222 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2224 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2225 $xo = parse_xml_string($item['object'],false);
2226 $xt = parse_xml_string($item['target'],false);
2227 if($xt->type === ACTIVITY_OBJ_NOTE) {
2228 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2230 intval($importer['importer_uid'])
2234 // For tags, the owner cannot remove the tag on the author's copy of the post.
2236 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2237 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2238 $author_copy = (($item['origin']) ? true : false);
2240 if($owner_remove && $author_copy)
2242 if($author_remove || $owner_remove) {
2243 $tags = explode(',',$i[0]['tag']);
2246 foreach($tags as $tag)
2247 if(trim($tag) !== trim($xo->body))
2248 $newtags[] = trim($tag);
2250 q("update item set tag = '%s' where id = %d",
2251 dbesc(implode(',',$newtags)),
2254 create_tags_from_item($i[0]['id']);
2260 if($item['uri'] == $item['parent-uri']) {
2261 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2262 `body` = '', `title` = ''
2263 WHERE `parent-uri` = '%s' AND `uid` = %d",
2265 dbesc(datetime_convert()),
2266 dbesc($item['uri']),
2267 intval($importer['uid'])
2269 create_tags_from_itemuri($item['uri'], $importer['uid']);
2270 create_files_from_itemuri($item['uri'], $importer['uid']);
2271 update_thread_uri($item['uri'], $importer['uid']);
2274 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2275 `body` = '', `title` = ''
2276 WHERE `uri` = '%s' AND `uid` = %d",
2278 dbesc(datetime_convert()),
2280 intval($importer['uid'])
2282 create_tags_from_itemuri($uri, $importer['uid']);
2283 create_files_from_itemuri($uri, $importer['uid']);
2284 if($item['last-child']) {
2285 // ensure that last-child is set in case the comment that had it just got wiped.
2286 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2287 dbesc(datetime_convert()),
2288 dbesc($item['parent-uri']),
2289 intval($item['uid'])
2291 // who is the last child now?
2292 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2293 ORDER BY `created` DESC LIMIT 1",
2294 dbesc($item['parent-uri']),
2295 intval($importer['uid'])
2298 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2309 // Now process the feed
2311 if($feed->get_item_quantity()) {
2313 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2315 // in inverse date order
2317 $items = array_reverse($feed->get_items());
2319 $items = $feed->get_items();
2322 foreach($items as $item) {
2325 $item_id = $item->get_id();
2326 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2327 if(isset($rawthread[0]['attribs']['']['ref'])) {
2329 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2332 if(($is_reply) && is_array($contact)) {
2337 // not allowed to post
2339 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2343 // Have we seen it? If not, import it.
2345 $item_id = $item->get_id();
2346 $datarray = get_atom_elements($feed, $item, $contact);
2348 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2349 $datarray['author-name'] = $contact['name'];
2350 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2351 $datarray['author-link'] = $contact['url'];
2352 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2353 $datarray['author-avatar'] = $contact['thumb'];
2355 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2356 logger('consume_feed: no author information! ' . print_r($datarray,true));
2360 $force_parent = false;
2361 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2362 if($contact['network'] === NETWORK_OSTATUS)
2363 $force_parent = true;
2364 if(strlen($datarray['title']))
2365 unset($datarray['title']);
2366 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2367 dbesc(datetime_convert()),
2369 intval($importer['uid'])
2371 $datarray['last-child'] = 1;
2372 update_thread_uri($parent_uri, $importer['uid']);
2376 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2378 intval($importer['uid'])
2381 // Update content if 'updated' changes
2384 if (edited_timestamp_is_newer($r[0], $datarray)) {
2386 // do not accept (ignore) an earlier edit than one we currently have.
2387 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2390 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2391 dbesc($datarray['title']),
2392 dbesc($datarray['body']),
2393 dbesc($datarray['tag']),
2394 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2395 dbesc(datetime_convert()),
2397 intval($importer['uid'])
2399 create_tags_from_itemuri($item_id, $importer['uid']);
2400 update_thread_uri($item_id, $importer['uid']);
2403 // update last-child if it changes
2405 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2406 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2407 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2408 dbesc(datetime_convert()),
2410 intval($importer['uid'])
2412 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2413 intval($allow[0]['data']),
2414 dbesc(datetime_convert()),
2416 intval($importer['uid'])
2418 update_thread_uri($item_id, $importer['uid']);
2424 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2425 // one way feed - no remote comment ability
2426 $datarray['last-child'] = 0;
2428 $datarray['parent-uri'] = $parent_uri;
2429 $datarray['uid'] = $importer['uid'];
2430 $datarray['contact-id'] = $contact['id'];
2431 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2432 $datarray['type'] = 'activity';
2433 $datarray['gravity'] = GRAVITY_LIKE;
2434 // only one like or dislike per person
2435 // splitted into two queries for performance issues
2436 $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",
2437 intval($datarray['uid']),
2438 intval($datarray['contact-id']),
2439 dbesc($datarray['verb']),
2445 $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",
2446 intval($datarray['uid']),
2447 intval($datarray['contact-id']),
2448 dbesc($datarray['verb']),
2455 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2456 $xo = parse_xml_string($datarray['object'],false);
2457 $xt = parse_xml_string($datarray['target'],false);
2459 if($xt->type == ACTIVITY_OBJ_NOTE) {
2460 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2462 intval($importer['importer_uid'])
2467 // extract tag, if not duplicate, add to parent item
2468 if($xo->id && $xo->content) {
2469 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2470 if(! (stristr($r[0]['tag'],$newtag))) {
2471 q("UPDATE item SET tag = '%s' WHERE id = %d",
2472 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2475 create_tags_from_item($r[0]['id']);
2481 $r = item_store($datarray,$force_parent);
2487 // Head post of a conversation. Have we seen it? If not, import it.
2489 $item_id = $item->get_id();
2491 $datarray = get_atom_elements($feed, $item, $contact);
2493 if(is_array($contact)) {
2494 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2495 $datarray['author-name'] = $contact['name'];
2496 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2497 $datarray['author-link'] = $contact['url'];
2498 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2499 $datarray['author-avatar'] = $contact['thumb'];
2502 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2503 logger('consume_feed: no author information! ' . print_r($datarray,true));
2507 // special handling for events
2509 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2510 $ev = bbtoevent($datarray['body']);
2511 if(x($ev,'desc') && x($ev,'start')) {
2512 $ev['uid'] = $importer['uid'];
2513 $ev['uri'] = $item_id;
2514 $ev['edited'] = $datarray['edited'];
2515 $ev['private'] = $datarray['private'];
2517 if(is_array($contact))
2518 $ev['cid'] = $contact['id'];
2519 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2521 intval($importer['uid'])
2524 $ev['id'] = $r[0]['id'];
2525 $xyz = event_store($ev);
2530 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2531 if(strlen($datarray['title']))
2532 unset($datarray['title']);
2533 $datarray['last-child'] = 1;
2537 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2539 intval($importer['uid'])
2542 // Update content if 'updated' changes
2545 if (edited_timestamp_is_newer($r[0], $datarray)) {
2547 // do not accept (ignore) an earlier edit than one we currently have.
2548 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2551 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2552 dbesc($datarray['title']),
2553 dbesc($datarray['body']),
2554 dbesc($datarray['tag']),
2555 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2556 dbesc(datetime_convert()),
2558 intval($importer['uid'])
2560 create_tags_from_itemuri($item_id, $importer['uid']);
2561 update_thread_uri($item_id, $importer['uid']);
2564 // update last-child if it changes
2566 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2567 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2568 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2569 intval($allow[0]['data']),
2570 dbesc(datetime_convert()),
2572 intval($importer['uid'])
2574 update_thread_uri($item_id, $importer['uid']);
2579 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2580 logger('consume-feed: New follower');
2581 new_follower($importer,$contact,$datarray,$item);
2584 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2585 lose_follower($importer,$contact,$datarray,$item);
2589 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2590 logger('consume-feed: New friend request');
2591 new_follower($importer,$contact,$datarray,$item,true);
2594 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2595 lose_sharer($importer,$contact,$datarray,$item);
2600 if(! is_array($contact))
2604 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2605 // one way feed - no remote comment ability
2606 $datarray['last-child'] = 0;
2608 if($contact['network'] === NETWORK_FEED)
2609 $datarray['private'] = 2;
2611 $datarray['parent-uri'] = $item_id;
2612 $datarray['uid'] = $importer['uid'];
2613 $datarray['contact-id'] = $contact['id'];
2615 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2616 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2617 // but otherwise there's a possible data mixup on the sender's system.
2618 // the tgroup delivery code called from item_store will correct it if it's a forum,
2619 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2620 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2621 $datarray['owner-name'] = $contact['name'];
2622 $datarray['owner-link'] = $contact['url'];
2623 $datarray['owner-avatar'] = $contact['thumb'];
2626 // We've allowed "followers" to reach this point so we can decide if they are
2627 // posting an @-tag delivery, which followers are allowed to do for certain
2628 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2630 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2633 // This is my contact on another system, but it's really me.
2634 // Turn this into a wall post.
2635 $notify = item_is_remote_self($contact, $datarray);
2637 $r = item_store($datarray, false, $notify);
2638 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2646 function item_is_remote_self($contact, &$datarray) {
2649 if (!$contact['remote_self'])
2652 // Prevent the forwarding of posts that are forwarded
2653 if ($datarray["extid"] == NETWORK_DFRN)
2656 // Prevent to forward already forwarded posts
2657 if ($datarray["app"] == $a->get_hostname())
2660 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2663 $datarray2 = $datarray;
2664 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2665 if ($contact['remote_self'] == 2) {
2666 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
2667 intval($contact['uid']));
2669 $datarray['contact-id'] = $r[0]["id"];
2671 $datarray['owner-name'] = $r[0]["name"];
2672 $datarray['owner-link'] = $r[0]["url"];
2673 $datarray['owner-avatar'] = $r[0]["avatar"];
2675 $datarray['author-name'] = $datarray['owner-name'];
2676 $datarray['author-link'] = $datarray['owner-link'];
2677 $datarray['author-avatar'] = $datarray['owner-avatar'];
2680 if ($contact['network'] != NETWORK_FEED) {
2681 $datarray["guid"] = get_guid(32);
2682 unset($datarray["plink"]);
2683 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2684 $datarray["parent-uri"] = $datarray["uri"];
2685 $datarray["extid"] = $contact['network'];
2686 $urlpart = parse_url($datarray2['author-link']);
2687 $datarray["app"] = $urlpart["host"];
2689 $datarray['private'] = 0;
2692 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2693 // $datarray["app"] = network_to_name($contact['network']);
2695 if ($contact['network'] != NETWORK_FEED) {
2696 // Store the original post
2697 $r = item_store($datarray2, false, false);
2698 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2700 $datarray["app"] = "Feed";
2705 function local_delivery($importer,$data) {
2708 logger(__function__, LOGGER_TRACE);
2710 if($importer['readonly']) {
2711 // We aren't receiving stuff from this person. But we will quietly ignore them
2712 // rather than a blatant "go away" message.
2713 logger('local_delivery: ignoring');
2718 // Consume notification feed. This may differ from consuming a public feed in several ways
2719 // - might contain email or friend suggestions
2720 // - might contain remote followup to our message
2721 // - in which case we need to accept it and then notify other conversants
2722 // - we may need to send various email notifications
2724 $feed = new SimplePie();
2725 $feed->set_raw_data($data);
2726 $feed->enable_order_by_date(false);
2731 logger('local_delivery: Error parsing XML: ' . $feed->error());
2734 // Check at the feed level for updated contact name and/or photo
2738 $photo_timestamp = '';
2742 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2744 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2746 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2749 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2750 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2751 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2752 $new_name = $elems['name'][0]['data'];
2754 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2755 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2756 $photo_url = $elems['link'][0]['attribs']['']['href'];
2760 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2761 logger('local_delivery: Updating photo for ' . $importer['name']);
2762 require_once("include/Photo.php");
2763 $photo_failure = false;
2764 $have_photo = false;
2766 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2767 intval($importer['id']),
2768 intval($importer['importer_uid'])
2771 $resource_id = $r[0]['resource-id'];
2775 $resource_id = photo_new_resource();
2778 $img_str = fetch_url($photo_url,true);
2779 // guess mimetype from headers or filename
2780 $type = guess_image_type($photo_url,true);
2783 $img = new Photo($img_str, $type);
2784 if($img->is_valid()) {
2786 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2787 dbesc($resource_id),
2788 intval($importer['id']),
2789 intval($importer['importer_uid'])
2793 $img->scaleImageSquare(175);
2795 $hash = $resource_id;
2796 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2798 $img->scaleImage(80);
2799 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2801 $img->scaleImage(48);
2802 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2806 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2807 WHERE `uid` = %d AND `id` = %d",
2808 dbesc(datetime_convert()),
2809 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2810 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2811 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2812 intval($importer['importer_uid']),
2813 intval($importer['id'])
2818 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2819 $r = q("select * from contact where uid = %d and id = %d limit 1",
2820 intval($importer['importer_uid']),
2821 intval($importer['id'])
2824 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2825 dbesc(notags(trim($new_name))),
2826 dbesc(datetime_convert()),
2827 intval($importer['importer_uid']),
2828 intval($importer['id'])
2831 // do our best to update the name on content items
2834 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2835 dbesc(notags(trim($new_name))),
2836 dbesc($r[0]['name']),
2837 dbesc($r[0]['url']),
2838 intval($importer['importer_uid'])
2845 // Currently unsupported - needs a lot of work
2846 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2847 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2848 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2850 $newloc['uid'] = $importer['importer_uid'];
2851 $newloc['cid'] = $importer['id'];
2852 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2853 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2854 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2855 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2856 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2857 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2858 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2859 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2860 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2861 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2862 /** relocated user must have original key pair */
2863 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2864 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2866 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2869 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2870 intval($importer['id']),
2871 intval($importer['importer_uid']));
2876 $x = q("UPDATE contact SET
2886 `site-pubkey` = '%s'
2887 WHERE id=%d AND uid=%d;",
2888 dbesc($newloc['name']),
2889 dbesc($newloc['photo']),
2890 dbesc($newloc['thumb']),
2891 dbesc($newloc['micro']),
2892 dbesc($newloc['url']),
2893 dbesc($newloc['request']),
2894 dbesc($newloc['confirm']),
2895 dbesc($newloc['notify']),
2896 dbesc($newloc['poll']),
2897 dbesc($newloc['sitepubkey']),
2898 intval($importer['id']),
2899 intval($importer['importer_uid']));
2905 'owner-link' => array($old['url'], $newloc['url']),
2906 'author-link' => array($old['url'], $newloc['url']),
2907 'owner-avatar' => array($old['photo'], $newloc['photo']),
2908 'author-avatar' => array($old['photo'], $newloc['photo']),
2910 foreach ($fields as $n=>$f){
2911 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2914 intval($importer['importer_uid']));
2920 // merge with current record, current contents have priority
2921 // update record, set url-updated
2922 // update profile photos
2928 // handle friend suggestion notification
2930 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2931 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2932 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2934 $fsugg['uid'] = $importer['importer_uid'];
2935 $fsugg['cid'] = $importer['id'];
2936 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2937 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2938 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2939 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2940 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2942 // Does our member already have a friend matching this description?
2944 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2945 dbesc($fsugg['name']),
2946 dbesc(normalise_link($fsugg['url'])),
2947 intval($fsugg['uid'])
2952 // Do we already have an fcontact record for this person?
2955 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2956 dbesc($fsugg['url']),
2957 dbesc($fsugg['name']),
2958 dbesc($fsugg['request'])
2963 // OK, we do. Do we already have an introduction for this person ?
2964 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2965 intval($fsugg['uid']),
2972 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2973 dbesc($fsugg['name']),
2974 dbesc($fsugg['url']),
2975 dbesc($fsugg['photo']),
2976 dbesc($fsugg['request'])
2978 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2979 dbesc($fsugg['url']),
2980 dbesc($fsugg['name']),
2981 dbesc($fsugg['request'])
2986 // database record did not get created. Quietly give up.
2991 $hash = random_string();
2993 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2994 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2995 intval($fsugg['uid']),
2997 intval($fsugg['cid']),
2998 dbesc($fsugg['body']),
3000 dbesc(datetime_convert()),
3005 'type' => NOTIFY_SUGGEST,
3006 'notify_flags' => $importer['notify-flags'],
3007 'language' => $importer['language'],
3008 'to_name' => $importer['username'],
3009 'to_email' => $importer['email'],
3010 'uid' => $importer['importer_uid'],
3012 'link' => $a->get_baseurl() . '/notifications/intros',
3013 'source_name' => $importer['name'],
3014 'source_link' => $importer['url'],
3015 'source_photo' => $importer['photo'],
3016 'verb' => ACTIVITY_REQ_FRIEND,
3025 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3026 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3028 logger('local_delivery: private message received');
3031 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3034 $msg['uid'] = $importer['importer_uid'];
3035 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3036 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3037 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3038 $msg['contact-id'] = $importer['id'];
3039 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3040 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3042 $msg['replied'] = 0;
3043 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3044 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3045 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3049 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3050 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3052 // send notifications.
3054 require_once('include/enotify.php');
3056 $notif_params = array(
3057 'type' => NOTIFY_MAIL,
3058 'notify_flags' => $importer['notify-flags'],
3059 'language' => $importer['language'],
3060 'to_name' => $importer['username'],
3061 'to_email' => $importer['email'],
3062 'uid' => $importer['importer_uid'],
3064 'source_name' => $msg['from-name'],
3065 'source_link' => $importer['url'],
3066 'source_photo' => $importer['thumb'],
3067 'verb' => ACTIVITY_POST,
3071 notification($notif_params);
3077 $community_page = 0;
3078 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3080 $community_page = intval($rawtags[0]['data']);
3082 if(intval($importer['forum']) != $community_page) {
3083 q("update contact set forum = %d where id = %d",
3084 intval($community_page),
3085 intval($importer['id'])
3087 $importer['forum'] = (string) $community_page;
3090 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3092 // process any deleted entries
3094 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3095 if(is_array($del_entries) && count($del_entries)) {
3096 foreach($del_entries as $dentry) {
3098 if(isset($dentry['attribs']['']['ref'])) {
3099 $uri = $dentry['attribs']['']['ref'];
3101 if(isset($dentry['attribs']['']['when'])) {
3102 $when = $dentry['attribs']['']['when'];
3103 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3106 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3110 // check for relayed deletes to our conversation
3113 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3115 intval($importer['importer_uid'])
3118 $parent_uri = $r[0]['parent-uri'];
3119 if($r[0]['id'] != $r[0]['parent'])
3126 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3129 logger('local_delivery: possible community delete');
3132 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3134 // was the top-level post for this reply written by somebody on this site?
3135 // Specifically, the recipient?
3137 $is_a_remote_delete = false;
3139 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3140 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3141 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3142 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3143 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3144 AND `item`.`uid` = %d
3150 intval($importer['importer_uid'])
3153 $is_a_remote_delete = true;
3155 // Does this have the characteristics of a community or private group comment?
3156 // If it's a reply to a wall post on a community/prvgroup page it's a
3157 // valid community comment. Also forum_mode makes it valid for sure.
3158 // If neither, it's not.
3160 if($is_a_remote_delete && $community) {
3161 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3162 $is_a_remote_delete = false;
3163 logger('local_delivery: not a community delete');
3167 if($is_a_remote_delete) {
3168 logger('local_delivery: received remote delete');
3172 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3173 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3175 intval($importer['importer_uid']),
3176 intval($importer['id'])
3182 if($item['deleted'])
3185 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3187 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3188 $xo = parse_xml_string($item['object'],false);
3189 $xt = parse_xml_string($item['target'],false);
3191 if($xt->type === ACTIVITY_OBJ_NOTE) {
3192 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3194 intval($importer['importer_uid'])
3198 // For tags, the owner cannot remove the tag on the author's copy of the post.
3200 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3201 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3202 $author_copy = (($item['origin']) ? true : false);
3204 if($owner_remove && $author_copy)
3206 if($author_remove || $owner_remove) {
3207 $tags = explode(',',$i[0]['tag']);
3210 foreach($tags as $tag)
3211 if(trim($tag) !== trim($xo->body))
3212 $newtags[] = trim($tag);
3214 q("update item set tag = '%s' where id = %d",
3215 dbesc(implode(',',$newtags)),
3218 create_tags_from_item($i[0]['id']);
3224 if($item['uri'] == $item['parent-uri']) {
3225 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3226 `body` = '', `title` = ''
3227 WHERE `parent-uri` = '%s' AND `uid` = %d",
3229 dbesc(datetime_convert()),
3230 dbesc($item['uri']),
3231 intval($importer['importer_uid'])
3233 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3234 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3235 update_thread_uri($item['uri'], $importer['importer_uid']);
3238 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3239 `body` = '', `title` = ''
3240 WHERE `uri` = '%s' AND `uid` = %d",
3242 dbesc(datetime_convert()),
3244 intval($importer['importer_uid'])
3246 create_tags_from_itemuri($uri, $importer['importer_uid']);
3247 create_files_from_itemuri($uri, $importer['importer_uid']);
3248 update_thread_uri($uri, $importer['importer_uid']);
3249 if($item['last-child']) {
3250 // ensure that last-child is set in case the comment that had it just got wiped.
3251 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3252 dbesc(datetime_convert()),
3253 dbesc($item['parent-uri']),
3254 intval($item['uid'])
3256 // who is the last child now?
3257 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3258 ORDER BY `created` DESC LIMIT 1",
3259 dbesc($item['parent-uri']),
3260 intval($importer['importer_uid'])
3263 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3268 // if this is a relayed delete, propagate it to other recipients
3270 if($is_a_remote_delete)
3271 proc_run('php',"include/notifier.php","drop",$item['id']);
3279 foreach($feed->get_items() as $item) {
3282 $item_id = $item->get_id();
3283 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3284 if(isset($rawthread[0]['attribs']['']['ref'])) {
3286 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3292 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3295 logger('local_delivery: possible community reply');
3298 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3300 // was the top-level post for this reply written by somebody on this site?
3301 // Specifically, the recipient?
3303 $is_a_remote_comment = false;
3304 $top_uri = $parent_uri;
3306 $r = q("select `item`.`parent-uri` from `item`
3307 WHERE `item`.`uri` = '%s'
3311 if($r && count($r)) {
3312 $top_uri = $r[0]['parent-uri'];
3314 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3315 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3316 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3317 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3318 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3319 AND `item`.`uid` = %d
3325 intval($importer['importer_uid'])
3328 $is_a_remote_comment = true;
3331 // Does this have the characteristics of a community or private group comment?
3332 // If it's a reply to a wall post on a community/prvgroup page it's a
3333 // valid community comment. Also forum_mode makes it valid for sure.
3334 // If neither, it's not.
3336 if($is_a_remote_comment && $community) {
3337 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3338 $is_a_remote_comment = false;
3339 logger('local_delivery: not a community reply');
3343 if($is_a_remote_comment) {
3344 logger('local_delivery: received remote comment');
3346 // remote reply to our post. Import and then notify everybody else.
3348 $datarray = get_atom_elements($feed, $item);
3350 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3352 intval($importer['importer_uid'])
3355 // Update content if 'updated' changes
3359 if (edited_timestamp_is_newer($r[0], $datarray)) {
3361 // do not accept (ignore) an earlier edit than one we currently have.
3362 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3365 logger('received updated comment' , LOGGER_DEBUG);
3366 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3367 dbesc($datarray['title']),
3368 dbesc($datarray['body']),
3369 dbesc($datarray['tag']),
3370 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3371 dbesc(datetime_convert()),
3373 intval($importer['importer_uid'])
3375 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3377 proc_run('php',"include/notifier.php","comment-import",$iid);
3386 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3387 intval($importer['importer_uid'])
3391 $datarray['type'] = 'remote-comment';
3392 $datarray['wall'] = 1;
3393 $datarray['parent-uri'] = $parent_uri;
3394 $datarray['uid'] = $importer['importer_uid'];
3395 $datarray['owner-name'] = $own[0]['name'];
3396 $datarray['owner-link'] = $own[0]['url'];
3397 $datarray['owner-avatar'] = $own[0]['thumb'];
3398 $datarray['contact-id'] = $importer['id'];
3400 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3402 $datarray['type'] = 'activity';
3403 $datarray['gravity'] = GRAVITY_LIKE;
3404 $datarray['last-child'] = 0;
3405 // only one like or dislike per person
3406 // splitted into two queries for performance issues
3407 $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",
3408 intval($datarray['uid']),
3409 intval($datarray['contact-id']),
3410 dbesc($datarray['verb']),
3411 dbesc($datarray['parent-uri'])
3417 $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",
3418 intval($datarray['uid']),
3419 intval($datarray['contact-id']),
3420 dbesc($datarray['verb']),
3421 dbesc($datarray['parent-uri'])
3428 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3430 $xo = parse_xml_string($datarray['object'],false);
3431 $xt = parse_xml_string($datarray['target'],false);
3433 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3435 // fetch the parent item
3437 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3439 intval($importer['importer_uid'])
3444 // extract tag, if not duplicate, and this user allows tags, add to parent item
3446 if($xo->id && $xo->content) {
3447 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3448 if(! (stristr($tagp[0]['tag'],$newtag))) {
3449 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3450 intval($importer['importer_uid'])
3452 if(count($i) && ! intval($i[0]['blocktags'])) {
3453 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3454 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3455 intval($tagp[0]['id']),
3456 dbesc(datetime_convert()),
3457 dbesc(datetime_convert())
3459 create_tags_from_item($tagp[0]['id']);
3467 $posted_id = item_store($datarray);
3471 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3473 intval($importer['importer_uid'])
3476 $parent = $r[0]['parent'];
3477 $parent_uri = $r[0]['parent-uri'];
3481 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3482 dbesc(datetime_convert()),
3483 intval($importer['importer_uid']),
3484 intval($r[0]['parent'])
3487 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3488 dbesc(datetime_convert()),
3489 intval($importer['importer_uid']),
3494 if($posted_id && $parent) {
3496 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3498 if((! $is_like) && (! $importer['self'])) {
3500 require_once('include/enotify.php');
3503 'type' => NOTIFY_COMMENT,
3504 'notify_flags' => $importer['notify-flags'],
3505 'language' => $importer['language'],
3506 'to_name' => $importer['username'],
3507 'to_email' => $importer['email'],
3508 'uid' => $importer['importer_uid'],
3509 'item' => $datarray,
3510 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3511 'source_name' => stripslashes($datarray['author-name']),
3512 'source_link' => $datarray['author-link'],
3513 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3514 ? $importer['thumb'] : $datarray['author-avatar']),
3515 'verb' => ACTIVITY_POST,
3517 'parent' => $parent,
3518 'parent_uri' => $parent_uri,
3530 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3532 $item_id = $item->get_id();
3533 $datarray = get_atom_elements($feed,$item);
3535 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3538 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3540 intval($importer['importer_uid'])
3543 // Update content if 'updated' changes
3546 if (edited_timestamp_is_newer($r[0], $datarray)) {
3548 // do not accept (ignore) an earlier edit than one we currently have.
3549 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3552 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3553 dbesc($datarray['title']),
3554 dbesc($datarray['body']),
3555 dbesc($datarray['tag']),
3556 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3557 dbesc(datetime_convert()),
3559 intval($importer['importer_uid'])
3561 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3564 // update last-child if it changes
3566 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3567 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3568 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3569 dbesc(datetime_convert()),
3571 intval($importer['importer_uid'])
3573 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3574 intval($allow[0]['data']),
3575 dbesc(datetime_convert()),
3577 intval($importer['importer_uid'])
3583 $datarray['parent-uri'] = $parent_uri;
3584 $datarray['uid'] = $importer['importer_uid'];
3585 $datarray['contact-id'] = $importer['id'];
3586 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3587 $datarray['type'] = 'activity';
3588 $datarray['gravity'] = GRAVITY_LIKE;
3589 // only one like or dislike per person
3590 // splitted into two queries for performance issues
3591 $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",
3592 intval($datarray['uid']),
3593 intval($datarray['contact-id']),
3594 dbesc($datarray['verb']),
3600 $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",
3601 intval($datarray['uid']),
3602 intval($datarray['contact-id']),
3603 dbesc($datarray['verb']),
3611 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3613 $xo = parse_xml_string($datarray['object'],false);
3614 $xt = parse_xml_string($datarray['target'],false);
3616 if($xt->type == ACTIVITY_OBJ_NOTE) {
3617 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3619 intval($importer['importer_uid'])
3624 // extract tag, if not duplicate, add to parent item
3626 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3627 q("UPDATE item SET tag = '%s' WHERE id = %d",
3628 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3631 create_tags_from_item($r[0]['id']);
3637 $posted_id = item_store($datarray);
3639 // find out if our user is involved in this conversation and wants to be notified.
3641 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3643 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3645 intval($importer['importer_uid'])
3648 if(count($myconv)) {
3649 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3651 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3652 if(! link_compare($datarray['author-link'],$importer_url)) {
3655 foreach($myconv as $conv) {
3657 // now if we find a match, it means we're in this conversation
3659 if(! link_compare($conv['author-link'],$importer_url))
3662 require_once('include/enotify.php');
3664 $conv_parent = $conv['parent'];
3667 'type' => NOTIFY_COMMENT,
3668 'notify_flags' => $importer['notify-flags'],
3669 'language' => $importer['language'],
3670 'to_name' => $importer['username'],
3671 'to_email' => $importer['email'],
3672 'uid' => $importer['importer_uid'],
3673 'item' => $datarray,
3674 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3675 'source_name' => stripslashes($datarray['author-name']),
3676 'source_link' => $datarray['author-link'],
3677 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3678 ? $importer['thumb'] : $datarray['author-avatar']),
3679 'verb' => ACTIVITY_POST,
3681 'parent' => $conv_parent,
3682 'parent_uri' => $parent_uri
3686 // only send one notification
3698 // Head post of a conversation. Have we seen it? If not, import it.
3701 $item_id = $item->get_id();
3702 $datarray = get_atom_elements($feed,$item);
3704 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3705 $ev = bbtoevent($datarray['body']);
3706 if(x($ev,'desc') && x($ev,'start')) {
3707 $ev['cid'] = $importer['id'];
3708 $ev['uid'] = $importer['uid'];
3709 $ev['uri'] = $item_id;
3710 $ev['edited'] = $datarray['edited'];
3711 $ev['private'] = $datarray['private'];
3713 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3715 intval($importer['uid'])
3718 $ev['id'] = $r[0]['id'];
3719 $xyz = event_store($ev);
3724 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3726 intval($importer['importer_uid'])
3729 // Update content if 'updated' changes
3732 if (edited_timestamp_is_newer($r[0], $datarray)) {
3734 // do not accept (ignore) an earlier edit than one we currently have.
3735 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3738 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3739 dbesc($datarray['title']),
3740 dbesc($datarray['body']),
3741 dbesc($datarray['tag']),
3742 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3743 dbesc(datetime_convert()),
3745 intval($importer['importer_uid'])
3747 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3748 update_thread_uri($item_id, $importer['importer_uid']);
3751 // update last-child if it changes
3753 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3754 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3755 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3756 intval($allow[0]['data']),
3757 dbesc(datetime_convert()),
3759 intval($importer['importer_uid'])
3765 $datarray['parent-uri'] = $item_id;
3766 $datarray['uid'] = $importer['importer_uid'];
3767 $datarray['contact-id'] = $importer['id'];
3770 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3771 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3772 // but otherwise there's a possible data mixup on the sender's system.
3773 // the tgroup delivery code called from item_store will correct it if it's a forum,
3774 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3775 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3776 $datarray['owner-name'] = $importer['senderName'];
3777 $datarray['owner-link'] = $importer['url'];
3778 $datarray['owner-avatar'] = $importer['thumb'];
3781 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3784 // This is my contact on another system, but it's really me.
3785 // Turn this into a wall post.
3786 $notify = item_is_remote_self($importer, $datarray);
3788 $posted_id = item_store($datarray, false, $notify);
3790 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3791 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3794 $xo = parse_xml_string($datarray['object'],false);
3796 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3798 // somebody was poked/prodded. Was it me?
3800 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3802 foreach($links->link as $l) {
3803 $atts = $l->attributes();
3804 switch($atts['rel']) {
3806 $Blink = $atts['href'];
3812 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3814 // send a notification
3815 require_once('include/enotify.php');
3818 'type' => NOTIFY_POKE,
3819 'notify_flags' => $importer['notify-flags'],
3820 'language' => $importer['language'],
3821 'to_name' => $importer['username'],
3822 'to_email' => $importer['email'],
3823 'uid' => $importer['importer_uid'],
3824 'item' => $datarray,
3825 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3826 'source_name' => stripslashes($datarray['author-name']),
3827 'source_link' => $datarray['author-link'],
3828 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3829 ? $importer['thumb'] : $datarray['author-avatar']),
3830 'verb' => $datarray['verb'],
3831 'otype' => 'person',
3832 'activity' => $verb,
3849 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3850 $url = notags(trim($datarray['author-link']));
3851 $name = notags(trim($datarray['author-name']));
3852 $photo = notags(trim($datarray['author-avatar']));
3854 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3855 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3856 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3858 if(is_array($contact)) {
3859 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3860 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3861 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3862 intval(CONTACT_IS_FRIEND),
3863 intval($contact['id']),
3864 intval($importer['uid'])
3867 // send email notification to owner?
3871 // create contact record
3873 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3874 `blocked`, `readonly`, `pending`, `writable` )
3875 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3876 intval($importer['uid']),
3877 dbesc(datetime_convert()),
3879 dbesc(normalise_link($url)),
3883 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3884 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3886 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3887 intval($importer['uid']),
3891 $contact_record = $r[0];
3893 // create notification
3894 $hash = random_string();
3896 if(is_array($contact_record)) {
3897 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3898 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3899 intval($importer['uid']),
3900 intval($contact_record['id']),
3902 dbesc(datetime_convert())
3906 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3907 intval($importer['uid'])
3912 if(intval($r[0]['def_gid'])) {
3913 require_once('include/group.php');
3914 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3917 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3918 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3923 'type' => NOTIFY_INTRO,
3924 'notify_flags' => $r[0]['notify-flags'],
3925 'language' => $r[0]['language'],
3926 'to_name' => $r[0]['username'],
3927 'to_email' => $r[0]['email'],
3928 'uid' => $r[0]['uid'],
3929 'link' => $a->get_baseurl() . '/notifications/intro',
3930 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3931 'source_link' => $contact_record['url'],
3932 'source_photo' => $contact_record['photo'],
3933 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3943 function lose_follower($importer,$contact,$datarray,$item) {
3945 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3946 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3947 intval(CONTACT_IS_SHARING),
3948 intval($contact['id'])
3952 contact_remove($contact['id']);
3956 function lose_sharer($importer,$contact,$datarray,$item) {
3958 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3959 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3960 intval(CONTACT_IS_FOLLOWER),
3961 intval($contact['id'])
3965 contact_remove($contact['id']);
3970 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3974 if(is_array($importer)) {
3975 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3976 intval($importer['uid'])
3980 // Diaspora has different message-ids in feeds than they do
3981 // through the direct Diaspora protocol. If we try and use
3982 // the feed, we'll get duplicates. So don't.
3984 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3987 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3989 // Use a single verify token, even if multiple hubs
3991 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3993 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3995 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3997 if(! strlen($contact['hub-verify'])) {
3998 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3999 dbesc($verify_token),
4000 intval($contact['id'])
4004 post_url($url,$params);
4006 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4013 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4017 $name = xmlify($name);
4018 $uri = xmlify($uri);
4021 $photo = xmlify($photo);
4025 $o .= "<name>$name</name>\r\n";
4026 $o .= "<uri>$uri</uri>\r\n";
4027 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4028 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4030 call_hooks('atom_author', $o);
4032 $o .= "</$tag>\r\n";
4036 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4040 if(! $item['parent'])
4043 if($item['deleted'])
4044 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4047 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4048 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4050 $body = $item['body'];
4052 $o = "\r\n\r\n<entry>\r\n";
4054 if(is_array($author))
4055 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4057 $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']));
4058 if(strlen($item['owner-name']))
4059 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4061 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4062 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4063 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4066 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4067 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4068 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4069 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4070 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4071 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4072 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4074 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4076 if($item['location']) {
4077 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4078 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4082 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4084 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4085 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4088 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4089 if($item['bookmark'])
4090 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4093 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4096 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4098 if($item['signed_text']) {
4099 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4100 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4103 $verb = construct_verb($item);
4104 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4105 $actobj = construct_activity_object($item);
4108 $actarg = construct_activity_target($item);
4112 $tags = item_getfeedtags($item);
4114 foreach($tags as $t) {
4115 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4119 $o .= item_getfeedattach($item);
4121 $mentioned = get_mentions($item);
4125 call_hooks('atom_entry', $o);
4127 $o .= '</entry>' . "\r\n";
4132 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4134 if(get_config('system','disable_embedded'))
4139 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4140 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4145 $img_start = strpos($orig_body, '[img');
4146 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4147 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4148 while( ($img_st_close !== false) && ($img_len !== false) ) {
4150 $img_st_close++; // make it point to AFTER the closing bracket
4151 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4153 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4156 if(stristr($image , $site . '/photo/')) {
4157 // Only embed locally hosted photos
4159 $i = basename($image);
4160 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4161 $x = strpos($i,'-');
4164 $res = substr($i,$x+1);
4165 $i = substr($i,0,$x);
4166 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4173 // Check to see if we should replace this photo link with an embedded image
4174 // 1. No need to do so if the photo is public
4175 // 2. If there's a contact-id provided, see if they're in the access list
4176 // for the photo. If so, embed it.
4177 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4178 // permissions, regardless of order but first check to see if they're an exact
4179 // match to save some processing overhead.
4181 if(has_permissions($r[0])) {
4183 $recips = enumerate_permissions($r[0]);
4184 if(in_array($cid, $recips)) {
4189 if(compare_permissions($item,$r[0]))
4194 $data = $r[0]['data'];
4195 $type = $r[0]['type'];
4197 // If a custom width and height were specified, apply before embedding
4198 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4199 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4201 $width = intval($match[1]);
4202 $height = intval($match[2]);
4204 $ph = new Photo($data, $type);
4205 if($ph->is_valid()) {
4206 $ph->scaleImage(max($width, $height));
4207 $data = $ph->imageString();
4208 $type = $ph->getType();
4212 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4213 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4214 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4220 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4221 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4222 if($orig_body === false)
4225 $img_start = strpos($orig_body, '[img');
4226 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4227 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4230 $new_body = $new_body . $orig_body;
4236 function has_permissions($obj) {
4237 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4242 function compare_permissions($obj1,$obj2) {
4243 // first part is easy. Check that these are exactly the same.
4244 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4245 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4246 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4247 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4250 // This is harder. Parse all the permissions and compare the resulting set.
4252 $recipients1 = enumerate_permissions($obj1);
4253 $recipients2 = enumerate_permissions($obj2);
4256 if($recipients1 == $recipients2)
4261 // returns an array of contact-ids that are allowed to see this object
4263 function enumerate_permissions($obj) {
4264 require_once('include/group.php');
4265 $allow_people = expand_acl($obj['allow_cid']);
4266 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4267 $deny_people = expand_acl($obj['deny_cid']);
4268 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4269 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4270 $deny = array_unique(array_merge($deny_people,$deny_groups));
4271 $recipients = array_diff($recipients,$deny);
4275 function item_getfeedtags($item) {
4278 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4280 for($x = 0; $x < $cnt; $x ++) {
4282 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4286 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4288 for($x = 0; $x < $cnt; $x ++) {
4290 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4296 function item_getfeedattach($item) {
4298 $arr = explode('[/attach],',$item['attach']);
4300 foreach($arr as $r) {
4302 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4304 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4305 if(intval($matches[2]))
4306 $ret .= 'length="' . intval($matches[2]) . '" ';
4307 if($matches[4] !== ' ')
4308 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4309 $ret .= ' />' . "\r\n";
4318 function item_expire($uid, $days, $network = "", $force = false) {
4320 if((! $uid) || ($days < 1))
4323 // $expire_network_only = save your own wall posts
4324 // and just expire conversations started by others
4326 $expire_network_only = get_pconfig($uid,'expire','network_only');
4327 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4329 if ($network != "") {
4330 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4331 // There is an index "uid_network_received" but not "uid_network_created"
4332 // This avoids the creation of another index just for one purpose.
4333 // And it doesn't really matter wether to look at "received" or "created"
4334 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4336 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4338 $r = q("SELECT * FROM `item`
4339 WHERE `uid` = %d $range
4350 $expire_items = get_pconfig($uid, 'expire','items');
4351 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4353 // Forcing expiring of items - but not notes and marked items
4355 $expire_items = true;
4357 $expire_notes = get_pconfig($uid, 'expire','notes');
4358 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4360 $expire_starred = get_pconfig($uid, 'expire','starred');
4361 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4363 $expire_photos = get_pconfig($uid, 'expire','photos');
4364 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4366 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4368 foreach($r as $item) {
4370 // don't expire filed items
4372 if(strpos($item['file'],'[') !== false)
4375 // Only expire posts, not photos and photo comments
4377 if($expire_photos==0 && strlen($item['resource-id']))
4379 if($expire_starred==0 && intval($item['starred']))
4381 if($expire_notes==0 && $item['type']=='note')
4383 if($expire_items==0 && $item['type']!='note')
4386 drop_item($item['id'],false);
4389 proc_run('php',"include/notifier.php","expire","$uid");
4394 function drop_items($items) {
4397 if(! local_user() && ! remote_user())
4401 foreach($items as $item) {
4402 $owner = drop_item($item,false);
4403 if($owner && ! $uid)
4408 // multiple threads may have been deleted, send an expire notification
4411 proc_run('php',"include/notifier.php","expire","$uid");
4415 function drop_item($id,$interactive = true) {
4419 // locate item to be deleted
4421 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4428 notice( t('Item not found.') . EOL);
4429 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4434 $owner = $item['uid'];
4438 // check if logged in user is either the author or owner of this item
4440 if(is_array($_SESSION['remote'])) {
4441 foreach($_SESSION['remote'] as $visitor) {
4442 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4443 $cid = $visitor['cid'];
4450 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4452 // Check if we should do HTML-based delete confirmation
4453 if($_REQUEST['confirm']) {
4454 // <form> can't take arguments in its "action" parameter
4455 // so add any arguments as hidden inputs
4456 $query = explode_querystring($a->query_string);
4458 foreach($query['args'] as $arg) {
4459 if(strpos($arg, 'confirm=') === false) {
4460 $arg_parts = explode('=', $arg);
4461 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4465 return replace_macros(get_markup_template('confirm.tpl'), array(
4467 '$message' => t('Do you really want to delete this item?'),
4468 '$extra_inputs' => $inputs,
4469 '$confirm' => t('Yes'),
4470 '$confirm_url' => $query['base'],
4471 '$confirm_name' => 'confirmed',
4472 '$cancel' => t('Cancel'),
4475 // Now check how the user responded to the confirmation query
4476 if($_REQUEST['canceled']) {
4477 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4480 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4483 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4484 dbesc(datetime_convert()),
4485 dbesc(datetime_convert()),
4488 create_tags_from_item($item['id']);
4489 create_files_from_item($item['id']);
4490 delete_thread($item['id']);
4492 // clean up categories and tags so they don't end up as orphans
4495 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4497 foreach($matches as $mtch) {
4498 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4504 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4506 foreach($matches as $mtch) {
4507 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4511 // If item is a link to a photo resource, nuke all the associated photos
4512 // (visitors will not have photo resources)
4513 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4514 // generate a resource-id and therefore aren't intimately linked to the item.
4516 if(strlen($item['resource-id'])) {
4517 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4518 dbesc($item['resource-id']),
4519 intval($item['uid'])
4521 // ignore the result
4524 // If item is a link to an event, nuke the event record.
4526 if(intval($item['event-id'])) {
4527 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4528 intval($item['event-id']),
4529 intval($item['uid'])
4531 // ignore the result
4534 // clean up item_id and sign meta-data tables
4537 // Old code - caused very long queries and warning entries in the mysql logfiles:
4539 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4540 intval($item['id']),
4541 intval($item['uid'])
4544 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4545 intval($item['id']),
4546 intval($item['uid'])
4550 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4552 // Creating list of parents
4553 $r = q("select id from item where parent = %d and uid = %d",
4554 intval($item['id']),
4555 intval($item['uid'])
4560 foreach ($r AS $row) {
4561 if ($parentid != "")
4564 $parentid .= $row["id"];
4568 if ($parentid != "") {
4569 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4571 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4574 // If it's the parent of a comment thread, kill all the kids
4576 if($item['uri'] == $item['parent-uri']) {
4577 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4578 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4579 dbesc(datetime_convert()),
4580 dbesc(datetime_convert()),
4581 dbesc($item['parent-uri']),
4582 intval($item['uid'])
4584 create_tags_from_item($item['parent-uri'], $item['uid']);
4585 create_files_from_item($item['parent-uri'], $item['uid']);
4586 delete_thread_uri($item['parent-uri'], $item['uid']);
4587 // ignore the result
4590 // ensure that last-child is set in case the comment that had it just got wiped.
4591 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4592 dbesc(datetime_convert()),
4593 dbesc($item['parent-uri']),
4594 intval($item['uid'])
4596 // who is the last child now?
4597 $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",
4598 dbesc($item['parent-uri']),
4599 intval($item['uid'])
4602 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4607 // Add a relayable_retraction signature for Diaspora.
4608 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4610 $drop_id = intval($item['id']);
4612 // send the notification upstream/downstream as the case may be
4614 proc_run('php',"include/notifier.php","drop","$drop_id");
4618 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4624 notice( t('Permission denied.') . EOL);
4625 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4632 function first_post_date($uid,$wall = false) {
4633 $r = q("select id, created from item
4634 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4636 order by created asc limit 1",
4638 intval($wall ? 1 : 0)
4641 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4642 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4647 function posted_dates($uid,$wall) {
4648 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4650 $dthen = first_post_date($uid,$wall);
4654 // Set the start and end date to the beginning of the month
4655 $dnow = substr($dnow,0,8).'01';
4656 $dthen = substr($dthen,0,8).'01';
4659 // Starting with the current month, get the first and last days of every
4660 // month down to and including the month of the first post
4661 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4662 $dstart = substr($dnow,0,8) . '01';
4663 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4664 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4665 $end_month = datetime_convert('','',$dend,'Y-m-d');
4666 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4667 $ret[] = array($str,$end_month,$start_month);
4668 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4674 function posted_date_widget($url,$uid,$wall) {
4677 if(! feature_enabled($uid,'archives'))
4680 // For former Facebook folks that left because of "timeline"
4682 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4685 $ret = posted_dates($uid,$wall);
4689 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4690 '$title' => t('Archives'),
4691 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4698 function store_diaspora_retract_sig($item, $user, $baseurl) {
4699 // Note that we can't add a target_author_signature
4700 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4701 // the comment, that means we're the home of the post, and Diaspora will only
4702 // check the parent_author_signature of retractions that it doesn't have to relay further
4704 // I don't think this function gets called for an "unlike," but I'll check anyway
4706 $enabled = intval(get_config('system','diaspora_enabled'));
4708 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4712 logger('drop_item: storing diaspora retraction signature');
4714 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4716 if(local_user() == $item['uid']) {
4718 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4719 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4722 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4723 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4726 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4727 // only handles DFRN deletes
4728 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4729 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4730 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4736 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4737 intval($item['id']),
4738 dbesc($signed_text),