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 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
442 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
444 $res['body'] = nl2br($res['body']);
447 // removing the content of the title if its identically to the body
448 // This helps with auto generated titles e.g. from tumblr
449 if (title_is_body($res["title"], $res["body"]))
453 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
457 // look for a photo. We should check media size and find the best one,
458 // but for now let's just find any author photo
460 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
462 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
463 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
464 foreach($base as $link) {
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
472 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
474 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
475 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
476 if($base && count($base)) {
477 foreach($base as $link) {
478 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
479 $res['author-link'] = unxmlify($link['attribs']['']['href']);
480 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
481 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
482 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
488 // No photo/profile-link on the item - look at the feed level
490 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
491 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
492 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
493 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 foreach($base as $link) {
495 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
496 $res['author-link'] = unxmlify($link['attribs']['']['href']);
497 if(! $res['author-avatar']) {
498 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
499 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
504 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
506 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
507 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
509 if($base && count($base)) {
510 foreach($base as $link) {
511 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
512 $res['author-link'] = unxmlify($link['attribs']['']['href']);
513 if(! (x($res,'author-avatar'))) {
514 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
515 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
522 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
523 if($apps && $apps[0]['attribs']['']['source']) {
524 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
525 if($res['app'] === 'web')
526 $res['app'] = 'OStatus';
529 // base64 encoded json structure representing Diaspora signature
531 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
533 $res['dsprsig'] = unxmlify($dsig[0]['data']);
536 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
538 $res['guid'] = unxmlify($dguid[0]['data']);
540 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
542 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
546 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
549 $have_real_body = false;
551 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
553 $have_real_body = true;
554 $res['body'] = $rawenv[0]['data'];
555 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
556 // make sure nobody is trying to sneak some html tags by us
557 $res['body'] = notags(base64url_decode($res['body']));
561 $res['body'] = limit_body_size($res['body']);
563 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
564 // the content type. Our own network only emits text normally, though it might have been converted to
565 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
566 // have to assume it is all html and needs to be purified.
568 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
569 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
570 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
573 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
575 $res['body'] = reltoabs($res['body'],$base_url);
577 $res['body'] = html2bb_video($res['body']);
579 $res['body'] = oembed_html2bbcode($res['body']);
581 $config = HTMLPurifier_Config::createDefault();
582 $config->set('Cache.DefinitionImpl', null);
584 // we shouldn't need a whitelist, because the bbcode converter
585 // will strip out any unsupported tags.
587 $purifier = new HTMLPurifier($config);
588 $res['body'] = $purifier->purify($res['body']);
590 $res['body'] = @html2bbcode($res['body']);
594 elseif(! $have_real_body) {
596 // it's not one of our messages and it has no tags
597 // so it's probably just text. We'll escape it just to be safe.
599 $res['body'] = escape_tags($res['body']);
603 // this tag is obsolete but we keep it for really old sites
605 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
606 if($allow && $allow[0]['data'] == 1)
607 $res['last-child'] = 1;
609 $res['last-child'] = 0;
611 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
612 if($private && intval($private[0]['data']) > 0)
613 $res['private'] = intval($private[0]['data']);
617 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
618 if($extid && $extid[0]['data'])
619 $res['extid'] = $extid[0]['data'];
621 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
623 $res['location'] = unxmlify($rawlocation[0]['data']);
626 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
628 $res['created'] = unxmlify($rawcreated[0]['data']);
631 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
633 $res['edited'] = unxmlify($rawedited[0]['data']);
635 if((x($res,'edited')) && (! (x($res,'created'))))
636 $res['created'] = $res['edited'];
638 if(! $res['created'])
639 $res['created'] = $item->get_date('c');
642 $res['edited'] = $item->get_date('c');
645 // Disallow time travelling posts
647 $d1 = strtotime($res['created']);
648 $d2 = strtotime($res['edited']);
649 $d3 = strtotime('now');
652 $res['created'] = datetime_convert();
654 $res['edited'] = datetime_convert();
656 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
657 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
658 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
659 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
660 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
661 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
662 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
663 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
664 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
666 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
667 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
669 foreach($base as $link) {
670 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
671 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
672 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
677 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
679 $res['coord'] = unxmlify($rawgeo[0]['data']);
681 if ($contact["network"] == NETWORK_FEED) {
682 $res['verb'] = ACTIVITY_POST;
683 $res['object-type'] = ACTIVITY_OBJ_NOTE;
686 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
688 // select between supported verbs
691 $res['verb'] = unxmlify($rawverb[0]['data']);
694 // translate OStatus unfollow to activity streams if it happened to get selected
696 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
697 $res['verb'] = ACTIVITY_UNFOLLOW;
699 $cats = $item->get_categories();
702 foreach($cats as $cat) {
703 $term = $cat->get_term();
705 $term = $cat->get_label();
706 $scheme = $cat->get_scheme();
707 if($scheme && $term && stristr($scheme,'X-DFRN:'))
708 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
710 $tag_arr[] = notags(trim($term));
712 $res['tag'] = implode(',', $tag_arr);
715 $attach = $item->get_enclosures();
718 foreach($attach as $att) {
719 $len = intval($att->get_length());
720 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
721 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
722 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
723 if(strpos($type,';'))
724 $type = substr($type,0,strpos($type,';'));
725 if((! $link) || (strpos($link,'http') !== 0))
731 $type = 'application/octet-stream';
733 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
735 $res['attach'] = implode(',', $att_arr);
738 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
741 $res['object'] = '<object>' . "\n";
742 $child = $rawobj[0]['child'];
743 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
744 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
745 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
748 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
750 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
751 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
752 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
754 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
756 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
757 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
758 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
759 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
761 $body = html2bb_video($body);
763 $config = HTMLPurifier_Config::createDefault();
764 $config->set('Cache.DefinitionImpl', null);
766 $purifier = new HTMLPurifier($config);
767 $body = $purifier->purify($body);
768 $body = html2bbcode($body);
771 $res['object'] .= '<content>' . $body . '</content>' . "\n";
774 $res['object'] .= '</object>' . "\n";
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
780 $res['target'] = '<target>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
786 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
788 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
790 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
795 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
796 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
797 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
799 $body = html2bb_video($body);
801 $config = HTMLPurifier_Config::createDefault();
802 $config->set('Cache.DefinitionImpl', null);
804 $purifier = new HTMLPurifier($config);
805 $body = $purifier->purify($body);
806 $body = html2bbcode($body);
809 $res['target'] .= '<content>' . $body . '</content>' . "\n";
812 $res['target'] .= '</target>' . "\n";
815 // This is some experimental stuff. By now retweets are shown with "RT:"
816 // But: There is data so that the message could be shown similar to native retweets
817 // There is some better way to parse this array - but it didn't worked for me.
818 $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"];
819 if (is_array($child)) {
820 logger('get_atom_elements: Looking for status.net repeated message');
822 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
823 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
824 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
825 $uri = $author["uri"][0]["data"];
826 $name = $author["name"][0]["data"];
827 $avatar = @array_shift($author["link"][2]["attribs"]);
828 $avatar = $avatar["href"];
830 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
831 logger('get_atom_elements: fixing sender of repeated message.');
833 if (!intval(get_config('system','wall-to-wall_share'))) {
834 $prefix = "[share author='".str_replace("'", "'",$name).
836 "' avatar='".$avatar.
837 "' link='".$orig_uri."']";
839 $res["body"] = $prefix.html2bbcode($message)."[/share]";
841 $res["owner-name"] = $res["author-name"];
842 $res["owner-link"] = $res["author-link"];
843 $res["owner-avatar"] = $res["author-avatar"];
845 $res["author-name"] = $name;
846 $res["author-link"] = $uri;
847 $res["author-avatar"] = $avatar;
849 $res["body"] = html2bbcode($message);
854 // Search for ostatus conversation url
855 $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"];
857 if (is_array($links)) {
858 foreach ($links as $link) {
859 $conversation = array_shift($link["attribs"]);
861 if ($conversation["rel"] == "ostatus:conversation") {
862 $res["ostatus_conversation"] = $conversation["href"];
863 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
868 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
869 $res["body"] = $res["title"].add_page_info($res['plink']);
871 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
872 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
873 $res["body"] = add_page_info_to_body($res["body"]);
874 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
875 $res["body"] = add_page_info_to_body($res["body"]);
878 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
880 call_hooks('parse_atom', $arr);
885 function add_page_info($url, $no_photos = false, $photo = "") {
886 require_once("mod/parse_url.php");
888 $data = parseurl_getsiteinfo($url, true);
890 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
892 // It maybe is a rich content, but if it does have everything that a link has,
893 // then treat it that way
894 if (($data["type"] == "rich") AND is_string($data["title"]) AND
895 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
896 $data["type"] = "link";
898 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
901 if ($no_photos AND ($data["type"] == "photo"))
904 if (($data["type"] != "photo") AND is_string($data["title"]))
905 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
907 if (($data["type"] != "video") AND ($photo != ""))
908 $text .= '[img]'.$photo.'[/img]';
909 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
910 $imagedata = $data["images"][0];
911 $text .= '[img]'.$imagedata["src"].'[/img]';
914 if (($data["type"] != "photo") AND is_string($data["text"]))
915 $text .= "[quote]".$data["text"]."[/quote]";
917 return("\n[class=type-".$data["type"]."]".$text."[/class]");
920 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
922 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
924 $URLSearchString = "^\[\]";
926 // Adding these spaces is a quick hack due to my problems with regular expressions :)
927 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
930 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
932 // Convert urls without bbcode elements
933 if (!$matches AND $texturl) {
934 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
936 // Yeah, a hack. I really hate regular expressions :)
938 $matches[1] = $matches[2];
942 $footer = add_page_info($matches[1], $no_photos);
944 // Remove the link from the body if the link is attached at the end of the post
945 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
946 $removedlink = trim(str_replace($matches[1], "", $body));
947 if (($removedlink == "") OR strstr($body, $removedlink))
948 $body = $removedlink;
950 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
951 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
952 if (($removedlink == "") OR strstr($body, $removedlink))
953 $body = $removedlink;
956 // Add the page information to the bottom
957 if (isset($footer) AND (trim($footer) != ""))
963 function encode_rel_links($links) {
965 if(! ((is_array($links)) && (count($links))))
967 foreach($links as $link) {
969 if($link['attribs']['']['rel'])
970 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
971 if($link['attribs']['']['type'])
972 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
973 if($link['attribs']['']['href'])
974 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
975 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
976 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
977 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
978 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
986 function item_store($arr,$force_parent = false, $notify = false) {
988 // If it is a posting where users should get notifications, then define it as wall posting
991 $arr['type'] = 'wall';
993 $arr['last-child'] = 1;
994 $arr['network'] = NETWORK_DFRN;
997 // If a Diaspora signature structure was passed in, pull it out of the
998 // item array and set it aside for later storage.
1001 if(x($arr,'dsprsig')) {
1002 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1003 unset($arr['dsprsig']);
1006 // if an OStatus conversation url was passed in, it is stored and then
1007 // removed from the array.
1008 $ostatus_conversation = null;
1010 if (isset($arr["ostatus_conversation"])) {
1011 $ostatus_conversation = $arr["ostatus_conversation"];
1012 unset($arr["ostatus_conversation"]);
1015 if(x($arr, 'gravity'))
1016 $arr['gravity'] = intval($arr['gravity']);
1017 elseif($arr['parent-uri'] === $arr['uri'])
1018 $arr['gravity'] = 0;
1019 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1020 $arr['gravity'] = 6;
1022 $arr['gravity'] = 6; // extensible catchall
1024 if(! x($arr,'type'))
1025 $arr['type'] = 'remote';
1029 /* check for create date and expire time */
1030 $uid = intval($arr['uid']);
1031 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1033 $expire_interval = $r[0]['expire'];
1034 if ($expire_interval>0) {
1035 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1036 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1037 if ($created_date < $expire_date) {
1038 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1044 // If there is no guid then take the same guid that was taken before for the same uri
1045 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1046 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1047 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1048 dbesc(trim($arr['uri']))
1052 $arr['guid'] = $r[0]["guid"];
1053 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1057 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1058 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1059 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1060 // $arr['body'] = strip_tags($arr['body']);
1063 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1064 require_once('library/langdet/Text/LanguageDetect.php');
1065 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1066 $l = new Text_LanguageDetect;
1067 //$lng = $l->detectConfidence($naked_body);
1068 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1069 $lng = $l->detect($naked_body, 3);
1071 if (sizeof($lng) > 0) {
1074 foreach ($lng as $language => $score) {
1075 if ($postopts == "")
1076 $postopts = "lang=";
1080 $postopts .= $language.";".$score;
1082 $arr['postopts'] = $postopts;
1086 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1087 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1088 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1089 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1090 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1091 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1092 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1093 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1094 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1095 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1096 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1097 $arr['commented'] = datetime_convert();
1098 $arr['received'] = datetime_convert();
1099 $arr['changed'] = datetime_convert();
1100 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1101 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1102 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1103 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1104 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1105 $arr['deleted'] = 0;
1106 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1107 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1108 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1109 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1110 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1111 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1112 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1113 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1114 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1115 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1116 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1117 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1118 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1119 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1120 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1121 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1122 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1123 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1124 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1125 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1127 if ($arr['plink'] == "") {
1129 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1132 if ($arr['network'] == "") {
1133 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1134 intval($arr['contact-id']),
1139 $arr['network'] = $r[0]["network"];
1141 // Fallback to friendica (why is it empty in some cases?)
1142 if ($arr['network'] == "")
1143 $arr['network'] = NETWORK_DFRN;
1145 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1148 $arr['thr-parent'] = $arr['parent-uri'];
1149 if($arr['parent-uri'] === $arr['uri']) {
1151 $parent_deleted = 0;
1152 $allow_cid = $arr['allow_cid'];
1153 $allow_gid = $arr['allow_gid'];
1154 $deny_cid = $arr['deny_cid'];
1155 $deny_gid = $arr['deny_gid'];
1156 $notify_type = 'wall-new';
1160 // find the parent and snarf the item id and ACLs
1161 // and anything else we need to inherit
1163 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1164 dbesc($arr['parent-uri']),
1170 // is the new message multi-level threaded?
1171 // even though we don't support it now, preserve the info
1172 // and re-attach to the conversation parent.
1174 if($r[0]['uri'] != $r[0]['parent-uri']) {
1175 $arr['parent-uri'] = $r[0]['parent-uri'];
1176 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1177 ORDER BY `id` ASC LIMIT 1",
1178 dbesc($r[0]['parent-uri']),
1179 dbesc($r[0]['parent-uri']),
1186 $parent_id = $r[0]['id'];
1187 $parent_deleted = $r[0]['deleted'];
1188 $allow_cid = $r[0]['allow_cid'];
1189 $allow_gid = $r[0]['allow_gid'];
1190 $deny_cid = $r[0]['deny_cid'];
1191 $deny_gid = $r[0]['deny_gid'];
1192 $arr['wall'] = $r[0]['wall'];
1193 $notify_type = 'comment-new';
1195 // if the parent is private, force privacy for the entire conversation
1196 // This differs from the above settings as it subtly allows comments from
1197 // email correspondents to be private even if the overall thread is not.
1199 if($r[0]['private'])
1200 $arr['private'] = $r[0]['private'];
1202 // Edge case. We host a public forum that was originally posted to privately.
1203 // The original author commented, but as this is a comment, the permissions
1204 // weren't fixed up so it will still show the comment as private unless we fix it here.
1206 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1207 $arr['private'] = 0;
1210 // If its a post from myself then tag the thread as "mention"
1211 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1212 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1215 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1216 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1217 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1218 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1219 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1225 // Allow one to see reply tweets from status.net even when
1226 // we don't have or can't see the original post.
1229 logger('item_store: $force_parent=true, reply converted to top-level post.');
1231 $arr['parent-uri'] = $arr['uri'];
1232 $arr['gravity'] = 0;
1235 logger('item_store: item parent was not found - ignoring item');
1239 $parent_deleted = 0;
1243 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1247 if($r && count($r)) {
1248 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1252 call_hooks('post_remote',$arr);
1254 if(x($arr,'cancel')) {
1255 logger('item_store: post cancelled by plugin.');
1261 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1263 $r = dbq("INSERT INTO `item` (`"
1264 . implode("`, `", array_keys($arr))
1266 . implode("', '", array_values($arr))
1269 // find the item we just created
1271 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1272 $arr['uri'], // already dbesc'd
1277 $current_post = $r[0]['id'];
1278 logger('item_store: created item ' . $current_post);
1280 // Only check for notifications on start posts
1281 if ($arr['parent-uri'] === $arr['uri']) {
1282 add_thread($r[0]['id']);
1283 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1285 // Send a notification for every new post?
1286 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1287 intval($arr['contact-id']),
1292 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1293 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1294 intval($arr['uid']));
1296 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1297 intval($current_post),
1303 require_once('include/enotify.php');
1305 'type' => NOTIFY_SHARE,
1306 'notify_flags' => $u[0]['notify-flags'],
1307 'language' => $u[0]['language'],
1308 'to_name' => $u[0]['username'],
1309 'to_email' => $u[0]['email'],
1310 'uid' => $u[0]['uid'],
1312 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1313 'source_name' => $item[0]['author-name'],
1314 'source_link' => $item[0]['author-link'],
1315 'source_photo' => $item[0]['author-avatar'],
1316 'verb' => ACTIVITY_TAG,
1319 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1324 logger('item_store: could not locate created item');
1328 logger('item_store: duplicated post occurred. Removing duplicates.');
1329 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1331 intval($arr['uid']),
1332 intval($current_post)
1336 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1337 $parent_id = $current_post;
1339 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1342 $private = $arr['private'];
1344 // Set parent id - and also make sure to inherit the parent's ACLs.
1346 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1347 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1354 intval($parent_deleted),
1355 intval($current_post)
1358 // Complete ostatus threads
1359 if ($ostatus_conversation)
1360 complete_conversation($current_post, $ostatus_conversation);
1362 $arr['id'] = $current_post;
1363 $arr['parent'] = $parent_id;
1364 $arr['allow_cid'] = $allow_cid;
1365 $arr['allow_gid'] = $allow_gid;
1366 $arr['deny_cid'] = $deny_cid;
1367 $arr['deny_gid'] = $deny_gid;
1368 $arr['private'] = $private;
1369 $arr['deleted'] = $parent_deleted;
1371 // update the commented timestamp on the parent
1373 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1374 dbesc(datetime_convert()),
1375 dbesc(datetime_convert()),
1378 update_thread($parent_id);
1381 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1382 intval($current_post),
1383 dbesc($dsprsig->signed_text),
1384 dbesc($dsprsig->signature),
1385 dbesc($dsprsig->signer)
1391 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1394 if($arr['last-child']) {
1395 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1397 intval($arr['uid']),
1398 intval($current_post)
1402 $deleted = tag_deliver($arr['uid'],$current_post);
1404 // current post can be deleted if is for a communuty page and no mention are
1408 // Store the fresh generated item into the cache
1409 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1411 if (($cachefile != '') AND !file_exists($cachefile)) {
1412 $s = prepare_text($arr['body']);
1414 $stamp1 = microtime(true);
1415 file_put_contents($cachefile, $s);
1416 $a->save_timestamp($stamp1, "file");
1417 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1420 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1421 if (count($r) == 1) {
1422 call_hooks('post_remote_end', $r[0]);
1424 logger('item_store: new item not found in DB, id ' . $current_post);
1428 create_tags_from_item($current_post);
1429 create_files_from_item($current_post);
1432 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1434 return $current_post;
1437 function get_item_guid($id) {
1438 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1440 return($r[0]["guid"]);
1445 function get_item_id($guid, $uid = 0) {
1451 $uid == local_user();
1453 // Does the given user have this item?
1455 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1456 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1457 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1460 $nick = $r[0]["nickname"];
1464 // Or is it anywhere on the server?
1466 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1467 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1468 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1469 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1470 AND `item`.`private` = 0 AND `item`.`wall` = 1
1471 AND `item`.`guid` = '%s'", dbesc($guid));
1474 $nick = $r[0]["nickname"];
1477 return(array("nick" => $nick, "id" => $id));
1481 function get_item_contact($item,$contacts) {
1482 if(! count($contacts) || (! is_array($item)))
1484 foreach($contacts as $contact) {
1485 if($contact['id'] == $item['contact-id']) {
1487 break; // NOTREACHED
1494 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1496 * @param int $item_id
1497 * @return bool true if item was deleted, else false
1499 function tag_deliver($uid,$item_id) {
1507 $u = q("select * from user where uid = %d limit 1",
1513 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1514 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1517 $i = q("select * from item where id = %d and uid = %d limit 1",
1526 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1528 // Diaspora uses their own hardwired link URL in @-tags
1529 // instead of the one we supply with webfinger
1531 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1533 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1535 foreach($matches as $mtch) {
1536 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1538 logger('tag_deliver: mention found: ' . $mtch[2]);
1544 if ( ($community_page || $prvgroup) &&
1545 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1546 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1548 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1549 q("DELETE FROM item WHERE id = %d and uid = %d",
1559 // send a notification
1561 // use a local photo if we have one
1563 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1564 intval($u[0]['uid']),
1565 dbesc(normalise_link($item['author-link']))
1567 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1570 require_once('include/enotify.php');
1572 'type' => NOTIFY_TAGSELF,
1573 'notify_flags' => $u[0]['notify-flags'],
1574 'language' => $u[0]['language'],
1575 'to_name' => $u[0]['username'],
1576 'to_email' => $u[0]['email'],
1577 'uid' => $u[0]['uid'],
1579 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1580 'source_name' => $item['author-name'],
1581 'source_link' => $item['author-link'],
1582 'source_photo' => $photo,
1583 'verb' => ACTIVITY_TAG,
1585 'parent' => $item['parent']
1589 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1591 call_hooks('tagged', $arr);
1593 if((! $community_page) && (! $prvgroup))
1597 // tgroup delivery - setup a second delivery chain
1598 // prevent delivery looping - only proceed
1599 // if the message originated elsewhere and is a top-level post
1601 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1604 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1607 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1608 intval($u[0]['uid'])
1613 // also reset all the privacy bits to the forum default permissions
1615 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1617 $forum_mode = (($prvgroup) ? 2 : 1);
1619 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1620 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1621 intval($forum_mode),
1622 dbesc($c[0]['name']),
1623 dbesc($c[0]['url']),
1624 dbesc($c[0]['thumb']),
1626 dbesc($u[0]['allow_cid']),
1627 dbesc($u[0]['allow_gid']),
1628 dbesc($u[0]['deny_cid']),
1629 dbesc($u[0]['deny_gid']),
1632 update_thread($item_id);
1634 proc_run('php','include/notifier.php','tgroup',$item_id);
1640 function tgroup_check($uid,$item) {
1646 // check that the message originated elsewhere and is a top-level post
1648 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1652 $u = q("select * from user where uid = %d limit 1",
1658 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1659 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1662 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1664 // Diaspora uses their own hardwired link URL in @-tags
1665 // instead of the one we supply with webfinger
1667 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1669 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1671 foreach($matches as $mtch) {
1672 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1674 logger('tgroup_check: mention found: ' . $mtch[2]);
1682 if((! $community_page) && (! $prvgroup))
1696 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1700 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1702 if($contact['duplex'] && $contact['dfrn-id'])
1703 $idtosend = '0:' . $orig_id;
1704 if($contact['duplex'] && $contact['issued-id'])
1705 $idtosend = '1:' . $orig_id;
1707 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1709 $rino_enable = get_config('system','rino_encrypt');
1714 $ssl_val = intval(get_config('system','ssl_policy'));
1718 case SSL_POLICY_FULL:
1719 $ssl_policy = 'full';
1721 case SSL_POLICY_SELFSIGN:
1722 $ssl_policy = 'self';
1724 case SSL_POLICY_NONE:
1726 $ssl_policy = 'none';
1730 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1732 logger('dfrn_deliver: ' . $url);
1734 $xml = fetch_url($url);
1736 $curl_stat = $a->get_curl_code();
1738 return(-1); // timed out
1740 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1745 if(strpos($xml,'<?xml') === false) {
1746 logger('dfrn_deliver: no valid XML returned');
1747 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1751 $res = parse_xml_string($xml);
1753 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1754 return (($res->status) ? $res->status : 3);
1756 $postvars = array();
1757 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1758 $challenge = hex2bin((string) $res->challenge);
1759 $perm = (($res->perm) ? $res->perm : null);
1760 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1761 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1762 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1764 if($owner['page-flags'] == PAGE_PRVGROUP)
1767 $final_dfrn_id = '';
1770 if((($perm == 'rw') && (! intval($contact['writable'])))
1771 || (($perm == 'r') && (intval($contact['writable'])))) {
1772 q("update contact set writable = %d where id = %d",
1773 intval(($perm == 'rw') ? 1 : 0),
1774 intval($contact['id'])
1776 $contact['writable'] = (string) 1 - intval($contact['writable']);
1780 if(($contact['duplex'] && strlen($contact['pubkey']))
1781 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1782 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1783 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1784 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1787 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1788 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1791 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1793 if(strpos($final_dfrn_id,':') == 1)
1794 $final_dfrn_id = substr($final_dfrn_id,2);
1796 if($final_dfrn_id != $orig_id) {
1797 logger('dfrn_deliver: wrong dfrn_id.');
1798 // did not decode properly - cannot trust this site
1802 $postvars['dfrn_id'] = $idtosend;
1803 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1805 $postvars['dissolve'] = '1';
1808 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1809 $postvars['data'] = $atom;
1810 $postvars['perm'] = 'rw';
1813 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1814 $postvars['perm'] = 'r';
1817 $postvars['ssl_policy'] = $ssl_policy;
1820 $postvars['page'] = $page;
1822 if($rino && $rino_allowed && (! $dissolve)) {
1823 $key = substr(random_string(),0,16);
1824 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1825 $postvars['data'] = $data;
1826 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1829 if($dfrn_version >= 2.1) {
1830 if(($contact['duplex'] && strlen($contact['pubkey']))
1831 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1832 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1834 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1837 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1841 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1842 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1845 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1849 logger('md5 rawkey ' . md5($postvars['key']));
1851 $postvars['key'] = bin2hex($postvars['key']);
1854 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1856 $xml = post_url($contact['notify'],$postvars);
1858 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1860 $curl_stat = $a->get_curl_code();
1861 if((! $curl_stat) || (! strlen($xml)))
1862 return(-1); // timed out
1864 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1867 if(strpos($xml,'<?xml') === false) {
1868 logger('dfrn_deliver: phase 2: no valid XML returned');
1869 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1873 if($contact['term-date'] != '0000-00-00 00:00:00') {
1874 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1875 require_once('include/Contact.php');
1876 unmark_for_death($contact);
1879 $res = parse_xml_string($xml);
1881 return $res->status;
1886 This function returns true if $update has an edited timestamp newer
1887 than $existing, i.e. $update contains new data which should override
1888 what's already there. If there is no timestamp yet, the update is
1889 assumed to be newer. If the update has no timestamp, the existing
1890 item is assumed to be up-to-date. If the timestamps are equal it
1891 assumes the update has been seen before and should be ignored.
1893 function edited_timestamp_is_newer($existing, $update) {
1894 if (!x($existing,'edited') || !$existing['edited']) {
1897 if (!x($update,'edited') || !$update['edited']) {
1900 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1901 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1902 return (strcmp($existing_edited, $update_edited) < 0);
1907 * consume_feed - process atom feed and update anything/everything we might need to update
1909 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1911 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1912 * It is this person's stuff that is going to be updated.
1913 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1914 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1915 * have a contact record.
1916 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1917 * might not) try and subscribe to it.
1918 * $datedir sorts in reverse order
1919 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1920 * imported prior to its children being seen in the stream unless we are certain
1921 * of how the feed is arranged/ordered.
1922 * With $pass = 1, we only pull parent items out of the stream.
1923 * With $pass = 2, we only pull children (comments/likes).
1925 * So running this twice, first with pass 1 and then with pass 2 will do the right
1926 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1927 * model where comments can have sub-threads. That would require some massive sorting
1928 * to get all the feed items into a mostly linear ordering, and might still require
1932 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1934 require_once('library/simplepie/simplepie.inc');
1936 if(! strlen($xml)) {
1937 logger('consume_feed: empty input');
1941 $feed = new SimplePie();
1942 $feed->set_raw_data($xml);
1944 $feed->enable_order_by_date(true);
1946 $feed->enable_order_by_date(false);
1950 logger('consume_feed: Error parsing XML: ' . $feed->error());
1952 $permalink = $feed->get_permalink();
1954 // Check at the feed level for updated contact name and/or photo
1958 $photo_timestamp = '';
1962 $hubs = $feed->get_links('hub');
1963 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1966 $hub = implode(',', $hubs);
1968 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1970 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1972 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1973 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1974 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1975 $new_name = $elems['name'][0]['data'];
1977 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1978 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1979 $photo_url = $elems['link'][0]['attribs']['']['href'];
1982 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1983 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1987 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1988 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1989 require_once("include/Photo.php");
1990 $photo_failure = false;
1991 $have_photo = false;
1993 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1994 intval($contact['id']),
1995 intval($contact['uid'])
1998 $resource_id = $r[0]['resource-id'];
2002 $resource_id = photo_new_resource();
2005 $img_str = fetch_url($photo_url,true);
2006 // guess mimetype from headers or filename
2007 $type = guess_image_type($photo_url,true);
2010 $img = new Photo($img_str, $type);
2011 if($img->is_valid()) {
2013 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2014 dbesc($resource_id),
2015 intval($contact['id']),
2016 intval($contact['uid'])
2020 $img->scaleImageSquare(175);
2022 $hash = $resource_id;
2023 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2025 $img->scaleImage(80);
2026 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2028 $img->scaleImage(48);
2029 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2033 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2034 WHERE `uid` = %d AND `id` = %d",
2035 dbesc(datetime_convert()),
2036 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2037 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2038 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2039 intval($contact['uid']),
2040 intval($contact['id'])
2045 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2046 $r = q("select * from contact where uid = %d and id = %d limit 1",
2047 intval($contact['uid']),
2048 intval($contact['id'])
2051 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2052 dbesc(notags(trim($new_name))),
2053 dbesc(datetime_convert()),
2054 intval($contact['uid']),
2055 intval($contact['id'])
2058 // do our best to update the name on content items
2061 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2062 dbesc(notags(trim($new_name))),
2063 dbesc($r[0]['name']),
2064 dbesc($r[0]['url']),
2065 intval($contact['uid'])
2070 if(strlen($birthday)) {
2071 if(substr($birthday,0,4) != $contact['bdyear']) {
2072 logger('consume_feed: updating birthday: ' . $birthday);
2076 * Add new birthday event for this person
2078 * $bdtext is just a readable placeholder in case the event is shared
2079 * with others. We will replace it during presentation to our $importer
2080 * to contain a sparkle link and perhaps a photo.
2084 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2085 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2088 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2089 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2090 intval($contact['uid']),
2091 intval($contact['id']),
2092 dbesc(datetime_convert()),
2093 dbesc(datetime_convert()),
2094 dbesc(datetime_convert('UTC','UTC', $birthday)),
2095 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2104 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2105 dbesc(substr($birthday,0,4)),
2106 intval($contact['uid']),
2107 intval($contact['id'])
2110 // This function is called twice without reloading the contact
2111 // Make sure we only create one event. This is why &$contact
2112 // is a reference var in this function
2114 $contact['bdyear'] = substr($birthday,0,4);
2119 $community_page = 0;
2120 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2122 $community_page = intval($rawtags[0]['data']);
2124 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2125 q("update contact set forum = %d where id = %d",
2126 intval($community_page),
2127 intval($contact['id'])
2129 $contact['forum'] = (string) $community_page;
2133 // process any deleted entries
2135 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2136 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2137 foreach($del_entries as $dentry) {
2139 if(isset($dentry['attribs']['']['ref'])) {
2140 $uri = $dentry['attribs']['']['ref'];
2142 if(isset($dentry['attribs']['']['when'])) {
2143 $when = $dentry['attribs']['']['when'];
2144 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2147 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2149 if($deleted && is_array($contact)) {
2150 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2151 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2153 intval($importer['uid']),
2154 intval($contact['id'])
2159 if(! $item['deleted'])
2160 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2162 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2163 $xo = parse_xml_string($item['object'],false);
2164 $xt = parse_xml_string($item['target'],false);
2165 if($xt->type === ACTIVITY_OBJ_NOTE) {
2166 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2168 intval($importer['importer_uid'])
2172 // For tags, the owner cannot remove the tag on the author's copy of the post.
2174 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2175 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2176 $author_copy = (($item['origin']) ? true : false);
2178 if($owner_remove && $author_copy)
2180 if($author_remove || $owner_remove) {
2181 $tags = explode(',',$i[0]['tag']);
2184 foreach($tags as $tag)
2185 if(trim($tag) !== trim($xo->body))
2186 $newtags[] = trim($tag);
2188 q("update item set tag = '%s' where id = %d",
2189 dbesc(implode(',',$newtags)),
2192 create_tags_from_item($i[0]['id']);
2198 if($item['uri'] == $item['parent-uri']) {
2199 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2200 `body` = '', `title` = ''
2201 WHERE `parent-uri` = '%s' AND `uid` = %d",
2203 dbesc(datetime_convert()),
2204 dbesc($item['uri']),
2205 intval($importer['uid'])
2207 create_tags_from_itemuri($item['uri'], $importer['uid']);
2208 create_files_from_itemuri($item['uri'], $importer['uid']);
2209 update_thread_uri($item['uri'], $importer['uid']);
2212 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2213 `body` = '', `title` = ''
2214 WHERE `uri` = '%s' AND `uid` = %d",
2216 dbesc(datetime_convert()),
2218 intval($importer['uid'])
2220 create_tags_from_itemuri($uri, $importer['uid']);
2221 create_files_from_itemuri($uri, $importer['uid']);
2222 if($item['last-child']) {
2223 // ensure that last-child is set in case the comment that had it just got wiped.
2224 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2225 dbesc(datetime_convert()),
2226 dbesc($item['parent-uri']),
2227 intval($item['uid'])
2229 // who is the last child now?
2230 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2231 ORDER BY `created` DESC LIMIT 1",
2232 dbesc($item['parent-uri']),
2233 intval($importer['uid'])
2236 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2247 // Now process the feed
2249 if($feed->get_item_quantity()) {
2251 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2253 // in inverse date order
2255 $items = array_reverse($feed->get_items());
2257 $items = $feed->get_items();
2260 foreach($items as $item) {
2263 $item_id = $item->get_id();
2264 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2265 if(isset($rawthread[0]['attribs']['']['ref'])) {
2267 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2270 if(($is_reply) && is_array($contact)) {
2275 // not allowed to post
2277 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2281 // Have we seen it? If not, import it.
2283 $item_id = $item->get_id();
2284 $datarray = get_atom_elements($feed, $item, $contact);
2286 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2287 $datarray['author-name'] = $contact['name'];
2288 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2289 $datarray['author-link'] = $contact['url'];
2290 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2291 $datarray['author-avatar'] = $contact['thumb'];
2293 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2294 logger('consume_feed: no author information! ' . print_r($datarray,true));
2298 $force_parent = false;
2299 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2300 if($contact['network'] === NETWORK_OSTATUS)
2301 $force_parent = true;
2302 if(strlen($datarray['title']))
2303 unset($datarray['title']);
2304 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2305 dbesc(datetime_convert()),
2307 intval($importer['uid'])
2309 $datarray['last-child'] = 1;
2310 update_thread_uri($parent_uri, $importer['uid']);
2314 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2316 intval($importer['uid'])
2319 // Update content if 'updated' changes
2322 if (edited_timestamp_is_newer($r[0], $datarray)) {
2324 // do not accept (ignore) an earlier edit than one we currently have.
2325 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2328 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2329 dbesc($datarray['title']),
2330 dbesc($datarray['body']),
2331 dbesc($datarray['tag']),
2332 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2333 dbesc(datetime_convert()),
2335 intval($importer['uid'])
2337 create_tags_from_itemuri($item_id, $importer['uid']);
2338 update_thread_uri($item_id, $importer['uid']);
2341 // update last-child if it changes
2343 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2344 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2345 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2346 dbesc(datetime_convert()),
2348 intval($importer['uid'])
2350 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2351 intval($allow[0]['data']),
2352 dbesc(datetime_convert()),
2354 intval($importer['uid'])
2356 update_thread_uri($item_id, $importer['uid']);
2362 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2363 // one way feed - no remote comment ability
2364 $datarray['last-child'] = 0;
2366 $datarray['parent-uri'] = $parent_uri;
2367 $datarray['uid'] = $importer['uid'];
2368 $datarray['contact-id'] = $contact['id'];
2369 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2370 $datarray['type'] = 'activity';
2371 $datarray['gravity'] = GRAVITY_LIKE;
2372 // only one like or dislike per person
2373 // splitted into two queries for performance issues
2374 $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",
2375 intval($datarray['uid']),
2376 intval($datarray['contact-id']),
2377 dbesc($datarray['verb']),
2383 $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",
2384 intval($datarray['uid']),
2385 intval($datarray['contact-id']),
2386 dbesc($datarray['verb']),
2393 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2394 $xo = parse_xml_string($datarray['object'],false);
2395 $xt = parse_xml_string($datarray['target'],false);
2397 if($xt->type == ACTIVITY_OBJ_NOTE) {
2398 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2400 intval($importer['importer_uid'])
2405 // extract tag, if not duplicate, add to parent item
2406 if($xo->id && $xo->content) {
2407 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2408 if(! (stristr($r[0]['tag'],$newtag))) {
2409 q("UPDATE item SET tag = '%s' WHERE id = %d",
2410 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2413 create_tags_from_item($r[0]['id']);
2419 $r = item_store($datarray,$force_parent);
2425 // Head post of a conversation. Have we seen it? If not, import it.
2427 $item_id = $item->get_id();
2429 $datarray = get_atom_elements($feed, $item, $contact);
2431 if(is_array($contact)) {
2432 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2433 $datarray['author-name'] = $contact['name'];
2434 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2435 $datarray['author-link'] = $contact['url'];
2436 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2437 $datarray['author-avatar'] = $contact['thumb'];
2440 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2441 logger('consume_feed: no author information! ' . print_r($datarray,true));
2445 // special handling for events
2447 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2448 $ev = bbtoevent($datarray['body']);
2449 if(x($ev,'desc') && x($ev,'start')) {
2450 $ev['uid'] = $importer['uid'];
2451 $ev['uri'] = $item_id;
2452 $ev['edited'] = $datarray['edited'];
2453 $ev['private'] = $datarray['private'];
2455 if(is_array($contact))
2456 $ev['cid'] = $contact['id'];
2457 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2459 intval($importer['uid'])
2462 $ev['id'] = $r[0]['id'];
2463 $xyz = event_store($ev);
2468 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2469 if(strlen($datarray['title']))
2470 unset($datarray['title']);
2471 $datarray['last-child'] = 1;
2475 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2477 intval($importer['uid'])
2480 // Update content if 'updated' changes
2483 if (edited_timestamp_is_newer($r[0], $datarray)) {
2485 // do not accept (ignore) an earlier edit than one we currently have.
2486 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2489 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2490 dbesc($datarray['title']),
2491 dbesc($datarray['body']),
2492 dbesc($datarray['tag']),
2493 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2494 dbesc(datetime_convert()),
2496 intval($importer['uid'])
2498 create_tags_from_itemuri($item_id, $importer['uid']);
2499 update_thread_uri($item_id, $importer['uid']);
2502 // update last-child if it changes
2504 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2505 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2506 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2507 intval($allow[0]['data']),
2508 dbesc(datetime_convert()),
2510 intval($importer['uid'])
2512 update_thread_uri($item_id, $importer['uid']);
2517 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2518 logger('consume-feed: New follower');
2519 new_follower($importer,$contact,$datarray,$item);
2522 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2523 lose_follower($importer,$contact,$datarray,$item);
2527 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2528 logger('consume-feed: New friend request');
2529 new_follower($importer,$contact,$datarray,$item,true);
2532 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2533 lose_sharer($importer,$contact,$datarray,$item);
2538 if(! is_array($contact))
2542 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2543 // one way feed - no remote comment ability
2544 $datarray['last-child'] = 0;
2546 if($contact['network'] === NETWORK_FEED)
2547 $datarray['private'] = 2;
2549 $datarray['parent-uri'] = $item_id;
2550 $datarray['uid'] = $importer['uid'];
2551 $datarray['contact-id'] = $contact['id'];
2553 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2554 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2555 // but otherwise there's a possible data mixup on the sender's system.
2556 // the tgroup delivery code called from item_store will correct it if it's a forum,
2557 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2558 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2559 $datarray['owner-name'] = $contact['name'];
2560 $datarray['owner-link'] = $contact['url'];
2561 $datarray['owner-avatar'] = $contact['thumb'];
2564 // We've allowed "followers" to reach this point so we can decide if they are
2565 // posting an @-tag delivery, which followers are allowed to do for certain
2566 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2568 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2571 // This is my contact on another system, but it's really me.
2572 // Turn this into a wall post.
2574 if($contact['remote_self']) {
2575 if ($contact['remote_self'] == 2) {
2576 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2578 $datarray['contact-id'] = $r[0]["id"];
2580 $datarray['owner-name'] = $r[0]["name"];
2581 $datarray['owner-link'] = $r[0]["url"];
2582 $datarray['owner-avatar'] = $r[0]["photo"];
2584 $datarray['author-name'] = $datarray['owner-name'];
2585 $datarray['author-link'] = $datarray['owner-link'];
2586 $datarray['author-avatar'] = $datarray['owner-avatar'];
2591 if($contact['network'] === NETWORK_FEED) {
2592 $datarray['private'] = 0;
2597 $r = item_store($datarray, false, $notify);
2605 function local_delivery($importer,$data) {
2608 logger(__function__, LOGGER_TRACE);
2610 if($importer['readonly']) {
2611 // We aren't receiving stuff from this person. But we will quietly ignore them
2612 // rather than a blatant "go away" message.
2613 logger('local_delivery: ignoring');
2618 // Consume notification feed. This may differ from consuming a public feed in several ways
2619 // - might contain email or friend suggestions
2620 // - might contain remote followup to our message
2621 // - in which case we need to accept it and then notify other conversants
2622 // - we may need to send various email notifications
2624 $feed = new SimplePie();
2625 $feed->set_raw_data($data);
2626 $feed->enable_order_by_date(false);
2631 logger('local_delivery: Error parsing XML: ' . $feed->error());
2634 // Check at the feed level for updated contact name and/or photo
2638 $photo_timestamp = '';
2642 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2644 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2646 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2649 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2650 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2651 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2652 $new_name = $elems['name'][0]['data'];
2654 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2655 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2656 $photo_url = $elems['link'][0]['attribs']['']['href'];
2660 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2661 logger('local_delivery: Updating photo for ' . $importer['name']);
2662 require_once("include/Photo.php");
2663 $photo_failure = false;
2664 $have_photo = false;
2666 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2667 intval($importer['id']),
2668 intval($importer['importer_uid'])
2671 $resource_id = $r[0]['resource-id'];
2675 $resource_id = photo_new_resource();
2678 $img_str = fetch_url($photo_url,true);
2679 // guess mimetype from headers or filename
2680 $type = guess_image_type($photo_url,true);
2683 $img = new Photo($img_str, $type);
2684 if($img->is_valid()) {
2686 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2687 dbesc($resource_id),
2688 intval($importer['id']),
2689 intval($importer['importer_uid'])
2693 $img->scaleImageSquare(175);
2695 $hash = $resource_id;
2696 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2698 $img->scaleImage(80);
2699 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2701 $img->scaleImage(48);
2702 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2706 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2707 WHERE `uid` = %d AND `id` = %d",
2708 dbesc(datetime_convert()),
2709 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2710 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2711 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2712 intval($importer['importer_uid']),
2713 intval($importer['id'])
2718 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2719 $r = q("select * from contact where uid = %d and id = %d limit 1",
2720 intval($importer['importer_uid']),
2721 intval($importer['id'])
2724 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2725 dbesc(notags(trim($new_name))),
2726 dbesc(datetime_convert()),
2727 intval($importer['importer_uid']),
2728 intval($importer['id'])
2731 // do our best to update the name on content items
2734 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2735 dbesc(notags(trim($new_name))),
2736 dbesc($r[0]['name']),
2737 dbesc($r[0]['url']),
2738 intval($importer['importer_uid'])
2745 // Currently unsupported - needs a lot of work
2746 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2747 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2748 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2750 $newloc['uid'] = $importer['importer_uid'];
2751 $newloc['cid'] = $importer['id'];
2752 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2753 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2754 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2755 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2756 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2757 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2758 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2759 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2760 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2761 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2762 /** relocated user must have original key pair */
2763 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2764 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2766 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2769 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2770 intval($importer['id']),
2771 intval($importer['importer_uid']));
2776 $x = q("UPDATE contact SET
2786 `site-pubkey` = '%s'
2787 WHERE id=%d AND uid=%d;",
2788 dbesc($newloc['name']),
2789 dbesc($newloc['photo']),
2790 dbesc($newloc['thumb']),
2791 dbesc($newloc['micro']),
2792 dbesc($newloc['url']),
2793 dbesc($newloc['request']),
2794 dbesc($newloc['confirm']),
2795 dbesc($newloc['notify']),
2796 dbesc($newloc['poll']),
2797 dbesc($newloc['sitepubkey']),
2798 intval($importer['id']),
2799 intval($importer['importer_uid']));
2805 'owner-link' => array($old['url'], $newloc['url']),
2806 'author-link' => array($old['url'], $newloc['url']),
2807 'owner-avatar' => array($old['photo'], $newloc['photo']),
2808 'author-avatar' => array($old['photo'], $newloc['photo']),
2810 foreach ($fields as $n=>$f){
2811 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2814 intval($importer['importer_uid']));
2820 // merge with current record, current contents have priority
2821 // update record, set url-updated
2822 // update profile photos
2828 // handle friend suggestion notification
2830 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2831 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2832 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2834 $fsugg['uid'] = $importer['importer_uid'];
2835 $fsugg['cid'] = $importer['id'];
2836 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2837 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2838 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2839 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2840 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2842 // Does our member already have a friend matching this description?
2844 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2845 dbesc($fsugg['name']),
2846 dbesc(normalise_link($fsugg['url'])),
2847 intval($fsugg['uid'])
2852 // Do we already have an fcontact record for this person?
2855 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2856 dbesc($fsugg['url']),
2857 dbesc($fsugg['name']),
2858 dbesc($fsugg['request'])
2863 // OK, we do. Do we already have an introduction for this person ?
2864 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2865 intval($fsugg['uid']),
2872 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2873 dbesc($fsugg['name']),
2874 dbesc($fsugg['url']),
2875 dbesc($fsugg['photo']),
2876 dbesc($fsugg['request'])
2878 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2879 dbesc($fsugg['url']),
2880 dbesc($fsugg['name']),
2881 dbesc($fsugg['request'])
2886 // database record did not get created. Quietly give up.
2891 $hash = random_string();
2893 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2894 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2895 intval($fsugg['uid']),
2897 intval($fsugg['cid']),
2898 dbesc($fsugg['body']),
2900 dbesc(datetime_convert()),
2905 'type' => NOTIFY_SUGGEST,
2906 'notify_flags' => $importer['notify-flags'],
2907 'language' => $importer['language'],
2908 'to_name' => $importer['username'],
2909 'to_email' => $importer['email'],
2910 'uid' => $importer['importer_uid'],
2912 'link' => $a->get_baseurl() . '/notifications/intros',
2913 'source_name' => $importer['name'],
2914 'source_link' => $importer['url'],
2915 'source_photo' => $importer['photo'],
2916 'verb' => ACTIVITY_REQ_FRIEND,
2925 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2926 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2928 logger('local_delivery: private message received');
2931 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2934 $msg['uid'] = $importer['importer_uid'];
2935 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2936 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2937 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2938 $msg['contact-id'] = $importer['id'];
2939 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2940 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2942 $msg['replied'] = 0;
2943 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2944 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2945 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2949 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2950 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2952 // send notifications.
2954 require_once('include/enotify.php');
2956 $notif_params = array(
2957 'type' => NOTIFY_MAIL,
2958 'notify_flags' => $importer['notify-flags'],
2959 'language' => $importer['language'],
2960 'to_name' => $importer['username'],
2961 'to_email' => $importer['email'],
2962 'uid' => $importer['importer_uid'],
2964 'source_name' => $msg['from-name'],
2965 'source_link' => $importer['url'],
2966 'source_photo' => $importer['thumb'],
2967 'verb' => ACTIVITY_POST,
2971 notification($notif_params);
2977 $community_page = 0;
2978 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2980 $community_page = intval($rawtags[0]['data']);
2982 if(intval($importer['forum']) != $community_page) {
2983 q("update contact set forum = %d where id = %d",
2984 intval($community_page),
2985 intval($importer['id'])
2987 $importer['forum'] = (string) $community_page;
2990 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2992 // process any deleted entries
2994 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2995 if(is_array($del_entries) && count($del_entries)) {
2996 foreach($del_entries as $dentry) {
2998 if(isset($dentry['attribs']['']['ref'])) {
2999 $uri = $dentry['attribs']['']['ref'];
3001 if(isset($dentry['attribs']['']['when'])) {
3002 $when = $dentry['attribs']['']['when'];
3003 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3006 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3010 // check for relayed deletes to our conversation
3013 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3015 intval($importer['importer_uid'])
3018 $parent_uri = $r[0]['parent-uri'];
3019 if($r[0]['id'] != $r[0]['parent'])
3026 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3029 logger('local_delivery: possible community delete');
3032 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3034 // was the top-level post for this reply written by somebody on this site?
3035 // Specifically, the recipient?
3037 $is_a_remote_delete = false;
3039 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3040 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3041 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3042 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3043 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3044 AND `item`.`uid` = %d
3050 intval($importer['importer_uid'])
3053 $is_a_remote_delete = true;
3055 // Does this have the characteristics of a community or private group comment?
3056 // If it's a reply to a wall post on a community/prvgroup page it's a
3057 // valid community comment. Also forum_mode makes it valid for sure.
3058 // If neither, it's not.
3060 if($is_a_remote_delete && $community) {
3061 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3062 $is_a_remote_delete = false;
3063 logger('local_delivery: not a community delete');
3067 if($is_a_remote_delete) {
3068 logger('local_delivery: received remote delete');
3072 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3073 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3075 intval($importer['importer_uid']),
3076 intval($importer['id'])
3082 if($item['deleted'])
3085 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3087 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3088 $xo = parse_xml_string($item['object'],false);
3089 $xt = parse_xml_string($item['target'],false);
3091 if($xt->type === ACTIVITY_OBJ_NOTE) {
3092 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3094 intval($importer['importer_uid'])
3098 // For tags, the owner cannot remove the tag on the author's copy of the post.
3100 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3101 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3102 $author_copy = (($item['origin']) ? true : false);
3104 if($owner_remove && $author_copy)
3106 if($author_remove || $owner_remove) {
3107 $tags = explode(',',$i[0]['tag']);
3110 foreach($tags as $tag)
3111 if(trim($tag) !== trim($xo->body))
3112 $newtags[] = trim($tag);
3114 q("update item set tag = '%s' where id = %d",
3115 dbesc(implode(',',$newtags)),
3118 create_tags_from_item($i[0]['id']);
3124 if($item['uri'] == $item['parent-uri']) {
3125 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3126 `body` = '', `title` = ''
3127 WHERE `parent-uri` = '%s' AND `uid` = %d",
3129 dbesc(datetime_convert()),
3130 dbesc($item['uri']),
3131 intval($importer['importer_uid'])
3133 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3134 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3135 update_thread_uri($item['uri'], $importer['importer_uid']);
3138 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3139 `body` = '', `title` = ''
3140 WHERE `uri` = '%s' AND `uid` = %d",
3142 dbesc(datetime_convert()),
3144 intval($importer['importer_uid'])
3146 create_tags_from_itemuri($uri, $importer['importer_uid']);
3147 create_files_from_itemuri($uri, $importer['importer_uid']);
3148 update_thread_uri($uri, $importer['importer_uid']);
3149 if($item['last-child']) {
3150 // ensure that last-child is set in case the comment that had it just got wiped.
3151 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3152 dbesc(datetime_convert()),
3153 dbesc($item['parent-uri']),
3154 intval($item['uid'])
3156 // who is the last child now?
3157 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3158 ORDER BY `created` DESC LIMIT 1",
3159 dbesc($item['parent-uri']),
3160 intval($importer['importer_uid'])
3163 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3168 // if this is a relayed delete, propagate it to other recipients
3170 if($is_a_remote_delete)
3171 proc_run('php',"include/notifier.php","drop",$item['id']);
3179 foreach($feed->get_items() as $item) {
3182 $item_id = $item->get_id();
3183 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3184 if(isset($rawthread[0]['attribs']['']['ref'])) {
3186 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3192 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3195 logger('local_delivery: possible community reply');
3198 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3200 // was the top-level post for this reply written by somebody on this site?
3201 // Specifically, the recipient?
3203 $is_a_remote_comment = false;
3204 $top_uri = $parent_uri;
3206 $r = q("select `item`.`parent-uri` from `item`
3207 WHERE `item`.`uri` = '%s'
3211 if($r && count($r)) {
3212 $top_uri = $r[0]['parent-uri'];
3214 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3215 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3216 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3217 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3218 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3219 AND `item`.`uid` = %d
3225 intval($importer['importer_uid'])
3228 $is_a_remote_comment = true;
3231 // Does this have the characteristics of a community or private group comment?
3232 // If it's a reply to a wall post on a community/prvgroup page it's a
3233 // valid community comment. Also forum_mode makes it valid for sure.
3234 // If neither, it's not.
3236 if($is_a_remote_comment && $community) {
3237 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3238 $is_a_remote_comment = false;
3239 logger('local_delivery: not a community reply');
3243 if($is_a_remote_comment) {
3244 logger('local_delivery: received remote comment');
3246 // remote reply to our post. Import and then notify everybody else.
3248 $datarray = get_atom_elements($feed, $item);
3250 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3252 intval($importer['importer_uid'])
3255 // Update content if 'updated' changes
3259 if (edited_timestamp_is_newer($r[0], $datarray)) {
3261 // do not accept (ignore) an earlier edit than one we currently have.
3262 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3265 logger('received updated comment' , LOGGER_DEBUG);
3266 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3267 dbesc($datarray['title']),
3268 dbesc($datarray['body']),
3269 dbesc($datarray['tag']),
3270 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3271 dbesc(datetime_convert()),
3273 intval($importer['importer_uid'])
3275 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3277 proc_run('php',"include/notifier.php","comment-import",$iid);
3286 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3287 intval($importer['importer_uid'])
3291 $datarray['type'] = 'remote-comment';
3292 $datarray['wall'] = 1;
3293 $datarray['parent-uri'] = $parent_uri;
3294 $datarray['uid'] = $importer['importer_uid'];
3295 $datarray['owner-name'] = $own[0]['name'];
3296 $datarray['owner-link'] = $own[0]['url'];
3297 $datarray['owner-avatar'] = $own[0]['thumb'];
3298 $datarray['contact-id'] = $importer['id'];
3300 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3302 $datarray['type'] = 'activity';
3303 $datarray['gravity'] = GRAVITY_LIKE;
3304 $datarray['last-child'] = 0;
3305 // only one like or dislike per person
3306 // splitted into two queries for performance issues
3307 $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",
3308 intval($datarray['uid']),
3309 intval($datarray['contact-id']),
3310 dbesc($datarray['verb']),
3311 dbesc($datarray['parent-uri'])
3317 $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",
3318 intval($datarray['uid']),
3319 intval($datarray['contact-id']),
3320 dbesc($datarray['verb']),
3321 dbesc($datarray['parent-uri'])
3328 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3330 $xo = parse_xml_string($datarray['object'],false);
3331 $xt = parse_xml_string($datarray['target'],false);
3333 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3335 // fetch the parent item
3337 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3339 intval($importer['importer_uid'])
3344 // extract tag, if not duplicate, and this user allows tags, add to parent item
3346 if($xo->id && $xo->content) {
3347 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3348 if(! (stristr($tagp[0]['tag'],$newtag))) {
3349 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3350 intval($importer['importer_uid'])
3352 if(count($i) && ! intval($i[0]['blocktags'])) {
3353 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3354 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3355 intval($tagp[0]['id']),
3356 dbesc(datetime_convert()),
3357 dbesc(datetime_convert())
3359 create_tags_from_item($tagp[0]['id']);
3367 $posted_id = item_store($datarray);
3371 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3373 intval($importer['importer_uid'])
3376 $parent = $r[0]['parent'];
3377 $parent_uri = $r[0]['parent-uri'];
3381 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3382 dbesc(datetime_convert()),
3383 intval($importer['importer_uid']),
3384 intval($r[0]['parent'])
3387 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3388 dbesc(datetime_convert()),
3389 intval($importer['importer_uid']),
3394 if($posted_id && $parent) {
3396 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3398 if((! $is_like) && (! $importer['self'])) {
3400 require_once('include/enotify.php');
3403 'type' => NOTIFY_COMMENT,
3404 'notify_flags' => $importer['notify-flags'],
3405 'language' => $importer['language'],
3406 'to_name' => $importer['username'],
3407 'to_email' => $importer['email'],
3408 'uid' => $importer['importer_uid'],
3409 'item' => $datarray,
3410 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3411 'source_name' => stripslashes($datarray['author-name']),
3412 'source_link' => $datarray['author-link'],
3413 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3414 ? $importer['thumb'] : $datarray['author-avatar']),
3415 'verb' => ACTIVITY_POST,
3417 'parent' => $parent,
3418 'parent_uri' => $parent_uri,
3430 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3432 $item_id = $item->get_id();
3433 $datarray = get_atom_elements($feed,$item);
3435 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3438 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3440 intval($importer['importer_uid'])
3443 // Update content if 'updated' changes
3446 if (edited_timestamp_is_newer($r[0], $datarray)) {
3448 // do not accept (ignore) an earlier edit than one we currently have.
3449 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3452 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3453 dbesc($datarray['title']),
3454 dbesc($datarray['body']),
3455 dbesc($datarray['tag']),
3456 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3457 dbesc(datetime_convert()),
3459 intval($importer['importer_uid'])
3461 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3464 // update last-child if it changes
3466 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3467 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3468 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3469 dbesc(datetime_convert()),
3471 intval($importer['importer_uid'])
3473 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3474 intval($allow[0]['data']),
3475 dbesc(datetime_convert()),
3477 intval($importer['importer_uid'])
3483 $datarray['parent-uri'] = $parent_uri;
3484 $datarray['uid'] = $importer['importer_uid'];
3485 $datarray['contact-id'] = $importer['id'];
3486 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3487 $datarray['type'] = 'activity';
3488 $datarray['gravity'] = GRAVITY_LIKE;
3489 // only one like or dislike per person
3490 // splitted into two queries for performance issues
3491 $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",
3492 intval($datarray['uid']),
3493 intval($datarray['contact-id']),
3494 dbesc($datarray['verb']),
3500 $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",
3501 intval($datarray['uid']),
3502 intval($datarray['contact-id']),
3503 dbesc($datarray['verb']),
3511 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3513 $xo = parse_xml_string($datarray['object'],false);
3514 $xt = parse_xml_string($datarray['target'],false);
3516 if($xt->type == ACTIVITY_OBJ_NOTE) {
3517 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3519 intval($importer['importer_uid'])
3524 // extract tag, if not duplicate, add to parent item
3526 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3527 q("UPDATE item SET tag = '%s' WHERE id = %d",
3528 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3531 create_tags_from_item($r[0]['id']);
3537 $posted_id = item_store($datarray);
3539 // find out if our user is involved in this conversation and wants to be notified.
3541 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3543 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3545 intval($importer['importer_uid'])
3548 if(count($myconv)) {
3549 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3551 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3552 if(! link_compare($datarray['author-link'],$importer_url)) {
3555 foreach($myconv as $conv) {
3557 // now if we find a match, it means we're in this conversation
3559 if(! link_compare($conv['author-link'],$importer_url))
3562 require_once('include/enotify.php');
3564 $conv_parent = $conv['parent'];
3567 'type' => NOTIFY_COMMENT,
3568 'notify_flags' => $importer['notify-flags'],
3569 'language' => $importer['language'],
3570 'to_name' => $importer['username'],
3571 'to_email' => $importer['email'],
3572 'uid' => $importer['importer_uid'],
3573 'item' => $datarray,
3574 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3575 'source_name' => stripslashes($datarray['author-name']),
3576 'source_link' => $datarray['author-link'],
3577 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3578 ? $importer['thumb'] : $datarray['author-avatar']),
3579 'verb' => ACTIVITY_POST,
3581 'parent' => $conv_parent,
3582 'parent_uri' => $parent_uri
3586 // only send one notification
3598 // Head post of a conversation. Have we seen it? If not, import it.
3601 $item_id = $item->get_id();
3602 $datarray = get_atom_elements($feed,$item);
3604 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3605 $ev = bbtoevent($datarray['body']);
3606 if(x($ev,'desc') && x($ev,'start')) {
3607 $ev['cid'] = $importer['id'];
3608 $ev['uid'] = $importer['uid'];
3609 $ev['uri'] = $item_id;
3610 $ev['edited'] = $datarray['edited'];
3611 $ev['private'] = $datarray['private'];
3613 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3615 intval($importer['uid'])
3618 $ev['id'] = $r[0]['id'];
3619 $xyz = event_store($ev);
3624 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3626 intval($importer['importer_uid'])
3629 // Update content if 'updated' changes
3632 if (edited_timestamp_is_newer($r[0], $datarray)) {
3634 // do not accept (ignore) an earlier edit than one we currently have.
3635 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3638 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3639 dbesc($datarray['title']),
3640 dbesc($datarray['body']),
3641 dbesc($datarray['tag']),
3642 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3643 dbesc(datetime_convert()),
3645 intval($importer['importer_uid'])
3647 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3648 update_thread_uri($item_id, $importer['importer_uid']);
3651 // update last-child if it changes
3653 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3654 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3655 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3656 intval($allow[0]['data']),
3657 dbesc(datetime_convert()),
3659 intval($importer['importer_uid'])
3665 $datarray['parent-uri'] = $item_id;
3666 $datarray['uid'] = $importer['importer_uid'];
3667 $datarray['contact-id'] = $importer['id'];
3670 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3671 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3672 // but otherwise there's a possible data mixup on the sender's system.
3673 // the tgroup delivery code called from item_store will correct it if it's a forum,
3674 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3675 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3676 $datarray['owner-name'] = $importer['senderName'];
3677 $datarray['owner-link'] = $importer['url'];
3678 $datarray['owner-avatar'] = $importer['thumb'];
3681 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3684 // This is my contact on another system, but it's really me.
3685 // Turn this into a wall post.
3687 if($importer['remote_self']) {
3688 if ($importer['remote_self'] == 2) {
3689 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3690 intval($importer['importer_uid']));
3692 $datarray['contact-id'] = $r[0]["id"];
3694 $datarray['owner-name'] = $r[0]["name"];
3695 $datarray['owner-link'] = $r[0]["url"];
3696 $datarray['owner-avatar'] = $r[0]["photo"];
3698 $datarray['author-name'] = $datarray['owner-name'];
3699 $datarray['author-link'] = $datarray['owner-link'];
3700 $datarray['author-avatar'] = $datarray['owner-avatar'];
3708 $posted_id = item_store($datarray, false, $notify);
3710 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3711 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3714 $xo = parse_xml_string($datarray['object'],false);
3716 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3718 // somebody was poked/prodded. Was it me?
3720 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3722 foreach($links->link as $l) {
3723 $atts = $l->attributes();
3724 switch($atts['rel']) {
3726 $Blink = $atts['href'];
3732 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3734 // send a notification
3735 require_once('include/enotify.php');
3738 'type' => NOTIFY_POKE,
3739 'notify_flags' => $importer['notify-flags'],
3740 'language' => $importer['language'],
3741 'to_name' => $importer['username'],
3742 'to_email' => $importer['email'],
3743 'uid' => $importer['importer_uid'],
3744 'item' => $datarray,
3745 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3746 'source_name' => stripslashes($datarray['author-name']),
3747 'source_link' => $datarray['author-link'],
3748 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3749 ? $importer['thumb'] : $datarray['author-avatar']),
3750 'verb' => $datarray['verb'],
3751 'otype' => 'person',
3752 'activity' => $verb,
3769 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3770 $url = notags(trim($datarray['author-link']));
3771 $name = notags(trim($datarray['author-name']));
3772 $photo = notags(trim($datarray['author-avatar']));
3774 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3775 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3776 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3778 if(is_array($contact)) {
3779 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3780 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3781 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3782 intval(CONTACT_IS_FRIEND),
3783 intval($contact['id']),
3784 intval($importer['uid'])
3787 // send email notification to owner?
3791 // create contact record
3793 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3794 `blocked`, `readonly`, `pending`, `writable` )
3795 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3796 intval($importer['uid']),
3797 dbesc(datetime_convert()),
3799 dbesc(normalise_link($url)),
3803 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3804 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3806 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3807 intval($importer['uid']),
3811 $contact_record = $r[0];
3813 // create notification
3814 $hash = random_string();
3816 if(is_array($contact_record)) {
3817 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3818 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3819 intval($importer['uid']),
3820 intval($contact_record['id']),
3822 dbesc(datetime_convert())
3825 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3826 intval($importer['uid'])
3831 if(intval($r[0]['def_gid'])) {
3832 require_once('include/group.php');
3833 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3836 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3837 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3838 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3839 $email = replace_macros($email_tpl, array(
3840 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3842 '$myname' => $r[0]['username'],
3843 '$siteurl' => $a->get_baseurl(),
3844 '$sitename' => $a->config['sitename']
3846 $res = mail($r[0]['email'],
3847 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'),
3849 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3850 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3851 . 'Content-transfer-encoding: 8bit' );
3858 function lose_follower($importer,$contact,$datarray,$item) {
3860 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3861 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3862 intval(CONTACT_IS_SHARING),
3863 intval($contact['id'])
3867 contact_remove($contact['id']);
3871 function lose_sharer($importer,$contact,$datarray,$item) {
3873 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3874 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3875 intval(CONTACT_IS_FOLLOWER),
3876 intval($contact['id'])
3880 contact_remove($contact['id']);
3885 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3889 if(is_array($importer)) {
3890 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3891 intval($importer['uid'])
3895 // Diaspora has different message-ids in feeds than they do
3896 // through the direct Diaspora protocol. If we try and use
3897 // the feed, we'll get duplicates. So don't.
3899 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3902 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3904 // Use a single verify token, even if multiple hubs
3906 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3908 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3910 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3912 if(! strlen($contact['hub-verify'])) {
3913 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3914 dbesc($verify_token),
3915 intval($contact['id'])
3919 post_url($url,$params);
3921 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3928 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3932 $name = xmlify($name);
3933 $uri = xmlify($uri);
3936 $photo = xmlify($photo);
3940 $o .= "<name>$name</name>\r\n";
3941 $o .= "<uri>$uri</uri>\r\n";
3942 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3943 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3945 call_hooks('atom_author', $o);
3947 $o .= "</$tag>\r\n";
3951 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3955 if(! $item['parent'])
3958 if($item['deleted'])
3959 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3962 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3963 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3965 $body = $item['body'];
3967 $o = "\r\n\r\n<entry>\r\n";
3969 if(is_array($author))
3970 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3972 $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']));
3973 if(strlen($item['owner-name']))
3974 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3976 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3977 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3978 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3981 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3982 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3983 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3984 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3985 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3986 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3987 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3989 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3991 if($item['location']) {
3992 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3993 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3997 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3999 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4000 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4003 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4004 if($item['bookmark'])
4005 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4008 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4011 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4013 if($item['signed_text']) {
4014 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4015 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4018 $verb = construct_verb($item);
4019 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4020 $actobj = construct_activity_object($item);
4023 $actarg = construct_activity_target($item);
4027 $tags = item_getfeedtags($item);
4029 foreach($tags as $t) {
4030 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4034 $o .= item_getfeedattach($item);
4036 $mentioned = get_mentions($item);
4040 call_hooks('atom_entry', $o);
4042 $o .= '</entry>' . "\r\n";
4047 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4049 if(get_config('system','disable_embedded'))
4054 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4055 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4060 $img_start = strpos($orig_body, '[img');
4061 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4062 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4063 while( ($img_st_close !== false) && ($img_len !== false) ) {
4065 $img_st_close++; // make it point to AFTER the closing bracket
4066 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4068 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4071 if(stristr($image , $site . '/photo/')) {
4072 // Only embed locally hosted photos
4074 $i = basename($image);
4075 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4076 $x = strpos($i,'-');
4079 $res = substr($i,$x+1);
4080 $i = substr($i,0,$x);
4081 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4088 // Check to see if we should replace this photo link with an embedded image
4089 // 1. No need to do so if the photo is public
4090 // 2. If there's a contact-id provided, see if they're in the access list
4091 // for the photo. If so, embed it.
4092 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4093 // permissions, regardless of order but first check to see if they're an exact
4094 // match to save some processing overhead.
4096 if(has_permissions($r[0])) {
4098 $recips = enumerate_permissions($r[0]);
4099 if(in_array($cid, $recips)) {
4104 if(compare_permissions($item,$r[0]))
4109 $data = $r[0]['data'];
4110 $type = $r[0]['type'];
4112 // If a custom width and height were specified, apply before embedding
4113 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4114 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4116 $width = intval($match[1]);
4117 $height = intval($match[2]);
4119 $ph = new Photo($data, $type);
4120 if($ph->is_valid()) {
4121 $ph->scaleImage(max($width, $height));
4122 $data = $ph->imageString();
4123 $type = $ph->getType();
4127 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4128 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4129 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4135 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4136 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4137 if($orig_body === false)
4140 $img_start = strpos($orig_body, '[img');
4141 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4142 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4145 $new_body = $new_body . $orig_body;
4151 function has_permissions($obj) {
4152 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4157 function compare_permissions($obj1,$obj2) {
4158 // first part is easy. Check that these are exactly the same.
4159 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4160 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4161 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4162 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4165 // This is harder. Parse all the permissions and compare the resulting set.
4167 $recipients1 = enumerate_permissions($obj1);
4168 $recipients2 = enumerate_permissions($obj2);
4171 if($recipients1 == $recipients2)
4176 // returns an array of contact-ids that are allowed to see this object
4178 function enumerate_permissions($obj) {
4179 require_once('include/group.php');
4180 $allow_people = expand_acl($obj['allow_cid']);
4181 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4182 $deny_people = expand_acl($obj['deny_cid']);
4183 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4184 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4185 $deny = array_unique(array_merge($deny_people,$deny_groups));
4186 $recipients = array_diff($recipients,$deny);
4190 function item_getfeedtags($item) {
4193 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4195 for($x = 0; $x < $cnt; $x ++) {
4197 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4201 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4203 for($x = 0; $x < $cnt; $x ++) {
4205 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4211 function item_getfeedattach($item) {
4213 $arr = explode('[/attach],',$item['attach']);
4215 foreach($arr as $r) {
4217 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4219 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4220 if(intval($matches[2]))
4221 $ret .= 'length="' . intval($matches[2]) . '" ';
4222 if($matches[4] !== ' ')
4223 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4224 $ret .= ' />' . "\r\n";
4233 function item_expire($uid, $days, $network = "", $force = false) {
4235 if((! $uid) || ($days < 1))
4238 // $expire_network_only = save your own wall posts
4239 // and just expire conversations started by others
4241 $expire_network_only = get_pconfig($uid,'expire','network_only');
4242 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4244 if ($network != "") {
4245 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4246 // There is an index "uid_network_received" but not "uid_network_created"
4247 // This avoids the creation of another index just for one purpose.
4248 // And it doesn't really matter wether to look at "received" or "created"
4249 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4251 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4253 $r = q("SELECT * FROM `item`
4254 WHERE `uid` = %d $range
4265 $expire_items = get_pconfig($uid, 'expire','items');
4266 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4268 // Forcing expiring of items - but not notes and marked items
4270 $expire_items = true;
4272 $expire_notes = get_pconfig($uid, 'expire','notes');
4273 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4275 $expire_starred = get_pconfig($uid, 'expire','starred');
4276 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4278 $expire_photos = get_pconfig($uid, 'expire','photos');
4279 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4281 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4283 foreach($r as $item) {
4285 // don't expire filed items
4287 if(strpos($item['file'],'[') !== false)
4290 // Only expire posts, not photos and photo comments
4292 if($expire_photos==0 && strlen($item['resource-id']))
4294 if($expire_starred==0 && intval($item['starred']))
4296 if($expire_notes==0 && $item['type']=='note')
4298 if($expire_items==0 && $item['type']!='note')
4301 drop_item($item['id'],false);
4304 proc_run('php',"include/notifier.php","expire","$uid");
4309 function drop_items($items) {
4312 if(! local_user() && ! remote_user())
4316 foreach($items as $item) {
4317 $owner = drop_item($item,false);
4318 if($owner && ! $uid)
4323 // multiple threads may have been deleted, send an expire notification
4326 proc_run('php',"include/notifier.php","expire","$uid");
4330 function drop_item($id,$interactive = true) {
4334 // locate item to be deleted
4336 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4343 notice( t('Item not found.') . EOL);
4344 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4349 $owner = $item['uid'];
4353 // check if logged in user is either the author or owner of this item
4355 if(is_array($_SESSION['remote'])) {
4356 foreach($_SESSION['remote'] as $visitor) {
4357 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4358 $cid = $visitor['cid'];
4365 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4367 // Check if we should do HTML-based delete confirmation
4368 if($_REQUEST['confirm']) {
4369 // <form> can't take arguments in its "action" parameter
4370 // so add any arguments as hidden inputs
4371 $query = explode_querystring($a->query_string);
4373 foreach($query['args'] as $arg) {
4374 if(strpos($arg, 'confirm=') === false) {
4375 $arg_parts = explode('=', $arg);
4376 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4380 return replace_macros(get_markup_template('confirm.tpl'), array(
4382 '$message' => t('Do you really want to delete this item?'),
4383 '$extra_inputs' => $inputs,
4384 '$confirm' => t('Yes'),
4385 '$confirm_url' => $query['base'],
4386 '$confirm_name' => 'confirmed',
4387 '$cancel' => t('Cancel'),
4390 // Now check how the user responded to the confirmation query
4391 if($_REQUEST['canceled']) {
4392 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4395 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4398 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4399 dbesc(datetime_convert()),
4400 dbesc(datetime_convert()),
4403 create_tags_from_item($item['id']);
4404 create_files_from_item($item['id']);
4405 delete_thread($item['id']);
4407 // clean up categories and tags so they don't end up as orphans
4410 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4412 foreach($matches as $mtch) {
4413 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4419 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4421 foreach($matches as $mtch) {
4422 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4426 // If item is a link to a photo resource, nuke all the associated photos
4427 // (visitors will not have photo resources)
4428 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4429 // generate a resource-id and therefore aren't intimately linked to the item.
4431 if(strlen($item['resource-id'])) {
4432 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4433 dbesc($item['resource-id']),
4434 intval($item['uid'])
4436 // ignore the result
4439 // If item is a link to an event, nuke the event record.
4441 if(intval($item['event-id'])) {
4442 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4443 intval($item['event-id']),
4444 intval($item['uid'])
4446 // ignore the result
4449 // clean up item_id and sign meta-data tables
4452 // Old code - caused very long queries and warning entries in the mysql logfiles:
4454 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4455 intval($item['id']),
4456 intval($item['uid'])
4459 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4460 intval($item['id']),
4461 intval($item['uid'])
4465 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4467 // Creating list of parents
4468 $r = q("select id from item where parent = %d and uid = %d",
4469 intval($item['id']),
4470 intval($item['uid'])
4475 foreach ($r AS $row) {
4476 if ($parentid != "")
4479 $parentid .= $row["id"];
4483 if ($parentid != "") {
4484 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4486 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4489 // If it's the parent of a comment thread, kill all the kids
4491 if($item['uri'] == $item['parent-uri']) {
4492 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4493 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4494 dbesc(datetime_convert()),
4495 dbesc(datetime_convert()),
4496 dbesc($item['parent-uri']),
4497 intval($item['uid'])
4499 create_tags_from_item($item['parent-uri'], $item['uid']);
4500 create_files_from_item($item['parent-uri'], $item['uid']);
4501 delete_thread_uri($item['parent-uri'], $item['uid']);
4502 // ignore the result
4505 // ensure that last-child is set in case the comment that had it just got wiped.
4506 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4507 dbesc(datetime_convert()),
4508 dbesc($item['parent-uri']),
4509 intval($item['uid'])
4511 // who is the last child now?
4512 $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",
4513 dbesc($item['parent-uri']),
4514 intval($item['uid'])
4517 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4522 // Add a relayable_retraction signature for Diaspora.
4523 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4525 $drop_id = intval($item['id']);
4527 // send the notification upstream/downstream as the case may be
4529 proc_run('php',"include/notifier.php","drop","$drop_id");
4533 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4539 notice( t('Permission denied.') . EOL);
4540 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4547 function first_post_date($uid,$wall = false) {
4548 $r = q("select id, created from item
4549 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4551 order by created asc limit 1",
4553 intval($wall ? 1 : 0)
4556 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4557 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4562 function posted_dates($uid,$wall) {
4563 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4565 $dthen = first_post_date($uid,$wall);
4569 // If it's near the end of a long month, backup to the 28th so that in
4570 // consecutive loops we'll always get a whole month difference.
4572 if(intval(substr($dnow,8)) > 28)
4573 $dnow = substr($dnow,0,8) . '28';
4574 if(intval(substr($dthen,8)) > 28)
4575 $dnow = substr($dthen,0,8) . '28';
4578 // Starting with the current month, get the first and last days of every
4579 // month down to and including the month of the first post
4580 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4581 $dstart = substr($dnow,0,8) . '01';
4582 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4583 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4584 $end_month = datetime_convert('','',$dend,'Y-m-d');
4585 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4586 $ret[] = array($str,$end_month,$start_month);
4587 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4593 function posted_date_widget($url,$uid,$wall) {
4596 if(! feature_enabled($uid,'archives'))
4599 // For former Facebook folks that left because of "timeline"
4601 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4604 $ret = posted_dates($uid,$wall);
4608 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4609 '$title' => t('Archives'),
4610 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4617 function store_diaspora_retract_sig($item, $user, $baseurl) {
4618 // Note that we can't add a target_author_signature
4619 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4620 // the comment, that means we're the home of the post, and Diaspora will only
4621 // check the parent_author_signature of retractions that it doesn't have to relay further
4623 // I don't think this function gets called for an "unlike," but I'll check anyway
4625 $enabled = intval(get_config('system','diaspora_enabled'));
4627 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4631 logger('drop_item: storing diaspora retraction signature');
4633 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4635 if(local_user() == $item['uid']) {
4637 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4638 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4641 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4642 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4645 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4646 // only handles DFRN deletes
4647 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4648 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4649 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4655 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4656 intval($item['id']),
4657 dbesc($signed_text),