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']) {
875 $res["body"] = $res["title"].add_page_info($res['plink'], false, "", ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
877 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
878 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
879 $res["body"] = add_page_info_to_body($res["body"]);
880 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
881 $res["body"] = add_page_info_to_body($res["body"]);
884 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
886 call_hooks('parse_atom', $arr);
891 function add_page_info_data($data) {
892 call_hooks('page_info_data', $data);
894 // It maybe is a rich content, but if it does have everything that a link has,
895 // then treat it that way
896 if (($data["type"] == "rich") AND is_string($data["title"]) AND
897 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
898 $data["type"] = "link";
900 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
903 if ($no_photos AND ($data["type"] == "photo"))
906 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
907 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
908 require_once("include/network.php");
909 $data["url"] = short_link($data["url"]);
912 if (($data["type"] != "photo") AND is_string($data["title"]))
913 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
915 if (($data["type"] != "video") AND ($photo != ""))
916 $text .= '[img]'.$photo.'[/img]';
917 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
918 $imagedata = $data["images"][0];
919 $text .= '[img]'.$imagedata["src"].'[/img]';
922 if (($data["type"] != "photo") AND is_string($data["text"]))
923 $text .= "[quote]".$data["text"]."[/quote]";
926 if (isset($data["keywords"]) AND count($data["keywords"])) {
929 foreach ($data["keywords"] AS $keyword) {
930 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
931 array("","", "", "", "", ""), $keyword);
932 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
936 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
939 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
940 require_once("mod/parse_url.php");
942 $data = parseurl_getsiteinfo($url, true);
944 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
946 if (!$keywords AND isset($data["keywords"]))
947 unset($data["keywords"]);
949 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
950 $list = explode(",", $keyword_blacklist);
951 foreach ($list AS $keyword) {
952 $keyword = trim($keyword);
953 $index = array_search($keyword, $data["keywords"]);
954 if ($index !== false)
955 unset($data["keywords"][$index]);
959 $text = add_page_info_data($data);
964 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
966 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
968 $URLSearchString = "^\[\]";
970 // Adding these spaces is a quick hack due to my problems with regular expressions :)
971 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
974 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
976 // Convert urls without bbcode elements
977 if (!$matches AND $texturl) {
978 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
980 // Yeah, a hack. I really hate regular expressions :)
982 $matches[1] = $matches[2];
986 $footer = add_page_info($matches[1], $no_photos);
988 // Remove the link from the body if the link is attached at the end of the post
989 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
990 $removedlink = trim(str_replace($matches[1], "", $body));
991 if (($removedlink == "") OR strstr($body, $removedlink))
992 $body = $removedlink;
994 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
995 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
996 if (($removedlink == "") OR strstr($body, $removedlink))
997 $body = $removedlink;
1000 // Add the page information to the bottom
1001 if (isset($footer) AND (trim($footer) != ""))
1007 function encode_rel_links($links) {
1009 if(! ((is_array($links)) && (count($links))))
1011 foreach($links as $link) {
1013 if($link['attribs']['']['rel'])
1014 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1015 if($link['attribs']['']['type'])
1016 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1017 if($link['attribs']['']['href'])
1018 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1019 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1020 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1021 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1022 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1023 $o .= ' />' . "\n" ;
1030 function item_store($arr,$force_parent = false, $notify = false) {
1032 // If it is a posting where users should get notifications, then define it as wall posting
1035 $arr['type'] = 'wall';
1037 $arr['last-child'] = 1;
1038 $arr['network'] = NETWORK_DFRN;
1041 // If a Diaspora signature structure was passed in, pull it out of the
1042 // item array and set it aside for later storage.
1045 if(x($arr,'dsprsig')) {
1046 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1047 unset($arr['dsprsig']);
1050 // if an OStatus conversation url was passed in, it is stored and then
1051 // removed from the array.
1052 $ostatus_conversation = null;
1054 if (isset($arr["ostatus_conversation"])) {
1055 $ostatus_conversation = $arr["ostatus_conversation"];
1056 unset($arr["ostatus_conversation"]);
1059 if(x($arr, 'gravity'))
1060 $arr['gravity'] = intval($arr['gravity']);
1061 elseif($arr['parent-uri'] === $arr['uri'])
1062 $arr['gravity'] = 0;
1063 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1064 $arr['gravity'] = 6;
1066 $arr['gravity'] = 6; // extensible catchall
1068 if(! x($arr,'type'))
1069 $arr['type'] = 'remote';
1073 /* check for create date and expire time */
1074 $uid = intval($arr['uid']);
1075 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1077 $expire_interval = $r[0]['expire'];
1078 if ($expire_interval>0) {
1079 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1080 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1081 if ($created_date < $expire_date) {
1082 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1088 // If there is no guid then take the same guid that was taken before for the same uri
1089 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1090 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1091 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1092 dbesc(trim($arr['uri']))
1096 $arr['guid'] = $r[0]["guid"];
1097 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1101 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1102 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1103 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1104 // $arr['body'] = strip_tags($arr['body']);
1107 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1108 require_once('library/langdet/Text/LanguageDetect.php');
1109 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1110 $l = new Text_LanguageDetect;
1111 //$lng = $l->detectConfidence($naked_body);
1112 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1113 $lng = $l->detect($naked_body, 3);
1115 if (sizeof($lng) > 0) {
1118 foreach ($lng as $language => $score) {
1119 if ($postopts == "")
1120 $postopts = "lang=";
1124 $postopts .= $language.";".$score;
1126 $arr['postopts'] = $postopts;
1130 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1131 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1132 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1133 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1134 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1135 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1136 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1137 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1138 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1139 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1140 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1141 $arr['commented'] = datetime_convert();
1142 $arr['received'] = datetime_convert();
1143 $arr['changed'] = datetime_convert();
1144 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1145 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1146 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1147 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1148 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1149 $arr['deleted'] = 0;
1150 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1151 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1152 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1153 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1154 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1155 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1156 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1157 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1158 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1159 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1160 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1161 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1162 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1163 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1164 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1165 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1166 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1167 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1168 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1169 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1170 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1171 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1172 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1173 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1174 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1176 if ($arr['plink'] == "") {
1178 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1181 if ($arr['network'] == "") {
1182 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1183 intval($arr['contact-id']),
1188 $arr['network'] = $r[0]["network"];
1190 // Fallback to friendica (why is it empty in some cases?)
1191 if ($arr['network'] == "")
1192 $arr['network'] = NETWORK_DFRN;
1194 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1197 $arr['thr-parent'] = $arr['parent-uri'];
1198 if($arr['parent-uri'] === $arr['uri']) {
1200 $parent_deleted = 0;
1201 $allow_cid = $arr['allow_cid'];
1202 $allow_gid = $arr['allow_gid'];
1203 $deny_cid = $arr['deny_cid'];
1204 $deny_gid = $arr['deny_gid'];
1205 $notify_type = 'wall-new';
1209 // find the parent and snarf the item id and ACLs
1210 // and anything else we need to inherit
1212 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1213 dbesc($arr['parent-uri']),
1219 // is the new message multi-level threaded?
1220 // even though we don't support it now, preserve the info
1221 // and re-attach to the conversation parent.
1223 if($r[0]['uri'] != $r[0]['parent-uri']) {
1224 $arr['parent-uri'] = $r[0]['parent-uri'];
1225 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1226 ORDER BY `id` ASC LIMIT 1",
1227 dbesc($r[0]['parent-uri']),
1228 dbesc($r[0]['parent-uri']),
1235 $parent_id = $r[0]['id'];
1236 $parent_deleted = $r[0]['deleted'];
1237 $allow_cid = $r[0]['allow_cid'];
1238 $allow_gid = $r[0]['allow_gid'];
1239 $deny_cid = $r[0]['deny_cid'];
1240 $deny_gid = $r[0]['deny_gid'];
1241 $arr['wall'] = $r[0]['wall'];
1242 $notify_type = 'comment-new';
1244 // if the parent is private, force privacy for the entire conversation
1245 // This differs from the above settings as it subtly allows comments from
1246 // email correspondents to be private even if the overall thread is not.
1248 if($r[0]['private'])
1249 $arr['private'] = $r[0]['private'];
1251 // Edge case. We host a public forum that was originally posted to privately.
1252 // The original author commented, but as this is a comment, the permissions
1253 // weren't fixed up so it will still show the comment as private unless we fix it here.
1255 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1256 $arr['private'] = 0;
1259 // If its a post from myself then tag the thread as "mention"
1260 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1261 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1264 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1265 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1266 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1267 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1268 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1274 // Allow one to see reply tweets from status.net even when
1275 // we don't have or can't see the original post.
1278 logger('item_store: $force_parent=true, reply converted to top-level post.');
1280 $arr['parent-uri'] = $arr['uri'];
1281 $arr['gravity'] = 0;
1284 logger('item_store: item parent was not found - ignoring item');
1288 $parent_deleted = 0;
1292 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1296 if($r && count($r)) {
1297 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1301 call_hooks('post_remote',$arr);
1303 if(x($arr,'cancel')) {
1304 logger('item_store: post cancelled by plugin.');
1310 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1312 $r = dbq("INSERT INTO `item` (`"
1313 . implode("`, `", array_keys($arr))
1315 . implode("', '", array_values($arr))
1318 // find the item we just created
1320 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1321 $arr['uri'], // already dbesc'd
1326 $current_post = $r[0]['id'];
1327 logger('item_store: created item ' . $current_post);
1329 // Only check for notifications on start posts
1330 if ($arr['parent-uri'] === $arr['uri']) {
1331 add_thread($r[0]['id']);
1332 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1334 // Send a notification for every new post?
1335 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1336 intval($arr['contact-id']),
1341 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1342 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1343 intval($arr['uid']));
1345 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1346 intval($current_post),
1352 require_once('include/enotify.php');
1354 'type' => NOTIFY_SHARE,
1355 'notify_flags' => $u[0]['notify-flags'],
1356 'language' => $u[0]['language'],
1357 'to_name' => $u[0]['username'],
1358 'to_email' => $u[0]['email'],
1359 'uid' => $u[0]['uid'],
1361 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1362 'source_name' => $item[0]['author-name'],
1363 'source_link' => $item[0]['author-link'],
1364 'source_photo' => $item[0]['author-avatar'],
1365 'verb' => ACTIVITY_TAG,
1368 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1373 logger('item_store: could not locate created item');
1377 logger('item_store: duplicated post occurred. Removing duplicates.');
1378 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1380 intval($arr['uid']),
1381 intval($current_post)
1385 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1386 $parent_id = $current_post;
1388 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1391 $private = $arr['private'];
1393 // Set parent id - and also make sure to inherit the parent's ACLs.
1395 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1396 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1403 intval($parent_deleted),
1404 intval($current_post)
1407 // Complete ostatus threads
1408 if ($ostatus_conversation)
1409 complete_conversation($current_post, $ostatus_conversation);
1411 $arr['id'] = $current_post;
1412 $arr['parent'] = $parent_id;
1413 $arr['allow_cid'] = $allow_cid;
1414 $arr['allow_gid'] = $allow_gid;
1415 $arr['deny_cid'] = $deny_cid;
1416 $arr['deny_gid'] = $deny_gid;
1417 $arr['private'] = $private;
1418 $arr['deleted'] = $parent_deleted;
1420 // update the commented timestamp on the parent
1422 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1423 dbesc(datetime_convert()),
1424 dbesc(datetime_convert()),
1427 update_thread($parent_id);
1430 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1431 intval($current_post),
1432 dbesc($dsprsig->signed_text),
1433 dbesc($dsprsig->signature),
1434 dbesc($dsprsig->signer)
1440 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1443 if($arr['last-child']) {
1444 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1446 intval($arr['uid']),
1447 intval($current_post)
1451 $deleted = tag_deliver($arr['uid'],$current_post);
1453 // current post can be deleted if is for a communuty page and no mention are
1457 // Store the fresh generated item into the cache
1458 $cachefile = get_cachefile(urlencode($arr["guid"])."-".hash("md5", $arr['body']));
1460 if (($cachefile != '') AND !file_exists($cachefile)) {
1461 $s = prepare_text($arr['body']);
1463 $stamp1 = microtime(true);
1464 file_put_contents($cachefile, $s);
1465 $a->save_timestamp($stamp1, "file");
1466 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1469 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1470 if (count($r) == 1) {
1471 call_hooks('post_remote_end', $r[0]);
1473 logger('item_store: new item not found in DB, id ' . $current_post);
1477 create_tags_from_item($current_post);
1478 create_files_from_item($current_post);
1481 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1483 return $current_post;
1486 function get_item_guid($id) {
1487 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1489 return($r[0]["guid"]);
1494 function get_item_id($guid, $uid = 0) {
1500 $uid == local_user();
1502 // Does the given user have this item?
1504 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1505 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1506 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1509 $nick = $r[0]["nickname"];
1513 // Or is it anywhere on the server?
1515 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1516 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1517 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1518 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1519 AND `item`.`private` = 0 AND `item`.`wall` = 1
1520 AND `item`.`guid` = '%s'", dbesc($guid));
1523 $nick = $r[0]["nickname"];
1526 return(array("nick" => $nick, "id" => $id));
1530 function get_item_contact($item,$contacts) {
1531 if(! count($contacts) || (! is_array($item)))
1533 foreach($contacts as $contact) {
1534 if($contact['id'] == $item['contact-id']) {
1536 break; // NOTREACHED
1543 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1545 * @param int $item_id
1546 * @return bool true if item was deleted, else false
1548 function tag_deliver($uid,$item_id) {
1556 $u = q("select * from user where uid = %d limit 1",
1562 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1563 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1566 $i = q("select * from item where id = %d and uid = %d limit 1",
1575 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1577 // Diaspora uses their own hardwired link URL in @-tags
1578 // instead of the one we supply with webfinger
1580 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1582 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1584 foreach($matches as $mtch) {
1585 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1587 logger('tag_deliver: mention found: ' . $mtch[2]);
1593 if ( ($community_page || $prvgroup) &&
1594 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1595 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1597 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1598 q("DELETE FROM item WHERE id = %d and uid = %d",
1608 // send a notification
1610 // use a local photo if we have one
1612 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1613 intval($u[0]['uid']),
1614 dbesc(normalise_link($item['author-link']))
1616 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1619 require_once('include/enotify.php');
1621 'type' => NOTIFY_TAGSELF,
1622 'notify_flags' => $u[0]['notify-flags'],
1623 'language' => $u[0]['language'],
1624 'to_name' => $u[0]['username'],
1625 'to_email' => $u[0]['email'],
1626 'uid' => $u[0]['uid'],
1628 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1629 'source_name' => $item['author-name'],
1630 'source_link' => $item['author-link'],
1631 'source_photo' => $photo,
1632 'verb' => ACTIVITY_TAG,
1634 'parent' => $item['parent']
1638 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1640 call_hooks('tagged', $arr);
1642 if((! $community_page) && (! $prvgroup))
1646 // tgroup delivery - setup a second delivery chain
1647 // prevent delivery looping - only proceed
1648 // if the message originated elsewhere and is a top-level post
1650 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1653 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1656 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1657 intval($u[0]['uid'])
1662 // also reset all the privacy bits to the forum default permissions
1664 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1666 $forum_mode = (($prvgroup) ? 2 : 1);
1668 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1669 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1670 intval($forum_mode),
1671 dbesc($c[0]['name']),
1672 dbesc($c[0]['url']),
1673 dbesc($c[0]['thumb']),
1675 dbesc($u[0]['allow_cid']),
1676 dbesc($u[0]['allow_gid']),
1677 dbesc($u[0]['deny_cid']),
1678 dbesc($u[0]['deny_gid']),
1681 update_thread($item_id);
1683 proc_run('php','include/notifier.php','tgroup',$item_id);
1689 function tgroup_check($uid,$item) {
1695 // check that the message originated elsewhere and is a top-level post
1697 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1701 $u = q("select * from user where uid = %d limit 1",
1707 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1708 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1711 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1713 // Diaspora uses their own hardwired link URL in @-tags
1714 // instead of the one we supply with webfinger
1716 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1718 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1720 foreach($matches as $mtch) {
1721 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1723 logger('tgroup_check: mention found: ' . $mtch[2]);
1731 if((! $community_page) && (! $prvgroup))
1745 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1749 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1751 if($contact['duplex'] && $contact['dfrn-id'])
1752 $idtosend = '0:' . $orig_id;
1753 if($contact['duplex'] && $contact['issued-id'])
1754 $idtosend = '1:' . $orig_id;
1756 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1758 $rino_enable = get_config('system','rino_encrypt');
1763 $ssl_val = intval(get_config('system','ssl_policy'));
1767 case SSL_POLICY_FULL:
1768 $ssl_policy = 'full';
1770 case SSL_POLICY_SELFSIGN:
1771 $ssl_policy = 'self';
1773 case SSL_POLICY_NONE:
1775 $ssl_policy = 'none';
1779 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1781 logger('dfrn_deliver: ' . $url);
1783 $xml = fetch_url($url);
1785 $curl_stat = $a->get_curl_code();
1787 return(-1); // timed out
1789 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1794 if(strpos($xml,'<?xml') === false) {
1795 logger('dfrn_deliver: no valid XML returned');
1796 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1800 $res = parse_xml_string($xml);
1802 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1803 return (($res->status) ? $res->status : 3);
1805 $postvars = array();
1806 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1807 $challenge = hex2bin((string) $res->challenge);
1808 $perm = (($res->perm) ? $res->perm : null);
1809 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1810 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1811 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1813 if($owner['page-flags'] == PAGE_PRVGROUP)
1816 $final_dfrn_id = '';
1819 if((($perm == 'rw') && (! intval($contact['writable'])))
1820 || (($perm == 'r') && (intval($contact['writable'])))) {
1821 q("update contact set writable = %d where id = %d",
1822 intval(($perm == 'rw') ? 1 : 0),
1823 intval($contact['id'])
1825 $contact['writable'] = (string) 1 - intval($contact['writable']);
1829 if(($contact['duplex'] && strlen($contact['pubkey']))
1830 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1831 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1832 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1833 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1836 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1837 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1840 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1842 if(strpos($final_dfrn_id,':') == 1)
1843 $final_dfrn_id = substr($final_dfrn_id,2);
1845 if($final_dfrn_id != $orig_id) {
1846 logger('dfrn_deliver: wrong dfrn_id.');
1847 // did not decode properly - cannot trust this site
1851 $postvars['dfrn_id'] = $idtosend;
1852 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1854 $postvars['dissolve'] = '1';
1857 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1858 $postvars['data'] = $atom;
1859 $postvars['perm'] = 'rw';
1862 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1863 $postvars['perm'] = 'r';
1866 $postvars['ssl_policy'] = $ssl_policy;
1869 $postvars['page'] = $page;
1871 if($rino && $rino_allowed && (! $dissolve)) {
1872 $key = substr(random_string(),0,16);
1873 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1874 $postvars['data'] = $data;
1875 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1878 if($dfrn_version >= 2.1) {
1879 if(($contact['duplex'] && strlen($contact['pubkey']))
1880 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1881 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1883 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1886 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1890 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1891 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1894 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1898 logger('md5 rawkey ' . md5($postvars['key']));
1900 $postvars['key'] = bin2hex($postvars['key']);
1903 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1905 $xml = post_url($contact['notify'],$postvars);
1907 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1909 $curl_stat = $a->get_curl_code();
1910 if((! $curl_stat) || (! strlen($xml)))
1911 return(-1); // timed out
1913 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1916 if(strpos($xml,'<?xml') === false) {
1917 logger('dfrn_deliver: phase 2: no valid XML returned');
1918 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1922 if($contact['term-date'] != '0000-00-00 00:00:00') {
1923 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1924 require_once('include/Contact.php');
1925 unmark_for_death($contact);
1928 $res = parse_xml_string($xml);
1930 return $res->status;
1935 This function returns true if $update has an edited timestamp newer
1936 than $existing, i.e. $update contains new data which should override
1937 what's already there. If there is no timestamp yet, the update is
1938 assumed to be newer. If the update has no timestamp, the existing
1939 item is assumed to be up-to-date. If the timestamps are equal it
1940 assumes the update has been seen before and should be ignored.
1942 function edited_timestamp_is_newer($existing, $update) {
1943 if (!x($existing,'edited') || !$existing['edited']) {
1946 if (!x($update,'edited') || !$update['edited']) {
1949 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1950 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1951 return (strcmp($existing_edited, $update_edited) < 0);
1956 * consume_feed - process atom feed and update anything/everything we might need to update
1958 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1960 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1961 * It is this person's stuff that is going to be updated.
1962 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1963 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1964 * have a contact record.
1965 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1966 * might not) try and subscribe to it.
1967 * $datedir sorts in reverse order
1968 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1969 * imported prior to its children being seen in the stream unless we are certain
1970 * of how the feed is arranged/ordered.
1971 * With $pass = 1, we only pull parent items out of the stream.
1972 * With $pass = 2, we only pull children (comments/likes).
1974 * So running this twice, first with pass 1 and then with pass 2 will do the right
1975 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1976 * model where comments can have sub-threads. That would require some massive sorting
1977 * to get all the feed items into a mostly linear ordering, and might still require
1981 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1983 require_once('library/simplepie/simplepie.inc');
1984 require_once('include/contact_selectors.php');
1986 if(! strlen($xml)) {
1987 logger('consume_feed: empty input');
1991 $feed = new SimplePie();
1992 $feed->set_raw_data($xml);
1994 $feed->enable_order_by_date(true);
1996 $feed->enable_order_by_date(false);
2000 logger('consume_feed: Error parsing XML: ' . $feed->error());
2002 $permalink = $feed->get_permalink();
2004 // Check at the feed level for updated contact name and/or photo
2008 $photo_timestamp = '';
2012 $hubs = $feed->get_links('hub');
2013 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2016 $hub = implode(',', $hubs);
2018 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2020 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2022 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2023 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2024 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2025 $new_name = $elems['name'][0]['data'];
2027 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2028 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2029 $photo_url = $elems['link'][0]['attribs']['']['href'];
2032 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2033 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2037 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2038 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2039 require_once("include/Photo.php");
2040 $photo_failure = false;
2041 $have_photo = false;
2043 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2044 intval($contact['id']),
2045 intval($contact['uid'])
2048 $resource_id = $r[0]['resource-id'];
2052 $resource_id = photo_new_resource();
2055 $img_str = fetch_url($photo_url,true);
2056 // guess mimetype from headers or filename
2057 $type = guess_image_type($photo_url,true);
2060 $img = new Photo($img_str, $type);
2061 if($img->is_valid()) {
2063 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2064 dbesc($resource_id),
2065 intval($contact['id']),
2066 intval($contact['uid'])
2070 $img->scaleImageSquare(175);
2072 $hash = $resource_id;
2073 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2075 $img->scaleImage(80);
2076 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2078 $img->scaleImage(48);
2079 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2083 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2084 WHERE `uid` = %d AND `id` = %d",
2085 dbesc(datetime_convert()),
2086 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2087 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2088 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2089 intval($contact['uid']),
2090 intval($contact['id'])
2095 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2096 $r = q("select * from contact where uid = %d and id = %d limit 1",
2097 intval($contact['uid']),
2098 intval($contact['id'])
2101 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2102 dbesc(notags(trim($new_name))),
2103 dbesc(datetime_convert()),
2104 intval($contact['uid']),
2105 intval($contact['id'])
2108 // do our best to update the name on content items
2111 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2112 dbesc(notags(trim($new_name))),
2113 dbesc($r[0]['name']),
2114 dbesc($r[0]['url']),
2115 intval($contact['uid'])
2120 if(strlen($birthday)) {
2121 if(substr($birthday,0,4) != $contact['bdyear']) {
2122 logger('consume_feed: updating birthday: ' . $birthday);
2126 * Add new birthday event for this person
2128 * $bdtext is just a readable placeholder in case the event is shared
2129 * with others. We will replace it during presentation to our $importer
2130 * to contain a sparkle link and perhaps a photo.
2134 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2135 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2138 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2139 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2140 intval($contact['uid']),
2141 intval($contact['id']),
2142 dbesc(datetime_convert()),
2143 dbesc(datetime_convert()),
2144 dbesc(datetime_convert('UTC','UTC', $birthday)),
2145 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2154 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2155 dbesc(substr($birthday,0,4)),
2156 intval($contact['uid']),
2157 intval($contact['id'])
2160 // This function is called twice without reloading the contact
2161 // Make sure we only create one event. This is why &$contact
2162 // is a reference var in this function
2164 $contact['bdyear'] = substr($birthday,0,4);
2169 $community_page = 0;
2170 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2172 $community_page = intval($rawtags[0]['data']);
2174 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2175 q("update contact set forum = %d where id = %d",
2176 intval($community_page),
2177 intval($contact['id'])
2179 $contact['forum'] = (string) $community_page;
2183 // process any deleted entries
2185 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2186 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2187 foreach($del_entries as $dentry) {
2189 if(isset($dentry['attribs']['']['ref'])) {
2190 $uri = $dentry['attribs']['']['ref'];
2192 if(isset($dentry['attribs']['']['when'])) {
2193 $when = $dentry['attribs']['']['when'];
2194 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2197 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2199 if($deleted && is_array($contact)) {
2200 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2201 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2203 intval($importer['uid']),
2204 intval($contact['id'])
2209 if(! $item['deleted'])
2210 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2212 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2213 $xo = parse_xml_string($item['object'],false);
2214 $xt = parse_xml_string($item['target'],false);
2215 if($xt->type === ACTIVITY_OBJ_NOTE) {
2216 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2218 intval($importer['importer_uid'])
2222 // For tags, the owner cannot remove the tag on the author's copy of the post.
2224 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2225 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2226 $author_copy = (($item['origin']) ? true : false);
2228 if($owner_remove && $author_copy)
2230 if($author_remove || $owner_remove) {
2231 $tags = explode(',',$i[0]['tag']);
2234 foreach($tags as $tag)
2235 if(trim($tag) !== trim($xo->body))
2236 $newtags[] = trim($tag);
2238 q("update item set tag = '%s' where id = %d",
2239 dbesc(implode(',',$newtags)),
2242 create_tags_from_item($i[0]['id']);
2248 if($item['uri'] == $item['parent-uri']) {
2249 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2250 `body` = '', `title` = ''
2251 WHERE `parent-uri` = '%s' AND `uid` = %d",
2253 dbesc(datetime_convert()),
2254 dbesc($item['uri']),
2255 intval($importer['uid'])
2257 create_tags_from_itemuri($item['uri'], $importer['uid']);
2258 create_files_from_itemuri($item['uri'], $importer['uid']);
2259 update_thread_uri($item['uri'], $importer['uid']);
2262 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2263 `body` = '', `title` = ''
2264 WHERE `uri` = '%s' AND `uid` = %d",
2266 dbesc(datetime_convert()),
2268 intval($importer['uid'])
2270 create_tags_from_itemuri($uri, $importer['uid']);
2271 create_files_from_itemuri($uri, $importer['uid']);
2272 if($item['last-child']) {
2273 // ensure that last-child is set in case the comment that had it just got wiped.
2274 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2275 dbesc(datetime_convert()),
2276 dbesc($item['parent-uri']),
2277 intval($item['uid'])
2279 // who is the last child now?
2280 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2281 ORDER BY `created` DESC LIMIT 1",
2282 dbesc($item['parent-uri']),
2283 intval($importer['uid'])
2286 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2297 // Now process the feed
2299 if($feed->get_item_quantity()) {
2301 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2303 // in inverse date order
2305 $items = array_reverse($feed->get_items());
2307 $items = $feed->get_items();
2310 foreach($items as $item) {
2313 $item_id = $item->get_id();
2314 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2315 if(isset($rawthread[0]['attribs']['']['ref'])) {
2317 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2320 if(($is_reply) && is_array($contact)) {
2325 // not allowed to post
2327 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2331 // Have we seen it? If not, import it.
2333 $item_id = $item->get_id();
2334 $datarray = get_atom_elements($feed, $item, $contact);
2336 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2337 $datarray['author-name'] = $contact['name'];
2338 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2339 $datarray['author-link'] = $contact['url'];
2340 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2341 $datarray['author-avatar'] = $contact['thumb'];
2343 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2344 logger('consume_feed: no author information! ' . print_r($datarray,true));
2348 $force_parent = false;
2349 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2350 if($contact['network'] === NETWORK_OSTATUS)
2351 $force_parent = true;
2352 if(strlen($datarray['title']))
2353 unset($datarray['title']);
2354 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2355 dbesc(datetime_convert()),
2357 intval($importer['uid'])
2359 $datarray['last-child'] = 1;
2360 update_thread_uri($parent_uri, $importer['uid']);
2364 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2366 intval($importer['uid'])
2369 // Update content if 'updated' changes
2372 if (edited_timestamp_is_newer($r[0], $datarray)) {
2374 // do not accept (ignore) an earlier edit than one we currently have.
2375 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2378 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2379 dbesc($datarray['title']),
2380 dbesc($datarray['body']),
2381 dbesc($datarray['tag']),
2382 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2383 dbesc(datetime_convert()),
2385 intval($importer['uid'])
2387 create_tags_from_itemuri($item_id, $importer['uid']);
2388 update_thread_uri($item_id, $importer['uid']);
2391 // update last-child if it changes
2393 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2394 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2395 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2396 dbesc(datetime_convert()),
2398 intval($importer['uid'])
2400 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2401 intval($allow[0]['data']),
2402 dbesc(datetime_convert()),
2404 intval($importer['uid'])
2406 update_thread_uri($item_id, $importer['uid']);
2412 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2413 // one way feed - no remote comment ability
2414 $datarray['last-child'] = 0;
2416 $datarray['parent-uri'] = $parent_uri;
2417 $datarray['uid'] = $importer['uid'];
2418 $datarray['contact-id'] = $contact['id'];
2419 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2420 $datarray['type'] = 'activity';
2421 $datarray['gravity'] = GRAVITY_LIKE;
2422 // only one like or dislike per person
2423 // splitted into two queries for performance issues
2424 $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",
2425 intval($datarray['uid']),
2426 intval($datarray['contact-id']),
2427 dbesc($datarray['verb']),
2433 $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",
2434 intval($datarray['uid']),
2435 intval($datarray['contact-id']),
2436 dbesc($datarray['verb']),
2443 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2444 $xo = parse_xml_string($datarray['object'],false);
2445 $xt = parse_xml_string($datarray['target'],false);
2447 if($xt->type == ACTIVITY_OBJ_NOTE) {
2448 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2450 intval($importer['importer_uid'])
2455 // extract tag, if not duplicate, add to parent item
2456 if($xo->id && $xo->content) {
2457 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2458 if(! (stristr($r[0]['tag'],$newtag))) {
2459 q("UPDATE item SET tag = '%s' WHERE id = %d",
2460 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2463 create_tags_from_item($r[0]['id']);
2469 $r = item_store($datarray,$force_parent);
2475 // Head post of a conversation. Have we seen it? If not, import it.
2477 $item_id = $item->get_id();
2479 $datarray = get_atom_elements($feed, $item, $contact);
2481 if(is_array($contact)) {
2482 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2483 $datarray['author-name'] = $contact['name'];
2484 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2485 $datarray['author-link'] = $contact['url'];
2486 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2487 $datarray['author-avatar'] = $contact['thumb'];
2490 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2491 logger('consume_feed: no author information! ' . print_r($datarray,true));
2495 // special handling for events
2497 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2498 $ev = bbtoevent($datarray['body']);
2499 if(x($ev,'desc') && x($ev,'start')) {
2500 $ev['uid'] = $importer['uid'];
2501 $ev['uri'] = $item_id;
2502 $ev['edited'] = $datarray['edited'];
2503 $ev['private'] = $datarray['private'];
2505 if(is_array($contact))
2506 $ev['cid'] = $contact['id'];
2507 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2509 intval($importer['uid'])
2512 $ev['id'] = $r[0]['id'];
2513 $xyz = event_store($ev);
2518 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2519 if(strlen($datarray['title']))
2520 unset($datarray['title']);
2521 $datarray['last-child'] = 1;
2525 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2527 intval($importer['uid'])
2530 // Update content if 'updated' changes
2533 if (edited_timestamp_is_newer($r[0], $datarray)) {
2535 // do not accept (ignore) an earlier edit than one we currently have.
2536 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2539 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2540 dbesc($datarray['title']),
2541 dbesc($datarray['body']),
2542 dbesc($datarray['tag']),
2543 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2544 dbesc(datetime_convert()),
2546 intval($importer['uid'])
2548 create_tags_from_itemuri($item_id, $importer['uid']);
2549 update_thread_uri($item_id, $importer['uid']);
2552 // update last-child if it changes
2554 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2555 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2556 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2557 intval($allow[0]['data']),
2558 dbesc(datetime_convert()),
2560 intval($importer['uid'])
2562 update_thread_uri($item_id, $importer['uid']);
2567 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2568 logger('consume-feed: New follower');
2569 new_follower($importer,$contact,$datarray,$item);
2572 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2573 lose_follower($importer,$contact,$datarray,$item);
2577 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2578 logger('consume-feed: New friend request');
2579 new_follower($importer,$contact,$datarray,$item,true);
2582 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2583 lose_sharer($importer,$contact,$datarray,$item);
2588 if(! is_array($contact))
2592 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2593 // one way feed - no remote comment ability
2594 $datarray['last-child'] = 0;
2596 if($contact['network'] === NETWORK_FEED)
2597 $datarray['private'] = 2;
2599 $datarray['parent-uri'] = $item_id;
2600 $datarray['uid'] = $importer['uid'];
2601 $datarray['contact-id'] = $contact['id'];
2603 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2604 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2605 // but otherwise there's a possible data mixup on the sender's system.
2606 // the tgroup delivery code called from item_store will correct it if it's a forum,
2607 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2608 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2609 $datarray['owner-name'] = $contact['name'];
2610 $datarray['owner-link'] = $contact['url'];
2611 $datarray['owner-avatar'] = $contact['thumb'];
2614 // We've allowed "followers" to reach this point so we can decide if they are
2615 // posting an @-tag delivery, which followers are allowed to do for certain
2616 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2618 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2621 // This is my contact on another system, but it's really me.
2622 // Turn this into a wall post.
2623 $notify = item_is_remote_self($contact, $datarray);
2625 $r = item_store($datarray, false, $notify);
2626 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2634 function item_is_remote_self($contact, &$datarray) {
2637 if (!$contact['remote_self'])
2640 // Prevent the forwarding of posts that are forwarded
2641 if ($datarray["extid"] == NETWORK_DFRN)
2644 // Prevent to forward already forwarded posts
2645 if ($datarray["app"] == $a->get_hostname())
2648 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2651 $datarray2 = $datarray;
2652 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2653 if ($contact['remote_self'] == 2) {
2654 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
2655 intval($contact['uid']));
2657 $datarray['contact-id'] = $r[0]["id"];
2659 $datarray['owner-name'] = $r[0]["name"];
2660 $datarray['owner-link'] = $r[0]["url"];
2661 $datarray['owner-avatar'] = $r[0]["avatar"];
2663 $datarray['author-name'] = $datarray['owner-name'];
2664 $datarray['author-link'] = $datarray['owner-link'];
2665 $datarray['author-avatar'] = $datarray['owner-avatar'];
2668 if ($contact['network'] != NETWORK_FEED) {
2669 $datarray["guid"] = get_guid(32);
2670 unset($datarray["plink"]);
2671 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2672 $datarray["parent-uri"] = $datarray["uri"];
2673 $datarray["extid"] = $contact['network'];
2674 $urlpart = parse_url($datarray2['author-link']);
2675 $datarray["app"] = $urlpart["host"];
2677 $datarray['private'] = 0;
2680 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2681 // $datarray["app"] = network_to_name($contact['network']);
2683 if ($contact['network'] != NETWORK_FEED) {
2684 // Store the original post
2685 $r = item_store($datarray2, false, false);
2686 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2688 $datarray["app"] = "Feed";
2693 function local_delivery($importer,$data) {
2696 logger(__function__, LOGGER_TRACE);
2698 if($importer['readonly']) {
2699 // We aren't receiving stuff from this person. But we will quietly ignore them
2700 // rather than a blatant "go away" message.
2701 logger('local_delivery: ignoring');
2706 // Consume notification feed. This may differ from consuming a public feed in several ways
2707 // - might contain email or friend suggestions
2708 // - might contain remote followup to our message
2709 // - in which case we need to accept it and then notify other conversants
2710 // - we may need to send various email notifications
2712 $feed = new SimplePie();
2713 $feed->set_raw_data($data);
2714 $feed->enable_order_by_date(false);
2719 logger('local_delivery: Error parsing XML: ' . $feed->error());
2722 // Check at the feed level for updated contact name and/or photo
2726 $photo_timestamp = '';
2730 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2732 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2734 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2737 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2738 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2739 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2740 $new_name = $elems['name'][0]['data'];
2742 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2743 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2744 $photo_url = $elems['link'][0]['attribs']['']['href'];
2748 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2749 logger('local_delivery: Updating photo for ' . $importer['name']);
2750 require_once("include/Photo.php");
2751 $photo_failure = false;
2752 $have_photo = false;
2754 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2755 intval($importer['id']),
2756 intval($importer['importer_uid'])
2759 $resource_id = $r[0]['resource-id'];
2763 $resource_id = photo_new_resource();
2766 $img_str = fetch_url($photo_url,true);
2767 // guess mimetype from headers or filename
2768 $type = guess_image_type($photo_url,true);
2771 $img = new Photo($img_str, $type);
2772 if($img->is_valid()) {
2774 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2775 dbesc($resource_id),
2776 intval($importer['id']),
2777 intval($importer['importer_uid'])
2781 $img->scaleImageSquare(175);
2783 $hash = $resource_id;
2784 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2786 $img->scaleImage(80);
2787 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2789 $img->scaleImage(48);
2790 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2794 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2795 WHERE `uid` = %d AND `id` = %d",
2796 dbesc(datetime_convert()),
2797 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2798 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2799 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2800 intval($importer['importer_uid']),
2801 intval($importer['id'])
2806 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2807 $r = q("select * from contact where uid = %d and id = %d limit 1",
2808 intval($importer['importer_uid']),
2809 intval($importer['id'])
2812 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2813 dbesc(notags(trim($new_name))),
2814 dbesc(datetime_convert()),
2815 intval($importer['importer_uid']),
2816 intval($importer['id'])
2819 // do our best to update the name on content items
2822 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2823 dbesc(notags(trim($new_name))),
2824 dbesc($r[0]['name']),
2825 dbesc($r[0]['url']),
2826 intval($importer['importer_uid'])
2833 // Currently unsupported - needs a lot of work
2834 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2835 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2836 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2838 $newloc['uid'] = $importer['importer_uid'];
2839 $newloc['cid'] = $importer['id'];
2840 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2841 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2842 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2843 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2844 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2845 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2846 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2847 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2848 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2849 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2850 /** relocated user must have original key pair */
2851 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2852 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2854 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2857 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2858 intval($importer['id']),
2859 intval($importer['importer_uid']));
2864 $x = q("UPDATE contact SET
2874 `site-pubkey` = '%s'
2875 WHERE id=%d AND uid=%d;",
2876 dbesc($newloc['name']),
2877 dbesc($newloc['photo']),
2878 dbesc($newloc['thumb']),
2879 dbesc($newloc['micro']),
2880 dbesc($newloc['url']),
2881 dbesc($newloc['request']),
2882 dbesc($newloc['confirm']),
2883 dbesc($newloc['notify']),
2884 dbesc($newloc['poll']),
2885 dbesc($newloc['sitepubkey']),
2886 intval($importer['id']),
2887 intval($importer['importer_uid']));
2893 'owner-link' => array($old['url'], $newloc['url']),
2894 'author-link' => array($old['url'], $newloc['url']),
2895 'owner-avatar' => array($old['photo'], $newloc['photo']),
2896 'author-avatar' => array($old['photo'], $newloc['photo']),
2898 foreach ($fields as $n=>$f){
2899 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2902 intval($importer['importer_uid']));
2908 // merge with current record, current contents have priority
2909 // update record, set url-updated
2910 // update profile photos
2916 // handle friend suggestion notification
2918 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2919 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2920 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2922 $fsugg['uid'] = $importer['importer_uid'];
2923 $fsugg['cid'] = $importer['id'];
2924 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2925 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2926 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2927 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2928 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2930 // Does our member already have a friend matching this description?
2932 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2933 dbesc($fsugg['name']),
2934 dbesc(normalise_link($fsugg['url'])),
2935 intval($fsugg['uid'])
2940 // Do we already have an fcontact record for this person?
2943 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2944 dbesc($fsugg['url']),
2945 dbesc($fsugg['name']),
2946 dbesc($fsugg['request'])
2951 // OK, we do. Do we already have an introduction for this person ?
2952 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2953 intval($fsugg['uid']),
2960 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2961 dbesc($fsugg['name']),
2962 dbesc($fsugg['url']),
2963 dbesc($fsugg['photo']),
2964 dbesc($fsugg['request'])
2966 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2967 dbesc($fsugg['url']),
2968 dbesc($fsugg['name']),
2969 dbesc($fsugg['request'])
2974 // database record did not get created. Quietly give up.
2979 $hash = random_string();
2981 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2982 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2983 intval($fsugg['uid']),
2985 intval($fsugg['cid']),
2986 dbesc($fsugg['body']),
2988 dbesc(datetime_convert()),
2993 'type' => NOTIFY_SUGGEST,
2994 'notify_flags' => $importer['notify-flags'],
2995 'language' => $importer['language'],
2996 'to_name' => $importer['username'],
2997 'to_email' => $importer['email'],
2998 'uid' => $importer['importer_uid'],
3000 'link' => $a->get_baseurl() . '/notifications/intros',
3001 'source_name' => $importer['name'],
3002 'source_link' => $importer['url'],
3003 'source_photo' => $importer['photo'],
3004 'verb' => ACTIVITY_REQ_FRIEND,
3013 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3014 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3016 logger('local_delivery: private message received');
3019 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3022 $msg['uid'] = $importer['importer_uid'];
3023 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3024 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3025 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3026 $msg['contact-id'] = $importer['id'];
3027 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3028 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3030 $msg['replied'] = 0;
3031 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3032 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3033 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3037 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3038 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3040 // send notifications.
3042 require_once('include/enotify.php');
3044 $notif_params = array(
3045 'type' => NOTIFY_MAIL,
3046 'notify_flags' => $importer['notify-flags'],
3047 'language' => $importer['language'],
3048 'to_name' => $importer['username'],
3049 'to_email' => $importer['email'],
3050 'uid' => $importer['importer_uid'],
3052 'source_name' => $msg['from-name'],
3053 'source_link' => $importer['url'],
3054 'source_photo' => $importer['thumb'],
3055 'verb' => ACTIVITY_POST,
3059 notification($notif_params);
3065 $community_page = 0;
3066 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3068 $community_page = intval($rawtags[0]['data']);
3070 if(intval($importer['forum']) != $community_page) {
3071 q("update contact set forum = %d where id = %d",
3072 intval($community_page),
3073 intval($importer['id'])
3075 $importer['forum'] = (string) $community_page;
3078 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3080 // process any deleted entries
3082 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3083 if(is_array($del_entries) && count($del_entries)) {
3084 foreach($del_entries as $dentry) {
3086 if(isset($dentry['attribs']['']['ref'])) {
3087 $uri = $dentry['attribs']['']['ref'];
3089 if(isset($dentry['attribs']['']['when'])) {
3090 $when = $dentry['attribs']['']['when'];
3091 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3094 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3098 // check for relayed deletes to our conversation
3101 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3103 intval($importer['importer_uid'])
3106 $parent_uri = $r[0]['parent-uri'];
3107 if($r[0]['id'] != $r[0]['parent'])
3114 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3117 logger('local_delivery: possible community delete');
3120 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3122 // was the top-level post for this reply written by somebody on this site?
3123 // Specifically, the recipient?
3125 $is_a_remote_delete = false;
3127 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3128 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3129 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3130 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3131 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3132 AND `item`.`uid` = %d
3138 intval($importer['importer_uid'])
3141 $is_a_remote_delete = true;
3143 // Does this have the characteristics of a community or private group comment?
3144 // If it's a reply to a wall post on a community/prvgroup page it's a
3145 // valid community comment. Also forum_mode makes it valid for sure.
3146 // If neither, it's not.
3148 if($is_a_remote_delete && $community) {
3149 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3150 $is_a_remote_delete = false;
3151 logger('local_delivery: not a community delete');
3155 if($is_a_remote_delete) {
3156 logger('local_delivery: received remote delete');
3160 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3161 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3163 intval($importer['importer_uid']),
3164 intval($importer['id'])
3170 if($item['deleted'])
3173 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3175 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3176 $xo = parse_xml_string($item['object'],false);
3177 $xt = parse_xml_string($item['target'],false);
3179 if($xt->type === ACTIVITY_OBJ_NOTE) {
3180 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3182 intval($importer['importer_uid'])
3186 // For tags, the owner cannot remove the tag on the author's copy of the post.
3188 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3189 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3190 $author_copy = (($item['origin']) ? true : false);
3192 if($owner_remove && $author_copy)
3194 if($author_remove || $owner_remove) {
3195 $tags = explode(',',$i[0]['tag']);
3198 foreach($tags as $tag)
3199 if(trim($tag) !== trim($xo->body))
3200 $newtags[] = trim($tag);
3202 q("update item set tag = '%s' where id = %d",
3203 dbesc(implode(',',$newtags)),
3206 create_tags_from_item($i[0]['id']);
3212 if($item['uri'] == $item['parent-uri']) {
3213 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3214 `body` = '', `title` = ''
3215 WHERE `parent-uri` = '%s' AND `uid` = %d",
3217 dbesc(datetime_convert()),
3218 dbesc($item['uri']),
3219 intval($importer['importer_uid'])
3221 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3222 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3223 update_thread_uri($item['uri'], $importer['importer_uid']);
3226 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3227 `body` = '', `title` = ''
3228 WHERE `uri` = '%s' AND `uid` = %d",
3230 dbesc(datetime_convert()),
3232 intval($importer['importer_uid'])
3234 create_tags_from_itemuri($uri, $importer['importer_uid']);
3235 create_files_from_itemuri($uri, $importer['importer_uid']);
3236 update_thread_uri($uri, $importer['importer_uid']);
3237 if($item['last-child']) {
3238 // ensure that last-child is set in case the comment that had it just got wiped.
3239 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3240 dbesc(datetime_convert()),
3241 dbesc($item['parent-uri']),
3242 intval($item['uid'])
3244 // who is the last child now?
3245 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3246 ORDER BY `created` DESC LIMIT 1",
3247 dbesc($item['parent-uri']),
3248 intval($importer['importer_uid'])
3251 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3256 // if this is a relayed delete, propagate it to other recipients
3258 if($is_a_remote_delete)
3259 proc_run('php',"include/notifier.php","drop",$item['id']);
3267 foreach($feed->get_items() as $item) {
3270 $item_id = $item->get_id();
3271 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3272 if(isset($rawthread[0]['attribs']['']['ref'])) {
3274 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3280 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3283 logger('local_delivery: possible community reply');
3286 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3288 // was the top-level post for this reply written by somebody on this site?
3289 // Specifically, the recipient?
3291 $is_a_remote_comment = false;
3292 $top_uri = $parent_uri;
3294 $r = q("select `item`.`parent-uri` from `item`
3295 WHERE `item`.`uri` = '%s'
3299 if($r && count($r)) {
3300 $top_uri = $r[0]['parent-uri'];
3302 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3303 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3304 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3305 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3306 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3307 AND `item`.`uid` = %d
3313 intval($importer['importer_uid'])
3316 $is_a_remote_comment = true;
3319 // Does this have the characteristics of a community or private group comment?
3320 // If it's a reply to a wall post on a community/prvgroup page it's a
3321 // valid community comment. Also forum_mode makes it valid for sure.
3322 // If neither, it's not.
3324 if($is_a_remote_comment && $community) {
3325 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3326 $is_a_remote_comment = false;
3327 logger('local_delivery: not a community reply');
3331 if($is_a_remote_comment) {
3332 logger('local_delivery: received remote comment');
3334 // remote reply to our post. Import and then notify everybody else.
3336 $datarray = get_atom_elements($feed, $item);
3338 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3340 intval($importer['importer_uid'])
3343 // Update content if 'updated' changes
3347 if (edited_timestamp_is_newer($r[0], $datarray)) {
3349 // do not accept (ignore) an earlier edit than one we currently have.
3350 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3353 logger('received updated comment' , LOGGER_DEBUG);
3354 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3355 dbesc($datarray['title']),
3356 dbesc($datarray['body']),
3357 dbesc($datarray['tag']),
3358 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3359 dbesc(datetime_convert()),
3361 intval($importer['importer_uid'])
3363 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3365 proc_run('php',"include/notifier.php","comment-import",$iid);
3374 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3375 intval($importer['importer_uid'])
3379 $datarray['type'] = 'remote-comment';
3380 $datarray['wall'] = 1;
3381 $datarray['parent-uri'] = $parent_uri;
3382 $datarray['uid'] = $importer['importer_uid'];
3383 $datarray['owner-name'] = $own[0]['name'];
3384 $datarray['owner-link'] = $own[0]['url'];
3385 $datarray['owner-avatar'] = $own[0]['thumb'];
3386 $datarray['contact-id'] = $importer['id'];
3388 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3390 $datarray['type'] = 'activity';
3391 $datarray['gravity'] = GRAVITY_LIKE;
3392 $datarray['last-child'] = 0;
3393 // only one like or dislike per person
3394 // splitted into two queries for performance issues
3395 $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",
3396 intval($datarray['uid']),
3397 intval($datarray['contact-id']),
3398 dbesc($datarray['verb']),
3399 dbesc($datarray['parent-uri'])
3405 $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",
3406 intval($datarray['uid']),
3407 intval($datarray['contact-id']),
3408 dbesc($datarray['verb']),
3409 dbesc($datarray['parent-uri'])
3416 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3418 $xo = parse_xml_string($datarray['object'],false);
3419 $xt = parse_xml_string($datarray['target'],false);
3421 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3423 // fetch the parent item
3425 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3427 intval($importer['importer_uid'])
3432 // extract tag, if not duplicate, and this user allows tags, add to parent item
3434 if($xo->id && $xo->content) {
3435 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3436 if(! (stristr($tagp[0]['tag'],$newtag))) {
3437 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3438 intval($importer['importer_uid'])
3440 if(count($i) && ! intval($i[0]['blocktags'])) {
3441 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3442 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3443 intval($tagp[0]['id']),
3444 dbesc(datetime_convert()),
3445 dbesc(datetime_convert())
3447 create_tags_from_item($tagp[0]['id']);
3455 $posted_id = item_store($datarray);
3459 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3461 intval($importer['importer_uid'])
3464 $parent = $r[0]['parent'];
3465 $parent_uri = $r[0]['parent-uri'];
3469 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3470 dbesc(datetime_convert()),
3471 intval($importer['importer_uid']),
3472 intval($r[0]['parent'])
3475 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3476 dbesc(datetime_convert()),
3477 intval($importer['importer_uid']),
3482 if($posted_id && $parent) {
3484 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3486 if((! $is_like) && (! $importer['self'])) {
3488 require_once('include/enotify.php');
3491 'type' => NOTIFY_COMMENT,
3492 'notify_flags' => $importer['notify-flags'],
3493 'language' => $importer['language'],
3494 'to_name' => $importer['username'],
3495 'to_email' => $importer['email'],
3496 'uid' => $importer['importer_uid'],
3497 'item' => $datarray,
3498 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3499 'source_name' => stripslashes($datarray['author-name']),
3500 'source_link' => $datarray['author-link'],
3501 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3502 ? $importer['thumb'] : $datarray['author-avatar']),
3503 'verb' => ACTIVITY_POST,
3505 'parent' => $parent,
3506 'parent_uri' => $parent_uri,
3518 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3520 $item_id = $item->get_id();
3521 $datarray = get_atom_elements($feed,$item);
3523 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3526 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3528 intval($importer['importer_uid'])
3531 // Update content if 'updated' changes
3534 if (edited_timestamp_is_newer($r[0], $datarray)) {
3536 // do not accept (ignore) an earlier edit than one we currently have.
3537 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3540 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3541 dbesc($datarray['title']),
3542 dbesc($datarray['body']),
3543 dbesc($datarray['tag']),
3544 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3545 dbesc(datetime_convert()),
3547 intval($importer['importer_uid'])
3549 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3552 // update last-child if it changes
3554 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3555 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3556 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3557 dbesc(datetime_convert()),
3559 intval($importer['importer_uid'])
3561 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3562 intval($allow[0]['data']),
3563 dbesc(datetime_convert()),
3565 intval($importer['importer_uid'])
3571 $datarray['parent-uri'] = $parent_uri;
3572 $datarray['uid'] = $importer['importer_uid'];
3573 $datarray['contact-id'] = $importer['id'];
3574 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3575 $datarray['type'] = 'activity';
3576 $datarray['gravity'] = GRAVITY_LIKE;
3577 // only one like or dislike per person
3578 // splitted into two queries for performance issues
3579 $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",
3580 intval($datarray['uid']),
3581 intval($datarray['contact-id']),
3582 dbesc($datarray['verb']),
3588 $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",
3589 intval($datarray['uid']),
3590 intval($datarray['contact-id']),
3591 dbesc($datarray['verb']),
3599 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3601 $xo = parse_xml_string($datarray['object'],false);
3602 $xt = parse_xml_string($datarray['target'],false);
3604 if($xt->type == ACTIVITY_OBJ_NOTE) {
3605 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3607 intval($importer['importer_uid'])
3612 // extract tag, if not duplicate, add to parent item
3614 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3615 q("UPDATE item SET tag = '%s' WHERE id = %d",
3616 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3619 create_tags_from_item($r[0]['id']);
3625 $posted_id = item_store($datarray);
3627 // find out if our user is involved in this conversation and wants to be notified.
3629 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3631 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3633 intval($importer['importer_uid'])
3636 if(count($myconv)) {
3637 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3639 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3640 if(! link_compare($datarray['author-link'],$importer_url)) {
3643 foreach($myconv as $conv) {
3645 // now if we find a match, it means we're in this conversation
3647 if(! link_compare($conv['author-link'],$importer_url))
3650 require_once('include/enotify.php');
3652 $conv_parent = $conv['parent'];
3655 'type' => NOTIFY_COMMENT,
3656 'notify_flags' => $importer['notify-flags'],
3657 'language' => $importer['language'],
3658 'to_name' => $importer['username'],
3659 'to_email' => $importer['email'],
3660 'uid' => $importer['importer_uid'],
3661 'item' => $datarray,
3662 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3663 'source_name' => stripslashes($datarray['author-name']),
3664 'source_link' => $datarray['author-link'],
3665 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3666 ? $importer['thumb'] : $datarray['author-avatar']),
3667 'verb' => ACTIVITY_POST,
3669 'parent' => $conv_parent,
3670 'parent_uri' => $parent_uri
3674 // only send one notification
3686 // Head post of a conversation. Have we seen it? If not, import it.
3689 $item_id = $item->get_id();
3690 $datarray = get_atom_elements($feed,$item);
3692 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3693 $ev = bbtoevent($datarray['body']);
3694 if(x($ev,'desc') && x($ev,'start')) {
3695 $ev['cid'] = $importer['id'];
3696 $ev['uid'] = $importer['uid'];
3697 $ev['uri'] = $item_id;
3698 $ev['edited'] = $datarray['edited'];
3699 $ev['private'] = $datarray['private'];
3701 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3703 intval($importer['uid'])
3706 $ev['id'] = $r[0]['id'];
3707 $xyz = event_store($ev);
3712 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3714 intval($importer['importer_uid'])
3717 // Update content if 'updated' changes
3720 if (edited_timestamp_is_newer($r[0], $datarray)) {
3722 // do not accept (ignore) an earlier edit than one we currently have.
3723 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3726 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3727 dbesc($datarray['title']),
3728 dbesc($datarray['body']),
3729 dbesc($datarray['tag']),
3730 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3731 dbesc(datetime_convert()),
3733 intval($importer['importer_uid'])
3735 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3736 update_thread_uri($item_id, $importer['importer_uid']);
3739 // update last-child if it changes
3741 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3742 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3743 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3744 intval($allow[0]['data']),
3745 dbesc(datetime_convert()),
3747 intval($importer['importer_uid'])
3753 $datarray['parent-uri'] = $item_id;
3754 $datarray['uid'] = $importer['importer_uid'];
3755 $datarray['contact-id'] = $importer['id'];
3758 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3759 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3760 // but otherwise there's a possible data mixup on the sender's system.
3761 // the tgroup delivery code called from item_store will correct it if it's a forum,
3762 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3763 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3764 $datarray['owner-name'] = $importer['senderName'];
3765 $datarray['owner-link'] = $importer['url'];
3766 $datarray['owner-avatar'] = $importer['thumb'];
3769 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3772 // This is my contact on another system, but it's really me.
3773 // Turn this into a wall post.
3774 $notify = item_is_remote_self($importer, $datarray);
3776 $posted_id = item_store($datarray, false, $notify);
3778 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3779 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3782 $xo = parse_xml_string($datarray['object'],false);
3784 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3786 // somebody was poked/prodded. Was it me?
3788 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3790 foreach($links->link as $l) {
3791 $atts = $l->attributes();
3792 switch($atts['rel']) {
3794 $Blink = $atts['href'];
3800 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3802 // send a notification
3803 require_once('include/enotify.php');
3806 'type' => NOTIFY_POKE,
3807 'notify_flags' => $importer['notify-flags'],
3808 'language' => $importer['language'],
3809 'to_name' => $importer['username'],
3810 'to_email' => $importer['email'],
3811 'uid' => $importer['importer_uid'],
3812 'item' => $datarray,
3813 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3814 'source_name' => stripslashes($datarray['author-name']),
3815 'source_link' => $datarray['author-link'],
3816 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3817 ? $importer['thumb'] : $datarray['author-avatar']),
3818 'verb' => $datarray['verb'],
3819 'otype' => 'person',
3820 'activity' => $verb,
3837 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3838 $url = notags(trim($datarray['author-link']));
3839 $name = notags(trim($datarray['author-name']));
3840 $photo = notags(trim($datarray['author-avatar']));
3842 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3843 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3844 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3846 if(is_array($contact)) {
3847 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3848 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3849 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3850 intval(CONTACT_IS_FRIEND),
3851 intval($contact['id']),
3852 intval($importer['uid'])
3855 // send email notification to owner?
3859 // create contact record
3861 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3862 `blocked`, `readonly`, `pending`, `writable` )
3863 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3864 intval($importer['uid']),
3865 dbesc(datetime_convert()),
3867 dbesc(normalise_link($url)),
3871 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3872 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3874 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3875 intval($importer['uid']),
3879 $contact_record = $r[0];
3881 // create notification
3882 $hash = random_string();
3884 if(is_array($contact_record)) {
3885 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3886 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3887 intval($importer['uid']),
3888 intval($contact_record['id']),
3890 dbesc(datetime_convert())
3894 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3895 intval($importer['uid'])
3900 if(intval($r[0]['def_gid'])) {
3901 require_once('include/group.php');
3902 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3905 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3906 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3911 'type' => NOTIFY_INTRO,
3912 'notify_flags' => $r[0]['notify-flags'],
3913 'language' => $r[0]['language'],
3914 'to_name' => $r[0]['username'],
3915 'to_email' => $r[0]['email'],
3916 'uid' => $r[0]['uid'],
3917 'link' => $a->get_baseurl() . '/notifications/intro',
3918 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3919 'source_link' => $contact_record['url'],
3920 'source_photo' => $contact_record['photo'],
3921 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3931 function lose_follower($importer,$contact,$datarray,$item) {
3933 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3934 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3935 intval(CONTACT_IS_SHARING),
3936 intval($contact['id'])
3940 contact_remove($contact['id']);
3944 function lose_sharer($importer,$contact,$datarray,$item) {
3946 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3947 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3948 intval(CONTACT_IS_FOLLOWER),
3949 intval($contact['id'])
3953 contact_remove($contact['id']);
3958 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3962 if(is_array($importer)) {
3963 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3964 intval($importer['uid'])
3968 // Diaspora has different message-ids in feeds than they do
3969 // through the direct Diaspora protocol. If we try and use
3970 // the feed, we'll get duplicates. So don't.
3972 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3975 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3977 // Use a single verify token, even if multiple hubs
3979 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3981 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3983 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3985 if(! strlen($contact['hub-verify'])) {
3986 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3987 dbesc($verify_token),
3988 intval($contact['id'])
3992 post_url($url,$params);
3994 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4001 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4005 $name = xmlify($name);
4006 $uri = xmlify($uri);
4009 $photo = xmlify($photo);
4013 $o .= "<name>$name</name>\r\n";
4014 $o .= "<uri>$uri</uri>\r\n";
4015 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4016 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4018 call_hooks('atom_author', $o);
4020 $o .= "</$tag>\r\n";
4024 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4028 if(! $item['parent'])
4031 if($item['deleted'])
4032 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4035 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4036 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4038 $body = $item['body'];
4040 $o = "\r\n\r\n<entry>\r\n";
4042 if(is_array($author))
4043 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4045 $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']));
4046 if(strlen($item['owner-name']))
4047 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4049 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4050 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4051 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4054 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4055 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4056 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4057 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4058 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4059 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4060 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4062 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4064 if($item['location']) {
4065 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4066 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4070 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4072 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4073 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4076 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4077 if($item['bookmark'])
4078 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4081 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4084 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4086 if($item['signed_text']) {
4087 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4088 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4091 $verb = construct_verb($item);
4092 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4093 $actobj = construct_activity_object($item);
4096 $actarg = construct_activity_target($item);
4100 $tags = item_getfeedtags($item);
4102 foreach($tags as $t) {
4103 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4107 $o .= item_getfeedattach($item);
4109 $mentioned = get_mentions($item);
4113 call_hooks('atom_entry', $o);
4115 $o .= '</entry>' . "\r\n";
4120 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4122 if(get_config('system','disable_embedded'))
4127 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4128 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4133 $img_start = strpos($orig_body, '[img');
4134 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4135 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4136 while( ($img_st_close !== false) && ($img_len !== false) ) {
4138 $img_st_close++; // make it point to AFTER the closing bracket
4139 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4141 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4144 if(stristr($image , $site . '/photo/')) {
4145 // Only embed locally hosted photos
4147 $i = basename($image);
4148 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4149 $x = strpos($i,'-');
4152 $res = substr($i,$x+1);
4153 $i = substr($i,0,$x);
4154 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4161 // Check to see if we should replace this photo link with an embedded image
4162 // 1. No need to do so if the photo is public
4163 // 2. If there's a contact-id provided, see if they're in the access list
4164 // for the photo. If so, embed it.
4165 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4166 // permissions, regardless of order but first check to see if they're an exact
4167 // match to save some processing overhead.
4169 if(has_permissions($r[0])) {
4171 $recips = enumerate_permissions($r[0]);
4172 if(in_array($cid, $recips)) {
4177 if(compare_permissions($item,$r[0]))
4182 $data = $r[0]['data'];
4183 $type = $r[0]['type'];
4185 // If a custom width and height were specified, apply before embedding
4186 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4187 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4189 $width = intval($match[1]);
4190 $height = intval($match[2]);
4192 $ph = new Photo($data, $type);
4193 if($ph->is_valid()) {
4194 $ph->scaleImage(max($width, $height));
4195 $data = $ph->imageString();
4196 $type = $ph->getType();
4200 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4201 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4202 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4208 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4209 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4210 if($orig_body === false)
4213 $img_start = strpos($orig_body, '[img');
4214 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4215 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4218 $new_body = $new_body . $orig_body;
4224 function has_permissions($obj) {
4225 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4230 function compare_permissions($obj1,$obj2) {
4231 // first part is easy. Check that these are exactly the same.
4232 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4233 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4234 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4235 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4238 // This is harder. Parse all the permissions and compare the resulting set.
4240 $recipients1 = enumerate_permissions($obj1);
4241 $recipients2 = enumerate_permissions($obj2);
4244 if($recipients1 == $recipients2)
4249 // returns an array of contact-ids that are allowed to see this object
4251 function enumerate_permissions($obj) {
4252 require_once('include/group.php');
4253 $allow_people = expand_acl($obj['allow_cid']);
4254 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4255 $deny_people = expand_acl($obj['deny_cid']);
4256 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4257 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4258 $deny = array_unique(array_merge($deny_people,$deny_groups));
4259 $recipients = array_diff($recipients,$deny);
4263 function item_getfeedtags($item) {
4266 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4268 for($x = 0; $x < $cnt; $x ++) {
4270 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4274 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4276 for($x = 0; $x < $cnt; $x ++) {
4278 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4284 function item_getfeedattach($item) {
4286 $arr = explode('[/attach],',$item['attach']);
4288 foreach($arr as $r) {
4290 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4292 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4293 if(intval($matches[2]))
4294 $ret .= 'length="' . intval($matches[2]) . '" ';
4295 if($matches[4] !== ' ')
4296 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4297 $ret .= ' />' . "\r\n";
4306 function item_expire($uid, $days, $network = "", $force = false) {
4308 if((! $uid) || ($days < 1))
4311 // $expire_network_only = save your own wall posts
4312 // and just expire conversations started by others
4314 $expire_network_only = get_pconfig($uid,'expire','network_only');
4315 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4317 if ($network != "") {
4318 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4319 // There is an index "uid_network_received" but not "uid_network_created"
4320 // This avoids the creation of another index just for one purpose.
4321 // And it doesn't really matter wether to look at "received" or "created"
4322 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4324 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4326 $r = q("SELECT * FROM `item`
4327 WHERE `uid` = %d $range
4338 $expire_items = get_pconfig($uid, 'expire','items');
4339 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4341 // Forcing expiring of items - but not notes and marked items
4343 $expire_items = true;
4345 $expire_notes = get_pconfig($uid, 'expire','notes');
4346 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4348 $expire_starred = get_pconfig($uid, 'expire','starred');
4349 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4351 $expire_photos = get_pconfig($uid, 'expire','photos');
4352 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4354 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4356 foreach($r as $item) {
4358 // don't expire filed items
4360 if(strpos($item['file'],'[') !== false)
4363 // Only expire posts, not photos and photo comments
4365 if($expire_photos==0 && strlen($item['resource-id']))
4367 if($expire_starred==0 && intval($item['starred']))
4369 if($expire_notes==0 && $item['type']=='note')
4371 if($expire_items==0 && $item['type']!='note')
4374 drop_item($item['id'],false);
4377 proc_run('php',"include/notifier.php","expire","$uid");
4382 function drop_items($items) {
4385 if(! local_user() && ! remote_user())
4389 foreach($items as $item) {
4390 $owner = drop_item($item,false);
4391 if($owner && ! $uid)
4396 // multiple threads may have been deleted, send an expire notification
4399 proc_run('php',"include/notifier.php","expire","$uid");
4403 function drop_item($id,$interactive = true) {
4407 // locate item to be deleted
4409 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4416 notice( t('Item not found.') . EOL);
4417 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4422 $owner = $item['uid'];
4426 // check if logged in user is either the author or owner of this item
4428 if(is_array($_SESSION['remote'])) {
4429 foreach($_SESSION['remote'] as $visitor) {
4430 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4431 $cid = $visitor['cid'];
4438 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4440 // Check if we should do HTML-based delete confirmation
4441 if($_REQUEST['confirm']) {
4442 // <form> can't take arguments in its "action" parameter
4443 // so add any arguments as hidden inputs
4444 $query = explode_querystring($a->query_string);
4446 foreach($query['args'] as $arg) {
4447 if(strpos($arg, 'confirm=') === false) {
4448 $arg_parts = explode('=', $arg);
4449 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4453 return replace_macros(get_markup_template('confirm.tpl'), array(
4455 '$message' => t('Do you really want to delete this item?'),
4456 '$extra_inputs' => $inputs,
4457 '$confirm' => t('Yes'),
4458 '$confirm_url' => $query['base'],
4459 '$confirm_name' => 'confirmed',
4460 '$cancel' => t('Cancel'),
4463 // Now check how the user responded to the confirmation query
4464 if($_REQUEST['canceled']) {
4465 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4468 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4471 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4472 dbesc(datetime_convert()),
4473 dbesc(datetime_convert()),
4476 create_tags_from_item($item['id']);
4477 create_files_from_item($item['id']);
4478 delete_thread($item['id']);
4480 // clean up categories and tags so they don't end up as orphans
4483 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4485 foreach($matches as $mtch) {
4486 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4492 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4494 foreach($matches as $mtch) {
4495 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4499 // If item is a link to a photo resource, nuke all the associated photos
4500 // (visitors will not have photo resources)
4501 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4502 // generate a resource-id and therefore aren't intimately linked to the item.
4504 if(strlen($item['resource-id'])) {
4505 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4506 dbesc($item['resource-id']),
4507 intval($item['uid'])
4509 // ignore the result
4512 // If item is a link to an event, nuke the event record.
4514 if(intval($item['event-id'])) {
4515 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4516 intval($item['event-id']),
4517 intval($item['uid'])
4519 // ignore the result
4522 // clean up item_id and sign meta-data tables
4525 // Old code - caused very long queries and warning entries in the mysql logfiles:
4527 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4528 intval($item['id']),
4529 intval($item['uid'])
4532 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4533 intval($item['id']),
4534 intval($item['uid'])
4538 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4540 // Creating list of parents
4541 $r = q("select id from item where parent = %d and uid = %d",
4542 intval($item['id']),
4543 intval($item['uid'])
4548 foreach ($r AS $row) {
4549 if ($parentid != "")
4552 $parentid .= $row["id"];
4556 if ($parentid != "") {
4557 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4559 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4562 // If it's the parent of a comment thread, kill all the kids
4564 if($item['uri'] == $item['parent-uri']) {
4565 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4566 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4567 dbesc(datetime_convert()),
4568 dbesc(datetime_convert()),
4569 dbesc($item['parent-uri']),
4570 intval($item['uid'])
4572 create_tags_from_item($item['parent-uri'], $item['uid']);
4573 create_files_from_item($item['parent-uri'], $item['uid']);
4574 delete_thread_uri($item['parent-uri'], $item['uid']);
4575 // ignore the result
4578 // ensure that last-child is set in case the comment that had it just got wiped.
4579 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4580 dbesc(datetime_convert()),
4581 dbesc($item['parent-uri']),
4582 intval($item['uid'])
4584 // who is the last child now?
4585 $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",
4586 dbesc($item['parent-uri']),
4587 intval($item['uid'])
4590 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4595 // Add a relayable_retraction signature for Diaspora.
4596 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4598 $drop_id = intval($item['id']);
4600 // send the notification upstream/downstream as the case may be
4602 proc_run('php',"include/notifier.php","drop","$drop_id");
4606 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4612 notice( t('Permission denied.') . EOL);
4613 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4620 function first_post_date($uid,$wall = false) {
4621 $r = q("select id, created from item
4622 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4624 order by created asc limit 1",
4626 intval($wall ? 1 : 0)
4629 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4630 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4635 function posted_dates($uid,$wall) {
4636 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4638 $dthen = first_post_date($uid,$wall);
4642 // Set the start and end date to the beginning of the month
4643 $dnow = substr($dnow,0,8).'01';
4644 $dthen = substr($dthen,0,8).'01';
4647 // Starting with the current month, get the first and last days of every
4648 // month down to and including the month of the first post
4649 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4650 $dstart = substr($dnow,0,8) . '01';
4651 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4652 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4653 $end_month = datetime_convert('','',$dend,'Y-m-d');
4654 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4655 $ret[] = array($str,$end_month,$start_month);
4656 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4662 function posted_date_widget($url,$uid,$wall) {
4665 if(! feature_enabled($uid,'archives'))
4668 // For former Facebook folks that left because of "timeline"
4670 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4673 $ret = posted_dates($uid,$wall);
4677 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4678 '$title' => t('Archives'),
4679 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4686 function store_diaspora_retract_sig($item, $user, $baseurl) {
4687 // Note that we can't add a target_author_signature
4688 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4689 // the comment, that means we're the home of the post, and Diaspora will only
4690 // check the parent_author_signature of retractions that it doesn't have to relay further
4692 // I don't think this function gets called for an "unlike," but I'll check anyway
4694 $enabled = intval(get_config('system','diaspora_enabled'));
4696 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4700 logger('drop_item: storing diaspora retraction signature');
4702 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4704 if(local_user() == $item['uid']) {
4706 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4707 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4710 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4711 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4714 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4715 // only handles DFRN deletes
4716 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4717 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4718 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4724 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4725 intval($item['id']),
4726 dbesc($signed_text),