3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $alternatelink = $owner['url'];
169 $alternatelink .= "/category/".$category;
171 $atom .= replace_macros($feed_template, array(
172 '$version' => xmlify(FRIENDICA_VERSION),
173 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
174 '$feed_title' => xmlify($owner['name']),
175 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
177 '$salmon' => $salmon,
178 '$alternatelink' => xmlify($alternatelink),
179 '$name' => xmlify($owner['name']),
180 '$profile_page' => xmlify($owner['url']),
181 '$photo' => xmlify($owner['photo']),
182 '$thumb' => xmlify($owner['thumb']),
183 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
184 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
185 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
186 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
187 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
190 call_hooks('atom_feed', $atom);
192 if(! count($items)) {
194 call_hooks('atom_feed_end', $atom);
196 $atom .= '</feed>' . "\r\n";
200 foreach($items as $item) {
202 // prevent private email from leaking.
203 if($item['network'] === NETWORK_MAIL)
206 // public feeds get html, our own nodes use bbcode
210 // catch any email that's in a public conversation and make sure it doesn't leak
218 $atom .= atom_entry($item,$type,null,$owner,true);
221 call_hooks('atom_feed_end', $atom);
223 $atom .= '</feed>' . "\r\n";
229 function construct_verb($item) {
231 return $item['verb'];
232 return ACTIVITY_POST;
235 function construct_activity_object($item) {
237 if($item['object']) {
238 $o = '<as:object>' . "\r\n";
239 $r = parse_xml_string($item['object'],false);
245 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
247 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
249 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
251 if(substr($r->link,0,1) === '<') {
252 // patch up some facebook "like" activity objects that got stored incorrectly
253 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
254 // we can probably remove this hack here and in the following function in a few months time.
255 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
256 $r->link = str_replace('&','&', $r->link);
257 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
261 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
264 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
265 $o .= '</as:object>' . "\r\n";
272 function construct_activity_target($item) {
274 if($item['target']) {
275 $o = '<as:target>' . "\r\n";
276 $r = parse_xml_string($item['target'],false);
280 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
282 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
284 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
286 if(substr($r->link,0,1) === '<') {
287 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
288 $r->link = str_replace('&','&', $r->link);
289 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
293 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
296 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
297 $o .= '</as:target>' . "\r\n";
306 * The purpose of this function is to apply system message length limits to
307 * imported messages without including any embedded photos in the length
309 if(! function_exists('limit_body_size')) {
310 function limit_body_size($body) {
312 // logger('limit_body_size: start', LOGGER_DEBUG);
314 $maxlen = get_max_import_size();
316 // If the length of the body, including the embedded images, is smaller
317 // than the maximum, then don't waste time looking for the images
318 if($maxlen && (strlen($body) > $maxlen)) {
320 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
327 $img_start = strpos($orig_body, '[img');
328 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
329 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
330 while(($img_st_close !== false) && ($img_end !== false)) {
332 $img_st_close++; // make it point to AFTER the closing bracket
333 $img_end += $img_start;
334 $img_end += strlen('[/img]');
336 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
337 // This is an embedded image
339 if( ($textlen + $img_start) > $maxlen ) {
340 if($textlen < $maxlen) {
341 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
342 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
347 $new_body = $new_body . substr($orig_body, 0, $img_start);
348 $textlen += $img_start;
351 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
355 if( ($textlen + $img_end) > $maxlen ) {
356 if($textlen < $maxlen) {
357 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
358 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
363 $new_body = $new_body . substr($orig_body, 0, $img_end);
364 $textlen += $img_end;
367 $orig_body = substr($orig_body, $img_end);
369 if($orig_body === false) // in case the body ends on a closing image tag
372 $img_start = strpos($orig_body, '[img');
373 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
374 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
377 if( ($textlen + strlen($orig_body)) > $maxlen) {
378 if($textlen < $maxlen) {
379 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
380 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
385 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
386 $new_body = $new_body . $orig_body;
387 $textlen += strlen($orig_body);
396 function title_is_body($title, $body) {
398 $title = strip_tags($title);
399 $title = trim($title);
400 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
401 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
403 $body = strip_tags($body);
405 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
406 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
408 if (strlen($title) < strlen($body))
409 $body = substr($body, 0, strlen($title));
411 if (($title != $body) and (substr($title, -3) == "...")) {
412 $pos = strrpos($title, "...");
414 $title = substr($title, 0, $pos);
415 $body = substr($body, 0, $pos);
419 return($title == $body);
424 function get_atom_elements($feed, $item, $contact = array()) {
426 require_once('library/HTMLPurifier.auto.php');
427 require_once('include/html2bbcode.php');
429 $best_photo = array();
433 $author = $item->get_author();
435 $res['author-name'] = unxmlify($author->get_name());
436 $res['author-link'] = unxmlify($author->get_link());
439 $res['author-name'] = unxmlify($feed->get_title());
440 $res['author-link'] = unxmlify($feed->get_permalink());
442 $res['uri'] = unxmlify($item->get_id());
443 $res['title'] = unxmlify($item->get_title());
444 $res['body'] = unxmlify($item->get_content());
445 $res['plink'] = unxmlify($item->get_link(0));
447 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
448 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
450 $res['body'] = nl2br($res['body']);
453 // removing the content of the title if its identically to the body
454 // This helps with auto generated titles e.g. from tumblr
455 if (title_is_body($res["title"], $res["body"]))
459 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
463 // look for a photo. We should check media size and find the best one,
464 // but for now let's just find any author photo
466 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
468 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
469 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
470 foreach($base as $link) {
471 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
472 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
473 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
478 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
480 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
481 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
482 if($base && count($base)) {
483 foreach($base as $link) {
484 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
485 $res['author-link'] = unxmlify($link['attribs']['']['href']);
486 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
487 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
488 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
494 // No photo/profile-link on the item - look at the feed level
496 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
497 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
498 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
499 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
503 if(! $res['author-avatar']) {
504 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
505 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
510 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
512 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
513 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
515 if($base && count($base)) {
516 foreach($base as $link) {
517 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
518 $res['author-link'] = unxmlify($link['attribs']['']['href']);
519 if(! (x($res,'author-avatar'))) {
520 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
521 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
528 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
529 if($apps && $apps[0]['attribs']['']['source']) {
530 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
531 if($res['app'] === 'web')
532 $res['app'] = 'OStatus';
535 // base64 encoded json structure representing Diaspora signature
537 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
539 $res['dsprsig'] = unxmlify($dsig[0]['data']);
542 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
544 $res['guid'] = unxmlify($dguid[0]['data']);
546 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
548 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
552 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
555 $have_real_body = false;
557 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
559 $have_real_body = true;
560 $res['body'] = $rawenv[0]['data'];
561 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
562 // make sure nobody is trying to sneak some html tags by us
563 $res['body'] = notags(base64url_decode($res['body']));
567 $res['body'] = limit_body_size($res['body']);
569 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
570 // the content type. Our own network only emits text normally, though it might have been converted to
571 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
572 // have to assume it is all html and needs to be purified.
574 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
575 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
576 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
579 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
581 $res['body'] = reltoabs($res['body'],$base_url);
583 $res['body'] = html2bb_video($res['body']);
585 $res['body'] = oembed_html2bbcode($res['body']);
587 $config = HTMLPurifier_Config::createDefault();
588 $config->set('Cache.DefinitionImpl', null);
590 // we shouldn't need a whitelist, because the bbcode converter
591 // will strip out any unsupported tags.
593 $purifier = new HTMLPurifier($config);
594 $res['body'] = $purifier->purify($res['body']);
596 $res['body'] = @html2bbcode($res['body']);
600 elseif(! $have_real_body) {
602 // it's not one of our messages and it has no tags
603 // so it's probably just text. We'll escape it just to be safe.
605 $res['body'] = escape_tags($res['body']);
609 // this tag is obsolete but we keep it for really old sites
611 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
612 if($allow && $allow[0]['data'] == 1)
613 $res['last-child'] = 1;
615 $res['last-child'] = 0;
617 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
618 if($private && intval($private[0]['data']) > 0)
619 $res['private'] = intval($private[0]['data']);
623 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
624 if($extid && $extid[0]['data'])
625 $res['extid'] = $extid[0]['data'];
627 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
629 $res['location'] = unxmlify($rawlocation[0]['data']);
632 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
634 $res['created'] = unxmlify($rawcreated[0]['data']);
637 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
639 $res['edited'] = unxmlify($rawedited[0]['data']);
641 if((x($res,'edited')) && (! (x($res,'created'))))
642 $res['created'] = $res['edited'];
644 if(! $res['created'])
645 $res['created'] = $item->get_date('c');
648 $res['edited'] = $item->get_date('c');
651 // Disallow time travelling posts
653 $d1 = strtotime($res['created']);
654 $d2 = strtotime($res['edited']);
655 $d3 = strtotime('now');
658 $res['created'] = datetime_convert();
660 $res['edited'] = datetime_convert();
662 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
663 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
664 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
665 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
667 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
668 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
669 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
672 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
673 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
675 foreach($base as $link) {
676 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
677 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
678 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
683 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
685 $res['coord'] = unxmlify($rawgeo[0]['data']);
687 if ($contact["network"] == NETWORK_FEED) {
688 $res['verb'] = ACTIVITY_POST;
689 $res['object-type'] = ACTIVITY_OBJ_NOTE;
692 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
694 // select between supported verbs
697 $res['verb'] = unxmlify($rawverb[0]['data']);
700 // translate OStatus unfollow to activity streams if it happened to get selected
702 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
703 $res['verb'] = ACTIVITY_UNFOLLOW;
705 $cats = $item->get_categories();
708 foreach($cats as $cat) {
709 $term = $cat->get_term();
711 $term = $cat->get_label();
712 $scheme = $cat->get_scheme();
713 if($scheme && $term && stristr($scheme,'X-DFRN:'))
714 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
716 $tag_arr[] = notags(trim($term));
718 $res['tag'] = implode(',', $tag_arr);
721 $attach = $item->get_enclosures();
724 foreach($attach as $att) {
725 $len = intval($att->get_length());
726 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
727 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
728 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
729 if(strpos($type,';'))
730 $type = substr($type,0,strpos($type,';'));
731 if((! $link) || (strpos($link,'http') !== 0))
737 $type = 'application/octet-stream';
739 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
741 $res['attach'] = implode(',', $att_arr);
744 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
747 $res['object'] = '<object>' . "\n";
748 $child = $rawobj[0]['child'];
749 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
750 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
751 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
754 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
756 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
758 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
760 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
763 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
764 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
765 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
767 $body = html2bb_video($body);
769 $config = HTMLPurifier_Config::createDefault();
770 $config->set('Cache.DefinitionImpl', null);
772 $purifier = new HTMLPurifier($config);
773 $body = $purifier->purify($body);
774 $body = html2bbcode($body);
777 $res['object'] .= '<content>' . $body . '</content>' . "\n";
780 $res['object'] .= '</object>' . "\n";
783 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
786 $res['target'] = '<target>' . "\n";
787 $child = $rawobj[0]['child'];
788 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
789 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
792 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
794 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
796 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
798 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
801 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
802 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
803 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
805 $body = html2bb_video($body);
807 $config = HTMLPurifier_Config::createDefault();
808 $config->set('Cache.DefinitionImpl', null);
810 $purifier = new HTMLPurifier($config);
811 $body = $purifier->purify($body);
812 $body = html2bbcode($body);
815 $res['target'] .= '<content>' . $body . '</content>' . "\n";
818 $res['target'] .= '</target>' . "\n";
821 // This is some experimental stuff. By now retweets are shown with "RT:"
822 // But: There is data so that the message could be shown similar to native retweets
823 // There is some better way to parse this array - but it didn't worked for me.
824 $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
825 if (is_array($child)) {
826 logger('get_atom_elements: Looking for status.net repeated message');
828 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
829 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
830 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
831 $uri = $author["uri"][0]["data"];
832 $name = $author["name"][0]["data"];
833 $avatar = @array_shift($author["link"][2]["attribs"]);
834 $avatar = $avatar["href"];
836 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
837 logger('get_atom_elements: fixing sender of repeated message.');
839 if (!intval(get_config('system','wall-to-wall_share'))) {
840 $prefix = "[share author='".str_replace("'", "'",$name).
842 "' avatar='".$avatar.
843 "' link='".$orig_uri."']";
845 $res["body"] = $prefix.html2bbcode($message)."[/share]";
847 $res["owner-name"] = $res["author-name"];
848 $res["owner-link"] = $res["author-link"];
849 $res["owner-avatar"] = $res["author-avatar"];
851 $res["author-name"] = $name;
852 $res["author-link"] = $uri;
853 $res["author-avatar"] = $avatar;
855 $res["body"] = html2bbcode($message);
860 // Search for ostatus conversation url
861 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
863 if (is_array($links)) {
864 foreach ($links as $link) {
865 $conversation = array_shift($link["attribs"]);
867 if ($conversation["rel"] == "ostatus:conversation") {
868 $res["ostatus_conversation"] = $conversation["href"];
869 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
874 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
875 $res["body"] = $res["title"].add_page_info($res['plink']);
877 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
878 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
879 $res["body"] = add_page_info_to_body($res["body"]);
880 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
881 $res["body"] = add_page_info_to_body($res["body"]);
884 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
886 call_hooks('parse_atom', $arr);
891 function add_page_info($url, $no_photos = false, $photo = "") {
892 require_once("mod/parse_url.php");
894 $data = parseurl_getsiteinfo($url, true);
896 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
898 // It maybe is a rich content, but if it does have everything that a link has,
899 // then treat it that way
900 if (($data["type"] == "rich") AND is_string($data["title"]) AND
901 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
902 $data["type"] = "link";
904 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
907 if ($no_photos AND ($data["type"] == "photo"))
910 if (($data["type"] != "photo") AND is_string($data["title"]))
911 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
913 if (($data["type"] != "video") AND ($photo != ""))
914 $text .= '[img]'.$photo.'[/img]';
915 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
916 $imagedata = $data["images"][0];
917 $text .= '[img]'.$imagedata["src"].'[/img]';
920 if (($data["type"] != "photo") AND is_string($data["text"]))
921 $text .= "[quote]".$data["text"]."[/quote]";
923 return("\n[class=type-".$data["type"]."]".$text."[/class]");
926 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
928 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
930 $URLSearchString = "^\[\]";
932 // Adding these spaces is a quick hack due to my problems with regular expressions :)
933 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
936 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
938 // Convert urls without bbcode elements
939 if (!$matches AND $texturl) {
940 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
942 // Yeah, a hack. I really hate regular expressions :)
944 $matches[1] = $matches[2];
948 $footer = add_page_info($matches[1], $no_photos);
950 // Remove the link from the body if the link is attached at the end of the post
951 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
952 $removedlink = trim(str_replace($matches[1], "", $body));
953 if (($removedlink == "") OR strstr($body, $removedlink))
954 $body = $removedlink;
956 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
957 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
958 if (($removedlink == "") OR strstr($body, $removedlink))
959 $body = $removedlink;
962 // Add the page information to the bottom
963 if (isset($footer) AND (trim($footer) != ""))
969 function encode_rel_links($links) {
971 if(! ((is_array($links)) && (count($links))))
973 foreach($links as $link) {
975 if($link['attribs']['']['rel'])
976 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
977 if($link['attribs']['']['type'])
978 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
979 if($link['attribs']['']['href'])
980 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
981 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
982 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
983 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
984 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
992 function item_store($arr,$force_parent = false, $notify = false) {
994 // If it is a posting where users should get notifications, then define it as wall posting
997 $arr['type'] = 'wall';
999 $arr['last-child'] = 1;
1000 $arr['network'] = NETWORK_DFRN;
1003 // If a Diaspora signature structure was passed in, pull it out of the
1004 // item array and set it aside for later storage.
1007 if(x($arr,'dsprsig')) {
1008 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1009 unset($arr['dsprsig']);
1012 // if an OStatus conversation url was passed in, it is stored and then
1013 // removed from the array.
1014 $ostatus_conversation = null;
1016 if (isset($arr["ostatus_conversation"])) {
1017 $ostatus_conversation = $arr["ostatus_conversation"];
1018 unset($arr["ostatus_conversation"]);
1021 if(x($arr, 'gravity'))
1022 $arr['gravity'] = intval($arr['gravity']);
1023 elseif($arr['parent-uri'] === $arr['uri'])
1024 $arr['gravity'] = 0;
1025 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1026 $arr['gravity'] = 6;
1028 $arr['gravity'] = 6; // extensible catchall
1030 if(! x($arr,'type'))
1031 $arr['type'] = 'remote';
1035 /* check for create date and expire time */
1036 $uid = intval($arr['uid']);
1037 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1039 $expire_interval = $r[0]['expire'];
1040 if ($expire_interval>0) {
1041 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1042 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1043 if ($created_date < $expire_date) {
1044 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1050 // If there is no guid then take the same guid that was taken before for the same uri
1051 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1052 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1053 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1054 dbesc(trim($arr['uri']))
1058 $arr['guid'] = $r[0]["guid"];
1059 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1063 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1064 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1065 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1066 // $arr['body'] = strip_tags($arr['body']);
1069 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1070 require_once('library/langdet/Text/LanguageDetect.php');
1071 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1072 $l = new Text_LanguageDetect;
1073 //$lng = $l->detectConfidence($naked_body);
1074 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1075 $lng = $l->detect($naked_body, 3);
1077 if (sizeof($lng) > 0) {
1080 foreach ($lng as $language => $score) {
1081 if ($postopts == "")
1082 $postopts = "lang=";
1086 $postopts .= $language.";".$score;
1088 $arr['postopts'] = $postopts;
1092 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1093 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1094 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1095 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1096 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1097 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1098 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1099 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1100 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1101 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1102 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1103 $arr['commented'] = datetime_convert();
1104 $arr['received'] = datetime_convert();
1105 $arr['changed'] = datetime_convert();
1106 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1107 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1108 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1109 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1110 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1111 $arr['deleted'] = 0;
1112 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1113 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1114 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1115 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1116 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1117 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1118 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1119 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1120 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1121 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1122 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1123 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1124 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1125 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1126 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1127 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1128 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1129 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1130 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1131 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1133 if ($arr['plink'] == "") {
1135 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1138 if ($arr['network'] == "") {
1139 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1140 intval($arr['contact-id']),
1145 $arr['network'] = $r[0]["network"];
1147 // Fallback to friendica (why is it empty in some cases?)
1148 if ($arr['network'] == "")
1149 $arr['network'] = NETWORK_DFRN;
1151 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1154 $arr['thr-parent'] = $arr['parent-uri'];
1155 if($arr['parent-uri'] === $arr['uri']) {
1157 $parent_deleted = 0;
1158 $allow_cid = $arr['allow_cid'];
1159 $allow_gid = $arr['allow_gid'];
1160 $deny_cid = $arr['deny_cid'];
1161 $deny_gid = $arr['deny_gid'];
1162 $notify_type = 'wall-new';
1166 // find the parent and snarf the item id and ACLs
1167 // and anything else we need to inherit
1169 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1170 dbesc($arr['parent-uri']),
1176 // is the new message multi-level threaded?
1177 // even though we don't support it now, preserve the info
1178 // and re-attach to the conversation parent.
1180 if($r[0]['uri'] != $r[0]['parent-uri']) {
1181 $arr['parent-uri'] = $r[0]['parent-uri'];
1182 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1183 ORDER BY `id` ASC LIMIT 1",
1184 dbesc($r[0]['parent-uri']),
1185 dbesc($r[0]['parent-uri']),
1192 $parent_id = $r[0]['id'];
1193 $parent_deleted = $r[0]['deleted'];
1194 $allow_cid = $r[0]['allow_cid'];
1195 $allow_gid = $r[0]['allow_gid'];
1196 $deny_cid = $r[0]['deny_cid'];
1197 $deny_gid = $r[0]['deny_gid'];
1198 $arr['wall'] = $r[0]['wall'];
1199 $notify_type = 'comment-new';
1201 // if the parent is private, force privacy for the entire conversation
1202 // This differs from the above settings as it subtly allows comments from
1203 // email correspondents to be private even if the overall thread is not.
1205 if($r[0]['private'])
1206 $arr['private'] = $r[0]['private'];
1208 // Edge case. We host a public forum that was originally posted to privately.
1209 // The original author commented, but as this is a comment, the permissions
1210 // weren't fixed up so it will still show the comment as private unless we fix it here.
1212 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1213 $arr['private'] = 0;
1216 // If its a post from myself then tag the thread as "mention"
1217 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1218 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1221 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1222 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1223 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1224 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1225 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1231 // Allow one to see reply tweets from status.net even when
1232 // we don't have or can't see the original post.
1235 logger('item_store: $force_parent=true, reply converted to top-level post.');
1237 $arr['parent-uri'] = $arr['uri'];
1238 $arr['gravity'] = 0;
1241 logger('item_store: item parent was not found - ignoring item');
1245 $parent_deleted = 0;
1249 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1253 if($r && count($r)) {
1254 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1258 call_hooks('post_remote',$arr);
1260 if(x($arr,'cancel')) {
1261 logger('item_store: post cancelled by plugin.');
1267 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1269 $r = dbq("INSERT INTO `item` (`"
1270 . implode("`, `", array_keys($arr))
1272 . implode("', '", array_values($arr))
1275 // find the item we just created
1277 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1278 $arr['uri'], // already dbesc'd
1283 $current_post = $r[0]['id'];
1284 logger('item_store: created item ' . $current_post);
1286 // Only check for notifications on start posts
1287 if ($arr['parent-uri'] === $arr['uri']) {
1288 add_thread($r[0]['id']);
1289 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1291 // Send a notification for every new post?
1292 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1293 intval($arr['contact-id']),
1298 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1299 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1300 intval($arr['uid']));
1302 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1303 intval($current_post),
1309 require_once('include/enotify.php');
1311 'type' => NOTIFY_SHARE,
1312 'notify_flags' => $u[0]['notify-flags'],
1313 'language' => $u[0]['language'],
1314 'to_name' => $u[0]['username'],
1315 'to_email' => $u[0]['email'],
1316 'uid' => $u[0]['uid'],
1318 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1319 'source_name' => $item[0]['author-name'],
1320 'source_link' => $item[0]['author-link'],
1321 'source_photo' => $item[0]['author-avatar'],
1322 'verb' => ACTIVITY_TAG,
1325 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1330 logger('item_store: could not locate created item');
1334 logger('item_store: duplicated post occurred. Removing duplicates.');
1335 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1337 intval($arr['uid']),
1338 intval($current_post)
1342 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1343 $parent_id = $current_post;
1345 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1348 $private = $arr['private'];
1350 // Set parent id - and also make sure to inherit the parent's ACLs.
1352 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1353 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1360 intval($parent_deleted),
1361 intval($current_post)
1364 // Complete ostatus threads
1365 if ($ostatus_conversation)
1366 complete_conversation($current_post, $ostatus_conversation);
1368 $arr['id'] = $current_post;
1369 $arr['parent'] = $parent_id;
1370 $arr['allow_cid'] = $allow_cid;
1371 $arr['allow_gid'] = $allow_gid;
1372 $arr['deny_cid'] = $deny_cid;
1373 $arr['deny_gid'] = $deny_gid;
1374 $arr['private'] = $private;
1375 $arr['deleted'] = $parent_deleted;
1377 // update the commented timestamp on the parent
1379 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1380 dbesc(datetime_convert()),
1381 dbesc(datetime_convert()),
1384 update_thread($parent_id);
1387 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1388 intval($current_post),
1389 dbesc($dsprsig->signed_text),
1390 dbesc($dsprsig->signature),
1391 dbesc($dsprsig->signer)
1397 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1400 if($arr['last-child']) {
1401 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1403 intval($arr['uid']),
1404 intval($current_post)
1408 $deleted = tag_deliver($arr['uid'],$current_post);
1410 // current post can be deleted if is for a communuty page and no mention are
1414 // Store the fresh generated item into the cache
1415 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1417 if (($cachefile != '') AND !file_exists($cachefile)) {
1418 $s = prepare_text($arr['body']);
1420 $stamp1 = microtime(true);
1421 file_put_contents($cachefile, $s);
1422 $a->save_timestamp($stamp1, "file");
1423 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1426 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1427 if (count($r) == 1) {
1428 call_hooks('post_remote_end', $r[0]);
1430 logger('item_store: new item not found in DB, id ' . $current_post);
1434 create_tags_from_item($current_post);
1435 create_files_from_item($current_post);
1438 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1440 return $current_post;
1443 function get_item_guid($id) {
1444 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1446 return($r[0]["guid"]);
1451 function get_item_id($guid, $uid = 0) {
1457 $uid == local_user();
1459 // Does the given user have this item?
1461 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1462 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1463 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1466 $nick = $r[0]["nickname"];
1470 // Or is it anywhere on the server?
1472 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1473 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1474 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1475 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1476 AND `item`.`private` = 0 AND `item`.`wall` = 1
1477 AND `item`.`guid` = '%s'", dbesc($guid));
1480 $nick = $r[0]["nickname"];
1483 return(array("nick" => $nick, "id" => $id));
1487 function get_item_contact($item,$contacts) {
1488 if(! count($contacts) || (! is_array($item)))
1490 foreach($contacts as $contact) {
1491 if($contact['id'] == $item['contact-id']) {
1493 break; // NOTREACHED
1500 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1502 * @param int $item_id
1503 * @return bool true if item was deleted, else false
1505 function tag_deliver($uid,$item_id) {
1513 $u = q("select * from user where uid = %d limit 1",
1519 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1520 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1523 $i = q("select * from item where id = %d and uid = %d limit 1",
1532 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1534 // Diaspora uses their own hardwired link URL in @-tags
1535 // instead of the one we supply with webfinger
1537 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1539 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1541 foreach($matches as $mtch) {
1542 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1544 logger('tag_deliver: mention found: ' . $mtch[2]);
1550 if ( ($community_page || $prvgroup) &&
1551 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1552 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1554 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1555 q("DELETE FROM item WHERE id = %d and uid = %d",
1565 // send a notification
1567 // use a local photo if we have one
1569 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1570 intval($u[0]['uid']),
1571 dbesc(normalise_link($item['author-link']))
1573 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1576 require_once('include/enotify.php');
1578 'type' => NOTIFY_TAGSELF,
1579 'notify_flags' => $u[0]['notify-flags'],
1580 'language' => $u[0]['language'],
1581 'to_name' => $u[0]['username'],
1582 'to_email' => $u[0]['email'],
1583 'uid' => $u[0]['uid'],
1585 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1586 'source_name' => $item['author-name'],
1587 'source_link' => $item['author-link'],
1588 'source_photo' => $photo,
1589 'verb' => ACTIVITY_TAG,
1591 'parent' => $item['parent']
1595 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1597 call_hooks('tagged', $arr);
1599 if((! $community_page) && (! $prvgroup))
1603 // tgroup delivery - setup a second delivery chain
1604 // prevent delivery looping - only proceed
1605 // if the message originated elsewhere and is a top-level post
1607 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1610 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1613 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1614 intval($u[0]['uid'])
1619 // also reset all the privacy bits to the forum default permissions
1621 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1623 $forum_mode = (($prvgroup) ? 2 : 1);
1625 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1626 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1627 intval($forum_mode),
1628 dbesc($c[0]['name']),
1629 dbesc($c[0]['url']),
1630 dbesc($c[0]['thumb']),
1632 dbesc($u[0]['allow_cid']),
1633 dbesc($u[0]['allow_gid']),
1634 dbesc($u[0]['deny_cid']),
1635 dbesc($u[0]['deny_gid']),
1638 update_thread($item_id);
1640 proc_run('php','include/notifier.php','tgroup',$item_id);
1646 function tgroup_check($uid,$item) {
1652 // check that the message originated elsewhere and is a top-level post
1654 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1658 $u = q("select * from user where uid = %d limit 1",
1664 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1665 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1668 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1670 // Diaspora uses their own hardwired link URL in @-tags
1671 // instead of the one we supply with webfinger
1673 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1675 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1677 foreach($matches as $mtch) {
1678 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1680 logger('tgroup_check: mention found: ' . $mtch[2]);
1688 if((! $community_page) && (! $prvgroup))
1702 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1706 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1708 if($contact['duplex'] && $contact['dfrn-id'])
1709 $idtosend = '0:' . $orig_id;
1710 if($contact['duplex'] && $contact['issued-id'])
1711 $idtosend = '1:' . $orig_id;
1713 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1715 $rino_enable = get_config('system','rino_encrypt');
1720 $ssl_val = intval(get_config('system','ssl_policy'));
1724 case SSL_POLICY_FULL:
1725 $ssl_policy = 'full';
1727 case SSL_POLICY_SELFSIGN:
1728 $ssl_policy = 'self';
1730 case SSL_POLICY_NONE:
1732 $ssl_policy = 'none';
1736 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1738 logger('dfrn_deliver: ' . $url);
1740 $xml = fetch_url($url);
1742 $curl_stat = $a->get_curl_code();
1744 return(-1); // timed out
1746 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1751 if(strpos($xml,'<?xml') === false) {
1752 logger('dfrn_deliver: no valid XML returned');
1753 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1757 $res = parse_xml_string($xml);
1759 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1760 return (($res->status) ? $res->status : 3);
1762 $postvars = array();
1763 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1764 $challenge = hex2bin((string) $res->challenge);
1765 $perm = (($res->perm) ? $res->perm : null);
1766 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1767 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1768 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1770 if($owner['page-flags'] == PAGE_PRVGROUP)
1773 $final_dfrn_id = '';
1776 if((($perm == 'rw') && (! intval($contact['writable'])))
1777 || (($perm == 'r') && (intval($contact['writable'])))) {
1778 q("update contact set writable = %d where id = %d",
1779 intval(($perm == 'rw') ? 1 : 0),
1780 intval($contact['id'])
1782 $contact['writable'] = (string) 1 - intval($contact['writable']);
1786 if(($contact['duplex'] && strlen($contact['pubkey']))
1787 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1788 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1789 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1790 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1793 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1794 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1797 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1799 if(strpos($final_dfrn_id,':') == 1)
1800 $final_dfrn_id = substr($final_dfrn_id,2);
1802 if($final_dfrn_id != $orig_id) {
1803 logger('dfrn_deliver: wrong dfrn_id.');
1804 // did not decode properly - cannot trust this site
1808 $postvars['dfrn_id'] = $idtosend;
1809 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1811 $postvars['dissolve'] = '1';
1814 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1815 $postvars['data'] = $atom;
1816 $postvars['perm'] = 'rw';
1819 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1820 $postvars['perm'] = 'r';
1823 $postvars['ssl_policy'] = $ssl_policy;
1826 $postvars['page'] = $page;
1828 if($rino && $rino_allowed && (! $dissolve)) {
1829 $key = substr(random_string(),0,16);
1830 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1831 $postvars['data'] = $data;
1832 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1835 if($dfrn_version >= 2.1) {
1836 if(($contact['duplex'] && strlen($contact['pubkey']))
1837 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1838 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1840 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1843 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1847 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1848 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1851 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1855 logger('md5 rawkey ' . md5($postvars['key']));
1857 $postvars['key'] = bin2hex($postvars['key']);
1860 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1862 $xml = post_url($contact['notify'],$postvars);
1864 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1866 $curl_stat = $a->get_curl_code();
1867 if((! $curl_stat) || (! strlen($xml)))
1868 return(-1); // timed out
1870 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1873 if(strpos($xml,'<?xml') === false) {
1874 logger('dfrn_deliver: phase 2: no valid XML returned');
1875 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1879 if($contact['term-date'] != '0000-00-00 00:00:00') {
1880 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1881 require_once('include/Contact.php');
1882 unmark_for_death($contact);
1885 $res = parse_xml_string($xml);
1887 return $res->status;
1892 This function returns true if $update has an edited timestamp newer
1893 than $existing, i.e. $update contains new data which should override
1894 what's already there. If there is no timestamp yet, the update is
1895 assumed to be newer. If the update has no timestamp, the existing
1896 item is assumed to be up-to-date. If the timestamps are equal it
1897 assumes the update has been seen before and should be ignored.
1899 function edited_timestamp_is_newer($existing, $update) {
1900 if (!x($existing,'edited') || !$existing['edited']) {
1903 if (!x($update,'edited') || !$update['edited']) {
1906 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1907 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1908 return (strcmp($existing_edited, $update_edited) < 0);
1913 * consume_feed - process atom feed and update anything/everything we might need to update
1915 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1917 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1918 * It is this person's stuff that is going to be updated.
1919 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1920 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1921 * have a contact record.
1922 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1923 * might not) try and subscribe to it.
1924 * $datedir sorts in reverse order
1925 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1926 * imported prior to its children being seen in the stream unless we are certain
1927 * of how the feed is arranged/ordered.
1928 * With $pass = 1, we only pull parent items out of the stream.
1929 * With $pass = 2, we only pull children (comments/likes).
1931 * So running this twice, first with pass 1 and then with pass 2 will do the right
1932 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1933 * model where comments can have sub-threads. That would require some massive sorting
1934 * to get all the feed items into a mostly linear ordering, and might still require
1938 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1940 require_once('library/simplepie/simplepie.inc');
1942 if(! strlen($xml)) {
1943 logger('consume_feed: empty input');
1947 $feed = new SimplePie();
1948 $feed->set_raw_data($xml);
1950 $feed->enable_order_by_date(true);
1952 $feed->enable_order_by_date(false);
1956 logger('consume_feed: Error parsing XML: ' . $feed->error());
1958 $permalink = $feed->get_permalink();
1960 // Check at the feed level for updated contact name and/or photo
1964 $photo_timestamp = '';
1968 $hubs = $feed->get_links('hub');
1969 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1972 $hub = implode(',', $hubs);
1974 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1976 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1978 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1979 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1980 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1981 $new_name = $elems['name'][0]['data'];
1983 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1984 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1985 $photo_url = $elems['link'][0]['attribs']['']['href'];
1988 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1989 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1993 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1994 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1995 require_once("include/Photo.php");
1996 $photo_failure = false;
1997 $have_photo = false;
1999 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2000 intval($contact['id']),
2001 intval($contact['uid'])
2004 $resource_id = $r[0]['resource-id'];
2008 $resource_id = photo_new_resource();
2011 $img_str = fetch_url($photo_url,true);
2012 // guess mimetype from headers or filename
2013 $type = guess_image_type($photo_url,true);
2016 $img = new Photo($img_str, $type);
2017 if($img->is_valid()) {
2019 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2020 dbesc($resource_id),
2021 intval($contact['id']),
2022 intval($contact['uid'])
2026 $img->scaleImageSquare(175);
2028 $hash = $resource_id;
2029 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2031 $img->scaleImage(80);
2032 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2034 $img->scaleImage(48);
2035 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2039 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2040 WHERE `uid` = %d AND `id` = %d",
2041 dbesc(datetime_convert()),
2042 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2043 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2044 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2045 intval($contact['uid']),
2046 intval($contact['id'])
2051 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2052 $r = q("select * from contact where uid = %d and id = %d limit 1",
2053 intval($contact['uid']),
2054 intval($contact['id'])
2057 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2058 dbesc(notags(trim($new_name))),
2059 dbesc(datetime_convert()),
2060 intval($contact['uid']),
2061 intval($contact['id'])
2064 // do our best to update the name on content items
2067 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2068 dbesc(notags(trim($new_name))),
2069 dbesc($r[0]['name']),
2070 dbesc($r[0]['url']),
2071 intval($contact['uid'])
2076 if(strlen($birthday)) {
2077 if(substr($birthday,0,4) != $contact['bdyear']) {
2078 logger('consume_feed: updating birthday: ' . $birthday);
2082 * Add new birthday event for this person
2084 * $bdtext is just a readable placeholder in case the event is shared
2085 * with others. We will replace it during presentation to our $importer
2086 * to contain a sparkle link and perhaps a photo.
2090 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2091 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2094 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2095 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2096 intval($contact['uid']),
2097 intval($contact['id']),
2098 dbesc(datetime_convert()),
2099 dbesc(datetime_convert()),
2100 dbesc(datetime_convert('UTC','UTC', $birthday)),
2101 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2110 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2111 dbesc(substr($birthday,0,4)),
2112 intval($contact['uid']),
2113 intval($contact['id'])
2116 // This function is called twice without reloading the contact
2117 // Make sure we only create one event. This is why &$contact
2118 // is a reference var in this function
2120 $contact['bdyear'] = substr($birthday,0,4);
2125 $community_page = 0;
2126 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2128 $community_page = intval($rawtags[0]['data']);
2130 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2131 q("update contact set forum = %d where id = %d",
2132 intval($community_page),
2133 intval($contact['id'])
2135 $contact['forum'] = (string) $community_page;
2139 // process any deleted entries
2141 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2142 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2143 foreach($del_entries as $dentry) {
2145 if(isset($dentry['attribs']['']['ref'])) {
2146 $uri = $dentry['attribs']['']['ref'];
2148 if(isset($dentry['attribs']['']['when'])) {
2149 $when = $dentry['attribs']['']['when'];
2150 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2153 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2155 if($deleted && is_array($contact)) {
2156 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2157 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2159 intval($importer['uid']),
2160 intval($contact['id'])
2165 if(! $item['deleted'])
2166 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2168 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2169 $xo = parse_xml_string($item['object'],false);
2170 $xt = parse_xml_string($item['target'],false);
2171 if($xt->type === ACTIVITY_OBJ_NOTE) {
2172 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2174 intval($importer['importer_uid'])
2178 // For tags, the owner cannot remove the tag on the author's copy of the post.
2180 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2181 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2182 $author_copy = (($item['origin']) ? true : false);
2184 if($owner_remove && $author_copy)
2186 if($author_remove || $owner_remove) {
2187 $tags = explode(',',$i[0]['tag']);
2190 foreach($tags as $tag)
2191 if(trim($tag) !== trim($xo->body))
2192 $newtags[] = trim($tag);
2194 q("update item set tag = '%s' where id = %d",
2195 dbesc(implode(',',$newtags)),
2198 create_tags_from_item($i[0]['id']);
2204 if($item['uri'] == $item['parent-uri']) {
2205 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2206 `body` = '', `title` = ''
2207 WHERE `parent-uri` = '%s' AND `uid` = %d",
2209 dbesc(datetime_convert()),
2210 dbesc($item['uri']),
2211 intval($importer['uid'])
2213 create_tags_from_itemuri($item['uri'], $importer['uid']);
2214 create_files_from_itemuri($item['uri'], $importer['uid']);
2215 update_thread_uri($item['uri'], $importer['uid']);
2218 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2219 `body` = '', `title` = ''
2220 WHERE `uri` = '%s' AND `uid` = %d",
2222 dbesc(datetime_convert()),
2224 intval($importer['uid'])
2226 create_tags_from_itemuri($uri, $importer['uid']);
2227 create_files_from_itemuri($uri, $importer['uid']);
2228 if($item['last-child']) {
2229 // ensure that last-child is set in case the comment that had it just got wiped.
2230 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2231 dbesc(datetime_convert()),
2232 dbesc($item['parent-uri']),
2233 intval($item['uid'])
2235 // who is the last child now?
2236 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2237 ORDER BY `created` DESC LIMIT 1",
2238 dbesc($item['parent-uri']),
2239 intval($importer['uid'])
2242 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2253 // Now process the feed
2255 if($feed->get_item_quantity()) {
2257 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2259 // in inverse date order
2261 $items = array_reverse($feed->get_items());
2263 $items = $feed->get_items();
2266 foreach($items as $item) {
2269 $item_id = $item->get_id();
2270 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2271 if(isset($rawthread[0]['attribs']['']['ref'])) {
2273 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2276 if(($is_reply) && is_array($contact)) {
2281 // not allowed to post
2283 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2287 // Have we seen it? If not, import it.
2289 $item_id = $item->get_id();
2290 $datarray = get_atom_elements($feed, $item, $contact);
2292 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2293 $datarray['author-name'] = $contact['name'];
2294 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2295 $datarray['author-link'] = $contact['url'];
2296 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2297 $datarray['author-avatar'] = $contact['thumb'];
2299 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2300 logger('consume_feed: no author information! ' . print_r($datarray,true));
2304 $force_parent = false;
2305 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2306 if($contact['network'] === NETWORK_OSTATUS)
2307 $force_parent = true;
2308 if(strlen($datarray['title']))
2309 unset($datarray['title']);
2310 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2311 dbesc(datetime_convert()),
2313 intval($importer['uid'])
2315 $datarray['last-child'] = 1;
2316 update_thread_uri($parent_uri, $importer['uid']);
2320 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2322 intval($importer['uid'])
2325 // Update content if 'updated' changes
2328 if (edited_timestamp_is_newer($r[0], $datarray)) {
2330 // do not accept (ignore) an earlier edit than one we currently have.
2331 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2334 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2335 dbesc($datarray['title']),
2336 dbesc($datarray['body']),
2337 dbesc($datarray['tag']),
2338 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2339 dbesc(datetime_convert()),
2341 intval($importer['uid'])
2343 create_tags_from_itemuri($item_id, $importer['uid']);
2344 update_thread_uri($item_id, $importer['uid']);
2347 // update last-child if it changes
2349 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2350 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2351 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2352 dbesc(datetime_convert()),
2354 intval($importer['uid'])
2356 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2357 intval($allow[0]['data']),
2358 dbesc(datetime_convert()),
2360 intval($importer['uid'])
2362 update_thread_uri($item_id, $importer['uid']);
2368 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2369 // one way feed - no remote comment ability
2370 $datarray['last-child'] = 0;
2372 $datarray['parent-uri'] = $parent_uri;
2373 $datarray['uid'] = $importer['uid'];
2374 $datarray['contact-id'] = $contact['id'];
2375 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2376 $datarray['type'] = 'activity';
2377 $datarray['gravity'] = GRAVITY_LIKE;
2378 // only one like or dislike per person
2379 // splitted into two queries for performance issues
2380 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2381 intval($datarray['uid']),
2382 intval($datarray['contact-id']),
2383 dbesc($datarray['verb']),
2389 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2390 intval($datarray['uid']),
2391 intval($datarray['contact-id']),
2392 dbesc($datarray['verb']),
2399 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2400 $xo = parse_xml_string($datarray['object'],false);
2401 $xt = parse_xml_string($datarray['target'],false);
2403 if($xt->type == ACTIVITY_OBJ_NOTE) {
2404 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2406 intval($importer['importer_uid'])
2411 // extract tag, if not duplicate, add to parent item
2412 if($xo->id && $xo->content) {
2413 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2414 if(! (stristr($r[0]['tag'],$newtag))) {
2415 q("UPDATE item SET tag = '%s' WHERE id = %d",
2416 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2419 create_tags_from_item($r[0]['id']);
2425 $r = item_store($datarray,$force_parent);
2431 // Head post of a conversation. Have we seen it? If not, import it.
2433 $item_id = $item->get_id();
2435 $datarray = get_atom_elements($feed, $item, $contact);
2437 if(is_array($contact)) {
2438 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2439 $datarray['author-name'] = $contact['name'];
2440 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2441 $datarray['author-link'] = $contact['url'];
2442 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2443 $datarray['author-avatar'] = $contact['thumb'];
2446 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2447 logger('consume_feed: no author information! ' . print_r($datarray,true));
2451 // special handling for events
2453 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2454 $ev = bbtoevent($datarray['body']);
2455 if(x($ev,'desc') && x($ev,'start')) {
2456 $ev['uid'] = $importer['uid'];
2457 $ev['uri'] = $item_id;
2458 $ev['edited'] = $datarray['edited'];
2459 $ev['private'] = $datarray['private'];
2461 if(is_array($contact))
2462 $ev['cid'] = $contact['id'];
2463 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2465 intval($importer['uid'])
2468 $ev['id'] = $r[0]['id'];
2469 $xyz = event_store($ev);
2474 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2475 if(strlen($datarray['title']))
2476 unset($datarray['title']);
2477 $datarray['last-child'] = 1;
2481 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2483 intval($importer['uid'])
2486 // Update content if 'updated' changes
2489 if (edited_timestamp_is_newer($r[0], $datarray)) {
2491 // do not accept (ignore) an earlier edit than one we currently have.
2492 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2495 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2496 dbesc($datarray['title']),
2497 dbesc($datarray['body']),
2498 dbesc($datarray['tag']),
2499 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2500 dbesc(datetime_convert()),
2502 intval($importer['uid'])
2504 create_tags_from_itemuri($item_id, $importer['uid']);
2505 update_thread_uri($item_id, $importer['uid']);
2508 // update last-child if it changes
2510 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2511 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2512 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2513 intval($allow[0]['data']),
2514 dbesc(datetime_convert()),
2516 intval($importer['uid'])
2518 update_thread_uri($item_id, $importer['uid']);
2523 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2524 logger('consume-feed: New follower');
2525 new_follower($importer,$contact,$datarray,$item);
2528 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2529 lose_follower($importer,$contact,$datarray,$item);
2533 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2534 logger('consume-feed: New friend request');
2535 new_follower($importer,$contact,$datarray,$item,true);
2538 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2539 lose_sharer($importer,$contact,$datarray,$item);
2544 if(! is_array($contact))
2548 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2549 // one way feed - no remote comment ability
2550 $datarray['last-child'] = 0;
2552 if($contact['network'] === NETWORK_FEED)
2553 $datarray['private'] = 2;
2555 $datarray['parent-uri'] = $item_id;
2556 $datarray['uid'] = $importer['uid'];
2557 $datarray['contact-id'] = $contact['id'];
2559 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2560 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2561 // but otherwise there's a possible data mixup on the sender's system.
2562 // the tgroup delivery code called from item_store will correct it if it's a forum,
2563 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2564 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2565 $datarray['owner-name'] = $contact['name'];
2566 $datarray['owner-link'] = $contact['url'];
2567 $datarray['owner-avatar'] = $contact['thumb'];
2570 // We've allowed "followers" to reach this point so we can decide if they are
2571 // posting an @-tag delivery, which followers are allowed to do for certain
2572 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2574 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2577 // This is my contact on another system, but it's really me.
2578 // Turn this into a wall post.
2580 if($contact['remote_self']) {
2581 if ($contact['remote_self'] == 2) {
2582 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2584 $datarray['contact-id'] = $r[0]["id"];
2586 $datarray['owner-name'] = $r[0]["name"];
2587 $datarray['owner-link'] = $r[0]["url"];
2588 $datarray['owner-avatar'] = $r[0]["photo"];
2590 $datarray['author-name'] = $datarray['owner-name'];
2591 $datarray['author-link'] = $datarray['owner-link'];
2592 $datarray['author-avatar'] = $datarray['owner-avatar'];
2597 if($contact['network'] === NETWORK_FEED) {
2598 $datarray['private'] = 0;
2603 $r = item_store($datarray, false, $notify);
2611 function local_delivery($importer,$data) {
2614 logger(__function__, LOGGER_TRACE);
2616 if($importer['readonly']) {
2617 // We aren't receiving stuff from this person. But we will quietly ignore them
2618 // rather than a blatant "go away" message.
2619 logger('local_delivery: ignoring');
2624 // Consume notification feed. This may differ from consuming a public feed in several ways
2625 // - might contain email or friend suggestions
2626 // - might contain remote followup to our message
2627 // - in which case we need to accept it and then notify other conversants
2628 // - we may need to send various email notifications
2630 $feed = new SimplePie();
2631 $feed->set_raw_data($data);
2632 $feed->enable_order_by_date(false);
2637 logger('local_delivery: Error parsing XML: ' . $feed->error());
2640 // Check at the feed level for updated contact name and/or photo
2644 $photo_timestamp = '';
2648 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2650 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2652 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2655 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2656 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2657 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2658 $new_name = $elems['name'][0]['data'];
2660 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2661 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2662 $photo_url = $elems['link'][0]['attribs']['']['href'];
2666 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2667 logger('local_delivery: Updating photo for ' . $importer['name']);
2668 require_once("include/Photo.php");
2669 $photo_failure = false;
2670 $have_photo = false;
2672 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2673 intval($importer['id']),
2674 intval($importer['importer_uid'])
2677 $resource_id = $r[0]['resource-id'];
2681 $resource_id = photo_new_resource();
2684 $img_str = fetch_url($photo_url,true);
2685 // guess mimetype from headers or filename
2686 $type = guess_image_type($photo_url,true);
2689 $img = new Photo($img_str, $type);
2690 if($img->is_valid()) {
2692 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2693 dbesc($resource_id),
2694 intval($importer['id']),
2695 intval($importer['importer_uid'])
2699 $img->scaleImageSquare(175);
2701 $hash = $resource_id;
2702 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2704 $img->scaleImage(80);
2705 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2707 $img->scaleImage(48);
2708 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2712 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2713 WHERE `uid` = %d AND `id` = %d",
2714 dbesc(datetime_convert()),
2715 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2716 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2717 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2718 intval($importer['importer_uid']),
2719 intval($importer['id'])
2724 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2725 $r = q("select * from contact where uid = %d and id = %d limit 1",
2726 intval($importer['importer_uid']),
2727 intval($importer['id'])
2730 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2731 dbesc(notags(trim($new_name))),
2732 dbesc(datetime_convert()),
2733 intval($importer['importer_uid']),
2734 intval($importer['id'])
2737 // do our best to update the name on content items
2740 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2741 dbesc(notags(trim($new_name))),
2742 dbesc($r[0]['name']),
2743 dbesc($r[0]['url']),
2744 intval($importer['importer_uid'])
2751 // Currently unsupported - needs a lot of work
2752 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2753 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2754 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2756 $newloc['uid'] = $importer['importer_uid'];
2757 $newloc['cid'] = $importer['id'];
2758 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2759 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2760 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2761 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2762 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2763 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2764 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2765 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2766 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2767 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2768 /** relocated user must have original key pair */
2769 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2770 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2772 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2775 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2776 intval($importer['id']),
2777 intval($importer['importer_uid']));
2782 $x = q("UPDATE contact SET
2792 `site-pubkey` = '%s'
2793 WHERE id=%d AND uid=%d;",
2794 dbesc($newloc['name']),
2795 dbesc($newloc['photo']),
2796 dbesc($newloc['thumb']),
2797 dbesc($newloc['micro']),
2798 dbesc($newloc['url']),
2799 dbesc($newloc['request']),
2800 dbesc($newloc['confirm']),
2801 dbesc($newloc['notify']),
2802 dbesc($newloc['poll']),
2803 dbesc($newloc['sitepubkey']),
2804 intval($importer['id']),
2805 intval($importer['importer_uid']));
2811 'owner-link' => array($old['url'], $newloc['url']),
2812 'author-link' => array($old['url'], $newloc['url']),
2813 'owner-avatar' => array($old['photo'], $newloc['photo']),
2814 'author-avatar' => array($old['photo'], $newloc['photo']),
2816 foreach ($fields as $n=>$f){
2817 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2820 intval($importer['importer_uid']));
2826 // merge with current record, current contents have priority
2827 // update record, set url-updated
2828 // update profile photos
2834 // handle friend suggestion notification
2836 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2837 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2838 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2840 $fsugg['uid'] = $importer['importer_uid'];
2841 $fsugg['cid'] = $importer['id'];
2842 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2843 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2844 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2845 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2846 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2848 // Does our member already have a friend matching this description?
2850 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2851 dbesc($fsugg['name']),
2852 dbesc(normalise_link($fsugg['url'])),
2853 intval($fsugg['uid'])
2858 // Do we already have an fcontact record for this person?
2861 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2862 dbesc($fsugg['url']),
2863 dbesc($fsugg['name']),
2864 dbesc($fsugg['request'])
2869 // OK, we do. Do we already have an introduction for this person ?
2870 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2871 intval($fsugg['uid']),
2878 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2879 dbesc($fsugg['name']),
2880 dbesc($fsugg['url']),
2881 dbesc($fsugg['photo']),
2882 dbesc($fsugg['request'])
2884 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2885 dbesc($fsugg['url']),
2886 dbesc($fsugg['name']),
2887 dbesc($fsugg['request'])
2892 // database record did not get created. Quietly give up.
2897 $hash = random_string();
2899 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2900 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2901 intval($fsugg['uid']),
2903 intval($fsugg['cid']),
2904 dbesc($fsugg['body']),
2906 dbesc(datetime_convert()),
2911 'type' => NOTIFY_SUGGEST,
2912 'notify_flags' => $importer['notify-flags'],
2913 'language' => $importer['language'],
2914 'to_name' => $importer['username'],
2915 'to_email' => $importer['email'],
2916 'uid' => $importer['importer_uid'],
2918 'link' => $a->get_baseurl() . '/notifications/intros',
2919 'source_name' => $importer['name'],
2920 'source_link' => $importer['url'],
2921 'source_photo' => $importer['photo'],
2922 'verb' => ACTIVITY_REQ_FRIEND,
2931 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2932 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2934 logger('local_delivery: private message received');
2937 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2940 $msg['uid'] = $importer['importer_uid'];
2941 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2942 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2943 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2944 $msg['contact-id'] = $importer['id'];
2945 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2946 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2948 $msg['replied'] = 0;
2949 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2950 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2951 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2955 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2956 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2958 // send notifications.
2960 require_once('include/enotify.php');
2962 $notif_params = array(
2963 'type' => NOTIFY_MAIL,
2964 'notify_flags' => $importer['notify-flags'],
2965 'language' => $importer['language'],
2966 'to_name' => $importer['username'],
2967 'to_email' => $importer['email'],
2968 'uid' => $importer['importer_uid'],
2970 'source_name' => $msg['from-name'],
2971 'source_link' => $importer['url'],
2972 'source_photo' => $importer['thumb'],
2973 'verb' => ACTIVITY_POST,
2977 notification($notif_params);
2983 $community_page = 0;
2984 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2986 $community_page = intval($rawtags[0]['data']);
2988 if(intval($importer['forum']) != $community_page) {
2989 q("update contact set forum = %d where id = %d",
2990 intval($community_page),
2991 intval($importer['id'])
2993 $importer['forum'] = (string) $community_page;
2996 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2998 // process any deleted entries
3000 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3001 if(is_array($del_entries) && count($del_entries)) {
3002 foreach($del_entries as $dentry) {
3004 if(isset($dentry['attribs']['']['ref'])) {
3005 $uri = $dentry['attribs']['']['ref'];
3007 if(isset($dentry['attribs']['']['when'])) {
3008 $when = $dentry['attribs']['']['when'];
3009 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3012 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3016 // check for relayed deletes to our conversation
3019 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3021 intval($importer['importer_uid'])
3024 $parent_uri = $r[0]['parent-uri'];
3025 if($r[0]['id'] != $r[0]['parent'])
3032 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3035 logger('local_delivery: possible community delete');
3038 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3040 // was the top-level post for this reply written by somebody on this site?
3041 // Specifically, the recipient?
3043 $is_a_remote_delete = false;
3045 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3046 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3047 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3048 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3049 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3050 AND `item`.`uid` = %d
3056 intval($importer['importer_uid'])
3059 $is_a_remote_delete = true;
3061 // Does this have the characteristics of a community or private group comment?
3062 // If it's a reply to a wall post on a community/prvgroup page it's a
3063 // valid community comment. Also forum_mode makes it valid for sure.
3064 // If neither, it's not.
3066 if($is_a_remote_delete && $community) {
3067 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3068 $is_a_remote_delete = false;
3069 logger('local_delivery: not a community delete');
3073 if($is_a_remote_delete) {
3074 logger('local_delivery: received remote delete');
3078 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3079 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3081 intval($importer['importer_uid']),
3082 intval($importer['id'])
3088 if($item['deleted'])
3091 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3093 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3094 $xo = parse_xml_string($item['object'],false);
3095 $xt = parse_xml_string($item['target'],false);
3097 if($xt->type === ACTIVITY_OBJ_NOTE) {
3098 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3100 intval($importer['importer_uid'])
3104 // For tags, the owner cannot remove the tag on the author's copy of the post.
3106 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3107 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3108 $author_copy = (($item['origin']) ? true : false);
3110 if($owner_remove && $author_copy)
3112 if($author_remove || $owner_remove) {
3113 $tags = explode(',',$i[0]['tag']);
3116 foreach($tags as $tag)
3117 if(trim($tag) !== trim($xo->body))
3118 $newtags[] = trim($tag);
3120 q("update item set tag = '%s' where id = %d",
3121 dbesc(implode(',',$newtags)),
3124 create_tags_from_item($i[0]['id']);
3130 if($item['uri'] == $item['parent-uri']) {
3131 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3132 `body` = '', `title` = ''
3133 WHERE `parent-uri` = '%s' AND `uid` = %d",
3135 dbesc(datetime_convert()),
3136 dbesc($item['uri']),
3137 intval($importer['importer_uid'])
3139 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3140 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3141 update_thread_uri($item['uri'], $importer['importer_uid']);
3144 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3145 `body` = '', `title` = ''
3146 WHERE `uri` = '%s' AND `uid` = %d",
3148 dbesc(datetime_convert()),
3150 intval($importer['importer_uid'])
3152 create_tags_from_itemuri($uri, $importer['importer_uid']);
3153 create_files_from_itemuri($uri, $importer['importer_uid']);
3154 update_thread_uri($uri, $importer['importer_uid']);
3155 if($item['last-child']) {
3156 // ensure that last-child is set in case the comment that had it just got wiped.
3157 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3158 dbesc(datetime_convert()),
3159 dbesc($item['parent-uri']),
3160 intval($item['uid'])
3162 // who is the last child now?
3163 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3164 ORDER BY `created` DESC LIMIT 1",
3165 dbesc($item['parent-uri']),
3166 intval($importer['importer_uid'])
3169 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3174 // if this is a relayed delete, propagate it to other recipients
3176 if($is_a_remote_delete)
3177 proc_run('php',"include/notifier.php","drop",$item['id']);
3185 foreach($feed->get_items() as $item) {
3188 $item_id = $item->get_id();
3189 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3190 if(isset($rawthread[0]['attribs']['']['ref'])) {
3192 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3198 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3201 logger('local_delivery: possible community reply');
3204 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3206 // was the top-level post for this reply written by somebody on this site?
3207 // Specifically, the recipient?
3209 $is_a_remote_comment = false;
3210 $top_uri = $parent_uri;
3212 $r = q("select `item`.`parent-uri` from `item`
3213 WHERE `item`.`uri` = '%s'
3217 if($r && count($r)) {
3218 $top_uri = $r[0]['parent-uri'];
3220 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3221 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3222 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3223 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3224 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3225 AND `item`.`uid` = %d
3231 intval($importer['importer_uid'])
3234 $is_a_remote_comment = true;
3237 // Does this have the characteristics of a community or private group comment?
3238 // If it's a reply to a wall post on a community/prvgroup page it's a
3239 // valid community comment. Also forum_mode makes it valid for sure.
3240 // If neither, it's not.
3242 if($is_a_remote_comment && $community) {
3243 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3244 $is_a_remote_comment = false;
3245 logger('local_delivery: not a community reply');
3249 if($is_a_remote_comment) {
3250 logger('local_delivery: received remote comment');
3252 // remote reply to our post. Import and then notify everybody else.
3254 $datarray = get_atom_elements($feed, $item);
3256 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3258 intval($importer['importer_uid'])
3261 // Update content if 'updated' changes
3265 if (edited_timestamp_is_newer($r[0], $datarray)) {
3267 // do not accept (ignore) an earlier edit than one we currently have.
3268 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3271 logger('received updated comment' , LOGGER_DEBUG);
3272 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3273 dbesc($datarray['title']),
3274 dbesc($datarray['body']),
3275 dbesc($datarray['tag']),
3276 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3277 dbesc(datetime_convert()),
3279 intval($importer['importer_uid'])
3281 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3283 proc_run('php',"include/notifier.php","comment-import",$iid);
3292 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3293 intval($importer['importer_uid'])
3297 $datarray['type'] = 'remote-comment';
3298 $datarray['wall'] = 1;
3299 $datarray['parent-uri'] = $parent_uri;
3300 $datarray['uid'] = $importer['importer_uid'];
3301 $datarray['owner-name'] = $own[0]['name'];
3302 $datarray['owner-link'] = $own[0]['url'];
3303 $datarray['owner-avatar'] = $own[0]['thumb'];
3304 $datarray['contact-id'] = $importer['id'];
3306 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3308 $datarray['type'] = 'activity';
3309 $datarray['gravity'] = GRAVITY_LIKE;
3310 $datarray['last-child'] = 0;
3311 // only one like or dislike per person
3312 // splitted into two queries for performance issues
3313 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3314 intval($datarray['uid']),
3315 intval($datarray['contact-id']),
3316 dbesc($datarray['verb']),
3317 dbesc($datarray['parent-uri'])
3323 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3324 intval($datarray['uid']),
3325 intval($datarray['contact-id']),
3326 dbesc($datarray['verb']),
3327 dbesc($datarray['parent-uri'])
3334 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3336 $xo = parse_xml_string($datarray['object'],false);
3337 $xt = parse_xml_string($datarray['target'],false);
3339 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3341 // fetch the parent item
3343 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3345 intval($importer['importer_uid'])
3350 // extract tag, if not duplicate, and this user allows tags, add to parent item
3352 if($xo->id && $xo->content) {
3353 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3354 if(! (stristr($tagp[0]['tag'],$newtag))) {
3355 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3356 intval($importer['importer_uid'])
3358 if(count($i) && ! intval($i[0]['blocktags'])) {
3359 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3360 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3361 intval($tagp[0]['id']),
3362 dbesc(datetime_convert()),
3363 dbesc(datetime_convert())
3365 create_tags_from_item($tagp[0]['id']);
3373 $posted_id = item_store($datarray);
3377 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3379 intval($importer['importer_uid'])
3382 $parent = $r[0]['parent'];
3383 $parent_uri = $r[0]['parent-uri'];
3387 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3388 dbesc(datetime_convert()),
3389 intval($importer['importer_uid']),
3390 intval($r[0]['parent'])
3393 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3394 dbesc(datetime_convert()),
3395 intval($importer['importer_uid']),
3400 if($posted_id && $parent) {
3402 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3404 if((! $is_like) && (! $importer['self'])) {
3406 require_once('include/enotify.php');
3409 'type' => NOTIFY_COMMENT,
3410 'notify_flags' => $importer['notify-flags'],
3411 'language' => $importer['language'],
3412 'to_name' => $importer['username'],
3413 'to_email' => $importer['email'],
3414 'uid' => $importer['importer_uid'],
3415 'item' => $datarray,
3416 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3417 'source_name' => stripslashes($datarray['author-name']),
3418 'source_link' => $datarray['author-link'],
3419 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3420 ? $importer['thumb'] : $datarray['author-avatar']),
3421 'verb' => ACTIVITY_POST,
3423 'parent' => $parent,
3424 'parent_uri' => $parent_uri,
3436 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3438 $item_id = $item->get_id();
3439 $datarray = get_atom_elements($feed,$item);
3441 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3444 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3446 intval($importer['importer_uid'])
3449 // Update content if 'updated' changes
3452 if (edited_timestamp_is_newer($r[0], $datarray)) {
3454 // do not accept (ignore) an earlier edit than one we currently have.
3455 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3458 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3459 dbesc($datarray['title']),
3460 dbesc($datarray['body']),
3461 dbesc($datarray['tag']),
3462 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3463 dbesc(datetime_convert()),
3465 intval($importer['importer_uid'])
3467 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3470 // update last-child if it changes
3472 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3473 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3474 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3475 dbesc(datetime_convert()),
3477 intval($importer['importer_uid'])
3479 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3480 intval($allow[0]['data']),
3481 dbesc(datetime_convert()),
3483 intval($importer['importer_uid'])
3489 $datarray['parent-uri'] = $parent_uri;
3490 $datarray['uid'] = $importer['importer_uid'];
3491 $datarray['contact-id'] = $importer['id'];
3492 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3493 $datarray['type'] = 'activity';
3494 $datarray['gravity'] = GRAVITY_LIKE;
3495 // only one like or dislike per person
3496 // splitted into two queries for performance issues
3497 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3498 intval($datarray['uid']),
3499 intval($datarray['contact-id']),
3500 dbesc($datarray['verb']),
3506 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3507 intval($datarray['uid']),
3508 intval($datarray['contact-id']),
3509 dbesc($datarray['verb']),
3517 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3519 $xo = parse_xml_string($datarray['object'],false);
3520 $xt = parse_xml_string($datarray['target'],false);
3522 if($xt->type == ACTIVITY_OBJ_NOTE) {
3523 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3525 intval($importer['importer_uid'])
3530 // extract tag, if not duplicate, add to parent item
3532 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3533 q("UPDATE item SET tag = '%s' WHERE id = %d",
3534 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3537 create_tags_from_item($r[0]['id']);
3543 $posted_id = item_store($datarray);
3545 // find out if our user is involved in this conversation and wants to be notified.
3547 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3549 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3551 intval($importer['importer_uid'])
3554 if(count($myconv)) {
3555 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3557 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3558 if(! link_compare($datarray['author-link'],$importer_url)) {
3561 foreach($myconv as $conv) {
3563 // now if we find a match, it means we're in this conversation
3565 if(! link_compare($conv['author-link'],$importer_url))
3568 require_once('include/enotify.php');
3570 $conv_parent = $conv['parent'];
3573 'type' => NOTIFY_COMMENT,
3574 'notify_flags' => $importer['notify-flags'],
3575 'language' => $importer['language'],
3576 'to_name' => $importer['username'],
3577 'to_email' => $importer['email'],
3578 'uid' => $importer['importer_uid'],
3579 'item' => $datarray,
3580 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3581 'source_name' => stripslashes($datarray['author-name']),
3582 'source_link' => $datarray['author-link'],
3583 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3584 ? $importer['thumb'] : $datarray['author-avatar']),
3585 'verb' => ACTIVITY_POST,
3587 'parent' => $conv_parent,
3588 'parent_uri' => $parent_uri
3592 // only send one notification
3604 // Head post of a conversation. Have we seen it? If not, import it.
3607 $item_id = $item->get_id();
3608 $datarray = get_atom_elements($feed,$item);
3610 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3611 $ev = bbtoevent($datarray['body']);
3612 if(x($ev,'desc') && x($ev,'start')) {
3613 $ev['cid'] = $importer['id'];
3614 $ev['uid'] = $importer['uid'];
3615 $ev['uri'] = $item_id;
3616 $ev['edited'] = $datarray['edited'];
3617 $ev['private'] = $datarray['private'];
3619 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3621 intval($importer['uid'])
3624 $ev['id'] = $r[0]['id'];
3625 $xyz = event_store($ev);
3630 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3632 intval($importer['importer_uid'])
3635 // Update content if 'updated' changes
3638 if (edited_timestamp_is_newer($r[0], $datarray)) {
3640 // do not accept (ignore) an earlier edit than one we currently have.
3641 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3644 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3645 dbesc($datarray['title']),
3646 dbesc($datarray['body']),
3647 dbesc($datarray['tag']),
3648 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3649 dbesc(datetime_convert()),
3651 intval($importer['importer_uid'])
3653 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3654 update_thread_uri($item_id, $importer['importer_uid']);
3657 // update last-child if it changes
3659 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3660 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3661 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3662 intval($allow[0]['data']),
3663 dbesc(datetime_convert()),
3665 intval($importer['importer_uid'])
3671 $datarray['parent-uri'] = $item_id;
3672 $datarray['uid'] = $importer['importer_uid'];
3673 $datarray['contact-id'] = $importer['id'];
3676 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3677 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3678 // but otherwise there's a possible data mixup on the sender's system.
3679 // the tgroup delivery code called from item_store will correct it if it's a forum,
3680 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3681 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3682 $datarray['owner-name'] = $importer['senderName'];
3683 $datarray['owner-link'] = $importer['url'];
3684 $datarray['owner-avatar'] = $importer['thumb'];
3687 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3690 // This is my contact on another system, but it's really me.
3691 // Turn this into a wall post.
3693 if($importer['remote_self']) {
3694 if ($importer['remote_self'] == 2) {
3695 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3696 intval($importer['importer_uid']));
3698 $datarray['contact-id'] = $r[0]["id"];
3700 $datarray['owner-name'] = $r[0]["name"];
3701 $datarray['owner-link'] = $r[0]["url"];
3702 $datarray['owner-avatar'] = $r[0]["photo"];
3704 $datarray['author-name'] = $datarray['owner-name'];
3705 $datarray['author-link'] = $datarray['owner-link'];
3706 $datarray['author-avatar'] = $datarray['owner-avatar'];
3714 $posted_id = item_store($datarray, false, $notify);
3716 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3717 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3720 $xo = parse_xml_string($datarray['object'],false);
3722 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3724 // somebody was poked/prodded. Was it me?
3726 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3728 foreach($links->link as $l) {
3729 $atts = $l->attributes();
3730 switch($atts['rel']) {
3732 $Blink = $atts['href'];
3738 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3740 // send a notification
3741 require_once('include/enotify.php');
3744 'type' => NOTIFY_POKE,
3745 'notify_flags' => $importer['notify-flags'],
3746 'language' => $importer['language'],
3747 'to_name' => $importer['username'],
3748 'to_email' => $importer['email'],
3749 'uid' => $importer['importer_uid'],
3750 'item' => $datarray,
3751 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3752 'source_name' => stripslashes($datarray['author-name']),
3753 'source_link' => $datarray['author-link'],
3754 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3755 ? $importer['thumb'] : $datarray['author-avatar']),
3756 'verb' => $datarray['verb'],
3757 'otype' => 'person',
3758 'activity' => $verb,
3775 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3776 $url = notags(trim($datarray['author-link']));
3777 $name = notags(trim($datarray['author-name']));
3778 $photo = notags(trim($datarray['author-avatar']));
3780 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3781 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3782 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3784 if(is_array($contact)) {
3785 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3786 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3787 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3788 intval(CONTACT_IS_FRIEND),
3789 intval($contact['id']),
3790 intval($importer['uid'])
3793 // send email notification to owner?
3797 // create contact record
3799 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3800 `blocked`, `readonly`, `pending`, `writable` )
3801 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3802 intval($importer['uid']),
3803 dbesc(datetime_convert()),
3805 dbesc(normalise_link($url)),
3809 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3810 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3812 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3813 intval($importer['uid']),
3817 $contact_record = $r[0];
3819 // create notification
3820 $hash = random_string();
3822 if(is_array($contact_record)) {
3823 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3824 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3825 intval($importer['uid']),
3826 intval($contact_record['id']),
3828 dbesc(datetime_convert())
3831 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3832 intval($importer['uid'])
3837 if(intval($r[0]['def_gid'])) {
3838 require_once('include/group.php');
3839 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3842 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3843 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3844 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3845 $email = replace_macros($email_tpl, array(
3846 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3848 '$myname' => $r[0]['username'],
3849 '$siteurl' => $a->get_baseurl(),
3850 '$sitename' => $a->config['sitename']
3852 $res = mail($r[0]['email'],
3853 email_header_encode((($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'],'UTF-8'),
3855 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3856 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3857 . 'Content-transfer-encoding: 8bit' );
3864 function lose_follower($importer,$contact,$datarray,$item) {
3866 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3867 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3868 intval(CONTACT_IS_SHARING),
3869 intval($contact['id'])
3873 contact_remove($contact['id']);
3877 function lose_sharer($importer,$contact,$datarray,$item) {
3879 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3880 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3881 intval(CONTACT_IS_FOLLOWER),
3882 intval($contact['id'])
3886 contact_remove($contact['id']);
3891 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3895 if(is_array($importer)) {
3896 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3897 intval($importer['uid'])
3901 // Diaspora has different message-ids in feeds than they do
3902 // through the direct Diaspora protocol. If we try and use
3903 // the feed, we'll get duplicates. So don't.
3905 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3908 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3910 // Use a single verify token, even if multiple hubs
3912 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3914 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3916 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3918 if(! strlen($contact['hub-verify'])) {
3919 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3920 dbesc($verify_token),
3921 intval($contact['id'])
3925 post_url($url,$params);
3927 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3934 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3938 $name = xmlify($name);
3939 $uri = xmlify($uri);
3942 $photo = xmlify($photo);
3946 $o .= "<name>$name</name>\r\n";
3947 $o .= "<uri>$uri</uri>\r\n";
3948 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3949 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3951 call_hooks('atom_author', $o);
3953 $o .= "</$tag>\r\n";
3957 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3961 if(! $item['parent'])
3964 if($item['deleted'])
3965 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3968 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3969 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3971 $body = $item['body'];
3973 $o = "\r\n\r\n<entry>\r\n";
3975 if(is_array($author))
3976 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3978 $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']));
3979 if(strlen($item['owner-name']))
3980 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3982 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3983 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3984 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3987 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3988 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3989 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3990 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3991 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3992 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3993 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3995 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3997 if($item['location']) {
3998 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3999 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4003 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4005 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4006 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4009 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4010 if($item['bookmark'])
4011 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4014 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4017 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4019 if($item['signed_text']) {
4020 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4021 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4024 $verb = construct_verb($item);
4025 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4026 $actobj = construct_activity_object($item);
4029 $actarg = construct_activity_target($item);
4033 $tags = item_getfeedtags($item);
4035 foreach($tags as $t) {
4036 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4040 $o .= item_getfeedattach($item);
4042 $mentioned = get_mentions($item);
4046 call_hooks('atom_entry', $o);
4048 $o .= '</entry>' . "\r\n";
4053 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4055 if(get_config('system','disable_embedded'))
4060 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4061 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4066 $img_start = strpos($orig_body, '[img');
4067 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4068 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4069 while( ($img_st_close !== false) && ($img_len !== false) ) {
4071 $img_st_close++; // make it point to AFTER the closing bracket
4072 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4074 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4077 if(stristr($image , $site . '/photo/')) {
4078 // Only embed locally hosted photos
4080 $i = basename($image);
4081 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4082 $x = strpos($i,'-');
4085 $res = substr($i,$x+1);
4086 $i = substr($i,0,$x);
4087 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4094 // Check to see if we should replace this photo link with an embedded image
4095 // 1. No need to do so if the photo is public
4096 // 2. If there's a contact-id provided, see if they're in the access list
4097 // for the photo. If so, embed it.
4098 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4099 // permissions, regardless of order but first check to see if they're an exact
4100 // match to save some processing overhead.
4102 if(has_permissions($r[0])) {
4104 $recips = enumerate_permissions($r[0]);
4105 if(in_array($cid, $recips)) {
4110 if(compare_permissions($item,$r[0]))
4115 $data = $r[0]['data'];
4116 $type = $r[0]['type'];
4118 // If a custom width and height were specified, apply before embedding
4119 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4120 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4122 $width = intval($match[1]);
4123 $height = intval($match[2]);
4125 $ph = new Photo($data, $type);
4126 if($ph->is_valid()) {
4127 $ph->scaleImage(max($width, $height));
4128 $data = $ph->imageString();
4129 $type = $ph->getType();
4133 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4134 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4135 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4141 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4142 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4143 if($orig_body === false)
4146 $img_start = strpos($orig_body, '[img');
4147 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4148 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4151 $new_body = $new_body . $orig_body;
4157 function has_permissions($obj) {
4158 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4163 function compare_permissions($obj1,$obj2) {
4164 // first part is easy. Check that these are exactly the same.
4165 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4166 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4167 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4168 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4171 // This is harder. Parse all the permissions and compare the resulting set.
4173 $recipients1 = enumerate_permissions($obj1);
4174 $recipients2 = enumerate_permissions($obj2);
4177 if($recipients1 == $recipients2)
4182 // returns an array of contact-ids that are allowed to see this object
4184 function enumerate_permissions($obj) {
4185 require_once('include/group.php');
4186 $allow_people = expand_acl($obj['allow_cid']);
4187 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4188 $deny_people = expand_acl($obj['deny_cid']);
4189 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4190 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4191 $deny = array_unique(array_merge($deny_people,$deny_groups));
4192 $recipients = array_diff($recipients,$deny);
4196 function item_getfeedtags($item) {
4199 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4201 for($x = 0; $x < $cnt; $x ++) {
4203 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4207 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4209 for($x = 0; $x < $cnt; $x ++) {
4211 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4217 function item_getfeedattach($item) {
4219 $arr = explode('[/attach],',$item['attach']);
4221 foreach($arr as $r) {
4223 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4225 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4226 if(intval($matches[2]))
4227 $ret .= 'length="' . intval($matches[2]) . '" ';
4228 if($matches[4] !== ' ')
4229 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4230 $ret .= ' />' . "\r\n";
4239 function item_expire($uid, $days, $network = "", $force = false) {
4241 if((! $uid) || ($days < 1))
4244 // $expire_network_only = save your own wall posts
4245 // and just expire conversations started by others
4247 $expire_network_only = get_pconfig($uid,'expire','network_only');
4248 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4250 if ($network != "") {
4251 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4252 // There is an index "uid_network_received" but not "uid_network_created"
4253 // This avoids the creation of another index just for one purpose.
4254 // And it doesn't really matter wether to look at "received" or "created"
4255 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4257 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4259 $r = q("SELECT * FROM `item`
4260 WHERE `uid` = %d $range
4271 $expire_items = get_pconfig($uid, 'expire','items');
4272 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4274 // Forcing expiring of items - but not notes and marked items
4276 $expire_items = true;
4278 $expire_notes = get_pconfig($uid, 'expire','notes');
4279 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4281 $expire_starred = get_pconfig($uid, 'expire','starred');
4282 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4284 $expire_photos = get_pconfig($uid, 'expire','photos');
4285 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4287 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4289 foreach($r as $item) {
4291 // don't expire filed items
4293 if(strpos($item['file'],'[') !== false)
4296 // Only expire posts, not photos and photo comments
4298 if($expire_photos==0 && strlen($item['resource-id']))
4300 if($expire_starred==0 && intval($item['starred']))
4302 if($expire_notes==0 && $item['type']=='note')
4304 if($expire_items==0 && $item['type']!='note')
4307 drop_item($item['id'],false);
4310 proc_run('php',"include/notifier.php","expire","$uid");
4315 function drop_items($items) {
4318 if(! local_user() && ! remote_user())
4322 foreach($items as $item) {
4323 $owner = drop_item($item,false);
4324 if($owner && ! $uid)
4329 // multiple threads may have been deleted, send an expire notification
4332 proc_run('php',"include/notifier.php","expire","$uid");
4336 function drop_item($id,$interactive = true) {
4340 // locate item to be deleted
4342 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4349 notice( t('Item not found.') . EOL);
4350 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4355 $owner = $item['uid'];
4359 // check if logged in user is either the author or owner of this item
4361 if(is_array($_SESSION['remote'])) {
4362 foreach($_SESSION['remote'] as $visitor) {
4363 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4364 $cid = $visitor['cid'];
4371 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4373 // Check if we should do HTML-based delete confirmation
4374 if($_REQUEST['confirm']) {
4375 // <form> can't take arguments in its "action" parameter
4376 // so add any arguments as hidden inputs
4377 $query = explode_querystring($a->query_string);
4379 foreach($query['args'] as $arg) {
4380 if(strpos($arg, 'confirm=') === false) {
4381 $arg_parts = explode('=', $arg);
4382 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4386 return replace_macros(get_markup_template('confirm.tpl'), array(
4388 '$message' => t('Do you really want to delete this item?'),
4389 '$extra_inputs' => $inputs,
4390 '$confirm' => t('Yes'),
4391 '$confirm_url' => $query['base'],
4392 '$confirm_name' => 'confirmed',
4393 '$cancel' => t('Cancel'),
4396 // Now check how the user responded to the confirmation query
4397 if($_REQUEST['canceled']) {
4398 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4401 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4404 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4405 dbesc(datetime_convert()),
4406 dbesc(datetime_convert()),
4409 create_tags_from_item($item['id']);
4410 create_files_from_item($item['id']);
4411 delete_thread($item['id']);
4413 // clean up categories and tags so they don't end up as orphans
4416 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4418 foreach($matches as $mtch) {
4419 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
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],false);
4432 // If item is a link to a photo resource, nuke all the associated photos
4433 // (visitors will not have photo resources)
4434 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4435 // generate a resource-id and therefore aren't intimately linked to the item.
4437 if(strlen($item['resource-id'])) {
4438 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4439 dbesc($item['resource-id']),
4440 intval($item['uid'])
4442 // ignore the result
4445 // If item is a link to an event, nuke the event record.
4447 if(intval($item['event-id'])) {
4448 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4449 intval($item['event-id']),
4450 intval($item['uid'])
4452 // ignore the result
4455 // clean up item_id and sign meta-data tables
4458 // Old code - caused very long queries and warning entries in the mysql logfiles:
4460 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4461 intval($item['id']),
4462 intval($item['uid'])
4465 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4466 intval($item['id']),
4467 intval($item['uid'])
4471 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4473 // Creating list of parents
4474 $r = q("select id from item where parent = %d and uid = %d",
4475 intval($item['id']),
4476 intval($item['uid'])
4481 foreach ($r AS $row) {
4482 if ($parentid != "")
4485 $parentid .= $row["id"];
4489 if ($parentid != "") {
4490 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4492 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4495 // If it's the parent of a comment thread, kill all the kids
4497 if($item['uri'] == $item['parent-uri']) {
4498 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4499 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4500 dbesc(datetime_convert()),
4501 dbesc(datetime_convert()),
4502 dbesc($item['parent-uri']),
4503 intval($item['uid'])
4505 create_tags_from_item($item['parent-uri'], $item['uid']);
4506 create_files_from_item($item['parent-uri'], $item['uid']);
4507 delete_thread_uri($item['parent-uri'], $item['uid']);
4508 // ignore the result
4511 // ensure that last-child is set in case the comment that had it just got wiped.
4512 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4513 dbesc(datetime_convert()),
4514 dbesc($item['parent-uri']),
4515 intval($item['uid'])
4517 // who is the last child now?
4518 $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",
4519 dbesc($item['parent-uri']),
4520 intval($item['uid'])
4523 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4528 // Add a relayable_retraction signature for Diaspora.
4529 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4531 $drop_id = intval($item['id']);
4533 // send the notification upstream/downstream as the case may be
4535 proc_run('php',"include/notifier.php","drop","$drop_id");
4539 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4545 notice( t('Permission denied.') . EOL);
4546 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4553 function first_post_date($uid,$wall = false) {
4554 $r = q("select id, created from item
4555 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4557 order by created asc limit 1",
4559 intval($wall ? 1 : 0)
4562 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4563 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4568 function posted_dates($uid,$wall) {
4569 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4571 $dthen = first_post_date($uid,$wall);
4575 // Set the start and end date to the beginning of the month
4576 $dnow = substr($dnow,0,8).'01';
4577 $dthen = substr($dthen,0,8).'01';
4580 // Starting with the current month, get the first and last days of every
4581 // month down to and including the month of the first post
4582 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4583 $dstart = substr($dnow,0,8) . '01';
4584 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4585 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4586 $end_month = datetime_convert('','',$dend,'Y-m-d');
4587 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4588 $ret[] = array($str,$end_month,$start_month);
4589 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4595 function posted_date_widget($url,$uid,$wall) {
4598 if(! feature_enabled($uid,'archives'))
4601 // For former Facebook folks that left because of "timeline"
4603 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4606 $ret = posted_dates($uid,$wall);
4610 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4611 '$title' => t('Archives'),
4612 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4619 function store_diaspora_retract_sig($item, $user, $baseurl) {
4620 // Note that we can't add a target_author_signature
4621 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4622 // the comment, that means we're the home of the post, and Diaspora will only
4623 // check the parent_author_signature of retractions that it doesn't have to relay further
4625 // I don't think this function gets called for an "unlike," but I'll check anyway
4627 $enabled = intval(get_config('system','diaspora_enabled'));
4629 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4633 logger('drop_item: storing diaspora retraction signature');
4635 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4637 if(local_user() == $item['uid']) {
4639 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4640 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4643 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4644 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4647 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4648 // only handles DFRN deletes
4649 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4650 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4651 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4657 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4658 intval($item['id']),
4659 dbesc($signed_text),