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']);
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($url, $no_photos = false, $photo = "") {
892 require_once("mod/parse_url.php");
894 $data = parseurl_getsiteinfo($url, true);
896 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
898 // It maybe is a rich content, but if it does have everything that a link has,
899 // then treat it that way
900 if (($data["type"] == "rich") AND is_string($data["title"]) AND
901 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
902 $data["type"] = "link";
904 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
907 if ($no_photos AND ($data["type"] == "photo"))
910 if (($data["type"] != "photo") AND is_string($data["title"]))
911 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
913 if (($data["type"] != "video") AND ($photo != ""))
914 $text .= '[img]'.$photo.'[/img]';
915 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
916 $imagedata = $data["images"][0];
917 $text .= '[img]'.$imagedata["src"].'[/img]';
920 if (($data["type"] != "photo") AND is_string($data["text"]))
921 $text .= "[quote]".$data["text"]."[/quote]";
923 return("\n[class=type-".$data["type"]."]".$text."[/class]");
926 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
928 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
930 $URLSearchString = "^\[\]";
932 // Adding these spaces is a quick hack due to my problems with regular expressions :)
933 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
936 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
938 // Convert urls without bbcode elements
939 if (!$matches AND $texturl) {
940 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
942 // Yeah, a hack. I really hate regular expressions :)
944 $matches[1] = $matches[2];
948 $footer = add_page_info($matches[1], $no_photos);
950 // Remove the link from the body if the link is attached at the end of the post
951 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
952 $removedlink = trim(str_replace($matches[1], "", $body));
953 if (($removedlink == "") OR strstr($body, $removedlink))
954 $body = $removedlink;
956 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
957 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
958 if (($removedlink == "") OR strstr($body, $removedlink))
959 $body = $removedlink;
962 // Add the page information to the bottom
963 if (isset($footer) AND (trim($footer) != ""))
969 function encode_rel_links($links) {
971 if(! ((is_array($links)) && (count($links))))
973 foreach($links as $link) {
975 if($link['attribs']['']['rel'])
976 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
977 if($link['attribs']['']['type'])
978 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
979 if($link['attribs']['']['href'])
980 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
981 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
982 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
983 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
984 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
992 function item_store($arr,$force_parent = false, $notify = false) {
994 // If it is a posting where users should get notifications, then define it as wall posting
997 $arr['type'] = 'wall';
999 $arr['last-child'] = 1;
1000 $arr['network'] = NETWORK_DFRN;
1003 // If a Diaspora signature structure was passed in, pull it out of the
1004 // item array and set it aside for later storage.
1007 if(x($arr,'dsprsig')) {
1008 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1009 unset($arr['dsprsig']);
1012 // if an OStatus conversation url was passed in, it is stored and then
1013 // removed from the array.
1014 $ostatus_conversation = null;
1016 if (isset($arr["ostatus_conversation"])) {
1017 $ostatus_conversation = $arr["ostatus_conversation"];
1018 unset($arr["ostatus_conversation"]);
1021 if(x($arr, 'gravity'))
1022 $arr['gravity'] = intval($arr['gravity']);
1023 elseif($arr['parent-uri'] === $arr['uri'])
1024 $arr['gravity'] = 0;
1025 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1026 $arr['gravity'] = 6;
1028 $arr['gravity'] = 6; // extensible catchall
1030 if(! x($arr,'type'))
1031 $arr['type'] = 'remote';
1035 /* check for create date and expire time */
1036 $uid = intval($arr['uid']);
1037 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1039 $expire_interval = $r[0]['expire'];
1040 if ($expire_interval>0) {
1041 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1042 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1043 if ($created_date < $expire_date) {
1044 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1050 // If there is no guid then take the same guid that was taken before for the same uri
1051 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1052 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1053 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1054 dbesc(trim($arr['uri']))
1058 $arr['guid'] = $r[0]["guid"];
1059 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1063 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1064 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1065 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1066 // $arr['body'] = strip_tags($arr['body']);
1069 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1070 require_once('library/langdet/Text/LanguageDetect.php');
1071 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1072 $l = new Text_LanguageDetect;
1073 //$lng = $l->detectConfidence($naked_body);
1074 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1075 $lng = $l->detect($naked_body, 3);
1077 if (sizeof($lng) > 0) {
1080 foreach ($lng as $language => $score) {
1081 if ($postopts == "")
1082 $postopts = "lang=";
1086 $postopts .= $language.";".$score;
1088 $arr['postopts'] = $postopts;
1092 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1093 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1094 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1095 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1096 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1097 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1098 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1099 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1100 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1101 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1102 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1103 $arr['commented'] = datetime_convert();
1104 $arr['received'] = datetime_convert();
1105 $arr['changed'] = datetime_convert();
1106 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1107 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1108 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1109 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1110 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1111 $arr['deleted'] = 0;
1112 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1113 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1114 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1115 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1116 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1117 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1118 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1119 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1120 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1121 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1122 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1123 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1124 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1125 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1126 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1127 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1128 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1129 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1130 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1131 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1133 if ($arr['plink'] == "") {
1135 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1138 if ($arr['network'] == "") {
1139 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1140 intval($arr['contact-id']),
1145 $arr['network'] = $r[0]["network"];
1147 // Fallback to friendica (why is it empty in some cases?)
1148 if ($arr['network'] == "")
1149 $arr['network'] = NETWORK_DFRN;
1151 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1154 $arr['thr-parent'] = $arr['parent-uri'];
1155 if($arr['parent-uri'] === $arr['uri']) {
1157 $parent_deleted = 0;
1158 $allow_cid = $arr['allow_cid'];
1159 $allow_gid = $arr['allow_gid'];
1160 $deny_cid = $arr['deny_cid'];
1161 $deny_gid = $arr['deny_gid'];
1162 $notify_type = 'wall-new';
1166 // find the parent and snarf the item id and ACLs
1167 // and anything else we need to inherit
1169 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1170 dbesc($arr['parent-uri']),
1176 // is the new message multi-level threaded?
1177 // even though we don't support it now, preserve the info
1178 // and re-attach to the conversation parent.
1180 if($r[0]['uri'] != $r[0]['parent-uri']) {
1181 $arr['parent-uri'] = $r[0]['parent-uri'];
1182 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1183 ORDER BY `id` ASC LIMIT 1",
1184 dbesc($r[0]['parent-uri']),
1185 dbesc($r[0]['parent-uri']),
1192 $parent_id = $r[0]['id'];
1193 $parent_deleted = $r[0]['deleted'];
1194 $allow_cid = $r[0]['allow_cid'];
1195 $allow_gid = $r[0]['allow_gid'];
1196 $deny_cid = $r[0]['deny_cid'];
1197 $deny_gid = $r[0]['deny_gid'];
1198 $arr['wall'] = $r[0]['wall'];
1199 $notify_type = 'comment-new';
1201 // if the parent is private, force privacy for the entire conversation
1202 // This differs from the above settings as it subtly allows comments from
1203 // email correspondents to be private even if the overall thread is not.
1205 if($r[0]['private'])
1206 $arr['private'] = $r[0]['private'];
1208 // Edge case. We host a public forum that was originally posted to privately.
1209 // The original author commented, but as this is a comment, the permissions
1210 // weren't fixed up so it will still show the comment as private unless we fix it here.
1212 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1213 $arr['private'] = 0;
1216 // If its a post from myself then tag the thread as "mention"
1217 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1218 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1221 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1222 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1223 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1224 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1225 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1231 // Allow one to see reply tweets from status.net even when
1232 // we don't have or can't see the original post.
1235 logger('item_store: $force_parent=true, reply converted to top-level post.');
1237 $arr['parent-uri'] = $arr['uri'];
1238 $arr['gravity'] = 0;
1241 logger('item_store: item parent was not found - ignoring item');
1245 $parent_deleted = 0;
1249 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1253 if($r && count($r)) {
1254 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1258 call_hooks('post_remote',$arr);
1260 if(x($arr,'cancel')) {
1261 logger('item_store: post cancelled by plugin.');
1267 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1269 $r = dbq("INSERT INTO `item` (`"
1270 . implode("`, `", array_keys($arr))
1272 . implode("', '", array_values($arr))
1275 // find the item we just created
1277 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1278 $arr['uri'], // already dbesc'd
1283 $current_post = $r[0]['id'];
1284 logger('item_store: created item ' . $current_post);
1286 // Only check for notifications on start posts
1287 if ($arr['parent-uri'] === $arr['uri']) {
1288 add_thread($r[0]['id']);
1289 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1291 // Send a notification for every new post?
1292 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1293 intval($arr['contact-id']),
1298 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1299 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1300 intval($arr['uid']));
1302 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1303 intval($current_post),
1309 require_once('include/enotify.php');
1311 'type' => NOTIFY_SHARE,
1312 'notify_flags' => $u[0]['notify-flags'],
1313 'language' => $u[0]['language'],
1314 'to_name' => $u[0]['username'],
1315 'to_email' => $u[0]['email'],
1316 'uid' => $u[0]['uid'],
1318 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1319 'source_name' => $item[0]['author-name'],
1320 'source_link' => $item[0]['author-link'],
1321 'source_photo' => $item[0]['author-avatar'],
1322 'verb' => ACTIVITY_TAG,
1325 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1330 logger('item_store: could not locate created item');
1334 logger('item_store: duplicated post occurred. Removing duplicates.');
1335 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1337 intval($arr['uid']),
1338 intval($current_post)
1342 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1343 $parent_id = $current_post;
1345 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1348 $private = $arr['private'];
1350 // Set parent id - and also make sure to inherit the parent's ACLs.
1352 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1353 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1360 intval($parent_deleted),
1361 intval($current_post)
1364 // Complete ostatus threads
1365 if ($ostatus_conversation)
1366 complete_conversation($current_post, $ostatus_conversation);
1368 $arr['id'] = $current_post;
1369 $arr['parent'] = $parent_id;
1370 $arr['allow_cid'] = $allow_cid;
1371 $arr['allow_gid'] = $allow_gid;
1372 $arr['deny_cid'] = $deny_cid;
1373 $arr['deny_gid'] = $deny_gid;
1374 $arr['private'] = $private;
1375 $arr['deleted'] = $parent_deleted;
1377 // update the commented timestamp on the parent
1379 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1380 dbesc(datetime_convert()),
1381 dbesc(datetime_convert()),
1384 update_thread($parent_id);
1387 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1388 intval($current_post),
1389 dbesc($dsprsig->signed_text),
1390 dbesc($dsprsig->signature),
1391 dbesc($dsprsig->signer)
1397 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1400 if($arr['last-child']) {
1401 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1403 intval($arr['uid']),
1404 intval($current_post)
1408 $deleted = tag_deliver($arr['uid'],$current_post);
1410 // current post can be deleted if is for a communuty page and no mention are
1414 // Store the fresh generated item into the cache
1415 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1417 if (($cachefile != '') AND !file_exists($cachefile)) {
1418 $s = prepare_text($arr['body']);
1420 $stamp1 = microtime(true);
1421 file_put_contents($cachefile, $s);
1422 $a->save_timestamp($stamp1, "file");
1423 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1426 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1427 if (count($r) == 1) {
1428 call_hooks('post_remote_end', $r[0]);
1430 logger('item_store: new item not found in DB, id ' . $current_post);
1434 create_tags_from_item($current_post);
1435 create_files_from_item($current_post);
1438 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1440 return $current_post;
1443 function get_item_guid($id) {
1444 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1446 return($r[0]["guid"]);
1451 function get_item_id($guid, $uid = 0) {
1457 $uid == local_user();
1459 // Does the given user have this item?
1461 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1462 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1463 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1466 $nick = $r[0]["nickname"];
1470 // Or is it anywhere on the server?
1472 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1473 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1474 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1475 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1476 AND `item`.`private` = 0 AND `item`.`wall` = 1
1477 AND `item`.`guid` = '%s'", dbesc($guid));
1480 $nick = $r[0]["nickname"];
1483 return(array("nick" => $nick, "id" => $id));
1487 function get_item_contact($item,$contacts) {
1488 if(! count($contacts) || (! is_array($item)))
1490 foreach($contacts as $contact) {
1491 if($contact['id'] == $item['contact-id']) {
1493 break; // NOTREACHED
1500 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1502 * @param int $item_id
1503 * @return bool true if item was deleted, else false
1505 function tag_deliver($uid,$item_id) {
1513 $u = q("select * from user where uid = %d limit 1",
1519 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1520 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1523 $i = q("select * from item where id = %d and uid = %d limit 1",
1532 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1534 // Diaspora uses their own hardwired link URL in @-tags
1535 // instead of the one we supply with webfinger
1537 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1539 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1541 foreach($matches as $mtch) {
1542 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1544 logger('tag_deliver: mention found: ' . $mtch[2]);
1550 if ( ($community_page || $prvgroup) &&
1551 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1552 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1554 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1555 q("DELETE FROM item WHERE id = %d and uid = %d",
1565 // send a notification
1567 // use a local photo if we have one
1569 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1570 intval($u[0]['uid']),
1571 dbesc(normalise_link($item['author-link']))
1573 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1576 require_once('include/enotify.php');
1578 'type' => NOTIFY_TAGSELF,
1579 'notify_flags' => $u[0]['notify-flags'],
1580 'language' => $u[0]['language'],
1581 'to_name' => $u[0]['username'],
1582 'to_email' => $u[0]['email'],
1583 'uid' => $u[0]['uid'],
1585 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1586 'source_name' => $item['author-name'],
1587 'source_link' => $item['author-link'],
1588 'source_photo' => $photo,
1589 'verb' => ACTIVITY_TAG,
1591 'parent' => $item['parent']
1595 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1597 call_hooks('tagged', $arr);
1599 if((! $community_page) && (! $prvgroup))
1603 // tgroup delivery - setup a second delivery chain
1604 // prevent delivery looping - only proceed
1605 // if the message originated elsewhere and is a top-level post
1607 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1610 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1613 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1614 intval($u[0]['uid'])
1619 // also reset all the privacy bits to the forum default permissions
1621 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1623 $forum_mode = (($prvgroup) ? 2 : 1);
1625 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1626 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1627 intval($forum_mode),
1628 dbesc($c[0]['name']),
1629 dbesc($c[0]['url']),
1630 dbesc($c[0]['thumb']),
1632 dbesc($u[0]['allow_cid']),
1633 dbesc($u[0]['allow_gid']),
1634 dbesc($u[0]['deny_cid']),
1635 dbesc($u[0]['deny_gid']),
1638 update_thread($item_id);
1640 proc_run('php','include/notifier.php','tgroup',$item_id);
1646 function tgroup_check($uid,$item) {
1652 // check that the message originated elsewhere and is a top-level post
1654 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1658 $u = q("select * from user where uid = %d limit 1",
1664 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1665 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1668 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1670 // Diaspora uses their own hardwired link URL in @-tags
1671 // instead of the one we supply with webfinger
1673 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1675 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1677 foreach($matches as $mtch) {
1678 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1680 logger('tgroup_check: mention found: ' . $mtch[2]);
1688 if((! $community_page) && (! $prvgroup))
1702 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1706 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1708 if($contact['duplex'] && $contact['dfrn-id'])
1709 $idtosend = '0:' . $orig_id;
1710 if($contact['duplex'] && $contact['issued-id'])
1711 $idtosend = '1:' . $orig_id;
1713 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1715 $rino_enable = get_config('system','rino_encrypt');
1720 $ssl_val = intval(get_config('system','ssl_policy'));
1724 case SSL_POLICY_FULL:
1725 $ssl_policy = 'full';
1727 case SSL_POLICY_SELFSIGN:
1728 $ssl_policy = 'self';
1730 case SSL_POLICY_NONE:
1732 $ssl_policy = 'none';
1736 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1738 logger('dfrn_deliver: ' . $url);
1740 $xml = fetch_url($url);
1742 $curl_stat = $a->get_curl_code();
1744 return(-1); // timed out
1746 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1751 if(strpos($xml,'<?xml') === false) {
1752 logger('dfrn_deliver: no valid XML returned');
1753 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1757 $res = parse_xml_string($xml);
1759 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1760 return (($res->status) ? $res->status : 3);
1762 $postvars = array();
1763 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1764 $challenge = hex2bin((string) $res->challenge);
1765 $perm = (($res->perm) ? $res->perm : null);
1766 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1767 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1768 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1770 if($owner['page-flags'] == PAGE_PRVGROUP)
1773 $final_dfrn_id = '';
1776 if((($perm == 'rw') && (! intval($contact['writable'])))
1777 || (($perm == 'r') && (intval($contact['writable'])))) {
1778 q("update contact set writable = %d where id = %d",
1779 intval(($perm == 'rw') ? 1 : 0),
1780 intval($contact['id'])
1782 $contact['writable'] = (string) 1 - intval($contact['writable']);
1786 if(($contact['duplex'] && strlen($contact['pubkey']))
1787 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1788 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1789 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1790 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1793 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1794 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1797 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1799 if(strpos($final_dfrn_id,':') == 1)
1800 $final_dfrn_id = substr($final_dfrn_id,2);
1802 if($final_dfrn_id != $orig_id) {
1803 logger('dfrn_deliver: wrong dfrn_id.');
1804 // did not decode properly - cannot trust this site
1808 $postvars['dfrn_id'] = $idtosend;
1809 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1811 $postvars['dissolve'] = '1';
1814 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1815 $postvars['data'] = $atom;
1816 $postvars['perm'] = 'rw';
1819 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1820 $postvars['perm'] = 'r';
1823 $postvars['ssl_policy'] = $ssl_policy;
1826 $postvars['page'] = $page;
1828 if($rino && $rino_allowed && (! $dissolve)) {
1829 $key = substr(random_string(),0,16);
1830 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1831 $postvars['data'] = $data;
1832 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1835 if($dfrn_version >= 2.1) {
1836 if(($contact['duplex'] && strlen($contact['pubkey']))
1837 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1838 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1840 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1843 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1847 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1848 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1851 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1855 logger('md5 rawkey ' . md5($postvars['key']));
1857 $postvars['key'] = bin2hex($postvars['key']);
1860 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1862 $xml = post_url($contact['notify'],$postvars);
1864 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1866 $curl_stat = $a->get_curl_code();
1867 if((! $curl_stat) || (! strlen($xml)))
1868 return(-1); // timed out
1870 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1873 if(strpos($xml,'<?xml') === false) {
1874 logger('dfrn_deliver: phase 2: no valid XML returned');
1875 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1879 if($contact['term-date'] != '0000-00-00 00:00:00') {
1880 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1881 require_once('include/Contact.php');
1882 unmark_for_death($contact);
1885 $res = parse_xml_string($xml);
1887 return $res->status;
1892 This function returns true if $update has an edited timestamp newer
1893 than $existing, i.e. $update contains new data which should override
1894 what's already there. If there is no timestamp yet, the update is
1895 assumed to be newer. If the update has no timestamp, the existing
1896 item is assumed to be up-to-date. If the timestamps are equal it
1897 assumes the update has been seen before and should be ignored.
1899 function edited_timestamp_is_newer($existing, $update) {
1900 if (!x($existing,'edited') || !$existing['edited']) {
1903 if (!x($update,'edited') || !$update['edited']) {
1906 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1907 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1908 return (strcmp($existing_edited, $update_edited) < 0);
1913 * consume_feed - process atom feed and update anything/everything we might need to update
1915 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1917 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1918 * It is this person's stuff that is going to be updated.
1919 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1920 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1921 * have a contact record.
1922 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1923 * might not) try and subscribe to it.
1924 * $datedir sorts in reverse order
1925 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1926 * imported prior to its children being seen in the stream unless we are certain
1927 * of how the feed is arranged/ordered.
1928 * With $pass = 1, we only pull parent items out of the stream.
1929 * With $pass = 2, we only pull children (comments/likes).
1931 * So running this twice, first with pass 1 and then with pass 2 will do the right
1932 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1933 * model where comments can have sub-threads. That would require some massive sorting
1934 * to get all the feed items into a mostly linear ordering, and might still require
1938 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1940 require_once('library/simplepie/simplepie.inc');
1942 if(! strlen($xml)) {
1943 logger('consume_feed: empty input');
1947 $feed = new SimplePie();
1948 $feed->set_raw_data($xml);
1950 $feed->enable_order_by_date(true);
1952 $feed->enable_order_by_date(false);
1956 logger('consume_feed: Error parsing XML: ' . $feed->error());
1958 $permalink = $feed->get_permalink();
1960 // Check at the feed level for updated contact name and/or photo
1964 $photo_timestamp = '';
1968 $hubs = $feed->get_links('hub');
1969 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1972 $hub = implode(',', $hubs);
1974 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1976 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1978 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1979 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1980 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1981 $new_name = $elems['name'][0]['data'];
1983 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1984 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1985 $photo_url = $elems['link'][0]['attribs']['']['href'];
1988 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1989 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1993 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1994 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1995 require_once("include/Photo.php");
1996 $photo_failure = false;
1997 $have_photo = false;
1999 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2000 intval($contact['id']),
2001 intval($contact['uid'])
2004 $resource_id = $r[0]['resource-id'];
2008 $resource_id = photo_new_resource();
2011 $img_str = fetch_url($photo_url,true);
2012 // guess mimetype from headers or filename
2013 $type = guess_image_type($photo_url,true);
2016 $img = new Photo($img_str, $type);
2017 if($img->is_valid()) {
2019 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2020 dbesc($resource_id),
2021 intval($contact['id']),
2022 intval($contact['uid'])
2026 $img->scaleImageSquare(175);
2028 $hash = $resource_id;
2029 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2031 $img->scaleImage(80);
2032 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2034 $img->scaleImage(48);
2035 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2039 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2040 WHERE `uid` = %d AND `id` = %d",
2041 dbesc(datetime_convert()),
2042 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2043 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2044 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2045 intval($contact['uid']),
2046 intval($contact['id'])
2051 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2052 $r = q("select * from contact where uid = %d and id = %d limit 1",
2053 intval($contact['uid']),
2054 intval($contact['id'])
2057 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2058 dbesc(notags(trim($new_name))),
2059 dbesc(datetime_convert()),
2060 intval($contact['uid']),
2061 intval($contact['id'])
2064 // do our best to update the name on content items
2067 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2068 dbesc(notags(trim($new_name))),
2069 dbesc($r[0]['name']),
2070 dbesc($r[0]['url']),
2071 intval($contact['uid'])
2076 if(strlen($birthday)) {
2077 if(substr($birthday,0,4) != $contact['bdyear']) {
2078 logger('consume_feed: updating birthday: ' . $birthday);
2082 * Add new birthday event for this person
2084 * $bdtext is just a readable placeholder in case the event is shared
2085 * with others. We will replace it during presentation to our $importer
2086 * to contain a sparkle link and perhaps a photo.
2090 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2091 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2094 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2095 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2096 intval($contact['uid']),
2097 intval($contact['id']),
2098 dbesc(datetime_convert()),
2099 dbesc(datetime_convert()),
2100 dbesc(datetime_convert('UTC','UTC', $birthday)),
2101 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2110 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2111 dbesc(substr($birthday,0,4)),
2112 intval($contact['uid']),
2113 intval($contact['id'])
2116 // This function is called twice without reloading the contact
2117 // Make sure we only create one event. This is why &$contact
2118 // is a reference var in this function
2120 $contact['bdyear'] = substr($birthday,0,4);
2125 $community_page = 0;
2126 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2128 $community_page = intval($rawtags[0]['data']);
2130 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2131 q("update contact set forum = %d where id = %d",
2132 intval($community_page),
2133 intval($contact['id'])
2135 $contact['forum'] = (string) $community_page;
2139 // process any deleted entries
2141 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2142 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2143 foreach($del_entries as $dentry) {
2145 if(isset($dentry['attribs']['']['ref'])) {
2146 $uri = $dentry['attribs']['']['ref'];
2148 if(isset($dentry['attribs']['']['when'])) {
2149 $when = $dentry['attribs']['']['when'];
2150 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2153 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2155 if($deleted && is_array($contact)) {
2156 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2157 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2159 intval($importer['uid']),
2160 intval($contact['id'])
2165 if(! $item['deleted'])
2166 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2168 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2169 $xo = parse_xml_string($item['object'],false);
2170 $xt = parse_xml_string($item['target'],false);
2171 if($xt->type === ACTIVITY_OBJ_NOTE) {
2172 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2174 intval($importer['importer_uid'])
2178 // For tags, the owner cannot remove the tag on the author's copy of the post.
2180 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2181 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2182 $author_copy = (($item['origin']) ? true : false);
2184 if($owner_remove && $author_copy)
2186 if($author_remove || $owner_remove) {
2187 $tags = explode(',',$i[0]['tag']);
2190 foreach($tags as $tag)
2191 if(trim($tag) !== trim($xo->body))
2192 $newtags[] = trim($tag);
2194 q("update item set tag = '%s' where id = %d",
2195 dbesc(implode(',',$newtags)),
2198 create_tags_from_item($i[0]['id']);
2204 if($item['uri'] == $item['parent-uri']) {
2205 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2206 `body` = '', `title` = ''
2207 WHERE `parent-uri` = '%s' AND `uid` = %d",
2209 dbesc(datetime_convert()),
2210 dbesc($item['uri']),
2211 intval($importer['uid'])
2213 create_tags_from_itemuri($item['uri'], $importer['uid']);
2214 create_files_from_itemuri($item['uri'], $importer['uid']);
2215 update_thread_uri($item['uri'], $importer['uid']);
2218 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2219 `body` = '', `title` = ''
2220 WHERE `uri` = '%s' AND `uid` = %d",
2222 dbesc(datetime_convert()),
2224 intval($importer['uid'])
2226 create_tags_from_itemuri($uri, $importer['uid']);
2227 create_files_from_itemuri($uri, $importer['uid']);
2228 if($item['last-child']) {
2229 // ensure that last-child is set in case the comment that had it just got wiped.
2230 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2231 dbesc(datetime_convert()),
2232 dbesc($item['parent-uri']),
2233 intval($item['uid'])
2235 // who is the last child now?
2236 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2237 ORDER BY `created` DESC LIMIT 1",
2238 dbesc($item['parent-uri']),
2239 intval($importer['uid'])
2242 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2253 // Now process the feed
2255 if($feed->get_item_quantity()) {
2257 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2259 // in inverse date order
2261 $items = array_reverse($feed->get_items());
2263 $items = $feed->get_items();
2266 foreach($items as $item) {
2269 $item_id = $item->get_id();
2270 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2271 if(isset($rawthread[0]['attribs']['']['ref'])) {
2273 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2276 if(($is_reply) && is_array($contact)) {
2281 // not allowed to post
2283 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2287 // Have we seen it? If not, import it.
2289 $item_id = $item->get_id();
2290 $datarray = get_atom_elements($feed, $item, $contact);
2292 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2293 $datarray['author-name'] = $contact['name'];
2294 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2295 $datarray['author-link'] = $contact['url'];
2296 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2297 $datarray['author-avatar'] = $contact['thumb'];
2299 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2300 logger('consume_feed: no author information! ' . print_r($datarray,true));
2304 $force_parent = false;
2305 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2306 if($contact['network'] === NETWORK_OSTATUS)
2307 $force_parent = true;
2308 if(strlen($datarray['title']))
2309 unset($datarray['title']);
2310 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2311 dbesc(datetime_convert()),
2313 intval($importer['uid'])
2315 $datarray['last-child'] = 1;
2316 update_thread_uri($parent_uri, $importer['uid']);
2320 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2322 intval($importer['uid'])
2325 // Update content if 'updated' changes
2328 if (edited_timestamp_is_newer($r[0], $datarray)) {
2330 // do not accept (ignore) an earlier edit than one we currently have.
2331 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2334 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2335 dbesc($datarray['title']),
2336 dbesc($datarray['body']),
2337 dbesc($datarray['tag']),
2338 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2339 dbesc(datetime_convert()),
2341 intval($importer['uid'])
2343 create_tags_from_itemuri($item_id, $importer['uid']);
2344 update_thread_uri($item_id, $importer['uid']);
2347 // update last-child if it changes
2349 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2350 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2351 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2352 dbesc(datetime_convert()),
2354 intval($importer['uid'])
2356 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2357 intval($allow[0]['data']),
2358 dbesc(datetime_convert()),
2360 intval($importer['uid'])
2362 update_thread_uri($item_id, $importer['uid']);
2368 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2369 // one way feed - no remote comment ability
2370 $datarray['last-child'] = 0;
2372 $datarray['parent-uri'] = $parent_uri;
2373 $datarray['uid'] = $importer['uid'];
2374 $datarray['contact-id'] = $contact['id'];
2375 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2376 $datarray['type'] = 'activity';
2377 $datarray['gravity'] = GRAVITY_LIKE;
2378 // only one like or dislike per person
2379 // splitted into two queries for performance issues
2380 $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",
2381 intval($datarray['uid']),
2382 intval($datarray['contact-id']),
2383 dbesc($datarray['verb']),
2389 $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",
2390 intval($datarray['uid']),
2391 intval($datarray['contact-id']),
2392 dbesc($datarray['verb']),
2399 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2400 $xo = parse_xml_string($datarray['object'],false);
2401 $xt = parse_xml_string($datarray['target'],false);
2403 if($xt->type == ACTIVITY_OBJ_NOTE) {
2404 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2406 intval($importer['importer_uid'])
2411 // extract tag, if not duplicate, add to parent item
2412 if($xo->id && $xo->content) {
2413 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2414 if(! (stristr($r[0]['tag'],$newtag))) {
2415 q("UPDATE item SET tag = '%s' WHERE id = %d",
2416 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2419 create_tags_from_item($r[0]['id']);
2425 $r = item_store($datarray,$force_parent);
2431 // Head post of a conversation. Have we seen it? If not, import it.
2433 $item_id = $item->get_id();
2435 $datarray = get_atom_elements($feed, $item, $contact);
2437 if(is_array($contact)) {
2438 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2439 $datarray['author-name'] = $contact['name'];
2440 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2441 $datarray['author-link'] = $contact['url'];
2442 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2443 $datarray['author-avatar'] = $contact['thumb'];
2446 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2447 logger('consume_feed: no author information! ' . print_r($datarray,true));
2451 // special handling for events
2453 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2454 $ev = bbtoevent($datarray['body']);
2455 if(x($ev,'desc') && x($ev,'start')) {
2456 $ev['uid'] = $importer['uid'];
2457 $ev['uri'] = $item_id;
2458 $ev['edited'] = $datarray['edited'];
2459 $ev['private'] = $datarray['private'];
2461 if(is_array($contact))
2462 $ev['cid'] = $contact['id'];
2463 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2465 intval($importer['uid'])
2468 $ev['id'] = $r[0]['id'];
2469 $xyz = event_store($ev);
2474 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2475 if(strlen($datarray['title']))
2476 unset($datarray['title']);
2477 $datarray['last-child'] = 1;
2481 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2483 intval($importer['uid'])
2486 // Update content if 'updated' changes
2489 if (edited_timestamp_is_newer($r[0], $datarray)) {
2491 // do not accept (ignore) an earlier edit than one we currently have.
2492 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2495 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2496 dbesc($datarray['title']),
2497 dbesc($datarray['body']),
2498 dbesc($datarray['tag']),
2499 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2500 dbesc(datetime_convert()),
2502 intval($importer['uid'])
2504 create_tags_from_itemuri($item_id, $importer['uid']);
2505 update_thread_uri($item_id, $importer['uid']);
2508 // update last-child if it changes
2510 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2511 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2512 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2513 intval($allow[0]['data']),
2514 dbesc(datetime_convert()),
2516 intval($importer['uid'])
2518 update_thread_uri($item_id, $importer['uid']);
2523 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2524 logger('consume-feed: New follower');
2525 new_follower($importer,$contact,$datarray,$item);
2528 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2529 lose_follower($importer,$contact,$datarray,$item);
2533 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2534 logger('consume-feed: New friend request');
2535 new_follower($importer,$contact,$datarray,$item,true);
2538 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2539 lose_sharer($importer,$contact,$datarray,$item);
2544 if(! is_array($contact))
2548 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2549 // one way feed - no remote comment ability
2550 $datarray['last-child'] = 0;
2552 if($contact['network'] === NETWORK_FEED)
2553 $datarray['private'] = 2;
2555 $datarray['parent-uri'] = $item_id;
2556 $datarray['uid'] = $importer['uid'];
2557 $datarray['contact-id'] = $contact['id'];
2559 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2560 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2561 // but otherwise there's a possible data mixup on the sender's system.
2562 // the tgroup delivery code called from item_store will correct it if it's a forum,
2563 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2564 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2565 $datarray['owner-name'] = $contact['name'];
2566 $datarray['owner-link'] = $contact['url'];
2567 $datarray['owner-avatar'] = $contact['thumb'];
2570 // We've allowed "followers" to reach this point so we can decide if they are
2571 // posting an @-tag delivery, which followers are allowed to do for certain
2572 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2574 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2577 // This is my contact on another system, but it's really me.
2578 // Turn this into a wall post.
2580 if($contact['remote_self']) {
2581 if ($contact['remote_self'] == 2) {
2582 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2584 $datarray['contact-id'] = $r[0]["id"];
2586 $datarray['owner-name'] = $r[0]["name"];
2587 $datarray['owner-link'] = $r[0]["url"];
2588 $datarray['owner-avatar'] = $r[0]["photo"];
2590 $datarray['author-name'] = $datarray['owner-name'];
2591 $datarray['author-link'] = $datarray['owner-link'];
2592 $datarray['author-avatar'] = $datarray['owner-avatar'];
2597 if($contact['network'] === NETWORK_FEED) {
2598 $datarray['private'] = 0;
2603 $r = item_store($datarray, false, $notify);
2611 function local_delivery($importer,$data) {
2614 logger(__function__, LOGGER_TRACE);
2616 if($importer['readonly']) {
2617 // We aren't receiving stuff from this person. But we will quietly ignore them
2618 // rather than a blatant "go away" message.
2619 logger('local_delivery: ignoring');
2624 // Consume notification feed. This may differ from consuming a public feed in several ways
2625 // - might contain email or friend suggestions
2626 // - might contain remote followup to our message
2627 // - in which case we need to accept it and then notify other conversants
2628 // - we may need to send various email notifications
2630 $feed = new SimplePie();
2631 $feed->set_raw_data($data);
2632 $feed->enable_order_by_date(false);
2637 logger('local_delivery: Error parsing XML: ' . $feed->error());
2640 // Check at the feed level for updated contact name and/or photo
2644 $photo_timestamp = '';
2648 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2650 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2652 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2655 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2656 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2657 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2658 $new_name = $elems['name'][0]['data'];
2660 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2661 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2662 $photo_url = $elems['link'][0]['attribs']['']['href'];
2666 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2667 logger('local_delivery: Updating photo for ' . $importer['name']);
2668 require_once("include/Photo.php");
2669 $photo_failure = false;
2670 $have_photo = false;
2672 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2673 intval($importer['id']),
2674 intval($importer['importer_uid'])
2677 $resource_id = $r[0]['resource-id'];
2681 $resource_id = photo_new_resource();
2684 $img_str = fetch_url($photo_url,true);
2685 // guess mimetype from headers or filename
2686 $type = guess_image_type($photo_url,true);
2689 $img = new Photo($img_str, $type);
2690 if($img->is_valid()) {
2692 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2693 dbesc($resource_id),
2694 intval($importer['id']),
2695 intval($importer['importer_uid'])
2699 $img->scaleImageSquare(175);
2701 $hash = $resource_id;
2702 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2704 $img->scaleImage(80);
2705 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2707 $img->scaleImage(48);
2708 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2712 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2713 WHERE `uid` = %d AND `id` = %d",
2714 dbesc(datetime_convert()),
2715 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2716 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2717 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2718 intval($importer['importer_uid']),
2719 intval($importer['id'])
2724 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2725 $r = q("select * from contact where uid = %d and id = %d limit 1",
2726 intval($importer['importer_uid']),
2727 intval($importer['id'])
2730 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2731 dbesc(notags(trim($new_name))),
2732 dbesc(datetime_convert()),
2733 intval($importer['importer_uid']),
2734 intval($importer['id'])
2737 // do our best to update the name on content items
2740 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2741 dbesc(notags(trim($new_name))),
2742 dbesc($r[0]['name']),
2743 dbesc($r[0]['url']),
2744 intval($importer['importer_uid'])
2751 // Currently unsupported - needs a lot of work
2752 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2753 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2754 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2756 $newloc['uid'] = $importer['importer_uid'];
2757 $newloc['cid'] = $importer['id'];
2758 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2759 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2760 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2761 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2762 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2763 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2764 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2765 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2766 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2767 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2768 /** relocated user must have original key pair */
2769 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2770 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2772 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2775 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2776 intval($importer['id']),
2777 intval($importer['importer_uid']));
2782 $x = q("UPDATE contact SET
2792 `site-pubkey` = '%s'
2793 WHERE id=%d AND uid=%d;",
2794 dbesc($newloc['name']),
2795 dbesc($newloc['photo']),
2796 dbesc($newloc['thumb']),
2797 dbesc($newloc['micro']),
2798 dbesc($newloc['url']),
2799 dbesc($newloc['request']),
2800 dbesc($newloc['confirm']),
2801 dbesc($newloc['notify']),
2802 dbesc($newloc['poll']),
2803 dbesc($newloc['sitepubkey']),
2804 intval($importer['id']),
2805 intval($importer['importer_uid']));
2811 'owner-link' => array($old['url'], $newloc['url']),
2812 'author-link' => array($old['url'], $newloc['url']),
2813 'owner-avatar' => array($old['photo'], $newloc['photo']),
2814 'author-avatar' => array($old['photo'], $newloc['photo']),
2816 foreach ($fields as $n=>$f){
2817 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2820 intval($importer['importer_uid']));
2826 // merge with current record, current contents have priority
2827 // update record, set url-updated
2828 // update profile photos
2834 // handle friend suggestion notification
2836 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2837 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2838 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2840 $fsugg['uid'] = $importer['importer_uid'];
2841 $fsugg['cid'] = $importer['id'];
2842 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2843 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2844 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2845 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2846 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2848 // Does our member already have a friend matching this description?
2850 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2851 dbesc($fsugg['name']),
2852 dbesc(normalise_link($fsugg['url'])),
2853 intval($fsugg['uid'])
2858 // Do we already have an fcontact record for this person?
2861 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2862 dbesc($fsugg['url']),
2863 dbesc($fsugg['name']),
2864 dbesc($fsugg['request'])
2869 // OK, we do. Do we already have an introduction for this person ?
2870 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2871 intval($fsugg['uid']),
2878 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2879 dbesc($fsugg['name']),
2880 dbesc($fsugg['url']),
2881 dbesc($fsugg['photo']),
2882 dbesc($fsugg['request'])
2884 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2885 dbesc($fsugg['url']),
2886 dbesc($fsugg['name']),
2887 dbesc($fsugg['request'])
2892 // database record did not get created. Quietly give up.
2897 $hash = random_string();
2899 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2900 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2901 intval($fsugg['uid']),
2903 intval($fsugg['cid']),
2904 dbesc($fsugg['body']),
2906 dbesc(datetime_convert()),
2911 'type' => NOTIFY_SUGGEST,
2912 'notify_flags' => $importer['notify-flags'],
2913 'language' => $importer['language'],
2914 'to_name' => $importer['username'],
2915 'to_email' => $importer['email'],
2916 'uid' => $importer['importer_uid'],
2918 'link' => $a->get_baseurl() . '/notifications/intros',
2919 'source_name' => $importer['name'],
2920 'source_link' => $importer['url'],
2921 'source_photo' => $importer['photo'],
2922 'verb' => ACTIVITY_REQ_FRIEND,
2931 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2932 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2934 logger('local_delivery: private message received');
2937 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2940 $msg['uid'] = $importer['importer_uid'];
2941 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2942 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2943 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2944 $msg['contact-id'] = $importer['id'];
2945 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2946 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2948 $msg['replied'] = 0;
2949 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2950 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2951 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2955 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2956 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2958 // send notifications.
2960 require_once('include/enotify.php');
2962 $notif_params = array(
2963 'type' => NOTIFY_MAIL,
2964 'notify_flags' => $importer['notify-flags'],
2965 'language' => $importer['language'],
2966 'to_name' => $importer['username'],
2967 'to_email' => $importer['email'],
2968 'uid' => $importer['importer_uid'],
2970 'source_name' => $msg['from-name'],
2971 'source_link' => $importer['url'],
2972 'source_photo' => $importer['thumb'],
2973 'verb' => ACTIVITY_POST,
2977 notification($notif_params);
2983 $community_page = 0;
2984 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2986 $community_page = intval($rawtags[0]['data']);
2988 if(intval($importer['forum']) != $community_page) {
2989 q("update contact set forum = %d where id = %d",
2990 intval($community_page),
2991 intval($importer['id'])
2993 $importer['forum'] = (string) $community_page;
2996 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2998 // process any deleted entries
3000 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3001 if(is_array($del_entries) && count($del_entries)) {
3002 foreach($del_entries as $dentry) {
3004 if(isset($dentry['attribs']['']['ref'])) {
3005 $uri = $dentry['attribs']['']['ref'];
3007 if(isset($dentry['attribs']['']['when'])) {
3008 $when = $dentry['attribs']['']['when'];
3009 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3012 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3016 // check for relayed deletes to our conversation
3019 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3021 intval($importer['importer_uid'])
3024 $parent_uri = $r[0]['parent-uri'];
3025 if($r[0]['id'] != $r[0]['parent'])
3032 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3035 logger('local_delivery: possible community delete');
3038 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3040 // was the top-level post for this reply written by somebody on this site?
3041 // Specifically, the recipient?
3043 $is_a_remote_delete = false;
3045 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3046 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3047 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3048 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3049 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3050 AND `item`.`uid` = %d
3056 intval($importer['importer_uid'])
3059 $is_a_remote_delete = true;
3061 // Does this have the characteristics of a community or private group comment?
3062 // If it's a reply to a wall post on a community/prvgroup page it's a
3063 // valid community comment. Also forum_mode makes it valid for sure.
3064 // If neither, it's not.
3066 if($is_a_remote_delete && $community) {
3067 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3068 $is_a_remote_delete = false;
3069 logger('local_delivery: not a community delete');
3073 if($is_a_remote_delete) {
3074 logger('local_delivery: received remote delete');
3078 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3079 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3081 intval($importer['importer_uid']),
3082 intval($importer['id'])
3088 if($item['deleted'])
3091 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3093 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3094 $xo = parse_xml_string($item['object'],false);
3095 $xt = parse_xml_string($item['target'],false);
3097 if($xt->type === ACTIVITY_OBJ_NOTE) {
3098 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3100 intval($importer['importer_uid'])
3104 // For tags, the owner cannot remove the tag on the author's copy of the post.
3106 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3107 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3108 $author_copy = (($item['origin']) ? true : false);
3110 if($owner_remove && $author_copy)
3112 if($author_remove || $owner_remove) {
3113 $tags = explode(',',$i[0]['tag']);
3116 foreach($tags as $tag)
3117 if(trim($tag) !== trim($xo->body))
3118 $newtags[] = trim($tag);
3120 q("update item set tag = '%s' where id = %d",
3121 dbesc(implode(',',$newtags)),
3124 create_tags_from_item($i[0]['id']);
3130 if($item['uri'] == $item['parent-uri']) {
3131 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3132 `body` = '', `title` = ''
3133 WHERE `parent-uri` = '%s' AND `uid` = %d",
3135 dbesc(datetime_convert()),
3136 dbesc($item['uri']),
3137 intval($importer['importer_uid'])
3139 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3140 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3141 update_thread_uri($item['uri'], $importer['importer_uid']);
3144 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3145 `body` = '', `title` = ''
3146 WHERE `uri` = '%s' AND `uid` = %d",
3148 dbesc(datetime_convert()),
3150 intval($importer['importer_uid'])
3152 create_tags_from_itemuri($uri, $importer['importer_uid']);
3153 create_files_from_itemuri($uri, $importer['importer_uid']);
3154 update_thread_uri($uri, $importer['importer_uid']);
3155 if($item['last-child']) {
3156 // ensure that last-child is set in case the comment that had it just got wiped.
3157 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3158 dbesc(datetime_convert()),
3159 dbesc($item['parent-uri']),
3160 intval($item['uid'])
3162 // who is the last child now?
3163 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3164 ORDER BY `created` DESC LIMIT 1",
3165 dbesc($item['parent-uri']),
3166 intval($importer['importer_uid'])
3169 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3174 // if this is a relayed delete, propagate it to other recipients
3176 if($is_a_remote_delete)
3177 proc_run('php',"include/notifier.php","drop",$item['id']);
3185 foreach($feed->get_items() as $item) {
3188 $item_id = $item->get_id();
3189 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3190 if(isset($rawthread[0]['attribs']['']['ref'])) {
3192 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3198 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3201 logger('local_delivery: possible community reply');
3204 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3206 // was the top-level post for this reply written by somebody on this site?
3207 // Specifically, the recipient?
3209 $is_a_remote_comment = false;
3210 $top_uri = $parent_uri;
3212 $r = q("select `item`.`parent-uri` from `item`
3213 WHERE `item`.`uri` = '%s'
3217 if($r && count($r)) {
3218 $top_uri = $r[0]['parent-uri'];
3220 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3221 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3222 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3223 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3224 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3225 AND `item`.`uid` = %d
3231 intval($importer['importer_uid'])
3234 $is_a_remote_comment = true;
3237 // Does this have the characteristics of a community or private group comment?
3238 // If it's a reply to a wall post on a community/prvgroup page it's a
3239 // valid community comment. Also forum_mode makes it valid for sure.
3240 // If neither, it's not.
3242 if($is_a_remote_comment && $community) {
3243 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3244 $is_a_remote_comment = false;
3245 logger('local_delivery: not a community reply');
3249 if($is_a_remote_comment) {
3250 logger('local_delivery: received remote comment');
3252 // remote reply to our post. Import and then notify everybody else.
3254 $datarray = get_atom_elements($feed, $item);
3256 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3258 intval($importer['importer_uid'])
3261 // Update content if 'updated' changes
3265 if (edited_timestamp_is_newer($r[0], $datarray)) {
3267 // do not accept (ignore) an earlier edit than one we currently have.
3268 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3271 logger('received updated comment' , LOGGER_DEBUG);
3272 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3273 dbesc($datarray['title']),
3274 dbesc($datarray['body']),
3275 dbesc($datarray['tag']),
3276 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3277 dbesc(datetime_convert()),
3279 intval($importer['importer_uid'])
3281 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3283 proc_run('php',"include/notifier.php","comment-import",$iid);
3292 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3293 intval($importer['importer_uid'])
3297 $datarray['type'] = 'remote-comment';
3298 $datarray['wall'] = 1;
3299 $datarray['parent-uri'] = $parent_uri;
3300 $datarray['uid'] = $importer['importer_uid'];
3301 $datarray['owner-name'] = $own[0]['name'];
3302 $datarray['owner-link'] = $own[0]['url'];
3303 $datarray['owner-avatar'] = $own[0]['thumb'];
3304 $datarray['contact-id'] = $importer['id'];
3306 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3308 $datarray['type'] = 'activity';
3309 $datarray['gravity'] = GRAVITY_LIKE;
3310 $datarray['last-child'] = 0;
3311 // only one like or dislike per person
3312 // splitted into two queries for performance issues
3313 $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",
3314 intval($datarray['uid']),
3315 intval($datarray['contact-id']),
3316 dbesc($datarray['verb']),
3317 dbesc($datarray['parent-uri'])
3323 $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",
3324 intval($datarray['uid']),
3325 intval($datarray['contact-id']),
3326 dbesc($datarray['verb']),
3327 dbesc($datarray['parent-uri'])
3334 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3336 $xo = parse_xml_string($datarray['object'],false);
3337 $xt = parse_xml_string($datarray['target'],false);
3339 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3341 // fetch the parent item
3343 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3345 intval($importer['importer_uid'])
3350 // extract tag, if not duplicate, and this user allows tags, add to parent item
3352 if($xo->id && $xo->content) {
3353 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3354 if(! (stristr($tagp[0]['tag'],$newtag))) {
3355 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3356 intval($importer['importer_uid'])
3358 if(count($i) && ! intval($i[0]['blocktags'])) {
3359 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3360 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3361 intval($tagp[0]['id']),
3362 dbesc(datetime_convert()),
3363 dbesc(datetime_convert())
3365 create_tags_from_item($tagp[0]['id']);
3373 $posted_id = item_store($datarray);
3377 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3379 intval($importer['importer_uid'])
3382 $parent = $r[0]['parent'];
3383 $parent_uri = $r[0]['parent-uri'];
3387 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3388 dbesc(datetime_convert()),
3389 intval($importer['importer_uid']),
3390 intval($r[0]['parent'])
3393 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3394 dbesc(datetime_convert()),
3395 intval($importer['importer_uid']),
3400 if($posted_id && $parent) {
3402 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3404 if((! $is_like) && (! $importer['self'])) {
3406 require_once('include/enotify.php');
3409 'type' => NOTIFY_COMMENT,
3410 'notify_flags' => $importer['notify-flags'],
3411 'language' => $importer['language'],
3412 'to_name' => $importer['username'],
3413 'to_email' => $importer['email'],
3414 'uid' => $importer['importer_uid'],
3415 'item' => $datarray,
3416 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3417 'source_name' => stripslashes($datarray['author-name']),
3418 'source_link' => $datarray['author-link'],
3419 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3420 ? $importer['thumb'] : $datarray['author-avatar']),
3421 'verb' => ACTIVITY_POST,
3423 'parent' => $parent,
3424 'parent_uri' => $parent_uri,
3436 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3438 $item_id = $item->get_id();
3439 $datarray = get_atom_elements($feed,$item);
3441 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3444 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3446 intval($importer['importer_uid'])
3449 // Update content if 'updated' changes
3452 if (edited_timestamp_is_newer($r[0], $datarray)) {
3454 // do not accept (ignore) an earlier edit than one we currently have.
3455 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3458 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3459 dbesc($datarray['title']),
3460 dbesc($datarray['body']),
3461 dbesc($datarray['tag']),
3462 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3463 dbesc(datetime_convert()),
3465 intval($importer['importer_uid'])
3467 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3470 // update last-child if it changes
3472 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3473 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3474 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3475 dbesc(datetime_convert()),
3477 intval($importer['importer_uid'])
3479 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3480 intval($allow[0]['data']),
3481 dbesc(datetime_convert()),
3483 intval($importer['importer_uid'])
3489 $datarray['parent-uri'] = $parent_uri;
3490 $datarray['uid'] = $importer['importer_uid'];
3491 $datarray['contact-id'] = $importer['id'];
3492 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3493 $datarray['type'] = 'activity';
3494 $datarray['gravity'] = GRAVITY_LIKE;
3495 // only one like or dislike per person
3496 // splitted into two queries for performance issues
3497 $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",
3498 intval($datarray['uid']),
3499 intval($datarray['contact-id']),
3500 dbesc($datarray['verb']),
3506 $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",
3507 intval($datarray['uid']),
3508 intval($datarray['contact-id']),
3509 dbesc($datarray['verb']),
3517 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3519 $xo = parse_xml_string($datarray['object'],false);
3520 $xt = parse_xml_string($datarray['target'],false);
3522 if($xt->type == ACTIVITY_OBJ_NOTE) {
3523 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3525 intval($importer['importer_uid'])
3530 // extract tag, if not duplicate, add to parent item
3532 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3533 q("UPDATE item SET tag = '%s' WHERE id = %d",
3534 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3537 create_tags_from_item($r[0]['id']);
3543 $posted_id = item_store($datarray);
3545 // find out if our user is involved in this conversation and wants to be notified.
3547 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3549 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3551 intval($importer['importer_uid'])
3554 if(count($myconv)) {
3555 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3557 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3558 if(! link_compare($datarray['author-link'],$importer_url)) {
3561 foreach($myconv as $conv) {
3563 // now if we find a match, it means we're in this conversation
3565 if(! link_compare($conv['author-link'],$importer_url))
3568 require_once('include/enotify.php');
3570 $conv_parent = $conv['parent'];
3573 'type' => NOTIFY_COMMENT,
3574 'notify_flags' => $importer['notify-flags'],
3575 'language' => $importer['language'],
3576 'to_name' => $importer['username'],
3577 'to_email' => $importer['email'],
3578 'uid' => $importer['importer_uid'],
3579 'item' => $datarray,
3580 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3581 'source_name' => stripslashes($datarray['author-name']),
3582 'source_link' => $datarray['author-link'],
3583 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3584 ? $importer['thumb'] : $datarray['author-avatar']),
3585 'verb' => ACTIVITY_POST,
3587 'parent' => $conv_parent,
3588 'parent_uri' => $parent_uri
3592 // only send one notification
3604 // Head post of a conversation. Have we seen it? If not, import it.
3607 $item_id = $item->get_id();
3608 $datarray = get_atom_elements($feed,$item);
3610 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3611 $ev = bbtoevent($datarray['body']);
3612 if(x($ev,'desc') && x($ev,'start')) {
3613 $ev['cid'] = $importer['id'];
3614 $ev['uid'] = $importer['uid'];
3615 $ev['uri'] = $item_id;
3616 $ev['edited'] = $datarray['edited'];
3617 $ev['private'] = $datarray['private'];
3619 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3621 intval($importer['uid'])
3624 $ev['id'] = $r[0]['id'];
3625 $xyz = event_store($ev);
3630 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3632 intval($importer['importer_uid'])
3635 // Update content if 'updated' changes
3638 if (edited_timestamp_is_newer($r[0], $datarray)) {
3640 // do not accept (ignore) an earlier edit than one we currently have.
3641 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3644 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3645 dbesc($datarray['title']),
3646 dbesc($datarray['body']),
3647 dbesc($datarray['tag']),
3648 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3649 dbesc(datetime_convert()),
3651 intval($importer['importer_uid'])
3653 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3654 update_thread_uri($item_id, $importer['importer_uid']);
3657 // update last-child if it changes
3659 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3660 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3661 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3662 intval($allow[0]['data']),
3663 dbesc(datetime_convert()),
3665 intval($importer['importer_uid'])
3671 $datarray['parent-uri'] = $item_id;
3672 $datarray['uid'] = $importer['importer_uid'];
3673 $datarray['contact-id'] = $importer['id'];
3676 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3677 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3678 // but otherwise there's a possible data mixup on the sender's system.
3679 // the tgroup delivery code called from item_store will correct it if it's a forum,
3680 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3681 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3682 $datarray['owner-name'] = $importer['senderName'];
3683 $datarray['owner-link'] = $importer['url'];
3684 $datarray['owner-avatar'] = $importer['thumb'];
3687 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3690 // This is my contact on another system, but it's really me.
3691 // Turn this into a wall post.
3693 if($importer['remote_self']) {
3694 if ($importer['remote_self'] == 2) {
3695 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3696 intval($importer['importer_uid']));
3698 $datarray['contact-id'] = $r[0]["id"];
3700 $datarray['owner-name'] = $r[0]["name"];
3701 $datarray['owner-link'] = $r[0]["url"];
3702 $datarray['owner-avatar'] = $r[0]["photo"];
3704 $datarray['author-name'] = $datarray['owner-name'];
3705 $datarray['author-link'] = $datarray['owner-link'];
3706 $datarray['author-avatar'] = $datarray['owner-avatar'];
3714 $posted_id = item_store($datarray, false, $notify);
3716 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3717 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3720 $xo = parse_xml_string($datarray['object'],false);
3722 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3724 // somebody was poked/prodded. Was it me?
3726 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3728 foreach($links->link as $l) {
3729 $atts = $l->attributes();
3730 switch($atts['rel']) {
3732 $Blink = $atts['href'];
3738 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3740 // send a notification
3741 require_once('include/enotify.php');
3744 'type' => NOTIFY_POKE,
3745 'notify_flags' => $importer['notify-flags'],
3746 'language' => $importer['language'],
3747 'to_name' => $importer['username'],
3748 'to_email' => $importer['email'],
3749 'uid' => $importer['importer_uid'],
3750 'item' => $datarray,
3751 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3752 'source_name' => stripslashes($datarray['author-name']),
3753 'source_link' => $datarray['author-link'],
3754 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3755 ? $importer['thumb'] : $datarray['author-avatar']),
3756 'verb' => $datarray['verb'],
3757 'otype' => 'person',
3758 'activity' => $verb,
3775 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3776 $url = notags(trim($datarray['author-link']));
3777 $name = notags(trim($datarray['author-name']));
3778 $photo = notags(trim($datarray['author-avatar']));
3780 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3781 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3782 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3784 if(is_array($contact)) {
3785 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3786 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3787 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3788 intval(CONTACT_IS_FRIEND),
3789 intval($contact['id']),
3790 intval($importer['uid'])
3793 // send email notification to owner?
3797 // create contact record
3799 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3800 `blocked`, `readonly`, `pending`, `writable` )
3801 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3802 intval($importer['uid']),
3803 dbesc(datetime_convert()),
3805 dbesc(normalise_link($url)),
3809 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3810 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3812 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3813 intval($importer['uid']),
3817 $contact_record = $r[0];
3819 // create notification
3820 $hash = random_string();
3822 if(is_array($contact_record)) {
3823 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3824 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3825 intval($importer['uid']),
3826 intval($contact_record['id']),
3828 dbesc(datetime_convert())
3832 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3833 intval($importer['uid'])
3838 if(intval($r[0]['def_gid'])) {
3839 require_once('include/group.php');
3840 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3843 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3844 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3849 'type' => NOTIFY_INTRO,
3850 'notify_flags' => $r[0]['notify-flags'],
3851 'language' => $r[0]['language'],
3852 'to_name' => $r[0]['username'],
3853 'to_email' => $r[0]['email'],
3854 'uid' => $r[0]['uid'],
3855 'link' => $a->get_baseurl() . '/notifications/intro',
3856 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3857 'source_link' => $contact_record['url'],
3858 'source_photo' => $contact_record['photo'],
3859 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3869 function lose_follower($importer,$contact,$datarray,$item) {
3871 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3872 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3873 intval(CONTACT_IS_SHARING),
3874 intval($contact['id'])
3878 contact_remove($contact['id']);
3882 function lose_sharer($importer,$contact,$datarray,$item) {
3884 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3885 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3886 intval(CONTACT_IS_FOLLOWER),
3887 intval($contact['id'])
3891 contact_remove($contact['id']);
3896 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3900 if(is_array($importer)) {
3901 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3902 intval($importer['uid'])
3906 // Diaspora has different message-ids in feeds than they do
3907 // through the direct Diaspora protocol. If we try and use
3908 // the feed, we'll get duplicates. So don't.
3910 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3913 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3915 // Use a single verify token, even if multiple hubs
3917 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3919 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3921 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3923 if(! strlen($contact['hub-verify'])) {
3924 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3925 dbesc($verify_token),
3926 intval($contact['id'])
3930 post_url($url,$params);
3932 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3939 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3943 $name = xmlify($name);
3944 $uri = xmlify($uri);
3947 $photo = xmlify($photo);
3951 $o .= "<name>$name</name>\r\n";
3952 $o .= "<uri>$uri</uri>\r\n";
3953 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3954 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3956 call_hooks('atom_author', $o);
3958 $o .= "</$tag>\r\n";
3962 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3966 if(! $item['parent'])
3969 if($item['deleted'])
3970 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3973 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3974 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3976 $body = $item['body'];
3978 $o = "\r\n\r\n<entry>\r\n";
3980 if(is_array($author))
3981 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3983 $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']));
3984 if(strlen($item['owner-name']))
3985 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3987 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3988 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3989 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3992 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3993 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3994 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3995 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3996 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3997 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3998 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4000 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4002 if($item['location']) {
4003 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4004 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4008 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4010 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4011 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4014 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4015 if($item['bookmark'])
4016 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4019 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4022 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4024 if($item['signed_text']) {
4025 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4026 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4029 $verb = construct_verb($item);
4030 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4031 $actobj = construct_activity_object($item);
4034 $actarg = construct_activity_target($item);
4038 $tags = item_getfeedtags($item);
4040 foreach($tags as $t) {
4041 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4045 $o .= item_getfeedattach($item);
4047 $mentioned = get_mentions($item);
4051 call_hooks('atom_entry', $o);
4053 $o .= '</entry>' . "\r\n";
4058 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4060 if(get_config('system','disable_embedded'))
4065 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4066 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4071 $img_start = strpos($orig_body, '[img');
4072 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4073 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4074 while( ($img_st_close !== false) && ($img_len !== false) ) {
4076 $img_st_close++; // make it point to AFTER the closing bracket
4077 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4079 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4082 if(stristr($image , $site . '/photo/')) {
4083 // Only embed locally hosted photos
4085 $i = basename($image);
4086 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4087 $x = strpos($i,'-');
4090 $res = substr($i,$x+1);
4091 $i = substr($i,0,$x);
4092 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4099 // Check to see if we should replace this photo link with an embedded image
4100 // 1. No need to do so if the photo is public
4101 // 2. If there's a contact-id provided, see if they're in the access list
4102 // for the photo. If so, embed it.
4103 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4104 // permissions, regardless of order but first check to see if they're an exact
4105 // match to save some processing overhead.
4107 if(has_permissions($r[0])) {
4109 $recips = enumerate_permissions($r[0]);
4110 if(in_array($cid, $recips)) {
4115 if(compare_permissions($item,$r[0]))
4120 $data = $r[0]['data'];
4121 $type = $r[0]['type'];
4123 // If a custom width and height were specified, apply before embedding
4124 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4125 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4127 $width = intval($match[1]);
4128 $height = intval($match[2]);
4130 $ph = new Photo($data, $type);
4131 if($ph->is_valid()) {
4132 $ph->scaleImage(max($width, $height));
4133 $data = $ph->imageString();
4134 $type = $ph->getType();
4138 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4139 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4140 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4146 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4147 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4148 if($orig_body === false)
4151 $img_start = strpos($orig_body, '[img');
4152 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4153 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4156 $new_body = $new_body . $orig_body;
4162 function has_permissions($obj) {
4163 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4168 function compare_permissions($obj1,$obj2) {
4169 // first part is easy. Check that these are exactly the same.
4170 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4171 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4172 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4173 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4176 // This is harder. Parse all the permissions and compare the resulting set.
4178 $recipients1 = enumerate_permissions($obj1);
4179 $recipients2 = enumerate_permissions($obj2);
4182 if($recipients1 == $recipients2)
4187 // returns an array of contact-ids that are allowed to see this object
4189 function enumerate_permissions($obj) {
4190 require_once('include/group.php');
4191 $allow_people = expand_acl($obj['allow_cid']);
4192 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4193 $deny_people = expand_acl($obj['deny_cid']);
4194 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4195 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4196 $deny = array_unique(array_merge($deny_people,$deny_groups));
4197 $recipients = array_diff($recipients,$deny);
4201 function item_getfeedtags($item) {
4204 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4206 for($x = 0; $x < $cnt; $x ++) {
4208 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4212 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4214 for($x = 0; $x < $cnt; $x ++) {
4216 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4222 function item_getfeedattach($item) {
4224 $arr = explode('[/attach],',$item['attach']);
4226 foreach($arr as $r) {
4228 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4230 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4231 if(intval($matches[2]))
4232 $ret .= 'length="' . intval($matches[2]) . '" ';
4233 if($matches[4] !== ' ')
4234 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4235 $ret .= ' />' . "\r\n";
4244 function item_expire($uid, $days, $network = "", $force = false) {
4246 if((! $uid) || ($days < 1))
4249 // $expire_network_only = save your own wall posts
4250 // and just expire conversations started by others
4252 $expire_network_only = get_pconfig($uid,'expire','network_only');
4253 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4255 if ($network != "") {
4256 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4257 // There is an index "uid_network_received" but not "uid_network_created"
4258 // This avoids the creation of another index just for one purpose.
4259 // And it doesn't really matter wether to look at "received" or "created"
4260 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4262 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4264 $r = q("SELECT * FROM `item`
4265 WHERE `uid` = %d $range
4276 $expire_items = get_pconfig($uid, 'expire','items');
4277 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4279 // Forcing expiring of items - but not notes and marked items
4281 $expire_items = true;
4283 $expire_notes = get_pconfig($uid, 'expire','notes');
4284 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4286 $expire_starred = get_pconfig($uid, 'expire','starred');
4287 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4289 $expire_photos = get_pconfig($uid, 'expire','photos');
4290 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4292 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4294 foreach($r as $item) {
4296 // don't expire filed items
4298 if(strpos($item['file'],'[') !== false)
4301 // Only expire posts, not photos and photo comments
4303 if($expire_photos==0 && strlen($item['resource-id']))
4305 if($expire_starred==0 && intval($item['starred']))
4307 if($expire_notes==0 && $item['type']=='note')
4309 if($expire_items==0 && $item['type']!='note')
4312 drop_item($item['id'],false);
4315 proc_run('php',"include/notifier.php","expire","$uid");
4320 function drop_items($items) {
4323 if(! local_user() && ! remote_user())
4327 foreach($items as $item) {
4328 $owner = drop_item($item,false);
4329 if($owner && ! $uid)
4334 // multiple threads may have been deleted, send an expire notification
4337 proc_run('php',"include/notifier.php","expire","$uid");
4341 function drop_item($id,$interactive = true) {
4345 // locate item to be deleted
4347 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4354 notice( t('Item not found.') . EOL);
4355 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4360 $owner = $item['uid'];
4364 // check if logged in user is either the author or owner of this item
4366 if(is_array($_SESSION['remote'])) {
4367 foreach($_SESSION['remote'] as $visitor) {
4368 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4369 $cid = $visitor['cid'];
4376 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4378 // Check if we should do HTML-based delete confirmation
4379 if($_REQUEST['confirm']) {
4380 // <form> can't take arguments in its "action" parameter
4381 // so add any arguments as hidden inputs
4382 $query = explode_querystring($a->query_string);
4384 foreach($query['args'] as $arg) {
4385 if(strpos($arg, 'confirm=') === false) {
4386 $arg_parts = explode('=', $arg);
4387 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4391 return replace_macros(get_markup_template('confirm.tpl'), array(
4393 '$message' => t('Do you really want to delete this item?'),
4394 '$extra_inputs' => $inputs,
4395 '$confirm' => t('Yes'),
4396 '$confirm_url' => $query['base'],
4397 '$confirm_name' => 'confirmed',
4398 '$cancel' => t('Cancel'),
4401 // Now check how the user responded to the confirmation query
4402 if($_REQUEST['canceled']) {
4403 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4406 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4409 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4410 dbesc(datetime_convert()),
4411 dbesc(datetime_convert()),
4414 create_tags_from_item($item['id']);
4415 create_files_from_item($item['id']);
4416 delete_thread($item['id']);
4418 // clean up categories and tags so they don't end up as orphans
4421 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4423 foreach($matches as $mtch) {
4424 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4430 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4432 foreach($matches as $mtch) {
4433 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4437 // If item is a link to a photo resource, nuke all the associated photos
4438 // (visitors will not have photo resources)
4439 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4440 // generate a resource-id and therefore aren't intimately linked to the item.
4442 if(strlen($item['resource-id'])) {
4443 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4444 dbesc($item['resource-id']),
4445 intval($item['uid'])
4447 // ignore the result
4450 // If item is a link to an event, nuke the event record.
4452 if(intval($item['event-id'])) {
4453 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4454 intval($item['event-id']),
4455 intval($item['uid'])
4457 // ignore the result
4460 // clean up item_id and sign meta-data tables
4463 // Old code - caused very long queries and warning entries in the mysql logfiles:
4465 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4466 intval($item['id']),
4467 intval($item['uid'])
4470 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4471 intval($item['id']),
4472 intval($item['uid'])
4476 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4478 // Creating list of parents
4479 $r = q("select id from item where parent = %d and uid = %d",
4480 intval($item['id']),
4481 intval($item['uid'])
4486 foreach ($r AS $row) {
4487 if ($parentid != "")
4490 $parentid .= $row["id"];
4494 if ($parentid != "") {
4495 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4497 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4500 // If it's the parent of a comment thread, kill all the kids
4502 if($item['uri'] == $item['parent-uri']) {
4503 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4504 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4505 dbesc(datetime_convert()),
4506 dbesc(datetime_convert()),
4507 dbesc($item['parent-uri']),
4508 intval($item['uid'])
4510 create_tags_from_item($item['parent-uri'], $item['uid']);
4511 create_files_from_item($item['parent-uri'], $item['uid']);
4512 delete_thread_uri($item['parent-uri'], $item['uid']);
4513 // ignore the result
4516 // ensure that last-child is set in case the comment that had it just got wiped.
4517 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4518 dbesc(datetime_convert()),
4519 dbesc($item['parent-uri']),
4520 intval($item['uid'])
4522 // who is the last child now?
4523 $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",
4524 dbesc($item['parent-uri']),
4525 intval($item['uid'])
4528 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4533 // Add a relayable_retraction signature for Diaspora.
4534 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4536 $drop_id = intval($item['id']);
4538 // send the notification upstream/downstream as the case may be
4540 proc_run('php',"include/notifier.php","drop","$drop_id");
4544 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4550 notice( t('Permission denied.') . EOL);
4551 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4558 function first_post_date($uid,$wall = false) {
4559 $r = q("select id, created from item
4560 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4562 order by created asc limit 1",
4564 intval($wall ? 1 : 0)
4567 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4568 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4573 function posted_dates($uid,$wall) {
4574 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4576 $dthen = first_post_date($uid,$wall);
4580 // Set the start and end date to the beginning of the month
4581 $dnow = substr($dnow,0,8).'01';
4582 $dthen = substr($dthen,0,8).'01';
4585 // Starting with the current month, get the first and last days of every
4586 // month down to and including the month of the first post
4587 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4588 $dstart = substr($dnow,0,8) . '01';
4589 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4590 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4591 $end_month = datetime_convert('','',$dend,'Y-m-d');
4592 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4593 $ret[] = array($str,$end_month,$start_month);
4594 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4600 function posted_date_widget($url,$uid,$wall) {
4603 if(! feature_enabled($uid,'archives'))
4606 // For former Facebook folks that left because of "timeline"
4608 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4611 $ret = posted_dates($uid,$wall);
4615 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4616 '$title' => t('Archives'),
4617 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4624 function store_diaspora_retract_sig($item, $user, $baseurl) {
4625 // Note that we can't add a target_author_signature
4626 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4627 // the comment, that means we're the home of the post, and Diaspora will only
4628 // check the parent_author_signature of retractions that it doesn't have to relay further
4630 // I don't think this function gets called for an "unlike," but I'll check anyway
4632 $enabled = intval(get_config('system','diaspora_enabled'));
4634 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4638 logger('drop_item: storing diaspora retraction signature');
4640 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4642 if(local_user() == $item['uid']) {
4644 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4645 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4648 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4649 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4652 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4653 // only handles DFRN deletes
4654 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4655 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4656 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4662 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4663 intval($item['id']),
4664 dbesc($signed_text),