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 (($data["type"] != "photo") AND is_string($data["title"]))
907 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
909 if (($data["type"] != "video") AND ($photo != ""))
910 $text .= '[img]'.$photo.'[/img]';
911 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
912 $imagedata = $data["images"][0];
913 $text .= '[img]'.$imagedata["src"].'[/img]';
916 if (($data["type"] != "photo") AND is_string($data["text"]))
917 $text .= "[quote]".$data["text"]."[/quote]";
920 if (isset($data["keywords"]) AND count($data["keywords"])) {
923 foreach ($data["keywords"] AS $keyword) {
924 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
925 array("","", "", "", "", ""), $keyword);
926 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
930 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
933 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
934 require_once("mod/parse_url.php");
936 $data = parseurl_getsiteinfo($url, true);
938 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
940 if (!$keywords AND isset($data["keywords"]))
941 unset($data["keywords"]);
943 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
944 $list = explode(",", $keyword_blacklist);
945 foreach ($list AS $keyword) {
946 $keyword = trim($keyword);
947 $index = array_search($keyword, $data["keywords"]);
948 if ($index !== false)
949 unset($data["keywords"][$index]);
953 $text = add_page_info_data($data);
958 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
960 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
962 $URLSearchString = "^\[\]";
964 // Adding these spaces is a quick hack due to my problems with regular expressions :)
965 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
968 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
970 // Convert urls without bbcode elements
971 if (!$matches AND $texturl) {
972 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
974 // Yeah, a hack. I really hate regular expressions :)
976 $matches[1] = $matches[2];
980 $footer = add_page_info($matches[1], $no_photos);
982 // Remove the link from the body if the link is attached at the end of the post
983 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
984 $removedlink = trim(str_replace($matches[1], "", $body));
985 if (($removedlink == "") OR strstr($body, $removedlink))
986 $body = $removedlink;
988 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
989 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
990 if (($removedlink == "") OR strstr($body, $removedlink))
991 $body = $removedlink;
994 // Add the page information to the bottom
995 if (isset($footer) AND (trim($footer) != ""))
1001 function encode_rel_links($links) {
1003 if(! ((is_array($links)) && (count($links))))
1005 foreach($links as $link) {
1007 if($link['attribs']['']['rel'])
1008 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1009 if($link['attribs']['']['type'])
1010 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1011 if($link['attribs']['']['href'])
1012 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1013 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1014 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1015 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1016 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1017 $o .= ' />' . "\n" ;
1024 function item_store($arr,$force_parent = false, $notify = false) {
1026 // If it is a posting where users should get notifications, then define it as wall posting
1029 $arr['type'] = 'wall';
1031 $arr['last-child'] = 1;
1032 $arr['network'] = NETWORK_DFRN;
1035 // If a Diaspora signature structure was passed in, pull it out of the
1036 // item array and set it aside for later storage.
1039 if(x($arr,'dsprsig')) {
1040 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1041 unset($arr['dsprsig']);
1044 // if an OStatus conversation url was passed in, it is stored and then
1045 // removed from the array.
1046 $ostatus_conversation = null;
1048 if (isset($arr["ostatus_conversation"])) {
1049 $ostatus_conversation = $arr["ostatus_conversation"];
1050 unset($arr["ostatus_conversation"]);
1053 if(x($arr, 'gravity'))
1054 $arr['gravity'] = intval($arr['gravity']);
1055 elseif($arr['parent-uri'] === $arr['uri'])
1056 $arr['gravity'] = 0;
1057 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1058 $arr['gravity'] = 6;
1060 $arr['gravity'] = 6; // extensible catchall
1062 if(! x($arr,'type'))
1063 $arr['type'] = 'remote';
1067 /* check for create date and expire time */
1068 $uid = intval($arr['uid']);
1069 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1071 $expire_interval = $r[0]['expire'];
1072 if ($expire_interval>0) {
1073 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1074 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1075 if ($created_date < $expire_date) {
1076 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1082 // If there is no guid then take the same guid that was taken before for the same uri
1083 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1084 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1085 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1086 dbesc(trim($arr['uri']))
1090 $arr['guid'] = $r[0]["guid"];
1091 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1095 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1096 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1097 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1098 // $arr['body'] = strip_tags($arr['body']);
1101 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1102 require_once('library/langdet/Text/LanguageDetect.php');
1103 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1104 $l = new Text_LanguageDetect;
1105 //$lng = $l->detectConfidence($naked_body);
1106 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1107 $lng = $l->detect($naked_body, 3);
1109 if (sizeof($lng) > 0) {
1112 foreach ($lng as $language => $score) {
1113 if ($postopts == "")
1114 $postopts = "lang=";
1118 $postopts .= $language.";".$score;
1120 $arr['postopts'] = $postopts;
1124 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1125 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1126 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1127 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1128 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1129 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1130 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1131 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1132 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1133 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1134 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1135 $arr['commented'] = datetime_convert();
1136 $arr['received'] = datetime_convert();
1137 $arr['changed'] = datetime_convert();
1138 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1139 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1140 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1141 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1142 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1143 $arr['deleted'] = 0;
1144 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1145 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1146 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1147 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1148 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1149 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1150 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1151 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1152 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1153 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1154 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1155 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1156 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1157 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1158 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1159 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1160 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1161 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1162 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1163 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1164 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1165 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1166 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1167 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1168 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1170 if ($arr['plink'] == "") {
1172 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1175 if ($arr['network'] == "") {
1176 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1177 intval($arr['contact-id']),
1182 $arr['network'] = $r[0]["network"];
1184 // Fallback to friendica (why is it empty in some cases?)
1185 if ($arr['network'] == "")
1186 $arr['network'] = NETWORK_DFRN;
1188 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1191 $arr['thr-parent'] = $arr['parent-uri'];
1192 if($arr['parent-uri'] === $arr['uri']) {
1194 $parent_deleted = 0;
1195 $allow_cid = $arr['allow_cid'];
1196 $allow_gid = $arr['allow_gid'];
1197 $deny_cid = $arr['deny_cid'];
1198 $deny_gid = $arr['deny_gid'];
1199 $notify_type = 'wall-new';
1203 // find the parent and snarf the item id and ACLs
1204 // and anything else we need to inherit
1206 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1207 dbesc($arr['parent-uri']),
1213 // is the new message multi-level threaded?
1214 // even though we don't support it now, preserve the info
1215 // and re-attach to the conversation parent.
1217 if($r[0]['uri'] != $r[0]['parent-uri']) {
1218 $arr['parent-uri'] = $r[0]['parent-uri'];
1219 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1220 ORDER BY `id` ASC LIMIT 1",
1221 dbesc($r[0]['parent-uri']),
1222 dbesc($r[0]['parent-uri']),
1229 $parent_id = $r[0]['id'];
1230 $parent_deleted = $r[0]['deleted'];
1231 $allow_cid = $r[0]['allow_cid'];
1232 $allow_gid = $r[0]['allow_gid'];
1233 $deny_cid = $r[0]['deny_cid'];
1234 $deny_gid = $r[0]['deny_gid'];
1235 $arr['wall'] = $r[0]['wall'];
1236 $notify_type = 'comment-new';
1238 // if the parent is private, force privacy for the entire conversation
1239 // This differs from the above settings as it subtly allows comments from
1240 // email correspondents to be private even if the overall thread is not.
1242 if($r[0]['private'])
1243 $arr['private'] = $r[0]['private'];
1245 // Edge case. We host a public forum that was originally posted to privately.
1246 // The original author commented, but as this is a comment, the permissions
1247 // weren't fixed up so it will still show the comment as private unless we fix it here.
1249 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1250 $arr['private'] = 0;
1253 // If its a post from myself then tag the thread as "mention"
1254 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1255 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1258 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1259 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1260 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1261 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1262 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1268 // Allow one to see reply tweets from status.net even when
1269 // we don't have or can't see the original post.
1272 logger('item_store: $force_parent=true, reply converted to top-level post.');
1274 $arr['parent-uri'] = $arr['uri'];
1275 $arr['gravity'] = 0;
1278 logger('item_store: item parent was not found - ignoring item');
1282 $parent_deleted = 0;
1286 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1290 if($r && count($r)) {
1291 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1295 call_hooks('post_remote',$arr);
1297 if(x($arr,'cancel')) {
1298 logger('item_store: post cancelled by plugin.');
1304 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1306 $r = dbq("INSERT INTO `item` (`"
1307 . implode("`, `", array_keys($arr))
1309 . implode("', '", array_values($arr))
1312 // find the item we just created
1314 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1315 $arr['uri'], // already dbesc'd
1320 $current_post = $r[0]['id'];
1321 logger('item_store: created item ' . $current_post);
1323 // Only check for notifications on start posts
1324 if ($arr['parent-uri'] === $arr['uri']) {
1325 add_thread($r[0]['id']);
1326 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1328 // Send a notification for every new post?
1329 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1330 intval($arr['contact-id']),
1335 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1336 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1337 intval($arr['uid']));
1339 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1340 intval($current_post),
1346 require_once('include/enotify.php');
1348 'type' => NOTIFY_SHARE,
1349 'notify_flags' => $u[0]['notify-flags'],
1350 'language' => $u[0]['language'],
1351 'to_name' => $u[0]['username'],
1352 'to_email' => $u[0]['email'],
1353 'uid' => $u[0]['uid'],
1355 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1356 'source_name' => $item[0]['author-name'],
1357 'source_link' => $item[0]['author-link'],
1358 'source_photo' => $item[0]['author-avatar'],
1359 'verb' => ACTIVITY_TAG,
1362 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1367 logger('item_store: could not locate created item');
1371 logger('item_store: duplicated post occurred. Removing duplicates.');
1372 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1374 intval($arr['uid']),
1375 intval($current_post)
1379 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1380 $parent_id = $current_post;
1382 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1385 $private = $arr['private'];
1387 // Set parent id - and also make sure to inherit the parent's ACLs.
1389 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1390 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1397 intval($parent_deleted),
1398 intval($current_post)
1401 // Complete ostatus threads
1402 if ($ostatus_conversation)
1403 complete_conversation($current_post, $ostatus_conversation);
1405 $arr['id'] = $current_post;
1406 $arr['parent'] = $parent_id;
1407 $arr['allow_cid'] = $allow_cid;
1408 $arr['allow_gid'] = $allow_gid;
1409 $arr['deny_cid'] = $deny_cid;
1410 $arr['deny_gid'] = $deny_gid;
1411 $arr['private'] = $private;
1412 $arr['deleted'] = $parent_deleted;
1414 // update the commented timestamp on the parent
1416 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1417 dbesc(datetime_convert()),
1418 dbesc(datetime_convert()),
1421 update_thread($parent_id);
1424 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1425 intval($current_post),
1426 dbesc($dsprsig->signed_text),
1427 dbesc($dsprsig->signature),
1428 dbesc($dsprsig->signer)
1434 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1437 if($arr['last-child']) {
1438 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1440 intval($arr['uid']),
1441 intval($current_post)
1445 $deleted = tag_deliver($arr['uid'],$current_post);
1447 // current post can be deleted if is for a communuty page and no mention are
1451 // Store the fresh generated item into the cache
1452 $cachefile = get_cachefile(urlencode($arr["guid"])."-".hash("md5", $arr['body']));
1454 if (($cachefile != '') AND !file_exists($cachefile)) {
1455 $s = prepare_text($arr['body']);
1457 $stamp1 = microtime(true);
1458 file_put_contents($cachefile, $s);
1459 $a->save_timestamp($stamp1, "file");
1460 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1463 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1464 if (count($r) == 1) {
1465 call_hooks('post_remote_end', $r[0]);
1467 logger('item_store: new item not found in DB, id ' . $current_post);
1471 create_tags_from_item($current_post);
1472 create_files_from_item($current_post);
1475 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1477 return $current_post;
1480 function get_item_guid($id) {
1481 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1483 return($r[0]["guid"]);
1488 function get_item_id($guid, $uid = 0) {
1494 $uid == local_user();
1496 // Does the given user have this item?
1498 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1499 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1500 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1503 $nick = $r[0]["nickname"];
1507 // Or is it anywhere on the server?
1509 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1510 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1511 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1512 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1513 AND `item`.`private` = 0 AND `item`.`wall` = 1
1514 AND `item`.`guid` = '%s'", dbesc($guid));
1517 $nick = $r[0]["nickname"];
1520 return(array("nick" => $nick, "id" => $id));
1524 function get_item_contact($item,$contacts) {
1525 if(! count($contacts) || (! is_array($item)))
1527 foreach($contacts as $contact) {
1528 if($contact['id'] == $item['contact-id']) {
1530 break; // NOTREACHED
1537 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1539 * @param int $item_id
1540 * @return bool true if item was deleted, else false
1542 function tag_deliver($uid,$item_id) {
1550 $u = q("select * from user where uid = %d limit 1",
1556 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1557 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1560 $i = q("select * from item where id = %d and uid = %d limit 1",
1569 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1571 // Diaspora uses their own hardwired link URL in @-tags
1572 // instead of the one we supply with webfinger
1574 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1576 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1578 foreach($matches as $mtch) {
1579 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1581 logger('tag_deliver: mention found: ' . $mtch[2]);
1587 if ( ($community_page || $prvgroup) &&
1588 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1589 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1591 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1592 q("DELETE FROM item WHERE id = %d and uid = %d",
1602 // send a notification
1604 // use a local photo if we have one
1606 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1607 intval($u[0]['uid']),
1608 dbesc(normalise_link($item['author-link']))
1610 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1613 require_once('include/enotify.php');
1615 'type' => NOTIFY_TAGSELF,
1616 'notify_flags' => $u[0]['notify-flags'],
1617 'language' => $u[0]['language'],
1618 'to_name' => $u[0]['username'],
1619 'to_email' => $u[0]['email'],
1620 'uid' => $u[0]['uid'],
1622 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1623 'source_name' => $item['author-name'],
1624 'source_link' => $item['author-link'],
1625 'source_photo' => $photo,
1626 'verb' => ACTIVITY_TAG,
1628 'parent' => $item['parent']
1632 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1634 call_hooks('tagged', $arr);
1636 if((! $community_page) && (! $prvgroup))
1640 // tgroup delivery - setup a second delivery chain
1641 // prevent delivery looping - only proceed
1642 // if the message originated elsewhere and is a top-level post
1644 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1647 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1650 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1651 intval($u[0]['uid'])
1656 // also reset all the privacy bits to the forum default permissions
1658 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1660 $forum_mode = (($prvgroup) ? 2 : 1);
1662 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1663 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1664 intval($forum_mode),
1665 dbesc($c[0]['name']),
1666 dbesc($c[0]['url']),
1667 dbesc($c[0]['thumb']),
1669 dbesc($u[0]['allow_cid']),
1670 dbesc($u[0]['allow_gid']),
1671 dbesc($u[0]['deny_cid']),
1672 dbesc($u[0]['deny_gid']),
1675 update_thread($item_id);
1677 proc_run('php','include/notifier.php','tgroup',$item_id);
1683 function tgroup_check($uid,$item) {
1689 // check that the message originated elsewhere and is a top-level post
1691 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1695 $u = q("select * from user where uid = %d limit 1",
1701 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1702 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1705 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1707 // Diaspora uses their own hardwired link URL in @-tags
1708 // instead of the one we supply with webfinger
1710 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1712 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1714 foreach($matches as $mtch) {
1715 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1717 logger('tgroup_check: mention found: ' . $mtch[2]);
1725 if((! $community_page) && (! $prvgroup))
1739 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1743 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1745 if($contact['duplex'] && $contact['dfrn-id'])
1746 $idtosend = '0:' . $orig_id;
1747 if($contact['duplex'] && $contact['issued-id'])
1748 $idtosend = '1:' . $orig_id;
1750 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1752 $rino_enable = get_config('system','rino_encrypt');
1757 $ssl_val = intval(get_config('system','ssl_policy'));
1761 case SSL_POLICY_FULL:
1762 $ssl_policy = 'full';
1764 case SSL_POLICY_SELFSIGN:
1765 $ssl_policy = 'self';
1767 case SSL_POLICY_NONE:
1769 $ssl_policy = 'none';
1773 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1775 logger('dfrn_deliver: ' . $url);
1777 $xml = fetch_url($url);
1779 $curl_stat = $a->get_curl_code();
1781 return(-1); // timed out
1783 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1788 if(strpos($xml,'<?xml') === false) {
1789 logger('dfrn_deliver: no valid XML returned');
1790 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1794 $res = parse_xml_string($xml);
1796 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1797 return (($res->status) ? $res->status : 3);
1799 $postvars = array();
1800 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1801 $challenge = hex2bin((string) $res->challenge);
1802 $perm = (($res->perm) ? $res->perm : null);
1803 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1804 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1805 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1807 if($owner['page-flags'] == PAGE_PRVGROUP)
1810 $final_dfrn_id = '';
1813 if((($perm == 'rw') && (! intval($contact['writable'])))
1814 || (($perm == 'r') && (intval($contact['writable'])))) {
1815 q("update contact set writable = %d where id = %d",
1816 intval(($perm == 'rw') ? 1 : 0),
1817 intval($contact['id'])
1819 $contact['writable'] = (string) 1 - intval($contact['writable']);
1823 if(($contact['duplex'] && strlen($contact['pubkey']))
1824 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1825 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1826 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1827 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1830 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1831 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1834 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1836 if(strpos($final_dfrn_id,':') == 1)
1837 $final_dfrn_id = substr($final_dfrn_id,2);
1839 if($final_dfrn_id != $orig_id) {
1840 logger('dfrn_deliver: wrong dfrn_id.');
1841 // did not decode properly - cannot trust this site
1845 $postvars['dfrn_id'] = $idtosend;
1846 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1848 $postvars['dissolve'] = '1';
1851 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1852 $postvars['data'] = $atom;
1853 $postvars['perm'] = 'rw';
1856 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1857 $postvars['perm'] = 'r';
1860 $postvars['ssl_policy'] = $ssl_policy;
1863 $postvars['page'] = $page;
1865 if($rino && $rino_allowed && (! $dissolve)) {
1866 $key = substr(random_string(),0,16);
1867 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1868 $postvars['data'] = $data;
1869 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1872 if($dfrn_version >= 2.1) {
1873 if(($contact['duplex'] && strlen($contact['pubkey']))
1874 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1875 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1877 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1880 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1884 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1885 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1888 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1892 logger('md5 rawkey ' . md5($postvars['key']));
1894 $postvars['key'] = bin2hex($postvars['key']);
1897 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1899 $xml = post_url($contact['notify'],$postvars);
1901 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1903 $curl_stat = $a->get_curl_code();
1904 if((! $curl_stat) || (! strlen($xml)))
1905 return(-1); // timed out
1907 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1910 if(strpos($xml,'<?xml') === false) {
1911 logger('dfrn_deliver: phase 2: no valid XML returned');
1912 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1916 if($contact['term-date'] != '0000-00-00 00:00:00') {
1917 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1918 require_once('include/Contact.php');
1919 unmark_for_death($contact);
1922 $res = parse_xml_string($xml);
1924 return $res->status;
1929 This function returns true if $update has an edited timestamp newer
1930 than $existing, i.e. $update contains new data which should override
1931 what's already there. If there is no timestamp yet, the update is
1932 assumed to be newer. If the update has no timestamp, the existing
1933 item is assumed to be up-to-date. If the timestamps are equal it
1934 assumes the update has been seen before and should be ignored.
1936 function edited_timestamp_is_newer($existing, $update) {
1937 if (!x($existing,'edited') || !$existing['edited']) {
1940 if (!x($update,'edited') || !$update['edited']) {
1943 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1944 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1945 return (strcmp($existing_edited, $update_edited) < 0);
1950 * consume_feed - process atom feed and update anything/everything we might need to update
1952 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1954 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1955 * It is this person's stuff that is going to be updated.
1956 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1957 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1958 * have a contact record.
1959 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1960 * might not) try and subscribe to it.
1961 * $datedir sorts in reverse order
1962 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1963 * imported prior to its children being seen in the stream unless we are certain
1964 * of how the feed is arranged/ordered.
1965 * With $pass = 1, we only pull parent items out of the stream.
1966 * With $pass = 2, we only pull children (comments/likes).
1968 * So running this twice, first with pass 1 and then with pass 2 will do the right
1969 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1970 * model where comments can have sub-threads. That would require some massive sorting
1971 * to get all the feed items into a mostly linear ordering, and might still require
1975 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1977 require_once('library/simplepie/simplepie.inc');
1978 require_once('include/contact_selectors.php');
1980 if(! strlen($xml)) {
1981 logger('consume_feed: empty input');
1985 $feed = new SimplePie();
1986 $feed->set_raw_data($xml);
1988 $feed->enable_order_by_date(true);
1990 $feed->enable_order_by_date(false);
1994 logger('consume_feed: Error parsing XML: ' . $feed->error());
1996 $permalink = $feed->get_permalink();
1998 // Check at the feed level for updated contact name and/or photo
2002 $photo_timestamp = '';
2006 $hubs = $feed->get_links('hub');
2007 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2010 $hub = implode(',', $hubs);
2012 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2014 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2016 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2017 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2018 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2019 $new_name = $elems['name'][0]['data'];
2021 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2022 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2023 $photo_url = $elems['link'][0]['attribs']['']['href'];
2026 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2027 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2031 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2032 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2033 require_once("include/Photo.php");
2034 $photo_failure = false;
2035 $have_photo = false;
2037 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2038 intval($contact['id']),
2039 intval($contact['uid'])
2042 $resource_id = $r[0]['resource-id'];
2046 $resource_id = photo_new_resource();
2049 $img_str = fetch_url($photo_url,true);
2050 // guess mimetype from headers or filename
2051 $type = guess_image_type($photo_url,true);
2054 $img = new Photo($img_str, $type);
2055 if($img->is_valid()) {
2057 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2058 dbesc($resource_id),
2059 intval($contact['id']),
2060 intval($contact['uid'])
2064 $img->scaleImageSquare(175);
2066 $hash = $resource_id;
2067 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2069 $img->scaleImage(80);
2070 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2072 $img->scaleImage(48);
2073 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2077 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2078 WHERE `uid` = %d AND `id` = %d",
2079 dbesc(datetime_convert()),
2080 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2081 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2082 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2083 intval($contact['uid']),
2084 intval($contact['id'])
2089 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2090 $r = q("select * from contact where uid = %d and id = %d limit 1",
2091 intval($contact['uid']),
2092 intval($contact['id'])
2095 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2096 dbesc(notags(trim($new_name))),
2097 dbesc(datetime_convert()),
2098 intval($contact['uid']),
2099 intval($contact['id'])
2102 // do our best to update the name on content items
2105 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2106 dbesc(notags(trim($new_name))),
2107 dbesc($r[0]['name']),
2108 dbesc($r[0]['url']),
2109 intval($contact['uid'])
2114 if(strlen($birthday)) {
2115 if(substr($birthday,0,4) != $contact['bdyear']) {
2116 logger('consume_feed: updating birthday: ' . $birthday);
2120 * Add new birthday event for this person
2122 * $bdtext is just a readable placeholder in case the event is shared
2123 * with others. We will replace it during presentation to our $importer
2124 * to contain a sparkle link and perhaps a photo.
2128 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2129 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2132 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2133 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2134 intval($contact['uid']),
2135 intval($contact['id']),
2136 dbesc(datetime_convert()),
2137 dbesc(datetime_convert()),
2138 dbesc(datetime_convert('UTC','UTC', $birthday)),
2139 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2148 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2149 dbesc(substr($birthday,0,4)),
2150 intval($contact['uid']),
2151 intval($contact['id'])
2154 // This function is called twice without reloading the contact
2155 // Make sure we only create one event. This is why &$contact
2156 // is a reference var in this function
2158 $contact['bdyear'] = substr($birthday,0,4);
2163 $community_page = 0;
2164 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2166 $community_page = intval($rawtags[0]['data']);
2168 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2169 q("update contact set forum = %d where id = %d",
2170 intval($community_page),
2171 intval($contact['id'])
2173 $contact['forum'] = (string) $community_page;
2177 // process any deleted entries
2179 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2180 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2181 foreach($del_entries as $dentry) {
2183 if(isset($dentry['attribs']['']['ref'])) {
2184 $uri = $dentry['attribs']['']['ref'];
2186 if(isset($dentry['attribs']['']['when'])) {
2187 $when = $dentry['attribs']['']['when'];
2188 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2191 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2193 if($deleted && is_array($contact)) {
2194 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2195 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2197 intval($importer['uid']),
2198 intval($contact['id'])
2203 if(! $item['deleted'])
2204 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2206 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2207 $xo = parse_xml_string($item['object'],false);
2208 $xt = parse_xml_string($item['target'],false);
2209 if($xt->type === ACTIVITY_OBJ_NOTE) {
2210 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2212 intval($importer['importer_uid'])
2216 // For tags, the owner cannot remove the tag on the author's copy of the post.
2218 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2219 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2220 $author_copy = (($item['origin']) ? true : false);
2222 if($owner_remove && $author_copy)
2224 if($author_remove || $owner_remove) {
2225 $tags = explode(',',$i[0]['tag']);
2228 foreach($tags as $tag)
2229 if(trim($tag) !== trim($xo->body))
2230 $newtags[] = trim($tag);
2232 q("update item set tag = '%s' where id = %d",
2233 dbesc(implode(',',$newtags)),
2236 create_tags_from_item($i[0]['id']);
2242 if($item['uri'] == $item['parent-uri']) {
2243 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2244 `body` = '', `title` = ''
2245 WHERE `parent-uri` = '%s' AND `uid` = %d",
2247 dbesc(datetime_convert()),
2248 dbesc($item['uri']),
2249 intval($importer['uid'])
2251 create_tags_from_itemuri($item['uri'], $importer['uid']);
2252 create_files_from_itemuri($item['uri'], $importer['uid']);
2253 update_thread_uri($item['uri'], $importer['uid']);
2256 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2257 `body` = '', `title` = ''
2258 WHERE `uri` = '%s' AND `uid` = %d",
2260 dbesc(datetime_convert()),
2262 intval($importer['uid'])
2264 create_tags_from_itemuri($uri, $importer['uid']);
2265 create_files_from_itemuri($uri, $importer['uid']);
2266 if($item['last-child']) {
2267 // ensure that last-child is set in case the comment that had it just got wiped.
2268 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2269 dbesc(datetime_convert()),
2270 dbesc($item['parent-uri']),
2271 intval($item['uid'])
2273 // who is the last child now?
2274 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2275 ORDER BY `created` DESC LIMIT 1",
2276 dbesc($item['parent-uri']),
2277 intval($importer['uid'])
2280 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2291 // Now process the feed
2293 if($feed->get_item_quantity()) {
2295 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2297 // in inverse date order
2299 $items = array_reverse($feed->get_items());
2301 $items = $feed->get_items();
2304 foreach($items as $item) {
2307 $item_id = $item->get_id();
2308 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2309 if(isset($rawthread[0]['attribs']['']['ref'])) {
2311 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2314 if(($is_reply) && is_array($contact)) {
2319 // not allowed to post
2321 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2325 // Have we seen it? If not, import it.
2327 $item_id = $item->get_id();
2328 $datarray = get_atom_elements($feed, $item, $contact);
2330 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2331 $datarray['author-name'] = $contact['name'];
2332 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2333 $datarray['author-link'] = $contact['url'];
2334 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2335 $datarray['author-avatar'] = $contact['thumb'];
2337 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2338 logger('consume_feed: no author information! ' . print_r($datarray,true));
2342 $force_parent = false;
2343 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2344 if($contact['network'] === NETWORK_OSTATUS)
2345 $force_parent = true;
2346 if(strlen($datarray['title']))
2347 unset($datarray['title']);
2348 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2349 dbesc(datetime_convert()),
2351 intval($importer['uid'])
2353 $datarray['last-child'] = 1;
2354 update_thread_uri($parent_uri, $importer['uid']);
2358 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2360 intval($importer['uid'])
2363 // Update content if 'updated' changes
2366 if (edited_timestamp_is_newer($r[0], $datarray)) {
2368 // do not accept (ignore) an earlier edit than one we currently have.
2369 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2372 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2373 dbesc($datarray['title']),
2374 dbesc($datarray['body']),
2375 dbesc($datarray['tag']),
2376 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2377 dbesc(datetime_convert()),
2379 intval($importer['uid'])
2381 create_tags_from_itemuri($item_id, $importer['uid']);
2382 update_thread_uri($item_id, $importer['uid']);
2385 // update last-child if it changes
2387 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2388 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2389 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2390 dbesc(datetime_convert()),
2392 intval($importer['uid'])
2394 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2395 intval($allow[0]['data']),
2396 dbesc(datetime_convert()),
2398 intval($importer['uid'])
2400 update_thread_uri($item_id, $importer['uid']);
2406 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2407 // one way feed - no remote comment ability
2408 $datarray['last-child'] = 0;
2410 $datarray['parent-uri'] = $parent_uri;
2411 $datarray['uid'] = $importer['uid'];
2412 $datarray['contact-id'] = $contact['id'];
2413 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2414 $datarray['type'] = 'activity';
2415 $datarray['gravity'] = GRAVITY_LIKE;
2416 // only one like or dislike per person
2417 // splitted into two queries for performance issues
2418 $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",
2419 intval($datarray['uid']),
2420 intval($datarray['contact-id']),
2421 dbesc($datarray['verb']),
2427 $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",
2428 intval($datarray['uid']),
2429 intval($datarray['contact-id']),
2430 dbesc($datarray['verb']),
2437 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2438 $xo = parse_xml_string($datarray['object'],false);
2439 $xt = parse_xml_string($datarray['target'],false);
2441 if($xt->type == ACTIVITY_OBJ_NOTE) {
2442 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2444 intval($importer['importer_uid'])
2449 // extract tag, if not duplicate, add to parent item
2450 if($xo->id && $xo->content) {
2451 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2452 if(! (stristr($r[0]['tag'],$newtag))) {
2453 q("UPDATE item SET tag = '%s' WHERE id = %d",
2454 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2457 create_tags_from_item($r[0]['id']);
2463 $r = item_store($datarray,$force_parent);
2469 // Head post of a conversation. Have we seen it? If not, import it.
2471 $item_id = $item->get_id();
2473 $datarray = get_atom_elements($feed, $item, $contact);
2475 if(is_array($contact)) {
2476 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2477 $datarray['author-name'] = $contact['name'];
2478 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2479 $datarray['author-link'] = $contact['url'];
2480 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2481 $datarray['author-avatar'] = $contact['thumb'];
2484 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2485 logger('consume_feed: no author information! ' . print_r($datarray,true));
2489 // special handling for events
2491 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2492 $ev = bbtoevent($datarray['body']);
2493 if(x($ev,'desc') && x($ev,'start')) {
2494 $ev['uid'] = $importer['uid'];
2495 $ev['uri'] = $item_id;
2496 $ev['edited'] = $datarray['edited'];
2497 $ev['private'] = $datarray['private'];
2499 if(is_array($contact))
2500 $ev['cid'] = $contact['id'];
2501 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2503 intval($importer['uid'])
2506 $ev['id'] = $r[0]['id'];
2507 $xyz = event_store($ev);
2512 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2513 if(strlen($datarray['title']))
2514 unset($datarray['title']);
2515 $datarray['last-child'] = 1;
2519 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2521 intval($importer['uid'])
2524 // Update content if 'updated' changes
2527 if (edited_timestamp_is_newer($r[0], $datarray)) {
2529 // do not accept (ignore) an earlier edit than one we currently have.
2530 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2533 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2534 dbesc($datarray['title']),
2535 dbesc($datarray['body']),
2536 dbesc($datarray['tag']),
2537 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2538 dbesc(datetime_convert()),
2540 intval($importer['uid'])
2542 create_tags_from_itemuri($item_id, $importer['uid']);
2543 update_thread_uri($item_id, $importer['uid']);
2546 // update last-child if it changes
2548 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2549 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2550 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2551 intval($allow[0]['data']),
2552 dbesc(datetime_convert()),
2554 intval($importer['uid'])
2556 update_thread_uri($item_id, $importer['uid']);
2561 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2562 logger('consume-feed: New follower');
2563 new_follower($importer,$contact,$datarray,$item);
2566 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2567 lose_follower($importer,$contact,$datarray,$item);
2571 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2572 logger('consume-feed: New friend request');
2573 new_follower($importer,$contact,$datarray,$item,true);
2576 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2577 lose_sharer($importer,$contact,$datarray,$item);
2582 if(! is_array($contact))
2586 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2587 // one way feed - no remote comment ability
2588 $datarray['last-child'] = 0;
2590 if($contact['network'] === NETWORK_FEED)
2591 $datarray['private'] = 2;
2593 $datarray['parent-uri'] = $item_id;
2594 $datarray['uid'] = $importer['uid'];
2595 $datarray['contact-id'] = $contact['id'];
2597 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2598 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2599 // but otherwise there's a possible data mixup on the sender's system.
2600 // the tgroup delivery code called from item_store will correct it if it's a forum,
2601 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2602 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2603 $datarray['owner-name'] = $contact['name'];
2604 $datarray['owner-link'] = $contact['url'];
2605 $datarray['owner-avatar'] = $contact['thumb'];
2608 // We've allowed "followers" to reach this point so we can decide if they are
2609 // posting an @-tag delivery, which followers are allowed to do for certain
2610 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2612 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2615 // This is my contact on another system, but it's really me.
2616 // Turn this into a wall post.
2617 $notify = item_is_remote_self($contact, $datarray);
2619 $r = item_store($datarray, false, $notify);
2620 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2628 function item_is_remote_self($contact, &$datarray) {
2631 if (!$contact['remote_self'])
2634 // Prevent the forwarding of posts that are forwarded
2635 if ($datarray["extid"] == NETWORK_DFRN)
2638 // Prevent to forward already forwarded posts
2639 if ($datarray["app"] == $a->get_hostname())
2642 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2645 $datarray2 = $datarray;
2646 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2647 if ($contact['remote_self'] == 2) {
2648 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
2649 intval($contact['uid']));
2651 $datarray['contact-id'] = $r[0]["id"];
2653 $datarray['owner-name'] = $r[0]["name"];
2654 $datarray['owner-link'] = $r[0]["url"];
2655 $datarray['owner-avatar'] = $r[0]["avatar"];
2657 $datarray['author-name'] = $datarray['owner-name'];
2658 $datarray['author-link'] = $datarray['owner-link'];
2659 $datarray['author-avatar'] = $datarray['owner-avatar'];
2662 if ($contact['network'] != NETWORK_FEED) {
2663 $datarray["guid"] = get_guid(32);
2664 unset($datarray["plink"]);
2665 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2666 $datarray["parent-uri"] = $datarray["uri"];
2667 $datarray["extid"] = $contact['network'];
2668 $urlpart = parse_url($datarray2['author-link']);
2669 $datarray["app"] = $urlpart["host"];
2671 $datarray['private'] = 0;
2674 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2675 // $datarray["app"] = network_to_name($contact['network']);
2677 if ($contact['network'] != NETWORK_FEED) {
2678 // Store the original post
2679 $r = item_store($datarray2, false, false);
2680 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2682 $datarray["app"] = "Feed";
2687 function local_delivery($importer,$data) {
2690 logger(__function__, LOGGER_TRACE);
2692 if($importer['readonly']) {
2693 // We aren't receiving stuff from this person. But we will quietly ignore them
2694 // rather than a blatant "go away" message.
2695 logger('local_delivery: ignoring');
2700 // Consume notification feed. This may differ from consuming a public feed in several ways
2701 // - might contain email or friend suggestions
2702 // - might contain remote followup to our message
2703 // - in which case we need to accept it and then notify other conversants
2704 // - we may need to send various email notifications
2706 $feed = new SimplePie();
2707 $feed->set_raw_data($data);
2708 $feed->enable_order_by_date(false);
2713 logger('local_delivery: Error parsing XML: ' . $feed->error());
2716 // Check at the feed level for updated contact name and/or photo
2720 $photo_timestamp = '';
2724 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2726 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2728 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2731 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2732 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2733 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2734 $new_name = $elems['name'][0]['data'];
2736 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2737 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2738 $photo_url = $elems['link'][0]['attribs']['']['href'];
2742 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2743 logger('local_delivery: Updating photo for ' . $importer['name']);
2744 require_once("include/Photo.php");
2745 $photo_failure = false;
2746 $have_photo = false;
2748 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2749 intval($importer['id']),
2750 intval($importer['importer_uid'])
2753 $resource_id = $r[0]['resource-id'];
2757 $resource_id = photo_new_resource();
2760 $img_str = fetch_url($photo_url,true);
2761 // guess mimetype from headers or filename
2762 $type = guess_image_type($photo_url,true);
2765 $img = new Photo($img_str, $type);
2766 if($img->is_valid()) {
2768 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2769 dbesc($resource_id),
2770 intval($importer['id']),
2771 intval($importer['importer_uid'])
2775 $img->scaleImageSquare(175);
2777 $hash = $resource_id;
2778 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2780 $img->scaleImage(80);
2781 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2783 $img->scaleImage(48);
2784 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2788 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2789 WHERE `uid` = %d AND `id` = %d",
2790 dbesc(datetime_convert()),
2791 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2792 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2793 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2794 intval($importer['importer_uid']),
2795 intval($importer['id'])
2800 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2801 $r = q("select * from contact where uid = %d and id = %d limit 1",
2802 intval($importer['importer_uid']),
2803 intval($importer['id'])
2806 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2807 dbesc(notags(trim($new_name))),
2808 dbesc(datetime_convert()),
2809 intval($importer['importer_uid']),
2810 intval($importer['id'])
2813 // do our best to update the name on content items
2816 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2817 dbesc(notags(trim($new_name))),
2818 dbesc($r[0]['name']),
2819 dbesc($r[0]['url']),
2820 intval($importer['importer_uid'])
2827 // Currently unsupported - needs a lot of work
2828 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2829 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2830 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2832 $newloc['uid'] = $importer['importer_uid'];
2833 $newloc['cid'] = $importer['id'];
2834 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2835 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2836 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2837 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2838 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2839 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2840 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2841 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2842 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2843 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2844 /** relocated user must have original key pair */
2845 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2846 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2848 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2851 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2852 intval($importer['id']),
2853 intval($importer['importer_uid']));
2858 $x = q("UPDATE contact SET
2868 `site-pubkey` = '%s'
2869 WHERE id=%d AND uid=%d;",
2870 dbesc($newloc['name']),
2871 dbesc($newloc['photo']),
2872 dbesc($newloc['thumb']),
2873 dbesc($newloc['micro']),
2874 dbesc($newloc['url']),
2875 dbesc($newloc['request']),
2876 dbesc($newloc['confirm']),
2877 dbesc($newloc['notify']),
2878 dbesc($newloc['poll']),
2879 dbesc($newloc['sitepubkey']),
2880 intval($importer['id']),
2881 intval($importer['importer_uid']));
2887 'owner-link' => array($old['url'], $newloc['url']),
2888 'author-link' => array($old['url'], $newloc['url']),
2889 'owner-avatar' => array($old['photo'], $newloc['photo']),
2890 'author-avatar' => array($old['photo'], $newloc['photo']),
2892 foreach ($fields as $n=>$f){
2893 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2896 intval($importer['importer_uid']));
2902 // merge with current record, current contents have priority
2903 // update record, set url-updated
2904 // update profile photos
2910 // handle friend suggestion notification
2912 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2913 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2914 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2916 $fsugg['uid'] = $importer['importer_uid'];
2917 $fsugg['cid'] = $importer['id'];
2918 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2919 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2920 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2921 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2922 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2924 // Does our member already have a friend matching this description?
2926 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2927 dbesc($fsugg['name']),
2928 dbesc(normalise_link($fsugg['url'])),
2929 intval($fsugg['uid'])
2934 // Do we already have an fcontact record for this person?
2937 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2938 dbesc($fsugg['url']),
2939 dbesc($fsugg['name']),
2940 dbesc($fsugg['request'])
2945 // OK, we do. Do we already have an introduction for this person ?
2946 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2947 intval($fsugg['uid']),
2954 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2955 dbesc($fsugg['name']),
2956 dbesc($fsugg['url']),
2957 dbesc($fsugg['photo']),
2958 dbesc($fsugg['request'])
2960 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2961 dbesc($fsugg['url']),
2962 dbesc($fsugg['name']),
2963 dbesc($fsugg['request'])
2968 // database record did not get created. Quietly give up.
2973 $hash = random_string();
2975 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2976 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2977 intval($fsugg['uid']),
2979 intval($fsugg['cid']),
2980 dbesc($fsugg['body']),
2982 dbesc(datetime_convert()),
2987 'type' => NOTIFY_SUGGEST,
2988 'notify_flags' => $importer['notify-flags'],
2989 'language' => $importer['language'],
2990 'to_name' => $importer['username'],
2991 'to_email' => $importer['email'],
2992 'uid' => $importer['importer_uid'],
2994 'link' => $a->get_baseurl() . '/notifications/intros',
2995 'source_name' => $importer['name'],
2996 'source_link' => $importer['url'],
2997 'source_photo' => $importer['photo'],
2998 'verb' => ACTIVITY_REQ_FRIEND,
3007 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3008 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3010 logger('local_delivery: private message received');
3013 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3016 $msg['uid'] = $importer['importer_uid'];
3017 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3018 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3019 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3020 $msg['contact-id'] = $importer['id'];
3021 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3022 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3024 $msg['replied'] = 0;
3025 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3026 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3027 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3031 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3032 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3034 // send notifications.
3036 require_once('include/enotify.php');
3038 $notif_params = array(
3039 'type' => NOTIFY_MAIL,
3040 'notify_flags' => $importer['notify-flags'],
3041 'language' => $importer['language'],
3042 'to_name' => $importer['username'],
3043 'to_email' => $importer['email'],
3044 'uid' => $importer['importer_uid'],
3046 'source_name' => $msg['from-name'],
3047 'source_link' => $importer['url'],
3048 'source_photo' => $importer['thumb'],
3049 'verb' => ACTIVITY_POST,
3053 notification($notif_params);
3059 $community_page = 0;
3060 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3062 $community_page = intval($rawtags[0]['data']);
3064 if(intval($importer['forum']) != $community_page) {
3065 q("update contact set forum = %d where id = %d",
3066 intval($community_page),
3067 intval($importer['id'])
3069 $importer['forum'] = (string) $community_page;
3072 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3074 // process any deleted entries
3076 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3077 if(is_array($del_entries) && count($del_entries)) {
3078 foreach($del_entries as $dentry) {
3080 if(isset($dentry['attribs']['']['ref'])) {
3081 $uri = $dentry['attribs']['']['ref'];
3083 if(isset($dentry['attribs']['']['when'])) {
3084 $when = $dentry['attribs']['']['when'];
3085 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3088 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3092 // check for relayed deletes to our conversation
3095 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3097 intval($importer['importer_uid'])
3100 $parent_uri = $r[0]['parent-uri'];
3101 if($r[0]['id'] != $r[0]['parent'])
3108 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3111 logger('local_delivery: possible community delete');
3114 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3116 // was the top-level post for this reply written by somebody on this site?
3117 // Specifically, the recipient?
3119 $is_a_remote_delete = false;
3121 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3122 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3123 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3124 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3125 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3126 AND `item`.`uid` = %d
3132 intval($importer['importer_uid'])
3135 $is_a_remote_delete = true;
3137 // Does this have the characteristics of a community or private group comment?
3138 // If it's a reply to a wall post on a community/prvgroup page it's a
3139 // valid community comment. Also forum_mode makes it valid for sure.
3140 // If neither, it's not.
3142 if($is_a_remote_delete && $community) {
3143 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3144 $is_a_remote_delete = false;
3145 logger('local_delivery: not a community delete');
3149 if($is_a_remote_delete) {
3150 logger('local_delivery: received remote delete');
3154 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3155 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3157 intval($importer['importer_uid']),
3158 intval($importer['id'])
3164 if($item['deleted'])
3167 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3169 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3170 $xo = parse_xml_string($item['object'],false);
3171 $xt = parse_xml_string($item['target'],false);
3173 if($xt->type === ACTIVITY_OBJ_NOTE) {
3174 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3176 intval($importer['importer_uid'])
3180 // For tags, the owner cannot remove the tag on the author's copy of the post.
3182 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3183 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3184 $author_copy = (($item['origin']) ? true : false);
3186 if($owner_remove && $author_copy)
3188 if($author_remove || $owner_remove) {
3189 $tags = explode(',',$i[0]['tag']);
3192 foreach($tags as $tag)
3193 if(trim($tag) !== trim($xo->body))
3194 $newtags[] = trim($tag);
3196 q("update item set tag = '%s' where id = %d",
3197 dbesc(implode(',',$newtags)),
3200 create_tags_from_item($i[0]['id']);
3206 if($item['uri'] == $item['parent-uri']) {
3207 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3208 `body` = '', `title` = ''
3209 WHERE `parent-uri` = '%s' AND `uid` = %d",
3211 dbesc(datetime_convert()),
3212 dbesc($item['uri']),
3213 intval($importer['importer_uid'])
3215 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3216 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3217 update_thread_uri($item['uri'], $importer['importer_uid']);
3220 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3221 `body` = '', `title` = ''
3222 WHERE `uri` = '%s' AND `uid` = %d",
3224 dbesc(datetime_convert()),
3226 intval($importer['importer_uid'])
3228 create_tags_from_itemuri($uri, $importer['importer_uid']);
3229 create_files_from_itemuri($uri, $importer['importer_uid']);
3230 update_thread_uri($uri, $importer['importer_uid']);
3231 if($item['last-child']) {
3232 // ensure that last-child is set in case the comment that had it just got wiped.
3233 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3234 dbesc(datetime_convert()),
3235 dbesc($item['parent-uri']),
3236 intval($item['uid'])
3238 // who is the last child now?
3239 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3240 ORDER BY `created` DESC LIMIT 1",
3241 dbesc($item['parent-uri']),
3242 intval($importer['importer_uid'])
3245 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3250 // if this is a relayed delete, propagate it to other recipients
3252 if($is_a_remote_delete)
3253 proc_run('php',"include/notifier.php","drop",$item['id']);
3261 foreach($feed->get_items() as $item) {
3264 $item_id = $item->get_id();
3265 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3266 if(isset($rawthread[0]['attribs']['']['ref'])) {
3268 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3274 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3277 logger('local_delivery: possible community reply');
3280 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3282 // was the top-level post for this reply written by somebody on this site?
3283 // Specifically, the recipient?
3285 $is_a_remote_comment = false;
3286 $top_uri = $parent_uri;
3288 $r = q("select `item`.`parent-uri` from `item`
3289 WHERE `item`.`uri` = '%s'
3293 if($r && count($r)) {
3294 $top_uri = $r[0]['parent-uri'];
3296 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3297 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3298 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3299 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3300 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3301 AND `item`.`uid` = %d
3307 intval($importer['importer_uid'])
3310 $is_a_remote_comment = true;
3313 // Does this have the characteristics of a community or private group comment?
3314 // If it's a reply to a wall post on a community/prvgroup page it's a
3315 // valid community comment. Also forum_mode makes it valid for sure.
3316 // If neither, it's not.
3318 if($is_a_remote_comment && $community) {
3319 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3320 $is_a_remote_comment = false;
3321 logger('local_delivery: not a community reply');
3325 if($is_a_remote_comment) {
3326 logger('local_delivery: received remote comment');
3328 // remote reply to our post. Import and then notify everybody else.
3330 $datarray = get_atom_elements($feed, $item);
3332 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3334 intval($importer['importer_uid'])
3337 // Update content if 'updated' changes
3341 if (edited_timestamp_is_newer($r[0], $datarray)) {
3343 // do not accept (ignore) an earlier edit than one we currently have.
3344 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3347 logger('received updated comment' , LOGGER_DEBUG);
3348 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3349 dbesc($datarray['title']),
3350 dbesc($datarray['body']),
3351 dbesc($datarray['tag']),
3352 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3353 dbesc(datetime_convert()),
3355 intval($importer['importer_uid'])
3357 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3359 proc_run('php',"include/notifier.php","comment-import",$iid);
3368 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3369 intval($importer['importer_uid'])
3373 $datarray['type'] = 'remote-comment';
3374 $datarray['wall'] = 1;
3375 $datarray['parent-uri'] = $parent_uri;
3376 $datarray['uid'] = $importer['importer_uid'];
3377 $datarray['owner-name'] = $own[0]['name'];
3378 $datarray['owner-link'] = $own[0]['url'];
3379 $datarray['owner-avatar'] = $own[0]['thumb'];
3380 $datarray['contact-id'] = $importer['id'];
3382 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3384 $datarray['type'] = 'activity';
3385 $datarray['gravity'] = GRAVITY_LIKE;
3386 $datarray['last-child'] = 0;
3387 // only one like or dislike per person
3388 // splitted into two queries for performance issues
3389 $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",
3390 intval($datarray['uid']),
3391 intval($datarray['contact-id']),
3392 dbesc($datarray['verb']),
3393 dbesc($datarray['parent-uri'])
3399 $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",
3400 intval($datarray['uid']),
3401 intval($datarray['contact-id']),
3402 dbesc($datarray['verb']),
3403 dbesc($datarray['parent-uri'])
3410 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3412 $xo = parse_xml_string($datarray['object'],false);
3413 $xt = parse_xml_string($datarray['target'],false);
3415 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3417 // fetch the parent item
3419 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3421 intval($importer['importer_uid'])
3426 // extract tag, if not duplicate, and this user allows tags, add to parent item
3428 if($xo->id && $xo->content) {
3429 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3430 if(! (stristr($tagp[0]['tag'],$newtag))) {
3431 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3432 intval($importer['importer_uid'])
3434 if(count($i) && ! intval($i[0]['blocktags'])) {
3435 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3436 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3437 intval($tagp[0]['id']),
3438 dbesc(datetime_convert()),
3439 dbesc(datetime_convert())
3441 create_tags_from_item($tagp[0]['id']);
3449 $posted_id = item_store($datarray);
3453 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3455 intval($importer['importer_uid'])
3458 $parent = $r[0]['parent'];
3459 $parent_uri = $r[0]['parent-uri'];
3463 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3464 dbesc(datetime_convert()),
3465 intval($importer['importer_uid']),
3466 intval($r[0]['parent'])
3469 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3470 dbesc(datetime_convert()),
3471 intval($importer['importer_uid']),
3476 if($posted_id && $parent) {
3478 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3480 if((! $is_like) && (! $importer['self'])) {
3482 require_once('include/enotify.php');
3485 'type' => NOTIFY_COMMENT,
3486 'notify_flags' => $importer['notify-flags'],
3487 'language' => $importer['language'],
3488 'to_name' => $importer['username'],
3489 'to_email' => $importer['email'],
3490 'uid' => $importer['importer_uid'],
3491 'item' => $datarray,
3492 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3493 'source_name' => stripslashes($datarray['author-name']),
3494 'source_link' => $datarray['author-link'],
3495 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3496 ? $importer['thumb'] : $datarray['author-avatar']),
3497 'verb' => ACTIVITY_POST,
3499 'parent' => $parent,
3500 'parent_uri' => $parent_uri,
3512 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3514 $item_id = $item->get_id();
3515 $datarray = get_atom_elements($feed,$item);
3517 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3520 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3522 intval($importer['importer_uid'])
3525 // Update content if 'updated' changes
3528 if (edited_timestamp_is_newer($r[0], $datarray)) {
3530 // do not accept (ignore) an earlier edit than one we currently have.
3531 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3534 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3535 dbesc($datarray['title']),
3536 dbesc($datarray['body']),
3537 dbesc($datarray['tag']),
3538 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3539 dbesc(datetime_convert()),
3541 intval($importer['importer_uid'])
3543 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3546 // update last-child if it changes
3548 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3549 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3550 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3551 dbesc(datetime_convert()),
3553 intval($importer['importer_uid'])
3555 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3556 intval($allow[0]['data']),
3557 dbesc(datetime_convert()),
3559 intval($importer['importer_uid'])
3565 $datarray['parent-uri'] = $parent_uri;
3566 $datarray['uid'] = $importer['importer_uid'];
3567 $datarray['contact-id'] = $importer['id'];
3568 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3569 $datarray['type'] = 'activity';
3570 $datarray['gravity'] = GRAVITY_LIKE;
3571 // only one like or dislike per person
3572 // splitted into two queries for performance issues
3573 $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",
3574 intval($datarray['uid']),
3575 intval($datarray['contact-id']),
3576 dbesc($datarray['verb']),
3582 $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",
3583 intval($datarray['uid']),
3584 intval($datarray['contact-id']),
3585 dbesc($datarray['verb']),
3593 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3595 $xo = parse_xml_string($datarray['object'],false);
3596 $xt = parse_xml_string($datarray['target'],false);
3598 if($xt->type == ACTIVITY_OBJ_NOTE) {
3599 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3601 intval($importer['importer_uid'])
3606 // extract tag, if not duplicate, add to parent item
3608 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3609 q("UPDATE item SET tag = '%s' WHERE id = %d",
3610 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3613 create_tags_from_item($r[0]['id']);
3619 $posted_id = item_store($datarray);
3621 // find out if our user is involved in this conversation and wants to be notified.
3623 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3625 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3627 intval($importer['importer_uid'])
3630 if(count($myconv)) {
3631 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3633 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3634 if(! link_compare($datarray['author-link'],$importer_url)) {
3637 foreach($myconv as $conv) {
3639 // now if we find a match, it means we're in this conversation
3641 if(! link_compare($conv['author-link'],$importer_url))
3644 require_once('include/enotify.php');
3646 $conv_parent = $conv['parent'];
3649 'type' => NOTIFY_COMMENT,
3650 'notify_flags' => $importer['notify-flags'],
3651 'language' => $importer['language'],
3652 'to_name' => $importer['username'],
3653 'to_email' => $importer['email'],
3654 'uid' => $importer['importer_uid'],
3655 'item' => $datarray,
3656 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3657 'source_name' => stripslashes($datarray['author-name']),
3658 'source_link' => $datarray['author-link'],
3659 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3660 ? $importer['thumb'] : $datarray['author-avatar']),
3661 'verb' => ACTIVITY_POST,
3663 'parent' => $conv_parent,
3664 'parent_uri' => $parent_uri
3668 // only send one notification
3680 // Head post of a conversation. Have we seen it? If not, import it.
3683 $item_id = $item->get_id();
3684 $datarray = get_atom_elements($feed,$item);
3686 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3687 $ev = bbtoevent($datarray['body']);
3688 if(x($ev,'desc') && x($ev,'start')) {
3689 $ev['cid'] = $importer['id'];
3690 $ev['uid'] = $importer['uid'];
3691 $ev['uri'] = $item_id;
3692 $ev['edited'] = $datarray['edited'];
3693 $ev['private'] = $datarray['private'];
3695 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3697 intval($importer['uid'])
3700 $ev['id'] = $r[0]['id'];
3701 $xyz = event_store($ev);
3706 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3708 intval($importer['importer_uid'])
3711 // Update content if 'updated' changes
3714 if (edited_timestamp_is_newer($r[0], $datarray)) {
3716 // do not accept (ignore) an earlier edit than one we currently have.
3717 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3720 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3721 dbesc($datarray['title']),
3722 dbesc($datarray['body']),
3723 dbesc($datarray['tag']),
3724 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3725 dbesc(datetime_convert()),
3727 intval($importer['importer_uid'])
3729 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3730 update_thread_uri($item_id, $importer['importer_uid']);
3733 // update last-child if it changes
3735 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3736 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3737 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3738 intval($allow[0]['data']),
3739 dbesc(datetime_convert()),
3741 intval($importer['importer_uid'])
3747 $datarray['parent-uri'] = $item_id;
3748 $datarray['uid'] = $importer['importer_uid'];
3749 $datarray['contact-id'] = $importer['id'];
3752 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3753 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3754 // but otherwise there's a possible data mixup on the sender's system.
3755 // the tgroup delivery code called from item_store will correct it if it's a forum,
3756 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3757 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3758 $datarray['owner-name'] = $importer['senderName'];
3759 $datarray['owner-link'] = $importer['url'];
3760 $datarray['owner-avatar'] = $importer['thumb'];
3763 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3766 // This is my contact on another system, but it's really me.
3767 // Turn this into a wall post.
3768 $notify = item_is_remote_self($importer, $datarray);
3770 $posted_id = item_store($datarray, false, $notify);
3772 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3773 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3776 $xo = parse_xml_string($datarray['object'],false);
3778 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3780 // somebody was poked/prodded. Was it me?
3782 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3784 foreach($links->link as $l) {
3785 $atts = $l->attributes();
3786 switch($atts['rel']) {
3788 $Blink = $atts['href'];
3794 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3796 // send a notification
3797 require_once('include/enotify.php');
3800 'type' => NOTIFY_POKE,
3801 'notify_flags' => $importer['notify-flags'],
3802 'language' => $importer['language'],
3803 'to_name' => $importer['username'],
3804 'to_email' => $importer['email'],
3805 'uid' => $importer['importer_uid'],
3806 'item' => $datarray,
3807 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3808 'source_name' => stripslashes($datarray['author-name']),
3809 'source_link' => $datarray['author-link'],
3810 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3811 ? $importer['thumb'] : $datarray['author-avatar']),
3812 'verb' => $datarray['verb'],
3813 'otype' => 'person',
3814 'activity' => $verb,
3831 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3832 $url = notags(trim($datarray['author-link']));
3833 $name = notags(trim($datarray['author-name']));
3834 $photo = notags(trim($datarray['author-avatar']));
3836 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3837 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3838 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3840 if(is_array($contact)) {
3841 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3842 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3843 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3844 intval(CONTACT_IS_FRIEND),
3845 intval($contact['id']),
3846 intval($importer['uid'])
3849 // send email notification to owner?
3853 // create contact record
3855 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3856 `blocked`, `readonly`, `pending`, `writable` )
3857 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3858 intval($importer['uid']),
3859 dbesc(datetime_convert()),
3861 dbesc(normalise_link($url)),
3865 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3866 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3868 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3869 intval($importer['uid']),
3873 $contact_record = $r[0];
3875 // create notification
3876 $hash = random_string();
3878 if(is_array($contact_record)) {
3879 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3880 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3881 intval($importer['uid']),
3882 intval($contact_record['id']),
3884 dbesc(datetime_convert())
3888 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3889 intval($importer['uid'])
3894 if(intval($r[0]['def_gid'])) {
3895 require_once('include/group.php');
3896 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3899 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3900 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3905 'type' => NOTIFY_INTRO,
3906 'notify_flags' => $r[0]['notify-flags'],
3907 'language' => $r[0]['language'],
3908 'to_name' => $r[0]['username'],
3909 'to_email' => $r[0]['email'],
3910 'uid' => $r[0]['uid'],
3911 'link' => $a->get_baseurl() . '/notifications/intro',
3912 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3913 'source_link' => $contact_record['url'],
3914 'source_photo' => $contact_record['photo'],
3915 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3925 function lose_follower($importer,$contact,$datarray,$item) {
3927 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3928 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3929 intval(CONTACT_IS_SHARING),
3930 intval($contact['id'])
3934 contact_remove($contact['id']);
3938 function lose_sharer($importer,$contact,$datarray,$item) {
3940 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3941 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3942 intval(CONTACT_IS_FOLLOWER),
3943 intval($contact['id'])
3947 contact_remove($contact['id']);
3952 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3956 if(is_array($importer)) {
3957 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3958 intval($importer['uid'])
3962 // Diaspora has different message-ids in feeds than they do
3963 // through the direct Diaspora protocol. If we try and use
3964 // the feed, we'll get duplicates. So don't.
3966 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3969 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3971 // Use a single verify token, even if multiple hubs
3973 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3975 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3977 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3979 if(! strlen($contact['hub-verify'])) {
3980 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3981 dbesc($verify_token),
3982 intval($contact['id'])
3986 post_url($url,$params);
3988 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3995 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3999 $name = xmlify($name);
4000 $uri = xmlify($uri);
4003 $photo = xmlify($photo);
4007 $o .= "<name>$name</name>\r\n";
4008 $o .= "<uri>$uri</uri>\r\n";
4009 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4010 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4012 call_hooks('atom_author', $o);
4014 $o .= "</$tag>\r\n";
4018 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4022 if(! $item['parent'])
4025 if($item['deleted'])
4026 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4029 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4030 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4032 $body = $item['body'];
4034 $o = "\r\n\r\n<entry>\r\n";
4036 if(is_array($author))
4037 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4039 $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']));
4040 if(strlen($item['owner-name']))
4041 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4043 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4044 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4045 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4048 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4049 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4050 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4051 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4052 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4053 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4054 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4056 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4058 if($item['location']) {
4059 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4060 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4064 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4066 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4067 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4070 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4071 if($item['bookmark'])
4072 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4075 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4078 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4080 if($item['signed_text']) {
4081 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4082 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4085 $verb = construct_verb($item);
4086 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4087 $actobj = construct_activity_object($item);
4090 $actarg = construct_activity_target($item);
4094 $tags = item_getfeedtags($item);
4096 foreach($tags as $t) {
4097 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4101 $o .= item_getfeedattach($item);
4103 $mentioned = get_mentions($item);
4107 call_hooks('atom_entry', $o);
4109 $o .= '</entry>' . "\r\n";
4114 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4116 if(get_config('system','disable_embedded'))
4121 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4122 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4127 $img_start = strpos($orig_body, '[img');
4128 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4129 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4130 while( ($img_st_close !== false) && ($img_len !== false) ) {
4132 $img_st_close++; // make it point to AFTER the closing bracket
4133 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4135 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4138 if(stristr($image , $site . '/photo/')) {
4139 // Only embed locally hosted photos
4141 $i = basename($image);
4142 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4143 $x = strpos($i,'-');
4146 $res = substr($i,$x+1);
4147 $i = substr($i,0,$x);
4148 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4155 // Check to see if we should replace this photo link with an embedded image
4156 // 1. No need to do so if the photo is public
4157 // 2. If there's a contact-id provided, see if they're in the access list
4158 // for the photo. If so, embed it.
4159 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4160 // permissions, regardless of order but first check to see if they're an exact
4161 // match to save some processing overhead.
4163 if(has_permissions($r[0])) {
4165 $recips = enumerate_permissions($r[0]);
4166 if(in_array($cid, $recips)) {
4171 if(compare_permissions($item,$r[0]))
4176 $data = $r[0]['data'];
4177 $type = $r[0]['type'];
4179 // If a custom width and height were specified, apply before embedding
4180 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4181 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4183 $width = intval($match[1]);
4184 $height = intval($match[2]);
4186 $ph = new Photo($data, $type);
4187 if($ph->is_valid()) {
4188 $ph->scaleImage(max($width, $height));
4189 $data = $ph->imageString();
4190 $type = $ph->getType();
4194 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4195 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4196 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4202 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4203 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4204 if($orig_body === false)
4207 $img_start = strpos($orig_body, '[img');
4208 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4209 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4212 $new_body = $new_body . $orig_body;
4218 function has_permissions($obj) {
4219 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4224 function compare_permissions($obj1,$obj2) {
4225 // first part is easy. Check that these are exactly the same.
4226 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4227 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4228 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4229 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4232 // This is harder. Parse all the permissions and compare the resulting set.
4234 $recipients1 = enumerate_permissions($obj1);
4235 $recipients2 = enumerate_permissions($obj2);
4238 if($recipients1 == $recipients2)
4243 // returns an array of contact-ids that are allowed to see this object
4245 function enumerate_permissions($obj) {
4246 require_once('include/group.php');
4247 $allow_people = expand_acl($obj['allow_cid']);
4248 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4249 $deny_people = expand_acl($obj['deny_cid']);
4250 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4251 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4252 $deny = array_unique(array_merge($deny_people,$deny_groups));
4253 $recipients = array_diff($recipients,$deny);
4257 function item_getfeedtags($item) {
4260 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4262 for($x = 0; $x < $cnt; $x ++) {
4264 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4268 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4270 for($x = 0; $x < $cnt; $x ++) {
4272 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4278 function item_getfeedattach($item) {
4280 $arr = explode('[/attach],',$item['attach']);
4282 foreach($arr as $r) {
4284 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4286 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4287 if(intval($matches[2]))
4288 $ret .= 'length="' . intval($matches[2]) . '" ';
4289 if($matches[4] !== ' ')
4290 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4291 $ret .= ' />' . "\r\n";
4300 function item_expire($uid, $days, $network = "", $force = false) {
4302 if((! $uid) || ($days < 1))
4305 // $expire_network_only = save your own wall posts
4306 // and just expire conversations started by others
4308 $expire_network_only = get_pconfig($uid,'expire','network_only');
4309 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4311 if ($network != "") {
4312 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4313 // There is an index "uid_network_received" but not "uid_network_created"
4314 // This avoids the creation of another index just for one purpose.
4315 // And it doesn't really matter wether to look at "received" or "created"
4316 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4318 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4320 $r = q("SELECT * FROM `item`
4321 WHERE `uid` = %d $range
4332 $expire_items = get_pconfig($uid, 'expire','items');
4333 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4335 // Forcing expiring of items - but not notes and marked items
4337 $expire_items = true;
4339 $expire_notes = get_pconfig($uid, 'expire','notes');
4340 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4342 $expire_starred = get_pconfig($uid, 'expire','starred');
4343 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4345 $expire_photos = get_pconfig($uid, 'expire','photos');
4346 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4348 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4350 foreach($r as $item) {
4352 // don't expire filed items
4354 if(strpos($item['file'],'[') !== false)
4357 // Only expire posts, not photos and photo comments
4359 if($expire_photos==0 && strlen($item['resource-id']))
4361 if($expire_starred==0 && intval($item['starred']))
4363 if($expire_notes==0 && $item['type']=='note')
4365 if($expire_items==0 && $item['type']!='note')
4368 drop_item($item['id'],false);
4371 proc_run('php',"include/notifier.php","expire","$uid");
4376 function drop_items($items) {
4379 if(! local_user() && ! remote_user())
4383 foreach($items as $item) {
4384 $owner = drop_item($item,false);
4385 if($owner && ! $uid)
4390 // multiple threads may have been deleted, send an expire notification
4393 proc_run('php',"include/notifier.php","expire","$uid");
4397 function drop_item($id,$interactive = true) {
4401 // locate item to be deleted
4403 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4410 notice( t('Item not found.') . EOL);
4411 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4416 $owner = $item['uid'];
4420 // check if logged in user is either the author or owner of this item
4422 if(is_array($_SESSION['remote'])) {
4423 foreach($_SESSION['remote'] as $visitor) {
4424 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4425 $cid = $visitor['cid'];
4432 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4434 // Check if we should do HTML-based delete confirmation
4435 if($_REQUEST['confirm']) {
4436 // <form> can't take arguments in its "action" parameter
4437 // so add any arguments as hidden inputs
4438 $query = explode_querystring($a->query_string);
4440 foreach($query['args'] as $arg) {
4441 if(strpos($arg, 'confirm=') === false) {
4442 $arg_parts = explode('=', $arg);
4443 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4447 return replace_macros(get_markup_template('confirm.tpl'), array(
4449 '$message' => t('Do you really want to delete this item?'),
4450 '$extra_inputs' => $inputs,
4451 '$confirm' => t('Yes'),
4452 '$confirm_url' => $query['base'],
4453 '$confirm_name' => 'confirmed',
4454 '$cancel' => t('Cancel'),
4457 // Now check how the user responded to the confirmation query
4458 if($_REQUEST['canceled']) {
4459 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4462 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4465 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4466 dbesc(datetime_convert()),
4467 dbesc(datetime_convert()),
4470 create_tags_from_item($item['id']);
4471 create_files_from_item($item['id']);
4472 delete_thread($item['id']);
4474 // clean up categories and tags so they don't end up as orphans
4477 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4479 foreach($matches as $mtch) {
4480 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4486 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4488 foreach($matches as $mtch) {
4489 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4493 // If item is a link to a photo resource, nuke all the associated photos
4494 // (visitors will not have photo resources)
4495 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4496 // generate a resource-id and therefore aren't intimately linked to the item.
4498 if(strlen($item['resource-id'])) {
4499 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4500 dbesc($item['resource-id']),
4501 intval($item['uid'])
4503 // ignore the result
4506 // If item is a link to an event, nuke the event record.
4508 if(intval($item['event-id'])) {
4509 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4510 intval($item['event-id']),
4511 intval($item['uid'])
4513 // ignore the result
4516 // clean up item_id and sign meta-data tables
4519 // Old code - caused very long queries and warning entries in the mysql logfiles:
4521 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4522 intval($item['id']),
4523 intval($item['uid'])
4526 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4527 intval($item['id']),
4528 intval($item['uid'])
4532 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4534 // Creating list of parents
4535 $r = q("select id from item where parent = %d and uid = %d",
4536 intval($item['id']),
4537 intval($item['uid'])
4542 foreach ($r AS $row) {
4543 if ($parentid != "")
4546 $parentid .= $row["id"];
4550 if ($parentid != "") {
4551 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4553 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4556 // If it's the parent of a comment thread, kill all the kids
4558 if($item['uri'] == $item['parent-uri']) {
4559 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4560 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4561 dbesc(datetime_convert()),
4562 dbesc(datetime_convert()),
4563 dbesc($item['parent-uri']),
4564 intval($item['uid'])
4566 create_tags_from_item($item['parent-uri'], $item['uid']);
4567 create_files_from_item($item['parent-uri'], $item['uid']);
4568 delete_thread_uri($item['parent-uri'], $item['uid']);
4569 // ignore the result
4572 // ensure that last-child is set in case the comment that had it just got wiped.
4573 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4574 dbesc(datetime_convert()),
4575 dbesc($item['parent-uri']),
4576 intval($item['uid'])
4578 // who is the last child now?
4579 $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",
4580 dbesc($item['parent-uri']),
4581 intval($item['uid'])
4584 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4589 // Add a relayable_retraction signature for Diaspora.
4590 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4592 $drop_id = intval($item['id']);
4594 // send the notification upstream/downstream as the case may be
4596 proc_run('php',"include/notifier.php","drop","$drop_id");
4600 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4606 notice( t('Permission denied.') . EOL);
4607 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4614 function first_post_date($uid,$wall = false) {
4615 $r = q("select id, created from item
4616 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4618 order by created asc limit 1",
4620 intval($wall ? 1 : 0)
4623 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4624 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4629 function posted_dates($uid,$wall) {
4630 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4632 $dthen = first_post_date($uid,$wall);
4636 // Set the start and end date to the beginning of the month
4637 $dnow = substr($dnow,0,8).'01';
4638 $dthen = substr($dthen,0,8).'01';
4641 // Starting with the current month, get the first and last days of every
4642 // month down to and including the month of the first post
4643 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4644 $dstart = substr($dnow,0,8) . '01';
4645 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4646 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4647 $end_month = datetime_convert('','',$dend,'Y-m-d');
4648 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4649 $ret[] = array($str,$end_month,$start_month);
4650 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4656 function posted_date_widget($url,$uid,$wall) {
4659 if(! feature_enabled($uid,'archives'))
4662 // For former Facebook folks that left because of "timeline"
4664 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4667 $ret = posted_dates($uid,$wall);
4671 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4672 '$title' => t('Archives'),
4673 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4680 function store_diaspora_retract_sig($item, $user, $baseurl) {
4681 // Note that we can't add a target_author_signature
4682 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4683 // the comment, that means we're the home of the post, and Diaspora will only
4684 // check the parent_author_signature of retractions that it doesn't have to relay further
4686 // I don't think this function gets called for an "unlike," but I'll check anyway
4688 $enabled = intval(get_config('system','diaspora_enabled'));
4690 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4694 logger('drop_item: storing diaspora retraction signature');
4696 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4698 if(local_user() == $item['uid']) {
4700 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4701 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4704 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4705 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4708 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4709 // only handles DFRN deletes
4710 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4711 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4712 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4718 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4719 intval($item['id']),
4720 dbesc($signed_text),