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(urlencode($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');
1941 require_once('include/contact_selectors.php');
1943 if(! strlen($xml)) {
1944 logger('consume_feed: empty input');
1948 $feed = new SimplePie();
1949 $feed->set_raw_data($xml);
1951 $feed->enable_order_by_date(true);
1953 $feed->enable_order_by_date(false);
1957 logger('consume_feed: Error parsing XML: ' . $feed->error());
1959 $permalink = $feed->get_permalink();
1961 // Check at the feed level for updated contact name and/or photo
1965 $photo_timestamp = '';
1969 $hubs = $feed->get_links('hub');
1970 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1973 $hub = implode(',', $hubs);
1975 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1977 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1979 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1980 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1981 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1982 $new_name = $elems['name'][0]['data'];
1984 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1985 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1986 $photo_url = $elems['link'][0]['attribs']['']['href'];
1989 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1990 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1994 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1995 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1996 require_once("include/Photo.php");
1997 $photo_failure = false;
1998 $have_photo = false;
2000 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2001 intval($contact['id']),
2002 intval($contact['uid'])
2005 $resource_id = $r[0]['resource-id'];
2009 $resource_id = photo_new_resource();
2012 $img_str = fetch_url($photo_url,true);
2013 // guess mimetype from headers or filename
2014 $type = guess_image_type($photo_url,true);
2017 $img = new Photo($img_str, $type);
2018 if($img->is_valid()) {
2020 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2021 dbesc($resource_id),
2022 intval($contact['id']),
2023 intval($contact['uid'])
2027 $img->scaleImageSquare(175);
2029 $hash = $resource_id;
2030 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2032 $img->scaleImage(80);
2033 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2035 $img->scaleImage(48);
2036 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2040 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2041 WHERE `uid` = %d AND `id` = %d",
2042 dbesc(datetime_convert()),
2043 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2044 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2045 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2046 intval($contact['uid']),
2047 intval($contact['id'])
2052 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2053 $r = q("select * from contact where uid = %d and id = %d limit 1",
2054 intval($contact['uid']),
2055 intval($contact['id'])
2058 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2059 dbesc(notags(trim($new_name))),
2060 dbesc(datetime_convert()),
2061 intval($contact['uid']),
2062 intval($contact['id'])
2065 // do our best to update the name on content items
2068 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2069 dbesc(notags(trim($new_name))),
2070 dbesc($r[0]['name']),
2071 dbesc($r[0]['url']),
2072 intval($contact['uid'])
2077 if(strlen($birthday)) {
2078 if(substr($birthday,0,4) != $contact['bdyear']) {
2079 logger('consume_feed: updating birthday: ' . $birthday);
2083 * Add new birthday event for this person
2085 * $bdtext is just a readable placeholder in case the event is shared
2086 * with others. We will replace it during presentation to our $importer
2087 * to contain a sparkle link and perhaps a photo.
2091 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2092 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2095 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2096 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2097 intval($contact['uid']),
2098 intval($contact['id']),
2099 dbesc(datetime_convert()),
2100 dbesc(datetime_convert()),
2101 dbesc(datetime_convert('UTC','UTC', $birthday)),
2102 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2111 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2112 dbesc(substr($birthday,0,4)),
2113 intval($contact['uid']),
2114 intval($contact['id'])
2117 // This function is called twice without reloading the contact
2118 // Make sure we only create one event. This is why &$contact
2119 // is a reference var in this function
2121 $contact['bdyear'] = substr($birthday,0,4);
2126 $community_page = 0;
2127 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2129 $community_page = intval($rawtags[0]['data']);
2131 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2132 q("update contact set forum = %d where id = %d",
2133 intval($community_page),
2134 intval($contact['id'])
2136 $contact['forum'] = (string) $community_page;
2140 // process any deleted entries
2142 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2143 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2144 foreach($del_entries as $dentry) {
2146 if(isset($dentry['attribs']['']['ref'])) {
2147 $uri = $dentry['attribs']['']['ref'];
2149 if(isset($dentry['attribs']['']['when'])) {
2150 $when = $dentry['attribs']['']['when'];
2151 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2154 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2156 if($deleted && is_array($contact)) {
2157 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2158 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2160 intval($importer['uid']),
2161 intval($contact['id'])
2166 if(! $item['deleted'])
2167 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2169 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2170 $xo = parse_xml_string($item['object'],false);
2171 $xt = parse_xml_string($item['target'],false);
2172 if($xt->type === ACTIVITY_OBJ_NOTE) {
2173 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2175 intval($importer['importer_uid'])
2179 // For tags, the owner cannot remove the tag on the author's copy of the post.
2181 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2182 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2183 $author_copy = (($item['origin']) ? true : false);
2185 if($owner_remove && $author_copy)
2187 if($author_remove || $owner_remove) {
2188 $tags = explode(',',$i[0]['tag']);
2191 foreach($tags as $tag)
2192 if(trim($tag) !== trim($xo->body))
2193 $newtags[] = trim($tag);
2195 q("update item set tag = '%s' where id = %d",
2196 dbesc(implode(',',$newtags)),
2199 create_tags_from_item($i[0]['id']);
2205 if($item['uri'] == $item['parent-uri']) {
2206 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2207 `body` = '', `title` = ''
2208 WHERE `parent-uri` = '%s' AND `uid` = %d",
2210 dbesc(datetime_convert()),
2211 dbesc($item['uri']),
2212 intval($importer['uid'])
2214 create_tags_from_itemuri($item['uri'], $importer['uid']);
2215 create_files_from_itemuri($item['uri'], $importer['uid']);
2216 update_thread_uri($item['uri'], $importer['uid']);
2219 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2220 `body` = '', `title` = ''
2221 WHERE `uri` = '%s' AND `uid` = %d",
2223 dbesc(datetime_convert()),
2225 intval($importer['uid'])
2227 create_tags_from_itemuri($uri, $importer['uid']);
2228 create_files_from_itemuri($uri, $importer['uid']);
2229 if($item['last-child']) {
2230 // ensure that last-child is set in case the comment that had it just got wiped.
2231 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2232 dbesc(datetime_convert()),
2233 dbesc($item['parent-uri']),
2234 intval($item['uid'])
2236 // who is the last child now?
2237 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2238 ORDER BY `created` DESC LIMIT 1",
2239 dbesc($item['parent-uri']),
2240 intval($importer['uid'])
2243 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2254 // Now process the feed
2256 if($feed->get_item_quantity()) {
2258 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2260 // in inverse date order
2262 $items = array_reverse($feed->get_items());
2264 $items = $feed->get_items();
2267 foreach($items as $item) {
2270 $item_id = $item->get_id();
2271 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2272 if(isset($rawthread[0]['attribs']['']['ref'])) {
2274 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2277 if(($is_reply) && is_array($contact)) {
2282 // not allowed to post
2284 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2288 // Have we seen it? If not, import it.
2290 $item_id = $item->get_id();
2291 $datarray = get_atom_elements($feed, $item, $contact);
2293 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2294 $datarray['author-name'] = $contact['name'];
2295 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2296 $datarray['author-link'] = $contact['url'];
2297 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2298 $datarray['author-avatar'] = $contact['thumb'];
2300 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2301 logger('consume_feed: no author information! ' . print_r($datarray,true));
2305 $force_parent = false;
2306 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2307 if($contact['network'] === NETWORK_OSTATUS)
2308 $force_parent = true;
2309 if(strlen($datarray['title']))
2310 unset($datarray['title']);
2311 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2312 dbesc(datetime_convert()),
2314 intval($importer['uid'])
2316 $datarray['last-child'] = 1;
2317 update_thread_uri($parent_uri, $importer['uid']);
2321 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2323 intval($importer['uid'])
2326 // Update content if 'updated' changes
2329 if (edited_timestamp_is_newer($r[0], $datarray)) {
2331 // do not accept (ignore) an earlier edit than one we currently have.
2332 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2335 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2336 dbesc($datarray['title']),
2337 dbesc($datarray['body']),
2338 dbesc($datarray['tag']),
2339 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2340 dbesc(datetime_convert()),
2342 intval($importer['uid'])
2344 create_tags_from_itemuri($item_id, $importer['uid']);
2345 update_thread_uri($item_id, $importer['uid']);
2348 // update last-child if it changes
2350 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2351 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2352 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2353 dbesc(datetime_convert()),
2355 intval($importer['uid'])
2357 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2358 intval($allow[0]['data']),
2359 dbesc(datetime_convert()),
2361 intval($importer['uid'])
2363 update_thread_uri($item_id, $importer['uid']);
2369 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2370 // one way feed - no remote comment ability
2371 $datarray['last-child'] = 0;
2373 $datarray['parent-uri'] = $parent_uri;
2374 $datarray['uid'] = $importer['uid'];
2375 $datarray['contact-id'] = $contact['id'];
2376 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2377 $datarray['type'] = 'activity';
2378 $datarray['gravity'] = GRAVITY_LIKE;
2379 // only one like or dislike per person
2380 // splitted into two queries for performance issues
2381 $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",
2382 intval($datarray['uid']),
2383 intval($datarray['contact-id']),
2384 dbesc($datarray['verb']),
2390 $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",
2391 intval($datarray['uid']),
2392 intval($datarray['contact-id']),
2393 dbesc($datarray['verb']),
2400 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2401 $xo = parse_xml_string($datarray['object'],false);
2402 $xt = parse_xml_string($datarray['target'],false);
2404 if($xt->type == ACTIVITY_OBJ_NOTE) {
2405 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2407 intval($importer['importer_uid'])
2412 // extract tag, if not duplicate, add to parent item
2413 if($xo->id && $xo->content) {
2414 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2415 if(! (stristr($r[0]['tag'],$newtag))) {
2416 q("UPDATE item SET tag = '%s' WHERE id = %d",
2417 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2420 create_tags_from_item($r[0]['id']);
2426 $r = item_store($datarray,$force_parent);
2432 // Head post of a conversation. Have we seen it? If not, import it.
2434 $item_id = $item->get_id();
2436 $datarray = get_atom_elements($feed, $item, $contact);
2438 if(is_array($contact)) {
2439 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2440 $datarray['author-name'] = $contact['name'];
2441 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2442 $datarray['author-link'] = $contact['url'];
2443 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2444 $datarray['author-avatar'] = $contact['thumb'];
2447 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2448 logger('consume_feed: no author information! ' . print_r($datarray,true));
2452 // special handling for events
2454 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2455 $ev = bbtoevent($datarray['body']);
2456 if(x($ev,'desc') && x($ev,'start')) {
2457 $ev['uid'] = $importer['uid'];
2458 $ev['uri'] = $item_id;
2459 $ev['edited'] = $datarray['edited'];
2460 $ev['private'] = $datarray['private'];
2462 if(is_array($contact))
2463 $ev['cid'] = $contact['id'];
2464 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2466 intval($importer['uid'])
2469 $ev['id'] = $r[0]['id'];
2470 $xyz = event_store($ev);
2475 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2476 if(strlen($datarray['title']))
2477 unset($datarray['title']);
2478 $datarray['last-child'] = 1;
2482 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2484 intval($importer['uid'])
2487 // Update content if 'updated' changes
2490 if (edited_timestamp_is_newer($r[0], $datarray)) {
2492 // do not accept (ignore) an earlier edit than one we currently have.
2493 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2496 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2497 dbesc($datarray['title']),
2498 dbesc($datarray['body']),
2499 dbesc($datarray['tag']),
2500 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2501 dbesc(datetime_convert()),
2503 intval($importer['uid'])
2505 create_tags_from_itemuri($item_id, $importer['uid']);
2506 update_thread_uri($item_id, $importer['uid']);
2509 // update last-child if it changes
2511 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2512 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2513 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2514 intval($allow[0]['data']),
2515 dbesc(datetime_convert()),
2517 intval($importer['uid'])
2519 update_thread_uri($item_id, $importer['uid']);
2524 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2525 logger('consume-feed: New follower');
2526 new_follower($importer,$contact,$datarray,$item);
2529 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2530 lose_follower($importer,$contact,$datarray,$item);
2534 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2535 logger('consume-feed: New friend request');
2536 new_follower($importer,$contact,$datarray,$item,true);
2539 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2540 lose_sharer($importer,$contact,$datarray,$item);
2545 if(! is_array($contact))
2549 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2550 // one way feed - no remote comment ability
2551 $datarray['last-child'] = 0;
2553 if($contact['network'] === NETWORK_FEED)
2554 $datarray['private'] = 2;
2556 $datarray['parent-uri'] = $item_id;
2557 $datarray['uid'] = $importer['uid'];
2558 $datarray['contact-id'] = $contact['id'];
2560 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2561 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2562 // but otherwise there's a possible data mixup on the sender's system.
2563 // the tgroup delivery code called from item_store will correct it if it's a forum,
2564 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2565 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2566 $datarray['owner-name'] = $contact['name'];
2567 $datarray['owner-link'] = $contact['url'];
2568 $datarray['owner-avatar'] = $contact['thumb'];
2571 // We've allowed "followers" to reach this point so we can decide if they are
2572 // posting an @-tag delivery, which followers are allowed to do for certain
2573 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2575 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2578 // This is my contact on another system, but it's really me.
2579 // Turn this into a wall post.
2581 if($contact['remote_self']) {
2582 if ($contact['remote_self'] == 2) {
2583 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2585 $datarray['contact-id'] = $r[0]["id"];
2587 $datarray['owner-name'] = $r[0]["name"];
2588 $datarray['owner-link'] = $r[0]["url"];
2589 $datarray['owner-avatar'] = $r[0]["photo"];
2591 $datarray['author-name'] = $datarray['owner-name'];
2592 $datarray['author-link'] = $datarray['owner-link'];
2593 $datarray['author-avatar'] = $datarray['owner-avatar'];
2597 if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2598 $datarray["app"] = network_to_name($contact['network']);
2601 if($contact['network'] === NETWORK_FEED) {
2602 $datarray['private'] = 0;
2607 $r = item_store($datarray, false, $notify);
2615 function local_delivery($importer,$data) {
2618 logger(__function__, LOGGER_TRACE);
2620 if($importer['readonly']) {
2621 // We aren't receiving stuff from this person. But we will quietly ignore them
2622 // rather than a blatant "go away" message.
2623 logger('local_delivery: ignoring');
2628 // Consume notification feed. This may differ from consuming a public feed in several ways
2629 // - might contain email or friend suggestions
2630 // - might contain remote followup to our message
2631 // - in which case we need to accept it and then notify other conversants
2632 // - we may need to send various email notifications
2634 $feed = new SimplePie();
2635 $feed->set_raw_data($data);
2636 $feed->enable_order_by_date(false);
2641 logger('local_delivery: Error parsing XML: ' . $feed->error());
2644 // Check at the feed level for updated contact name and/or photo
2648 $photo_timestamp = '';
2652 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2654 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2656 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2659 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2660 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2661 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2662 $new_name = $elems['name'][0]['data'];
2664 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2665 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2666 $photo_url = $elems['link'][0]['attribs']['']['href'];
2670 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2671 logger('local_delivery: Updating photo for ' . $importer['name']);
2672 require_once("include/Photo.php");
2673 $photo_failure = false;
2674 $have_photo = false;
2676 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2677 intval($importer['id']),
2678 intval($importer['importer_uid'])
2681 $resource_id = $r[0]['resource-id'];
2685 $resource_id = photo_new_resource();
2688 $img_str = fetch_url($photo_url,true);
2689 // guess mimetype from headers or filename
2690 $type = guess_image_type($photo_url,true);
2693 $img = new Photo($img_str, $type);
2694 if($img->is_valid()) {
2696 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2697 dbesc($resource_id),
2698 intval($importer['id']),
2699 intval($importer['importer_uid'])
2703 $img->scaleImageSquare(175);
2705 $hash = $resource_id;
2706 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2708 $img->scaleImage(80);
2709 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2711 $img->scaleImage(48);
2712 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2716 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2717 WHERE `uid` = %d AND `id` = %d",
2718 dbesc(datetime_convert()),
2719 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2720 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2721 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2722 intval($importer['importer_uid']),
2723 intval($importer['id'])
2728 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2729 $r = q("select * from contact where uid = %d and id = %d limit 1",
2730 intval($importer['importer_uid']),
2731 intval($importer['id'])
2734 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2735 dbesc(notags(trim($new_name))),
2736 dbesc(datetime_convert()),
2737 intval($importer['importer_uid']),
2738 intval($importer['id'])
2741 // do our best to update the name on content items
2744 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2745 dbesc(notags(trim($new_name))),
2746 dbesc($r[0]['name']),
2747 dbesc($r[0]['url']),
2748 intval($importer['importer_uid'])
2755 // Currently unsupported - needs a lot of work
2756 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2757 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2758 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2760 $newloc['uid'] = $importer['importer_uid'];
2761 $newloc['cid'] = $importer['id'];
2762 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2763 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2764 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2765 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2766 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2767 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2768 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2769 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2770 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2771 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2772 /** relocated user must have original key pair */
2773 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2774 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2776 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2779 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2780 intval($importer['id']),
2781 intval($importer['importer_uid']));
2786 $x = q("UPDATE contact SET
2796 `site-pubkey` = '%s'
2797 WHERE id=%d AND uid=%d;",
2798 dbesc($newloc['name']),
2799 dbesc($newloc['photo']),
2800 dbesc($newloc['thumb']),
2801 dbesc($newloc['micro']),
2802 dbesc($newloc['url']),
2803 dbesc($newloc['request']),
2804 dbesc($newloc['confirm']),
2805 dbesc($newloc['notify']),
2806 dbesc($newloc['poll']),
2807 dbesc($newloc['sitepubkey']),
2808 intval($importer['id']),
2809 intval($importer['importer_uid']));
2815 'owner-link' => array($old['url'], $newloc['url']),
2816 'author-link' => array($old['url'], $newloc['url']),
2817 'owner-avatar' => array($old['photo'], $newloc['photo']),
2818 'author-avatar' => array($old['photo'], $newloc['photo']),
2820 foreach ($fields as $n=>$f){
2821 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2824 intval($importer['importer_uid']));
2830 // merge with current record, current contents have priority
2831 // update record, set url-updated
2832 // update profile photos
2838 // handle friend suggestion notification
2840 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2841 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2842 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2844 $fsugg['uid'] = $importer['importer_uid'];
2845 $fsugg['cid'] = $importer['id'];
2846 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2847 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2848 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2849 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2850 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2852 // Does our member already have a friend matching this description?
2854 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2855 dbesc($fsugg['name']),
2856 dbesc(normalise_link($fsugg['url'])),
2857 intval($fsugg['uid'])
2862 // Do we already have an fcontact record for this person?
2865 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2866 dbesc($fsugg['url']),
2867 dbesc($fsugg['name']),
2868 dbesc($fsugg['request'])
2873 // OK, we do. Do we already have an introduction for this person ?
2874 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2875 intval($fsugg['uid']),
2882 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2883 dbesc($fsugg['name']),
2884 dbesc($fsugg['url']),
2885 dbesc($fsugg['photo']),
2886 dbesc($fsugg['request'])
2888 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2889 dbesc($fsugg['url']),
2890 dbesc($fsugg['name']),
2891 dbesc($fsugg['request'])
2896 // database record did not get created. Quietly give up.
2901 $hash = random_string();
2903 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2904 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2905 intval($fsugg['uid']),
2907 intval($fsugg['cid']),
2908 dbesc($fsugg['body']),
2910 dbesc(datetime_convert()),
2915 'type' => NOTIFY_SUGGEST,
2916 'notify_flags' => $importer['notify-flags'],
2917 'language' => $importer['language'],
2918 'to_name' => $importer['username'],
2919 'to_email' => $importer['email'],
2920 'uid' => $importer['importer_uid'],
2922 'link' => $a->get_baseurl() . '/notifications/intros',
2923 'source_name' => $importer['name'],
2924 'source_link' => $importer['url'],
2925 'source_photo' => $importer['photo'],
2926 'verb' => ACTIVITY_REQ_FRIEND,
2935 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2936 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2938 logger('local_delivery: private message received');
2941 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2944 $msg['uid'] = $importer['importer_uid'];
2945 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2946 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2947 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2948 $msg['contact-id'] = $importer['id'];
2949 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2950 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2952 $msg['replied'] = 0;
2953 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2954 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2955 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2959 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2960 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2962 // send notifications.
2964 require_once('include/enotify.php');
2966 $notif_params = array(
2967 'type' => NOTIFY_MAIL,
2968 'notify_flags' => $importer['notify-flags'],
2969 'language' => $importer['language'],
2970 'to_name' => $importer['username'],
2971 'to_email' => $importer['email'],
2972 'uid' => $importer['importer_uid'],
2974 'source_name' => $msg['from-name'],
2975 'source_link' => $importer['url'],
2976 'source_photo' => $importer['thumb'],
2977 'verb' => ACTIVITY_POST,
2981 notification($notif_params);
2987 $community_page = 0;
2988 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2990 $community_page = intval($rawtags[0]['data']);
2992 if(intval($importer['forum']) != $community_page) {
2993 q("update contact set forum = %d where id = %d",
2994 intval($community_page),
2995 intval($importer['id'])
2997 $importer['forum'] = (string) $community_page;
3000 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3002 // process any deleted entries
3004 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3005 if(is_array($del_entries) && count($del_entries)) {
3006 foreach($del_entries as $dentry) {
3008 if(isset($dentry['attribs']['']['ref'])) {
3009 $uri = $dentry['attribs']['']['ref'];
3011 if(isset($dentry['attribs']['']['when'])) {
3012 $when = $dentry['attribs']['']['when'];
3013 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3016 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3020 // check for relayed deletes to our conversation
3023 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3025 intval($importer['importer_uid'])
3028 $parent_uri = $r[0]['parent-uri'];
3029 if($r[0]['id'] != $r[0]['parent'])
3036 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3039 logger('local_delivery: possible community delete');
3042 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3044 // was the top-level post for this reply written by somebody on this site?
3045 // Specifically, the recipient?
3047 $is_a_remote_delete = false;
3049 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3050 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3051 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3052 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3053 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3054 AND `item`.`uid` = %d
3060 intval($importer['importer_uid'])
3063 $is_a_remote_delete = true;
3065 // Does this have the characteristics of a community or private group comment?
3066 // If it's a reply to a wall post on a community/prvgroup page it's a
3067 // valid community comment. Also forum_mode makes it valid for sure.
3068 // If neither, it's not.
3070 if($is_a_remote_delete && $community) {
3071 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3072 $is_a_remote_delete = false;
3073 logger('local_delivery: not a community delete');
3077 if($is_a_remote_delete) {
3078 logger('local_delivery: received remote delete');
3082 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3083 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3085 intval($importer['importer_uid']),
3086 intval($importer['id'])
3092 if($item['deleted'])
3095 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3097 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3098 $xo = parse_xml_string($item['object'],false);
3099 $xt = parse_xml_string($item['target'],false);
3101 if($xt->type === ACTIVITY_OBJ_NOTE) {
3102 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3104 intval($importer['importer_uid'])
3108 // For tags, the owner cannot remove the tag on the author's copy of the post.
3110 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3111 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3112 $author_copy = (($item['origin']) ? true : false);
3114 if($owner_remove && $author_copy)
3116 if($author_remove || $owner_remove) {
3117 $tags = explode(',',$i[0]['tag']);
3120 foreach($tags as $tag)
3121 if(trim($tag) !== trim($xo->body))
3122 $newtags[] = trim($tag);
3124 q("update item set tag = '%s' where id = %d",
3125 dbesc(implode(',',$newtags)),
3128 create_tags_from_item($i[0]['id']);
3134 if($item['uri'] == $item['parent-uri']) {
3135 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3136 `body` = '', `title` = ''
3137 WHERE `parent-uri` = '%s' AND `uid` = %d",
3139 dbesc(datetime_convert()),
3140 dbesc($item['uri']),
3141 intval($importer['importer_uid'])
3143 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3144 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3145 update_thread_uri($item['uri'], $importer['importer_uid']);
3148 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3149 `body` = '', `title` = ''
3150 WHERE `uri` = '%s' AND `uid` = %d",
3152 dbesc(datetime_convert()),
3154 intval($importer['importer_uid'])
3156 create_tags_from_itemuri($uri, $importer['importer_uid']);
3157 create_files_from_itemuri($uri, $importer['importer_uid']);
3158 update_thread_uri($uri, $importer['importer_uid']);
3159 if($item['last-child']) {
3160 // ensure that last-child is set in case the comment that had it just got wiped.
3161 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3162 dbesc(datetime_convert()),
3163 dbesc($item['parent-uri']),
3164 intval($item['uid'])
3166 // who is the last child now?
3167 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3168 ORDER BY `created` DESC LIMIT 1",
3169 dbesc($item['parent-uri']),
3170 intval($importer['importer_uid'])
3173 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3178 // if this is a relayed delete, propagate it to other recipients
3180 if($is_a_remote_delete)
3181 proc_run('php',"include/notifier.php","drop",$item['id']);
3189 foreach($feed->get_items() as $item) {
3192 $item_id = $item->get_id();
3193 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3194 if(isset($rawthread[0]['attribs']['']['ref'])) {
3196 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3202 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3205 logger('local_delivery: possible community reply');
3208 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3210 // was the top-level post for this reply written by somebody on this site?
3211 // Specifically, the recipient?
3213 $is_a_remote_comment = false;
3214 $top_uri = $parent_uri;
3216 $r = q("select `item`.`parent-uri` from `item`
3217 WHERE `item`.`uri` = '%s'
3221 if($r && count($r)) {
3222 $top_uri = $r[0]['parent-uri'];
3224 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3225 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3226 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3227 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3228 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3229 AND `item`.`uid` = %d
3235 intval($importer['importer_uid'])
3238 $is_a_remote_comment = true;
3241 // Does this have the characteristics of a community or private group comment?
3242 // If it's a reply to a wall post on a community/prvgroup page it's a
3243 // valid community comment. Also forum_mode makes it valid for sure.
3244 // If neither, it's not.
3246 if($is_a_remote_comment && $community) {
3247 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3248 $is_a_remote_comment = false;
3249 logger('local_delivery: not a community reply');
3253 if($is_a_remote_comment) {
3254 logger('local_delivery: received remote comment');
3256 // remote reply to our post. Import and then notify everybody else.
3258 $datarray = get_atom_elements($feed, $item);
3260 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3262 intval($importer['importer_uid'])
3265 // Update content if 'updated' changes
3269 if (edited_timestamp_is_newer($r[0], $datarray)) {
3271 // do not accept (ignore) an earlier edit than one we currently have.
3272 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3275 logger('received updated comment' , LOGGER_DEBUG);
3276 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3277 dbesc($datarray['title']),
3278 dbesc($datarray['body']),
3279 dbesc($datarray['tag']),
3280 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3281 dbesc(datetime_convert()),
3283 intval($importer['importer_uid'])
3285 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3287 proc_run('php',"include/notifier.php","comment-import",$iid);
3296 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3297 intval($importer['importer_uid'])
3301 $datarray['type'] = 'remote-comment';
3302 $datarray['wall'] = 1;
3303 $datarray['parent-uri'] = $parent_uri;
3304 $datarray['uid'] = $importer['importer_uid'];
3305 $datarray['owner-name'] = $own[0]['name'];
3306 $datarray['owner-link'] = $own[0]['url'];
3307 $datarray['owner-avatar'] = $own[0]['thumb'];
3308 $datarray['contact-id'] = $importer['id'];
3310 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3312 $datarray['type'] = 'activity';
3313 $datarray['gravity'] = GRAVITY_LIKE;
3314 $datarray['last-child'] = 0;
3315 // only one like or dislike per person
3316 // splitted into two queries for performance issues
3317 $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",
3318 intval($datarray['uid']),
3319 intval($datarray['contact-id']),
3320 dbesc($datarray['verb']),
3321 dbesc($datarray['parent-uri'])
3327 $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",
3328 intval($datarray['uid']),
3329 intval($datarray['contact-id']),
3330 dbesc($datarray['verb']),
3331 dbesc($datarray['parent-uri'])
3338 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3340 $xo = parse_xml_string($datarray['object'],false);
3341 $xt = parse_xml_string($datarray['target'],false);
3343 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3345 // fetch the parent item
3347 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3349 intval($importer['importer_uid'])
3354 // extract tag, if not duplicate, and this user allows tags, add to parent item
3356 if($xo->id && $xo->content) {
3357 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3358 if(! (stristr($tagp[0]['tag'],$newtag))) {
3359 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3360 intval($importer['importer_uid'])
3362 if(count($i) && ! intval($i[0]['blocktags'])) {
3363 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3364 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3365 intval($tagp[0]['id']),
3366 dbesc(datetime_convert()),
3367 dbesc(datetime_convert())
3369 create_tags_from_item($tagp[0]['id']);
3377 $posted_id = item_store($datarray);
3381 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3383 intval($importer['importer_uid'])
3386 $parent = $r[0]['parent'];
3387 $parent_uri = $r[0]['parent-uri'];
3391 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3392 dbesc(datetime_convert()),
3393 intval($importer['importer_uid']),
3394 intval($r[0]['parent'])
3397 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3398 dbesc(datetime_convert()),
3399 intval($importer['importer_uid']),
3404 if($posted_id && $parent) {
3406 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3408 if((! $is_like) && (! $importer['self'])) {
3410 require_once('include/enotify.php');
3413 'type' => NOTIFY_COMMENT,
3414 'notify_flags' => $importer['notify-flags'],
3415 'language' => $importer['language'],
3416 'to_name' => $importer['username'],
3417 'to_email' => $importer['email'],
3418 'uid' => $importer['importer_uid'],
3419 'item' => $datarray,
3420 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3421 'source_name' => stripslashes($datarray['author-name']),
3422 'source_link' => $datarray['author-link'],
3423 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3424 ? $importer['thumb'] : $datarray['author-avatar']),
3425 'verb' => ACTIVITY_POST,
3427 'parent' => $parent,
3428 'parent_uri' => $parent_uri,
3440 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3442 $item_id = $item->get_id();
3443 $datarray = get_atom_elements($feed,$item);
3445 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3448 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3450 intval($importer['importer_uid'])
3453 // Update content if 'updated' changes
3456 if (edited_timestamp_is_newer($r[0], $datarray)) {
3458 // do not accept (ignore) an earlier edit than one we currently have.
3459 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3462 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3463 dbesc($datarray['title']),
3464 dbesc($datarray['body']),
3465 dbesc($datarray['tag']),
3466 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3467 dbesc(datetime_convert()),
3469 intval($importer['importer_uid'])
3471 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3474 // update last-child if it changes
3476 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3477 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3478 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3479 dbesc(datetime_convert()),
3481 intval($importer['importer_uid'])
3483 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3484 intval($allow[0]['data']),
3485 dbesc(datetime_convert()),
3487 intval($importer['importer_uid'])
3493 $datarray['parent-uri'] = $parent_uri;
3494 $datarray['uid'] = $importer['importer_uid'];
3495 $datarray['contact-id'] = $importer['id'];
3496 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3497 $datarray['type'] = 'activity';
3498 $datarray['gravity'] = GRAVITY_LIKE;
3499 // only one like or dislike per person
3500 // splitted into two queries for performance issues
3501 $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",
3502 intval($datarray['uid']),
3503 intval($datarray['contact-id']),
3504 dbesc($datarray['verb']),
3510 $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",
3511 intval($datarray['uid']),
3512 intval($datarray['contact-id']),
3513 dbesc($datarray['verb']),
3521 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3523 $xo = parse_xml_string($datarray['object'],false);
3524 $xt = parse_xml_string($datarray['target'],false);
3526 if($xt->type == ACTIVITY_OBJ_NOTE) {
3527 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3529 intval($importer['importer_uid'])
3534 // extract tag, if not duplicate, add to parent item
3536 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3537 q("UPDATE item SET tag = '%s' WHERE id = %d",
3538 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3541 create_tags_from_item($r[0]['id']);
3547 $posted_id = item_store($datarray);
3549 // find out if our user is involved in this conversation and wants to be notified.
3551 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3553 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3555 intval($importer['importer_uid'])
3558 if(count($myconv)) {
3559 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3561 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3562 if(! link_compare($datarray['author-link'],$importer_url)) {
3565 foreach($myconv as $conv) {
3567 // now if we find a match, it means we're in this conversation
3569 if(! link_compare($conv['author-link'],$importer_url))
3572 require_once('include/enotify.php');
3574 $conv_parent = $conv['parent'];
3577 'type' => NOTIFY_COMMENT,
3578 'notify_flags' => $importer['notify-flags'],
3579 'language' => $importer['language'],
3580 'to_name' => $importer['username'],
3581 'to_email' => $importer['email'],
3582 'uid' => $importer['importer_uid'],
3583 'item' => $datarray,
3584 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3585 'source_name' => stripslashes($datarray['author-name']),
3586 'source_link' => $datarray['author-link'],
3587 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3588 ? $importer['thumb'] : $datarray['author-avatar']),
3589 'verb' => ACTIVITY_POST,
3591 'parent' => $conv_parent,
3592 'parent_uri' => $parent_uri
3596 // only send one notification
3608 // Head post of a conversation. Have we seen it? If not, import it.
3611 $item_id = $item->get_id();
3612 $datarray = get_atom_elements($feed,$item);
3614 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3615 $ev = bbtoevent($datarray['body']);
3616 if(x($ev,'desc') && x($ev,'start')) {
3617 $ev['cid'] = $importer['id'];
3618 $ev['uid'] = $importer['uid'];
3619 $ev['uri'] = $item_id;
3620 $ev['edited'] = $datarray['edited'];
3621 $ev['private'] = $datarray['private'];
3623 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3625 intval($importer['uid'])
3628 $ev['id'] = $r[0]['id'];
3629 $xyz = event_store($ev);
3634 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3636 intval($importer['importer_uid'])
3639 // Update content if 'updated' changes
3642 if (edited_timestamp_is_newer($r[0], $datarray)) {
3644 // do not accept (ignore) an earlier edit than one we currently have.
3645 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3648 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3649 dbesc($datarray['title']),
3650 dbesc($datarray['body']),
3651 dbesc($datarray['tag']),
3652 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3653 dbesc(datetime_convert()),
3655 intval($importer['importer_uid'])
3657 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3658 update_thread_uri($item_id, $importer['importer_uid']);
3661 // update last-child if it changes
3663 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3664 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3665 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3666 intval($allow[0]['data']),
3667 dbesc(datetime_convert()),
3669 intval($importer['importer_uid'])
3675 $datarray['parent-uri'] = $item_id;
3676 $datarray['uid'] = $importer['importer_uid'];
3677 $datarray['contact-id'] = $importer['id'];
3680 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3681 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3682 // but otherwise there's a possible data mixup on the sender's system.
3683 // the tgroup delivery code called from item_store will correct it if it's a forum,
3684 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3685 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3686 $datarray['owner-name'] = $importer['senderName'];
3687 $datarray['owner-link'] = $importer['url'];
3688 $datarray['owner-avatar'] = $importer['thumb'];
3691 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3694 // This is my contact on another system, but it's really me.
3695 // Turn this into a wall post.
3697 if($importer['remote_self']) {
3698 if ($importer['remote_self'] == 2) {
3699 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3700 intval($importer['importer_uid']));
3702 $datarray['contact-id'] = $r[0]["id"];
3704 $datarray['owner-name'] = $r[0]["name"];
3705 $datarray['owner-link'] = $r[0]["url"];
3706 $datarray['owner-avatar'] = $r[0]["photo"];
3708 $datarray['author-name'] = $datarray['owner-name'];
3709 $datarray['author-link'] = $datarray['owner-link'];
3710 $datarray['author-avatar'] = $datarray['owner-avatar'];
3718 $posted_id = item_store($datarray, false, $notify);
3720 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3721 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3724 $xo = parse_xml_string($datarray['object'],false);
3726 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3728 // somebody was poked/prodded. Was it me?
3730 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3732 foreach($links->link as $l) {
3733 $atts = $l->attributes();
3734 switch($atts['rel']) {
3736 $Blink = $atts['href'];
3742 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3744 // send a notification
3745 require_once('include/enotify.php');
3748 'type' => NOTIFY_POKE,
3749 'notify_flags' => $importer['notify-flags'],
3750 'language' => $importer['language'],
3751 'to_name' => $importer['username'],
3752 'to_email' => $importer['email'],
3753 'uid' => $importer['importer_uid'],
3754 'item' => $datarray,
3755 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3756 'source_name' => stripslashes($datarray['author-name']),
3757 'source_link' => $datarray['author-link'],
3758 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3759 ? $importer['thumb'] : $datarray['author-avatar']),
3760 'verb' => $datarray['verb'],
3761 'otype' => 'person',
3762 'activity' => $verb,
3779 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3780 $url = notags(trim($datarray['author-link']));
3781 $name = notags(trim($datarray['author-name']));
3782 $photo = notags(trim($datarray['author-avatar']));
3784 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3785 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3786 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3788 if(is_array($contact)) {
3789 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3790 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3791 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3792 intval(CONTACT_IS_FRIEND),
3793 intval($contact['id']),
3794 intval($importer['uid'])
3797 // send email notification to owner?
3801 // create contact record
3803 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3804 `blocked`, `readonly`, `pending`, `writable` )
3805 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3806 intval($importer['uid']),
3807 dbesc(datetime_convert()),
3809 dbesc(normalise_link($url)),
3813 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3814 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3816 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3817 intval($importer['uid']),
3821 $contact_record = $r[0];
3823 // create notification
3824 $hash = random_string();
3826 if(is_array($contact_record)) {
3827 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3828 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3829 intval($importer['uid']),
3830 intval($contact_record['id']),
3832 dbesc(datetime_convert())
3836 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3837 intval($importer['uid'])
3842 if(intval($r[0]['def_gid'])) {
3843 require_once('include/group.php');
3844 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3847 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3848 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3853 'type' => NOTIFY_INTRO,
3854 'notify_flags' => $r[0]['notify-flags'],
3855 'language' => $r[0]['language'],
3856 'to_name' => $r[0]['username'],
3857 'to_email' => $r[0]['email'],
3858 'uid' => $r[0]['uid'],
3859 'link' => $a->get_baseurl() . '/notifications/intro',
3860 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3861 'source_link' => $contact_record['url'],
3862 'source_photo' => $contact_record['photo'],
3863 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3873 function lose_follower($importer,$contact,$datarray,$item) {
3875 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3876 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3877 intval(CONTACT_IS_SHARING),
3878 intval($contact['id'])
3882 contact_remove($contact['id']);
3886 function lose_sharer($importer,$contact,$datarray,$item) {
3888 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3889 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3890 intval(CONTACT_IS_FOLLOWER),
3891 intval($contact['id'])
3895 contact_remove($contact['id']);
3900 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3904 if(is_array($importer)) {
3905 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3906 intval($importer['uid'])
3910 // Diaspora has different message-ids in feeds than they do
3911 // through the direct Diaspora protocol. If we try and use
3912 // the feed, we'll get duplicates. So don't.
3914 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3917 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3919 // Use a single verify token, even if multiple hubs
3921 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3923 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3925 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3927 if(! strlen($contact['hub-verify'])) {
3928 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3929 dbesc($verify_token),
3930 intval($contact['id'])
3934 post_url($url,$params);
3936 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3943 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3947 $name = xmlify($name);
3948 $uri = xmlify($uri);
3951 $photo = xmlify($photo);
3955 $o .= "<name>$name</name>\r\n";
3956 $o .= "<uri>$uri</uri>\r\n";
3957 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3958 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3960 call_hooks('atom_author', $o);
3962 $o .= "</$tag>\r\n";
3966 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3970 if(! $item['parent'])
3973 if($item['deleted'])
3974 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3977 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3978 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3980 $body = $item['body'];
3982 $o = "\r\n\r\n<entry>\r\n";
3984 if(is_array($author))
3985 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3987 $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']));
3988 if(strlen($item['owner-name']))
3989 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3991 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3992 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3993 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3996 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3997 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3998 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3999 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4000 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4001 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4002 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4004 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4006 if($item['location']) {
4007 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4008 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4012 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4014 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4015 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4018 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4019 if($item['bookmark'])
4020 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4023 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4026 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4028 if($item['signed_text']) {
4029 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4030 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4033 $verb = construct_verb($item);
4034 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4035 $actobj = construct_activity_object($item);
4038 $actarg = construct_activity_target($item);
4042 $tags = item_getfeedtags($item);
4044 foreach($tags as $t) {
4045 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4049 $o .= item_getfeedattach($item);
4051 $mentioned = get_mentions($item);
4055 call_hooks('atom_entry', $o);
4057 $o .= '</entry>' . "\r\n";
4062 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4064 if(get_config('system','disable_embedded'))
4069 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4070 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4075 $img_start = strpos($orig_body, '[img');
4076 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4077 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4078 while( ($img_st_close !== false) && ($img_len !== false) ) {
4080 $img_st_close++; // make it point to AFTER the closing bracket
4081 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4083 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4086 if(stristr($image , $site . '/photo/')) {
4087 // Only embed locally hosted photos
4089 $i = basename($image);
4090 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4091 $x = strpos($i,'-');
4094 $res = substr($i,$x+1);
4095 $i = substr($i,0,$x);
4096 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4103 // Check to see if we should replace this photo link with an embedded image
4104 // 1. No need to do so if the photo is public
4105 // 2. If there's a contact-id provided, see if they're in the access list
4106 // for the photo. If so, embed it.
4107 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4108 // permissions, regardless of order but first check to see if they're an exact
4109 // match to save some processing overhead.
4111 if(has_permissions($r[0])) {
4113 $recips = enumerate_permissions($r[0]);
4114 if(in_array($cid, $recips)) {
4119 if(compare_permissions($item,$r[0]))
4124 $data = $r[0]['data'];
4125 $type = $r[0]['type'];
4127 // If a custom width and height were specified, apply before embedding
4128 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4129 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4131 $width = intval($match[1]);
4132 $height = intval($match[2]);
4134 $ph = new Photo($data, $type);
4135 if($ph->is_valid()) {
4136 $ph->scaleImage(max($width, $height));
4137 $data = $ph->imageString();
4138 $type = $ph->getType();
4142 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4143 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4144 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4150 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4151 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4152 if($orig_body === false)
4155 $img_start = strpos($orig_body, '[img');
4156 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4157 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4160 $new_body = $new_body . $orig_body;
4166 function has_permissions($obj) {
4167 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4172 function compare_permissions($obj1,$obj2) {
4173 // first part is easy. Check that these are exactly the same.
4174 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4175 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4176 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4177 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4180 // This is harder. Parse all the permissions and compare the resulting set.
4182 $recipients1 = enumerate_permissions($obj1);
4183 $recipients2 = enumerate_permissions($obj2);
4186 if($recipients1 == $recipients2)
4191 // returns an array of contact-ids that are allowed to see this object
4193 function enumerate_permissions($obj) {
4194 require_once('include/group.php');
4195 $allow_people = expand_acl($obj['allow_cid']);
4196 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4197 $deny_people = expand_acl($obj['deny_cid']);
4198 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4199 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4200 $deny = array_unique(array_merge($deny_people,$deny_groups));
4201 $recipients = array_diff($recipients,$deny);
4205 function item_getfeedtags($item) {
4208 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4210 for($x = 0; $x < $cnt; $x ++) {
4212 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4216 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4218 for($x = 0; $x < $cnt; $x ++) {
4220 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4226 function item_getfeedattach($item) {
4228 $arr = explode('[/attach],',$item['attach']);
4230 foreach($arr as $r) {
4232 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4234 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4235 if(intval($matches[2]))
4236 $ret .= 'length="' . intval($matches[2]) . '" ';
4237 if($matches[4] !== ' ')
4238 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4239 $ret .= ' />' . "\r\n";
4248 function item_expire($uid, $days, $network = "", $force = false) {
4250 if((! $uid) || ($days < 1))
4253 // $expire_network_only = save your own wall posts
4254 // and just expire conversations started by others
4256 $expire_network_only = get_pconfig($uid,'expire','network_only');
4257 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4259 if ($network != "") {
4260 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4261 // There is an index "uid_network_received" but not "uid_network_created"
4262 // This avoids the creation of another index just for one purpose.
4263 // And it doesn't really matter wether to look at "received" or "created"
4264 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4266 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4268 $r = q("SELECT * FROM `item`
4269 WHERE `uid` = %d $range
4280 $expire_items = get_pconfig($uid, 'expire','items');
4281 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4283 // Forcing expiring of items - but not notes and marked items
4285 $expire_items = true;
4287 $expire_notes = get_pconfig($uid, 'expire','notes');
4288 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4290 $expire_starred = get_pconfig($uid, 'expire','starred');
4291 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4293 $expire_photos = get_pconfig($uid, 'expire','photos');
4294 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4296 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4298 foreach($r as $item) {
4300 // don't expire filed items
4302 if(strpos($item['file'],'[') !== false)
4305 // Only expire posts, not photos and photo comments
4307 if($expire_photos==0 && strlen($item['resource-id']))
4309 if($expire_starred==0 && intval($item['starred']))
4311 if($expire_notes==0 && $item['type']=='note')
4313 if($expire_items==0 && $item['type']!='note')
4316 drop_item($item['id'],false);
4319 proc_run('php',"include/notifier.php","expire","$uid");
4324 function drop_items($items) {
4327 if(! local_user() && ! remote_user())
4331 foreach($items as $item) {
4332 $owner = drop_item($item,false);
4333 if($owner && ! $uid)
4338 // multiple threads may have been deleted, send an expire notification
4341 proc_run('php',"include/notifier.php","expire","$uid");
4345 function drop_item($id,$interactive = true) {
4349 // locate item to be deleted
4351 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4358 notice( t('Item not found.') . EOL);
4359 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4364 $owner = $item['uid'];
4368 // check if logged in user is either the author or owner of this item
4370 if(is_array($_SESSION['remote'])) {
4371 foreach($_SESSION['remote'] as $visitor) {
4372 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4373 $cid = $visitor['cid'];
4380 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4382 // Check if we should do HTML-based delete confirmation
4383 if($_REQUEST['confirm']) {
4384 // <form> can't take arguments in its "action" parameter
4385 // so add any arguments as hidden inputs
4386 $query = explode_querystring($a->query_string);
4388 foreach($query['args'] as $arg) {
4389 if(strpos($arg, 'confirm=') === false) {
4390 $arg_parts = explode('=', $arg);
4391 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4395 return replace_macros(get_markup_template('confirm.tpl'), array(
4397 '$message' => t('Do you really want to delete this item?'),
4398 '$extra_inputs' => $inputs,
4399 '$confirm' => t('Yes'),
4400 '$confirm_url' => $query['base'],
4401 '$confirm_name' => 'confirmed',
4402 '$cancel' => t('Cancel'),
4405 // Now check how the user responded to the confirmation query
4406 if($_REQUEST['canceled']) {
4407 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4410 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4413 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4414 dbesc(datetime_convert()),
4415 dbesc(datetime_convert()),
4418 create_tags_from_item($item['id']);
4419 create_files_from_item($item['id']);
4420 delete_thread($item['id']);
4422 // clean up categories and tags so they don't end up as orphans
4425 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4427 foreach($matches as $mtch) {
4428 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4434 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4436 foreach($matches as $mtch) {
4437 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4441 // If item is a link to a photo resource, nuke all the associated photos
4442 // (visitors will not have photo resources)
4443 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4444 // generate a resource-id and therefore aren't intimately linked to the item.
4446 if(strlen($item['resource-id'])) {
4447 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4448 dbesc($item['resource-id']),
4449 intval($item['uid'])
4451 // ignore the result
4454 // If item is a link to an event, nuke the event record.
4456 if(intval($item['event-id'])) {
4457 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4458 intval($item['event-id']),
4459 intval($item['uid'])
4461 // ignore the result
4464 // clean up item_id and sign meta-data tables
4467 // Old code - caused very long queries and warning entries in the mysql logfiles:
4469 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4470 intval($item['id']),
4471 intval($item['uid'])
4474 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4475 intval($item['id']),
4476 intval($item['uid'])
4480 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4482 // Creating list of parents
4483 $r = q("select id from item where parent = %d and uid = %d",
4484 intval($item['id']),
4485 intval($item['uid'])
4490 foreach ($r AS $row) {
4491 if ($parentid != "")
4494 $parentid .= $row["id"];
4498 if ($parentid != "") {
4499 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4501 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4504 // If it's the parent of a comment thread, kill all the kids
4506 if($item['uri'] == $item['parent-uri']) {
4507 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4508 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4509 dbesc(datetime_convert()),
4510 dbesc(datetime_convert()),
4511 dbesc($item['parent-uri']),
4512 intval($item['uid'])
4514 create_tags_from_item($item['parent-uri'], $item['uid']);
4515 create_files_from_item($item['parent-uri'], $item['uid']);
4516 delete_thread_uri($item['parent-uri'], $item['uid']);
4517 // ignore the result
4520 // ensure that last-child is set in case the comment that had it just got wiped.
4521 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4522 dbesc(datetime_convert()),
4523 dbesc($item['parent-uri']),
4524 intval($item['uid'])
4526 // who is the last child now?
4527 $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",
4528 dbesc($item['parent-uri']),
4529 intval($item['uid'])
4532 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4537 // Add a relayable_retraction signature for Diaspora.
4538 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4540 $drop_id = intval($item['id']);
4542 // send the notification upstream/downstream as the case may be
4544 proc_run('php',"include/notifier.php","drop","$drop_id");
4548 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4554 notice( t('Permission denied.') . EOL);
4555 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4562 function first_post_date($uid,$wall = false) {
4563 $r = q("select id, created from item
4564 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4566 order by created asc limit 1",
4568 intval($wall ? 1 : 0)
4571 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4572 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4577 function posted_dates($uid,$wall) {
4578 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4580 $dthen = first_post_date($uid,$wall);
4584 // Set the start and end date to the beginning of the month
4585 $dnow = substr($dnow,0,8).'01';
4586 $dthen = substr($dthen,0,8).'01';
4589 // Starting with the current month, get the first and last days of every
4590 // month down to and including the month of the first post
4591 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4592 $dstart = substr($dnow,0,8) . '01';
4593 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4594 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4595 $end_month = datetime_convert('','',$dend,'Y-m-d');
4596 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4597 $ret[] = array($str,$end_month,$start_month);
4598 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4604 function posted_date_widget($url,$uid,$wall) {
4607 if(! feature_enabled($uid,'archives'))
4610 // For former Facebook folks that left because of "timeline"
4612 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4615 $ret = posted_dates($uid,$wall);
4619 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4620 '$title' => t('Archives'),
4621 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4628 function store_diaspora_retract_sig($item, $user, $baseurl) {
4629 // Note that we can't add a target_author_signature
4630 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4631 // the comment, that means we're the home of the post, and Diaspora will only
4632 // check the parent_author_signature of retractions that it doesn't have to relay further
4634 // I don't think this function gets called for an "unlike," but I'll check anyway
4636 $enabled = intval(get_config('system','diaspora_enabled'));
4638 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4642 logger('drop_item: storing diaspora retraction signature');
4644 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4646 if(local_user() == $item['uid']) {
4648 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4649 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4652 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4653 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4656 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4657 // only handles DFRN deletes
4658 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4659 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4660 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4666 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4667 intval($item['id']),
4668 dbesc($signed_text),