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/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('mod/share.php');
19 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
22 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
25 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
26 $public_feed = (($dfrn_id) ? false : true);
27 $starred = false; // not yet implemented, possible security issues
30 if($public_feed && $a->argc > 2) {
31 for($x = 2; $x < $a->argc; $x++) {
32 if($a->argv[$x] == 'converse')
34 if($a->argv[$x] == 'starred')
36 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
37 $category = $a->argv[$x+1];
43 // default permissions - anonymous user
45 $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
47 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
48 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
49 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
57 $owner_id = $owner['user_uid'];
58 $owner_nick = $owner['nickname'];
60 $birthday = feed_birthday($owner_id,$owner['timezone']);
70 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
74 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
75 $my_id = '1:' . $dfrn_id;
78 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
79 $my_id = '0:' . $dfrn_id;
86 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
94 require_once('include/security.php');
95 $groups = init_groups_visitor($contact['id']);
98 for($x = 0; $x < count($groups); $x ++)
99 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
100 $gs = implode('|', $groups);
103 $gs = '<<>>' ; // Impossible to match
105 $sql_extra = sprintf("
106 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
107 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
108 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
109 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
111 intval($contact['id']),
112 intval($contact['id']),
123 $date_field = "`changed`";
124 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
126 if(! strlen($last_update))
127 $last_update = 'now -30 days';
129 if(isset($category)) {
130 $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` ",
131 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
132 //$sql_extra .= file_tag_file_query('item',$category,'category');
137 $sql_extra .= " AND `contact`.`self` = 1 ";
140 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
142 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
143 // dbesc($check_date),
145 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
146 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
147 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
148 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
149 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
150 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
151 FROM `item` $sql_post_table
152 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
153 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
154 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
155 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
156 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
158 ORDER BY $sql_order LIMIT 0, 300",
164 // Will check further below if this actually returned results.
165 // We will provide an empty feed if that is the case.
169 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
173 $hubxml = feed_hublinks();
175 $salmon = feed_salmonlinks($owner_nick);
177 $alternatelink = $owner['url'];
180 $alternatelink .= "/category/".$category;
182 $atom .= replace_macros($feed_template, array(
183 '$version' => xmlify(FRIENDICA_VERSION),
184 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
185 '$feed_title' => xmlify($owner['name']),
186 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
188 '$salmon' => $salmon,
189 '$alternatelink' => xmlify($alternatelink),
190 '$name' => xmlify($owner['name']),
191 '$profile_page' => xmlify($owner['url']),
192 '$photo' => xmlify($owner['photo']),
193 '$thumb' => xmlify($owner['thumb']),
194 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
195 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
196 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
197 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
198 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
201 call_hooks('atom_feed', $atom);
203 if(! count($items)) {
205 call_hooks('atom_feed_end', $atom);
207 $atom .= '</feed>' . "\r\n";
211 foreach($items as $item) {
213 // prevent private email from leaking.
214 if($item['network'] === NETWORK_MAIL)
217 // public feeds get html, our own nodes use bbcode
221 // catch any email that's in a public conversation and make sure it doesn't leak
229 $atom .= atom_entry($item,$type,null,$owner,true);
232 call_hooks('atom_feed_end', $atom);
234 $atom .= '</feed>' . "\r\n";
240 function construct_verb($item) {
242 return $item['verb'];
243 return ACTIVITY_POST;
246 function construct_activity_object($item) {
248 if($item['object']) {
249 $o = '<as:object>' . "\r\n";
250 $r = parse_xml_string($item['object'],false);
256 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
258 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
260 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
262 if(substr($r->link,0,1) === '<') {
263 // patch up some facebook "like" activity objects that got stored incorrectly
264 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
265 // we can probably remove this hack here and in the following function in a few months time.
266 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
267 $r->link = str_replace('&','&', $r->link);
268 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
272 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
275 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
276 $o .= '</as:object>' . "\r\n";
283 function construct_activity_target($item) {
285 if($item['target']) {
286 $o = '<as:target>' . "\r\n";
287 $r = parse_xml_string($item['target'],false);
291 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
293 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
295 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
297 if(substr($r->link,0,1) === '<') {
298 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
299 $r->link = str_replace('&','&', $r->link);
300 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
304 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
307 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
308 $o .= '</as:target>' . "\r\n";
317 * The purpose of this function is to apply system message length limits to
318 * imported messages without including any embedded photos in the length
320 if(! function_exists('limit_body_size')) {
321 function limit_body_size($body) {
323 // logger('limit_body_size: start', LOGGER_DEBUG);
325 $maxlen = get_max_import_size();
327 // If the length of the body, including the embedded images, is smaller
328 // than the maximum, then don't waste time looking for the images
329 if($maxlen && (strlen($body) > $maxlen)) {
331 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
338 $img_start = strpos($orig_body, '[img');
339 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
340 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
341 while(($img_st_close !== false) && ($img_end !== false)) {
343 $img_st_close++; // make it point to AFTER the closing bracket
344 $img_end += $img_start;
345 $img_end += strlen('[/img]');
347 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
348 // This is an embedded image
350 if( ($textlen + $img_start) > $maxlen ) {
351 if($textlen < $maxlen) {
352 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
353 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
358 $new_body = $new_body . substr($orig_body, 0, $img_start);
359 $textlen += $img_start;
362 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
366 if( ($textlen + $img_end) > $maxlen ) {
367 if($textlen < $maxlen) {
368 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
369 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
374 $new_body = $new_body . substr($orig_body, 0, $img_end);
375 $textlen += $img_end;
378 $orig_body = substr($orig_body, $img_end);
380 if($orig_body === false) // in case the body ends on a closing image tag
383 $img_start = strpos($orig_body, '[img');
384 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
385 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
388 if( ($textlen + strlen($orig_body)) > $maxlen) {
389 if($textlen < $maxlen) {
390 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
391 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
396 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
397 $new_body = $new_body . $orig_body;
398 $textlen += strlen($orig_body);
407 function title_is_body($title, $body) {
409 $title = strip_tags($title);
410 $title = trim($title);
411 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
412 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
414 $body = strip_tags($body);
416 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
417 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
419 if (strlen($title) < strlen($body))
420 $body = substr($body, 0, strlen($title));
422 if (($title != $body) and (substr($title, -3) == "...")) {
423 $pos = strrpos($title, "...");
425 $title = substr($title, 0, $pos);
426 $body = substr($body, 0, $pos);
430 return($title == $body);
435 function get_atom_elements($feed, $item, $contact = array()) {
437 require_once('library/HTMLPurifier.auto.php');
438 require_once('include/html2bbcode.php');
440 $best_photo = array();
444 $author = $item->get_author();
446 $res['author-name'] = unxmlify($author->get_name());
447 $res['author-link'] = unxmlify($author->get_link());
450 $res['author-name'] = unxmlify($feed->get_title());
451 $res['author-link'] = unxmlify($feed->get_permalink());
453 $res['uri'] = unxmlify($item->get_id());
454 $res['title'] = unxmlify($item->get_title());
455 $res['body'] = unxmlify($item->get_content());
456 $res['plink'] = unxmlify($item->get_link(0));
458 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
459 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
461 $res['body'] = nl2br($res['body']);
464 // removing the content of the title if its identically to the body
465 // This helps with auto generated titles e.g. from tumblr
466 if (title_is_body($res["title"], $res["body"]))
470 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
474 // look for a photo. We should check media size and find the best one,
475 // but for now let's just find any author photo
476 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
478 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
479 if (is_array($authorlinks)) {
480 foreach ($authorlinks as $link) {
481 $linkdata = array_shift($link["attribs"]);
483 if ($linkdata["rel"] == "alternate")
484 $res["author-link"] = $linkdata["href"];
488 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
490 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
491 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
492 foreach($base as $link) {
493 if($link['attribs']['']['rel'] === 'alternate')
494 $res['author-link'] = unxmlify($link['attribs']['']['href']);
496 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
497 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
498 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
503 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
505 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
506 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
507 if($base && count($base)) {
508 foreach($base as $link) {
509 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
510 $res['author-link'] = unxmlify($link['attribs']['']['href']);
511 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
512 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
513 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
519 // No photo/profile-link on the item - look at the feed level
521 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
522 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
523 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
524 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
525 foreach($base as $link) {
526 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
527 $res['author-link'] = unxmlify($link['attribs']['']['href']);
528 if(! $res['author-avatar']) {
529 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
530 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
535 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
537 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
538 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
540 if($base && count($base)) {
541 foreach($base as $link) {
542 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
543 $res['author-link'] = unxmlify($link['attribs']['']['href']);
544 if(! (x($res,'author-avatar'))) {
545 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
546 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
553 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
554 if($apps && $apps[0]['attribs']['']['source']) {
555 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
556 if($res['app'] === 'web')
557 $res['app'] = 'OStatus';
560 // base64 encoded json structure representing Diaspora signature
562 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
564 $res['dsprsig'] = unxmlify($dsig[0]['data']);
567 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
569 $res['guid'] = unxmlify($dguid[0]['data']);
571 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
573 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
577 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
580 $have_real_body = false;
582 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
584 $have_real_body = true;
585 $res['body'] = $rawenv[0]['data'];
586 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
587 // make sure nobody is trying to sneak some html tags by us
588 $res['body'] = notags(base64url_decode($res['body']));
592 $res['body'] = limit_body_size($res['body']);
594 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
595 // the content type. Our own network only emits text normally, though it might have been converted to
596 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
597 // have to assume it is all html and needs to be purified.
599 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
600 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
601 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
604 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
606 $res['body'] = reltoabs($res['body'],$base_url);
608 $res['body'] = html2bb_video($res['body']);
610 $res['body'] = oembed_html2bbcode($res['body']);
612 $config = HTMLPurifier_Config::createDefault();
613 $config->set('Cache.DefinitionImpl', null);
615 // we shouldn't need a whitelist, because the bbcode converter
616 // will strip out any unsupported tags.
618 $purifier = new HTMLPurifier($config);
619 $res['body'] = $purifier->purify($res['body']);
621 $res['body'] = @html2bbcode($res['body']);
625 elseif(! $have_real_body) {
627 // it's not one of our messages and it has no tags
628 // so it's probably just text. We'll escape it just to be safe.
630 $res['body'] = escape_tags($res['body']);
634 // this tag is obsolete but we keep it for really old sites
636 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
637 if($allow && $allow[0]['data'] == 1)
638 $res['last-child'] = 1;
640 $res['last-child'] = 0;
642 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
643 if($private && intval($private[0]['data']) > 0)
644 $res['private'] = intval($private[0]['data']);
648 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
649 if($extid && $extid[0]['data'])
650 $res['extid'] = $extid[0]['data'];
652 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
654 $res['location'] = unxmlify($rawlocation[0]['data']);
657 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
659 $res['created'] = unxmlify($rawcreated[0]['data']);
662 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
664 $res['edited'] = unxmlify($rawedited[0]['data']);
666 if((x($res,'edited')) && (! (x($res,'created'))))
667 $res['created'] = $res['edited'];
669 if(! $res['created'])
670 $res['created'] = $item->get_date('c');
673 $res['edited'] = $item->get_date('c');
676 // Disallow time travelling posts
678 $d1 = strtotime($res['created']);
679 $d2 = strtotime($res['edited']);
680 $d3 = strtotime('now');
683 $res['created'] = datetime_convert();
685 $res['edited'] = datetime_convert();
687 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
688 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
689 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
690 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
691 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
692 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
693 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
694 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
695 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
697 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
698 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
700 foreach($base as $link) {
701 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
702 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
703 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
708 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
710 $res['coord'] = unxmlify($rawgeo[0]['data']);
712 if ($contact["network"] == NETWORK_FEED) {
713 $res['verb'] = ACTIVITY_POST;
714 $res['object-type'] = ACTIVITY_OBJ_NOTE;
717 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
719 // select between supported verbs
722 $res['verb'] = unxmlify($rawverb[0]['data']);
725 // translate OStatus unfollow to activity streams if it happened to get selected
727 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
728 $res['verb'] = ACTIVITY_UNFOLLOW;
730 $cats = $item->get_categories();
733 foreach($cats as $cat) {
734 $term = $cat->get_term();
736 $term = $cat->get_label();
737 $scheme = $cat->get_scheme();
738 if($scheme && $term && stristr($scheme,'X-DFRN:'))
739 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
741 $tag_arr[] = notags(trim($term));
743 $res['tag'] = implode(',', $tag_arr);
746 $attach = $item->get_enclosures();
749 foreach($attach as $att) {
750 $len = intval($att->get_length());
751 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
752 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
753 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
754 if(strpos($type,';'))
755 $type = substr($type,0,strpos($type,';'));
756 if((! $link) || (strpos($link,'http') !== 0))
762 $type = 'application/octet-stream';
764 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
766 $res['attach'] = implode(',', $att_arr);
769 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
772 $res['object'] = '<object>' . "\n";
773 $child = $rawobj[0]['child'];
774 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
775 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
776 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
778 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
779 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
780 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
781 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
782 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
783 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
784 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
785 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
787 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
788 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
789 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
790 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
792 $body = html2bb_video($body);
794 $config = HTMLPurifier_Config::createDefault();
795 $config->set('Cache.DefinitionImpl', null);
797 $purifier = new HTMLPurifier($config);
798 $body = $purifier->purify($body);
799 $body = html2bbcode($body);
802 $res['object'] .= '<content>' . $body . '</content>' . "\n";
805 $res['object'] .= '</object>' . "\n";
808 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
811 $res['target'] = '<target>' . "\n";
812 $child = $rawobj[0]['child'];
813 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
814 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
816 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
817 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
818 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
819 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
820 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
821 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
822 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
823 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
825 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
826 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
827 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
828 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
830 $body = html2bb_video($body);
832 $config = HTMLPurifier_Config::createDefault();
833 $config->set('Cache.DefinitionImpl', null);
835 $purifier = new HTMLPurifier($config);
836 $body = $purifier->purify($body);
837 $body = html2bbcode($body);
840 $res['target'] .= '<content>' . $body . '</content>' . "\n";
843 $res['target'] .= '</target>' . "\n";
846 // This is some experimental stuff. By now retweets are shown with "RT:"
847 // But: There is data so that the message could be shown similar to native retweets
848 // There is some better way to parse this array - but it didn't worked for me.
849 $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"];
850 if (is_array($child)) {
851 logger('get_atom_elements: Looking for status.net repeated message');
853 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
854 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
855 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
856 $uri = $author["uri"][0]["data"];
857 $name = $author["name"][0]["data"];
858 $avatar = @array_shift($author["link"][2]["attribs"]);
859 $avatar = $avatar["href"];
861 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
862 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
864 if (!intval(get_config('system','wall-to-wall_share'))) {
865 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
867 $res["body"] = $prefix.html2bbcode($message)."[/share]";
869 $res["owner-name"] = $res["author-name"];
870 $res["owner-link"] = $res["author-link"];
871 $res["owner-avatar"] = $res["author-avatar"];
873 $res["author-name"] = $name;
874 $res["author-link"] = $uri;
875 $res["author-avatar"] = $avatar;
877 $res["body"] = html2bbcode($message);
882 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
885 // Handle enclosures and treat them as preview picture
887 foreach ($attach AS $attachment)
888 if ($attachment->type == "image/jpeg")
889 $preview = $attachment->link;
891 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
892 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
894 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
895 unset($res["attach"]);
896 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
897 $res["body"] = add_page_info_to_body($res["body"]);
898 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
899 $res["body"] = add_page_info_to_body($res["body"]);
902 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
904 call_hooks('parse_atom', $arr);
909 function add_page_info_data($data) {
910 call_hooks('page_info_data', $data);
912 // It maybe is a rich content, but if it does have everything that a link has,
913 // then treat it that way
914 if (($data["type"] == "rich") AND is_string($data["title"]) AND
915 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
916 $data["type"] = "link";
918 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
921 if ($no_photos AND ($data["type"] == "photo"))
924 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
925 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
926 require_once("include/network.php");
927 $data["url"] = short_link($data["url"]);
930 if (($data["type"] != "photo") AND is_string($data["title"]))
931 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
933 if (($data["type"] != "video") AND ($photo != ""))
934 $text .= '[img]'.$photo.'[/img]';
935 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
936 $imagedata = $data["images"][0];
937 $text .= '[img]'.$imagedata["src"].'[/img]';
940 if (($data["type"] != "photo") AND is_string($data["text"]))
941 $text .= "[quote]".$data["text"]."[/quote]";
944 if (isset($data["keywords"]) AND count($data["keywords"])) {
947 foreach ($data["keywords"] AS $keyword) {
948 /// @todo make a positive list of allowed characters
949 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
950 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
951 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
955 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
958 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
959 require_once("mod/parse_url.php");
961 $data = parseurl_getsiteinfo_cached($url, true);
964 $data["images"][0]["src"] = $photo;
966 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
968 if (!$keywords AND isset($data["keywords"]))
969 unset($data["keywords"]);
971 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
972 $list = explode(",", $keyword_blacklist);
973 foreach ($list AS $keyword) {
974 $keyword = trim($keyword);
975 $index = array_search($keyword, $data["keywords"]);
976 if ($index !== false)
977 unset($data["keywords"][$index]);
984 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
985 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
988 if (isset($data["keywords"]) AND count($data["keywords"])) {
990 foreach ($data["keywords"] AS $keyword) {
991 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
992 array("","", "", "", "", ""), $keyword);
997 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1004 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1005 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1007 $text = add_page_info_data($data);
1012 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1014 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1016 $URLSearchString = "^\[\]";
1018 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1019 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1022 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1024 // Convert urls without bbcode elements
1025 if (!$matches AND $texturl) {
1026 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1028 // Yeah, a hack. I really hate regular expressions :)
1030 $matches[1] = $matches[2];
1034 $footer = add_page_info($matches[1], $no_photos);
1036 // Remove the link from the body if the link is attached at the end of the post
1037 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1038 $removedlink = trim(str_replace($matches[1], "", $body));
1039 if (($removedlink == "") OR strstr($body, $removedlink))
1040 $body = $removedlink;
1042 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1043 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1044 if (($removedlink == "") OR strstr($body, $removedlink))
1045 $body = $removedlink;
1048 // Add the page information to the bottom
1049 if (isset($footer) AND (trim($footer) != ""))
1055 function encode_rel_links($links) {
1057 if(! ((is_array($links)) && (count($links))))
1059 foreach($links as $link) {
1061 if($link['attribs']['']['rel'])
1062 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1063 if($link['attribs']['']['type'])
1064 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1065 if($link['attribs']['']['href'])
1066 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1067 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1068 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1069 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1070 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1071 $o .= ' />' . "\n" ;
1076 function add_guid($item) {
1077 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1081 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1082 dbesc($item["guid"]), dbesc($item["plink"]),
1083 dbesc($item["uri"]), dbesc($item["network"]));
1087 * Adds a "lang" specification in a "postopts" element of given $arr,
1088 * if possible and not already present.
1089 * Expects "body" element to exist in $arr.
1091 * @todo Add a parameter to request forcing override
1093 function item_add_language_opt(&$arr) {
1095 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1097 if ( x($arr, 'postopts') )
1099 if ( strstr($arr['postopts'], 'lang=') )
1102 /// @TODO Add parameter to request overriding
1105 $postopts = $arr['postopts'];
1112 require_once('library/langdet/Text/LanguageDetect.php');
1113 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1114 $l = new Text_LanguageDetect;
1115 //$lng = $l->detectConfidence($naked_body);
1116 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1117 $lng = $l->detect($naked_body, 3);
1119 if (sizeof($lng) > 0) {
1120 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
1121 $postopts .= 'lang=';
1123 foreach ($lng as $language => $score) {
1124 $postopts .= $sep . $language.";".$score;
1127 $arr['postopts'] = $postopts;
1131 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1133 // If it is a posting where users should get notifications, then define it as wall posting
1136 $arr['type'] = 'wall';
1138 $arr['last-child'] = 1;
1139 $arr['network'] = NETWORK_DFRN;
1142 // If a Diaspora signature structure was passed in, pull it out of the
1143 // item array and set it aside for later storage.
1146 if(x($arr,'dsprsig')) {
1147 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1148 unset($arr['dsprsig']);
1151 // Converting the plink
1152 if ($arr['network'] == NETWORK_OSTATUS) {
1153 if (isset($arr['plink']))
1154 $arr['plink'] = ostatus_convert_href($arr['plink']);
1155 elseif (isset($arr['uri']))
1156 $arr['plink'] = ostatus_convert_href($arr['uri']);
1159 if(x($arr, 'gravity'))
1160 $arr['gravity'] = intval($arr['gravity']);
1161 elseif($arr['parent-uri'] === $arr['uri'])
1162 $arr['gravity'] = 0;
1163 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1164 $arr['gravity'] = 6;
1166 $arr['gravity'] = 6; // extensible catchall
1168 if(! x($arr,'type'))
1169 $arr['type'] = 'remote';
1173 /* check for create date and expire time */
1174 $uid = intval($arr['uid']);
1175 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1177 $expire_interval = $r[0]['expire'];
1178 if ($expire_interval>0) {
1179 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1180 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1181 if ($created_date < $expire_date) {
1182 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1188 // Do we already have this item?
1189 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
1190 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
1191 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
1192 dbesc(trim($arr['uri'])),
1194 dbesc(NETWORK_DIASPORA),
1195 dbesc(NETWORK_DFRN),
1196 dbesc(NETWORK_OSTATUS)
1199 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
1201 logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
1202 return($r[0]["id"]);
1206 // If there is no guid then take the same guid that was taken before for the same uri
1207 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1208 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1209 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1210 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1213 $arr['guid'] = $r[0]["guid"];
1214 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1218 // If there is no guid then take the same guid that was taken before for the same plink
1219 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1220 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1221 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1222 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1225 $arr['guid'] = $r[0]["guid"];
1226 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1228 if ($r[0]["uri"] != $arr['uri'])
1229 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1233 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1234 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1235 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1236 // $arr['body'] = strip_tags($arr['body']);
1238 item_add_language_opt($arr);
1243 $parsed = parse_url($arr["author-link"]);
1244 $guid_prefix = hash("crc32", $parsed["host"]);
1247 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1248 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1249 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1250 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1251 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
1252 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1253 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1254 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
1255 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1256 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1257 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1258 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1259 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1260 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1261 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1262 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
1263 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
1264 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1265 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1266 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1267 $arr['deleted'] = 0;
1268 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1269 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1270 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1271 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1272 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1273 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1274 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1275 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1276 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1277 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1278 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1279 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1280 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1281 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1282 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1283 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1284 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1285 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1286 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1287 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1288 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1289 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1290 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1291 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1293 if ($arr['plink'] == "") {
1295 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1298 if ($arr['network'] == "") {
1299 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
1300 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1301 dbesc(normalise_link($arr['author-link'])),
1306 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
1307 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1308 dbesc(normalise_link($arr['author-link']))
1312 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1313 intval($arr['contact-id']),
1318 $arr['network'] = $r[0]["network"];
1320 // Fallback to friendica (why is it empty in some cases?)
1321 if ($arr['network'] == "")
1322 $arr['network'] = NETWORK_DFRN;
1324 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1327 if ($arr["gcontact-id"] == 0)
1328 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1329 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1331 if ($arr['guid'] != "") {
1332 // Checking if there is already an item with the same guid
1333 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1334 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1335 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1338 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1343 // Check for hashtags in the body and repair or add hashtag links
1344 item_body_set_hashtags($arr);
1346 $arr['thr-parent'] = $arr['parent-uri'];
1347 if($arr['parent-uri'] === $arr['uri']) {
1349 $parent_deleted = 0;
1350 $allow_cid = $arr['allow_cid'];
1351 $allow_gid = $arr['allow_gid'];
1352 $deny_cid = $arr['deny_cid'];
1353 $deny_gid = $arr['deny_gid'];
1354 $notify_type = 'wall-new';
1358 // find the parent and snarf the item id and ACLs
1359 // and anything else we need to inherit
1361 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1362 dbesc($arr['parent-uri']),
1368 // is the new message multi-level threaded?
1369 // even though we don't support it now, preserve the info
1370 // and re-attach to the conversation parent.
1372 if($r[0]['uri'] != $r[0]['parent-uri']) {
1373 $arr['parent-uri'] = $r[0]['parent-uri'];
1374 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1375 ORDER BY `id` ASC LIMIT 1",
1376 dbesc($r[0]['parent-uri']),
1377 dbesc($r[0]['parent-uri']),
1384 $parent_id = $r[0]['id'];
1385 $parent_deleted = $r[0]['deleted'];
1386 $allow_cid = $r[0]['allow_cid'];
1387 $allow_gid = $r[0]['allow_gid'];
1388 $deny_cid = $r[0]['deny_cid'];
1389 $deny_gid = $r[0]['deny_gid'];
1390 $arr['wall'] = $r[0]['wall'];
1391 $notify_type = 'comment-new';
1393 // if the parent is private, force privacy for the entire conversation
1394 // This differs from the above settings as it subtly allows comments from
1395 // email correspondents to be private even if the overall thread is not.
1397 if($r[0]['private'])
1398 $arr['private'] = $r[0]['private'];
1400 // Edge case. We host a public forum that was originally posted to privately.
1401 // The original author commented, but as this is a comment, the permissions
1402 // weren't fixed up so it will still show the comment as private unless we fix it here.
1404 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1405 $arr['private'] = 0;
1408 // If its a post from myself then tag the thread as "mention"
1409 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1410 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1413 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1414 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1415 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1416 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1417 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1423 // Allow one to see reply tweets from status.net even when
1424 // we don't have or can't see the original post.
1427 logger('item_store: $force_parent=true, reply converted to top-level post.');
1429 $arr['parent-uri'] = $arr['uri'];
1430 $arr['gravity'] = 0;
1433 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1437 $parent_deleted = 0;
1441 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1443 dbesc($arr['network']),
1444 dbesc(NETWORK_DFRN),
1447 if($r && count($r)) {
1448 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1452 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1453 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1454 dbesc($arr['body']),
1455 dbesc($arr['network']),
1456 dbesc($arr['created']),
1457 intval($arr['contact-id']),
1460 if($r && count($r)) {
1461 logger('duplicated item with the same body found. ' . print_r($arr,true));
1465 // Is this item available in the global items (with uid=0)?
1466 if ($arr["uid"] == 0) {
1467 $arr["global"] = true;
1469 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1471 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1473 $arr["global"] = (count($isglobal) > 0);
1476 // Fill the cache field
1477 put_item_in_cache($arr);
1480 call_hooks('post_local',$arr);
1482 call_hooks('post_remote',$arr);
1484 if(x($arr,'cancel')) {
1485 logger('item_store: post cancelled by plugin.');
1489 // Store the unescaped version
1494 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1496 $r = dbq("INSERT INTO `item` (`"
1497 . implode("`, `", array_keys($arr))
1499 . implode("', '", array_values($arr))
1505 // find the item that we just created
1506 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1508 intval($arr['uid']),
1509 dbesc($arr['network'])
1513 // There are duplicates. Keep the oldest one, delete the others
1514 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1515 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1517 intval($arr['uid']),
1518 dbesc($arr['network']),
1522 } elseif(count($r)) {
1524 // Store the guid and other relevant data
1527 $current_post = $r[0]['id'];
1528 logger('item_store: created item ' . $current_post);
1530 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1531 // This can be used to filter for inactive contacts.
1532 // Only do this for public postings to avoid privacy problems, since poco data is public.
1533 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1535 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1537 // Is it a forum? Then we don't care about the rules from above
1538 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1539 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1540 intval($arr['contact-id']));
1546 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1547 dbesc($arr['received']),
1548 dbesc($arr['received']),
1549 intval($arr['contact-id'])
1552 logger('item_store: could not locate created item');
1556 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1557 $parent_id = $current_post;
1559 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1562 $private = $arr['private'];
1564 // Set parent id - and also make sure to inherit the parent's ACLs.
1566 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1567 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1574 intval($parent_deleted),
1575 intval($current_post)
1578 $arr['id'] = $current_post;
1579 $arr['parent'] = $parent_id;
1580 $arr['allow_cid'] = $allow_cid;
1581 $arr['allow_gid'] = $allow_gid;
1582 $arr['deny_cid'] = $deny_cid;
1583 $arr['deny_gid'] = $deny_gid;
1584 $arr['private'] = $private;
1585 $arr['deleted'] = $parent_deleted;
1587 // update the commented timestamp on the parent
1588 // Only update "commented" if it is really a comment
1589 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1590 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1591 dbesc(datetime_convert()),
1592 dbesc(datetime_convert()),
1596 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1597 dbesc(datetime_convert()),
1603 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1604 // We can check for this condition when we decode and encode the stuff again.
1605 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1606 $dsprsig->signature = base64_decode($dsprsig->signature);
1607 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1610 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1611 intval($current_post),
1612 dbesc($dsprsig->signed_text),
1613 dbesc($dsprsig->signature),
1614 dbesc($dsprsig->signer)
1620 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1623 if($arr['last-child']) {
1624 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1626 intval($arr['uid']),
1627 intval($current_post)
1631 $deleted = tag_deliver($arr['uid'],$current_post);
1633 // current post can be deleted if is for a community page and no mention are
1635 if (!$deleted AND !$dontcache) {
1637 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1638 if (count($r) == 1) {
1640 call_hooks('post_local_end', $r[0]);
1642 call_hooks('post_remote_end', $r[0]);
1644 logger('item_store: new item not found in DB, id ' . $current_post);
1647 // Add every contact of the post to the global contact table
1650 create_tags_from_item($current_post);
1651 create_files_from_item($current_post);
1653 // Only check for notifications on start posts
1654 if ($arr['parent-uri'] === $arr['uri']) {
1655 add_thread($current_post);
1656 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1658 // Send a notification for every new post?
1659 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1660 intval($arr['contact-id']),
1663 $send_notification = count($r);
1665 if (!$send_notification) {
1666 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1667 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1670 foreach ($tags AS $tag) {
1671 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1672 normalise_link($tag["url"]), intval($arr['uid']));
1674 $send_notification = true;
1679 if ($send_notification) {
1680 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1681 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1682 intval($arr['uid']));
1684 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1685 intval($current_post),
1691 require_once('include/enotify.php');
1693 'type' => NOTIFY_SHARE,
1694 'notify_flags' => $u[0]['notify-flags'],
1695 'language' => $u[0]['language'],
1696 'to_name' => $u[0]['username'],
1697 'to_email' => $u[0]['email'],
1698 'uid' => $u[0]['uid'],
1700 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1701 'source_name' => $item[0]['author-name'],
1702 'source_link' => $item[0]['author-link'],
1703 'source_photo' => $item[0]['author-avatar'],
1704 'verb' => ACTIVITY_TAG,
1706 'parent' => $arr['parent']
1708 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1711 update_thread($parent_id);
1712 add_shadow_entry($arr);
1716 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1718 return $current_post;
1721 function item_body_set_hashtags(&$item) {
1723 $tags = get_tags($item["body"]);
1729 // This sorting is important when there are hashtags that are part of other hashtags
1730 // Otherwise there could be problems with hashtags like #test and #test2
1735 $URLSearchString = "^\[\]";
1737 // All hashtags should point to the home server
1738 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1739 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1741 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1742 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1744 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1745 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1747 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1750 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1752 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1755 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1757 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1760 // Repair recursive urls
1761 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1762 "#$2", $item["body"]);
1765 foreach($tags as $tag) {
1766 if(strpos($tag,'#') !== 0)
1769 if(strpos($tag,'[url='))
1772 $basetag = str_replace('_',' ',substr($tag,1));
1774 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1776 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1778 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1779 if(strlen($item["tag"]))
1780 $item["tag"] = ','.$item["tag"];
1781 $item["tag"] = $newtag.$item["tag"];
1785 // Convert back the masked hashtags
1786 $item["body"] = str_replace("#", "#", $item["body"]);
1789 function get_item_guid($id) {
1790 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1792 return($r[0]["guid"]);
1797 function get_item_id($guid, $uid = 0) {
1803 $uid == local_user();
1805 // Does the given user have this item?
1807 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1808 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1809 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1812 $nick = $r[0]["nickname"];
1816 // Or is it anywhere on the server?
1818 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1819 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1820 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1821 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1822 AND `item`.`private` = 0 AND `item`.`wall` = 1
1823 AND `item`.`guid` = '%s'", dbesc($guid));
1826 $nick = $r[0]["nickname"];
1829 return(array("nick" => $nick, "id" => $id));
1833 function get_item_contact($item,$contacts) {
1834 if(! count($contacts) || (! is_array($item)))
1836 foreach($contacts as $contact) {
1837 if($contact['id'] == $item['contact-id']) {
1839 break; // NOTREACHED
1846 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1848 * @param int $item_id
1849 * @return bool true if item was deleted, else false
1851 function tag_deliver($uid,$item_id) {
1859 $u = q("select * from user where uid = %d limit 1",
1865 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1866 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1869 $i = q("select * from item where id = %d and uid = %d limit 1",
1878 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1880 // Diaspora uses their own hardwired link URL in @-tags
1881 // instead of the one we supply with webfinger
1883 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1885 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1887 foreach($matches as $mtch) {
1888 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1890 logger('tag_deliver: mention found: ' . $mtch[2]);
1896 if ( ($community_page || $prvgroup) &&
1897 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1898 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1900 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1901 q("DELETE FROM item WHERE id = %d and uid = %d",
1911 // send a notification
1913 // use a local photo if we have one
1915 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1916 intval($u[0]['uid']),
1917 dbesc(normalise_link($item['author-link']))
1919 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1922 require_once('include/enotify.php');
1924 'type' => NOTIFY_TAGSELF,
1925 'notify_flags' => $u[0]['notify-flags'],
1926 'language' => $u[0]['language'],
1927 'to_name' => $u[0]['username'],
1928 'to_email' => $u[0]['email'],
1929 'uid' => $u[0]['uid'],
1931 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1932 'source_name' => $item['author-name'],
1933 'source_link' => $item['author-link'],
1934 'source_photo' => $photo,
1935 'verb' => ACTIVITY_TAG,
1937 'parent' => $item['parent']
1941 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1943 call_hooks('tagged', $arr);
1945 if((! $community_page) && (! $prvgroup))
1949 // tgroup delivery - setup a second delivery chain
1950 // prevent delivery looping - only proceed
1951 // if the message originated elsewhere and is a top-level post
1953 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1956 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1959 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1960 intval($u[0]['uid'])
1965 // also reset all the privacy bits to the forum default permissions
1967 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1969 $forum_mode = (($prvgroup) ? 2 : 1);
1971 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1972 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1973 intval($forum_mode),
1974 dbesc($c[0]['name']),
1975 dbesc($c[0]['url']),
1976 dbesc($c[0]['thumb']),
1978 dbesc($u[0]['allow_cid']),
1979 dbesc($u[0]['allow_gid']),
1980 dbesc($u[0]['deny_cid']),
1981 dbesc($u[0]['deny_gid']),
1984 update_thread($item_id);
1986 proc_run('php','include/notifier.php','tgroup',$item_id);
1992 function tgroup_check($uid,$item) {
1998 // check that the message originated elsewhere and is a top-level post
2000 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
2004 $u = q("select * from user where uid = %d limit 1",
2010 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
2011 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
2014 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
2016 // Diaspora uses their own hardwired link URL in @-tags
2017 // instead of the one we supply with webfinger
2019 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
2021 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
2023 foreach($matches as $mtch) {
2024 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
2026 logger('tgroup_check: mention found: ' . $mtch[2]);
2034 if((! $community_page) && (! $prvgroup))
2048 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2052 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2054 if($contact['duplex'] && $contact['dfrn-id'])
2055 $idtosend = '0:' . $orig_id;
2056 if($contact['duplex'] && $contact['issued-id'])
2057 $idtosend = '1:' . $orig_id;
2060 $rino = get_config('system','rino_encrypt');
2061 $rino = intval($rino);
2062 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2063 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2065 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2067 $ssl_val = intval(get_config('system','ssl_policy'));
2071 case SSL_POLICY_FULL:
2072 $ssl_policy = 'full';
2074 case SSL_POLICY_SELFSIGN:
2075 $ssl_policy = 'self';
2077 case SSL_POLICY_NONE:
2079 $ssl_policy = 'none';
2083 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2085 logger('dfrn_deliver: ' . $url);
2087 $xml = fetch_url($url);
2089 $curl_stat = $a->get_curl_code();
2091 return(-1); // timed out
2093 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2098 if(strpos($xml,'<?xml') === false) {
2099 logger('dfrn_deliver: no valid XML returned');
2100 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2104 $res = parse_xml_string($xml);
2106 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2107 return (($res->status) ? $res->status : 3);
2109 $postvars = array();
2110 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2111 $challenge = hex2bin((string) $res->challenge);
2112 $perm = (($res->perm) ? $res->perm : null);
2113 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2114 $rino_remote_version = intval($res->rino);
2115 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2117 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2119 if($owner['page-flags'] == PAGE_PRVGROUP)
2122 $final_dfrn_id = '';
2125 if((($perm == 'rw') && (! intval($contact['writable'])))
2126 || (($perm == 'r') && (intval($contact['writable'])))) {
2127 q("update contact set writable = %d where id = %d",
2128 intval(($perm == 'rw') ? 1 : 0),
2129 intval($contact['id'])
2131 $contact['writable'] = (string) 1 - intval($contact['writable']);
2135 if(($contact['duplex'] && strlen($contact['pubkey']))
2136 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2137 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2138 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2139 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2142 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2143 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2146 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2148 if(strpos($final_dfrn_id,':') == 1)
2149 $final_dfrn_id = substr($final_dfrn_id,2);
2151 if($final_dfrn_id != $orig_id) {
2152 logger('dfrn_deliver: wrong dfrn_id.');
2153 // did not decode properly - cannot trust this site
2157 $postvars['dfrn_id'] = $idtosend;
2158 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2160 $postvars['dissolve'] = '1';
2163 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2164 $postvars['data'] = $atom;
2165 $postvars['perm'] = 'rw';
2168 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2169 $postvars['perm'] = 'r';
2172 $postvars['ssl_policy'] = $ssl_policy;
2175 $postvars['page'] = $page;
2178 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2179 logger('rino version: '. $rino_remote_version);
2181 switch($rino_remote_version) {
2183 // Deprecated rino version!
2184 $key = substr(random_string(),0,16);
2185 $data = aes_encrypt($postvars['data'],$key);
2188 // RINO 2 based on php-encryption
2190 $key = Crypto::createNewRandomKey();
2191 } catch (CryptoTestFailed $ex) {
2192 logger('Cannot safely create a key');
2194 } catch (CannotPerformOperation $ex) {
2195 logger('Cannot safely create a key');
2199 $data = Crypto::encrypt($postvars['data'], $key);
2200 } catch (CryptoTestFailed $ex) {
2201 logger('Cannot safely perform encryption');
2203 } catch (CannotPerformOperation $ex) {
2204 logger('Cannot safely perform encryption');
2209 logger("rino: invalid requested verision '$rino_remote_version'");
2213 $postvars['rino'] = $rino_remote_version;
2214 $postvars['data'] = bin2hex($data);
2216 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2219 if($dfrn_version >= 2.1) {
2220 if(($contact['duplex'] && strlen($contact['pubkey']))
2221 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2222 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2224 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2227 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2231 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2232 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2235 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2239 logger('md5 rawkey ' . md5($postvars['key']));
2241 $postvars['key'] = bin2hex($postvars['key']);
2245 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2247 $xml = post_url($contact['notify'],$postvars);
2249 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2251 $curl_stat = $a->get_curl_code();
2252 if((! $curl_stat) || (! strlen($xml)))
2253 return(-1); // timed out
2255 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2258 if(strpos($xml,'<?xml') === false) {
2259 logger('dfrn_deliver: phase 2: no valid XML returned');
2260 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2264 if($contact['term-date'] != '0000-00-00 00:00:00') {
2265 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2266 require_once('include/Contact.php');
2267 unmark_for_death($contact);
2270 $res = parse_xml_string($xml);
2272 return $res->status;
2277 This function returns true if $update has an edited timestamp newer
2278 than $existing, i.e. $update contains new data which should override
2279 what's already there. If there is no timestamp yet, the update is
2280 assumed to be newer. If the update has no timestamp, the existing
2281 item is assumed to be up-to-date. If the timestamps are equal it
2282 assumes the update has been seen before and should be ignored.
2284 function edited_timestamp_is_newer($existing, $update) {
2285 if (!x($existing,'edited') || !$existing['edited']) {
2288 if (!x($update,'edited') || !$update['edited']) {
2291 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2292 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2293 return (strcmp($existing_edited, $update_edited) < 0);
2298 * consume_feed - process atom feed and update anything/everything we might need to update
2300 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2302 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2303 * It is this person's stuff that is going to be updated.
2304 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2305 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2306 * have a contact record.
2307 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2308 * might not) try and subscribe to it.
2309 * $datedir sorts in reverse order
2310 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2311 * imported prior to its children being seen in the stream unless we are certain
2312 * of how the feed is arranged/ordered.
2313 * With $pass = 1, we only pull parent items out of the stream.
2314 * With $pass = 2, we only pull children (comments/likes).
2316 * So running this twice, first with pass 1 and then with pass 2 will do the right
2317 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2318 * model where comments can have sub-threads. That would require some massive sorting
2319 * to get all the feed items into a mostly linear ordering, and might still require
2323 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2324 if ($contact['network'] === NETWORK_OSTATUS) {
2326 // Test - remove before flight
2327 //$tempfile = tempnam(get_temppath(), "ostatus2");
2328 //file_put_contents($tempfile, $xml);
2329 logger("Consume OStatus messages ", LOGGER_DEBUG);
2330 ostatus_import($xml,$importer,$contact, $hub);
2335 if ($contact['network'] === NETWORK_FEED) {
2337 logger("Consume feeds", LOGGER_DEBUG);
2338 feed_import($xml,$importer,$contact, $hub);
2343 require_once('library/simplepie/simplepie.inc');
2344 require_once('include/contact_selectors.php');
2346 if(! strlen($xml)) {
2347 logger('consume_feed: empty input');
2351 $feed = new SimplePie();
2352 $feed->set_raw_data($xml);
2354 $feed->enable_order_by_date(true);
2356 $feed->enable_order_by_date(false);
2360 logger('consume_feed: Error parsing XML: ' . $feed->error());
2362 $permalink = $feed->get_permalink();
2364 // Check at the feed level for updated contact name and/or photo
2368 $photo_timestamp = '';
2371 $contact_updated = '';
2373 $hubs = $feed->get_links('hub');
2374 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2377 $hub = implode(',', $hubs);
2379 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2381 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2383 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2384 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2385 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2386 $new_name = $elems['name'][0]['data'];
2388 // Manually checking for changed contact names
2389 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2390 $name_updated = date("c");
2391 $photo_timestamp = date("c");
2394 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2395 if ($photo_timestamp == "")
2396 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2397 $photo_url = $elems['link'][0]['attribs']['']['href'];
2400 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2401 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2405 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2406 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2408 $contact_updated = $photo_timestamp;
2410 require_once("include/Photo.php");
2411 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
2413 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2414 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2415 dbesc(datetime_convert()),
2419 intval($contact['uid']),
2420 intval($contact['id'])
2424 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2425 if ($name_updated > $contact_updated)
2426 $contact_updated = $name_updated;
2428 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2429 intval($contact['uid']),
2430 intval($contact['id'])
2433 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2434 dbesc(notags(trim($new_name))),
2435 dbesc(datetime_convert()),
2436 intval($contact['uid']),
2437 intval($contact['id']),
2438 dbesc(notags(trim($new_name)))
2441 // do our best to update the name on content items
2443 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2444 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2445 dbesc(notags(trim($new_name))),
2446 dbesc($r[0]['name']),
2447 dbesc($r[0]['url']),
2448 intval($contact['uid']),
2449 dbesc(notags(trim($new_name)))
2454 if ($contact_updated AND $new_name AND $photo_url)
2455 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2457 if(strlen($birthday)) {
2458 if(substr($birthday,0,4) != $contact['bdyear']) {
2459 logger('consume_feed: updating birthday: ' . $birthday);
2463 * Add new birthday event for this person
2465 * $bdtext is just a readable placeholder in case the event is shared
2466 * with others. We will replace it during presentation to our $importer
2467 * to contain a sparkle link and perhaps a photo.
2471 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2472 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2475 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2476 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2477 intval($contact['uid']),
2478 intval($contact['id']),
2479 dbesc(datetime_convert()),
2480 dbesc(datetime_convert()),
2481 dbesc(datetime_convert('UTC','UTC', $birthday)),
2482 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2491 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2492 dbesc(substr($birthday,0,4)),
2493 intval($contact['uid']),
2494 intval($contact['id'])
2497 // This function is called twice without reloading the contact
2498 // Make sure we only create one event. This is why &$contact
2499 // is a reference var in this function
2501 $contact['bdyear'] = substr($birthday,0,4);
2505 $community_page = 0;
2506 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2508 $community_page = intval($rawtags[0]['data']);
2510 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2511 q("update contact set forum = %d where id = %d",
2512 intval($community_page),
2513 intval($contact['id'])
2515 $contact['forum'] = (string) $community_page;
2519 // process any deleted entries
2521 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2522 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2523 foreach($del_entries as $dentry) {
2525 if(isset($dentry['attribs']['']['ref'])) {
2526 $uri = $dentry['attribs']['']['ref'];
2528 if(isset($dentry['attribs']['']['when'])) {
2529 $when = $dentry['attribs']['']['when'];
2530 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2533 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2535 if($deleted && is_array($contact)) {
2536 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2537 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2539 intval($importer['uid']),
2540 intval($contact['id'])
2545 if(! $item['deleted'])
2546 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2548 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2549 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2550 event_delete($item['event-id']);
2553 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2554 $xo = parse_xml_string($item['object'],false);
2555 $xt = parse_xml_string($item['target'],false);
2556 if($xt->type === ACTIVITY_OBJ_NOTE) {
2557 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2559 intval($importer['importer_uid'])
2563 // For tags, the owner cannot remove the tag on the author's copy of the post.
2565 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2566 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2567 $author_copy = (($item['origin']) ? true : false);
2569 if($owner_remove && $author_copy)
2571 if($author_remove || $owner_remove) {
2572 $tags = explode(',',$i[0]['tag']);
2575 foreach($tags as $tag)
2576 if(trim($tag) !== trim($xo->body))
2577 $newtags[] = trim($tag);
2579 q("update item set tag = '%s' where id = %d",
2580 dbesc(implode(',',$newtags)),
2583 create_tags_from_item($i[0]['id']);
2589 if($item['uri'] == $item['parent-uri']) {
2590 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2591 `body` = '', `title` = ''
2592 WHERE `parent-uri` = '%s' AND `uid` = %d",
2594 dbesc(datetime_convert()),
2595 dbesc($item['uri']),
2596 intval($importer['uid'])
2598 create_tags_from_itemuri($item['uri'], $importer['uid']);
2599 create_files_from_itemuri($item['uri'], $importer['uid']);
2600 update_thread_uri($item['uri'], $importer['uid']);
2603 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2604 `body` = '', `title` = ''
2605 WHERE `uri` = '%s' AND `uid` = %d",
2607 dbesc(datetime_convert()),
2609 intval($importer['uid'])
2611 create_tags_from_itemuri($uri, $importer['uid']);
2612 create_files_from_itemuri($uri, $importer['uid']);
2613 if($item['last-child']) {
2614 // ensure that last-child is set in case the comment that had it just got wiped.
2615 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2616 dbesc(datetime_convert()),
2617 dbesc($item['parent-uri']),
2618 intval($item['uid'])
2620 // who is the last child now?
2621 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2622 ORDER BY `created` DESC LIMIT 1",
2623 dbesc($item['parent-uri']),
2624 intval($importer['uid'])
2627 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2638 // Now process the feed
2640 if($feed->get_item_quantity()) {
2642 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2644 // in inverse date order
2646 $items = array_reverse($feed->get_items());
2648 $items = $feed->get_items();
2651 foreach($items as $item) {
2654 $item_id = $item->get_id();
2655 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2656 if(isset($rawthread[0]['attribs']['']['ref'])) {
2658 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2661 if(($is_reply) && is_array($contact)) {
2666 // not allowed to post
2668 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2672 // Have we seen it? If not, import it.
2674 $item_id = $item->get_id();
2675 $datarray = get_atom_elements($feed, $item, $contact);
2677 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2678 $datarray['author-name'] = $contact['name'];
2679 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2680 $datarray['author-link'] = $contact['url'];
2681 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2682 $datarray['author-avatar'] = $contact['thumb'];
2684 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2685 logger('consume_feed: no author information! ' . print_r($datarray,true));
2689 $force_parent = false;
2690 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2691 if($contact['network'] === NETWORK_OSTATUS)
2692 $force_parent = true;
2693 if(strlen($datarray['title']))
2694 unset($datarray['title']);
2695 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2696 dbesc(datetime_convert()),
2698 intval($importer['uid'])
2700 $datarray['last-child'] = 1;
2701 update_thread_uri($parent_uri, $importer['uid']);
2705 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2707 intval($importer['uid'])
2710 // Update content if 'updated' changes
2713 if (edited_timestamp_is_newer($r[0], $datarray)) {
2715 // do not accept (ignore) an earlier edit than one we currently have.
2716 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2719 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2720 dbesc($datarray['title']),
2721 dbesc($datarray['body']),
2722 dbesc($datarray['tag']),
2723 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2724 dbesc(datetime_convert()),
2726 intval($importer['uid'])
2728 create_tags_from_itemuri($item_id, $importer['uid']);
2729 update_thread_uri($item_id, $importer['uid']);
2732 // update last-child if it changes
2734 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2735 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2736 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2737 dbesc(datetime_convert()),
2739 intval($importer['uid'])
2741 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2742 intval($allow[0]['data']),
2743 dbesc(datetime_convert()),
2745 intval($importer['uid'])
2747 update_thread_uri($item_id, $importer['uid']);
2753 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2754 // one way feed - no remote comment ability
2755 $datarray['last-child'] = 0;
2757 $datarray['parent-uri'] = $parent_uri;
2758 $datarray['uid'] = $importer['uid'];
2759 $datarray['contact-id'] = $contact['id'];
2760 if(($datarray['verb'] === ACTIVITY_LIKE)
2761 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2762 || ($datarray['verb'] === ACTIVITY_ATTEND)
2763 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2764 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2765 $datarray['type'] = 'activity';
2766 $datarray['gravity'] = GRAVITY_LIKE;
2767 // only one like or dislike per person
2768 // splitted into two queries for performance issues
2769 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
2770 intval($datarray['uid']),
2771 dbesc($datarray['author-link']),
2772 dbesc($datarray['verb']),
2773 dbesc($datarray['parent-uri'])
2778 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
2779 intval($datarray['uid']),
2780 dbesc($datarray['author-link']),
2781 dbesc($datarray['verb']),
2782 dbesc($datarray['parent-uri'])
2788 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2789 $xo = parse_xml_string($datarray['object'],false);
2790 $xt = parse_xml_string($datarray['target'],false);
2792 if($xt->type == ACTIVITY_OBJ_NOTE) {
2793 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2795 intval($importer['importer_uid'])
2800 // extract tag, if not duplicate, add to parent item
2801 if($xo->id && $xo->content) {
2802 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2803 if(! (stristr($r[0]['tag'],$newtag))) {
2804 q("UPDATE item SET tag = '%s' WHERE id = %d",
2805 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2808 create_tags_from_item($r[0]['id']);
2814 $r = item_store($datarray,$force_parent);
2820 // Head post of a conversation. Have we seen it? If not, import it.
2822 $item_id = $item->get_id();
2824 $datarray = get_atom_elements($feed, $item, $contact);
2826 if(is_array($contact)) {
2827 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2828 $datarray['author-name'] = $contact['name'];
2829 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2830 $datarray['author-link'] = $contact['url'];
2831 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2832 $datarray['author-avatar'] = $contact['thumb'];
2835 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2836 logger('consume_feed: no author information! ' . print_r($datarray,true));
2840 // special handling for events
2842 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2843 $ev = bbtoevent($datarray['body']);
2844 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2845 $ev['uid'] = $importer['uid'];
2846 $ev['uri'] = $item_id;
2847 $ev['edited'] = $datarray['edited'];
2848 $ev['private'] = $datarray['private'];
2849 $ev['guid'] = $datarray['guid'];
2851 if(is_array($contact))
2852 $ev['cid'] = $contact['id'];
2853 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2855 intval($importer['uid'])
2858 $ev['id'] = $r[0]['id'];
2859 $xyz = event_store($ev);
2864 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2865 if(strlen($datarray['title']))
2866 unset($datarray['title']);
2867 $datarray['last-child'] = 1;
2871 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2873 intval($importer['uid'])
2876 // Update content if 'updated' changes
2879 if (edited_timestamp_is_newer($r[0], $datarray)) {
2881 // do not accept (ignore) an earlier edit than one we currently have.
2882 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2885 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2886 dbesc($datarray['title']),
2887 dbesc($datarray['body']),
2888 dbesc($datarray['tag']),
2889 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2890 dbesc(datetime_convert()),
2892 intval($importer['uid'])
2894 create_tags_from_itemuri($item_id, $importer['uid']);
2895 update_thread_uri($item_id, $importer['uid']);
2898 // update last-child if it changes
2900 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2901 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2902 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2903 intval($allow[0]['data']),
2904 dbesc(datetime_convert()),
2906 intval($importer['uid'])
2908 update_thread_uri($item_id, $importer['uid']);
2913 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2914 logger('consume-feed: New follower');
2915 new_follower($importer,$contact,$datarray,$item);
2918 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2919 lose_follower($importer,$contact,$datarray,$item);
2923 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2924 logger('consume-feed: New friend request');
2925 new_follower($importer,$contact,$datarray,$item,true);
2928 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2929 lose_sharer($importer,$contact,$datarray,$item);
2934 if(! is_array($contact))
2938 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2939 // one way feed - no remote comment ability
2940 $datarray['last-child'] = 0;
2942 if($contact['network'] === NETWORK_FEED)
2943 $datarray['private'] = 2;
2945 $datarray['parent-uri'] = $item_id;
2946 $datarray['uid'] = $importer['uid'];
2947 $datarray['contact-id'] = $contact['id'];
2949 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2950 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2951 // but otherwise there's a possible data mixup on the sender's system.
2952 // the tgroup delivery code called from item_store will correct it if it's a forum,
2953 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2954 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2955 $datarray['owner-name'] = $contact['name'];
2956 $datarray['owner-link'] = $contact['url'];
2957 $datarray['owner-avatar'] = $contact['thumb'];
2960 // We've allowed "followers" to reach this point so we can decide if they are
2961 // posting an @-tag delivery, which followers are allowed to do for certain
2962 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2964 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2967 // This is my contact on another system, but it's really me.
2968 // Turn this into a wall post.
2969 $notify = item_is_remote_self($contact, $datarray);
2971 $r = item_store($datarray, false, $notify);
2972 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2980 function item_is_remote_self($contact, &$datarray) {
2983 if (!$contact['remote_self'])
2986 // Prevent the forwarding of posts that are forwarded
2987 if ($datarray["extid"] == NETWORK_DFRN)
2990 // Prevent to forward already forwarded posts
2991 if ($datarray["app"] == $a->get_hostname())
2994 // Only forward posts
2995 if ($datarray["verb"] != ACTIVITY_POST)
2998 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
3001 $datarray2 = $datarray;
3002 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
3003 if ($contact['remote_self'] == 2) {
3004 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
3005 intval($contact['uid']));
3007 $datarray['contact-id'] = $r[0]["id"];
3009 $datarray['owner-name'] = $r[0]["name"];
3010 $datarray['owner-link'] = $r[0]["url"];
3011 $datarray['owner-avatar'] = $r[0]["thumb"];
3013 $datarray['author-name'] = $datarray['owner-name'];
3014 $datarray['author-link'] = $datarray['owner-link'];
3015 $datarray['author-avatar'] = $datarray['owner-avatar'];
3018 if ($contact['network'] != NETWORK_FEED) {
3019 $datarray["guid"] = get_guid(32);
3020 unset($datarray["plink"]);
3021 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3022 $datarray["parent-uri"] = $datarray["uri"];
3023 $datarray["extid"] = $contact['network'];
3024 $urlpart = parse_url($datarray2['author-link']);
3025 $datarray["app"] = $urlpart["host"];
3027 $datarray['private'] = 0;
3030 if ($contact['network'] != NETWORK_FEED) {
3031 // Store the original post
3032 $r = item_store($datarray2, false, false);
3033 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3035 $datarray["app"] = "Feed";
3040 function local_delivery($importer,$data) {
3043 logger(__function__, LOGGER_TRACE);
3045 if($importer['readonly']) {
3046 // We aren't receiving stuff from this person. But we will quietly ignore them
3047 // rather than a blatant "go away" message.
3048 logger('local_delivery: ignoring');
3053 // Consume notification feed. This may differ from consuming a public feed in several ways
3054 // - might contain email or friend suggestions
3055 // - might contain remote followup to our message
3056 // - in which case we need to accept it and then notify other conversants
3057 // - we may need to send various email notifications
3059 $feed = new SimplePie();
3060 $feed->set_raw_data($data);
3061 $feed->enable_order_by_date(false);
3066 logger('local_delivery: Error parsing XML: ' . $feed->error());
3069 // Check at the feed level for updated contact name and/or photo
3073 $photo_timestamp = '';
3075 $contact_updated = '';
3078 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3080 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3082 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3085 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3086 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3087 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3088 $new_name = $elems['name'][0]['data'];
3090 // Manually checking for changed contact names
3091 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3092 $name_updated = date("c");
3093 $photo_timestamp = date("c");
3096 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3097 if ($photo_timestamp == "")
3098 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3099 $photo_url = $elems['link'][0]['attribs']['']['href'];
3103 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3105 $contact_updated = $photo_timestamp;
3107 logger('local_delivery: Updating photo for ' . $importer['name']);
3108 require_once("include/Photo.php");
3110 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
3112 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3113 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
3114 dbesc(datetime_convert()),
3118 intval($importer['importer_uid']),
3119 intval($importer['id'])
3123 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3124 if ($name_updated > $contact_updated)
3125 $contact_updated = $name_updated;
3127 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
3128 intval($importer['importer_uid']),
3129 intval($importer['id'])
3132 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
3133 dbesc(notags(trim($new_name))),
3134 dbesc(datetime_convert()),
3135 intval($importer['importer_uid']),
3136 intval($importer['id']),
3137 dbesc(notags(trim($new_name)))
3140 // do our best to update the name on content items
3142 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
3143 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
3144 dbesc(notags(trim($new_name))),
3145 dbesc($r[0]['name']),
3146 dbesc($r[0]['url']),
3147 intval($importer['importer_uid']),
3148 dbesc(notags(trim($new_name)))
3153 if ($contact_updated AND $new_name AND $photo_url)
3154 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3156 // Currently unsupported - needs a lot of work
3157 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3158 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3159 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3161 $newloc['uid'] = $importer['importer_uid'];
3162 $newloc['cid'] = $importer['id'];
3163 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3164 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3165 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3166 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3167 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3168 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3169 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3170 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3171 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3172 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3173 /** relocated user must have original key pair */
3174 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3175 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3177 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3180 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3181 intval($importer['id']),
3182 intval($importer['importer_uid']));
3187 $x = q("UPDATE contact SET
3198 `site-pubkey` = '%s'
3199 WHERE id=%d AND uid=%d;",
3200 dbesc($newloc['name']),
3201 dbesc($newloc['photo']),
3202 dbesc($newloc['thumb']),
3203 dbesc($newloc['micro']),
3204 dbesc($newloc['url']),
3205 dbesc(normalise_link($newloc['url'])),
3206 dbesc($newloc['request']),
3207 dbesc($newloc['confirm']),
3208 dbesc($newloc['notify']),
3209 dbesc($newloc['poll']),
3210 dbesc($newloc['sitepubkey']),
3211 intval($importer['id']),
3212 intval($importer['importer_uid']));
3218 'owner-link' => array($old['url'], $newloc['url']),
3219 'author-link' => array($old['url'], $newloc['url']),
3220 'owner-avatar' => array($old['photo'], $newloc['photo']),
3221 'author-avatar' => array($old['photo'], $newloc['photo']),
3223 foreach ($fields as $n=>$f){
3224 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3227 intval($importer['importer_uid']));
3233 /// merge with current record, current contents have priority
3234 /// update record, set url-updated
3235 /// update profile photos
3236 /// schedule a scan?
3241 // handle friend suggestion notification
3243 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3244 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3245 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3247 $fsugg['uid'] = $importer['importer_uid'];
3248 $fsugg['cid'] = $importer['id'];
3249 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3250 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3251 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3252 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3253 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3255 // Does our member already have a friend matching this description?
3257 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3258 dbesc($fsugg['name']),
3259 dbesc(normalise_link($fsugg['url'])),
3260 intval($fsugg['uid'])
3265 // Do we already have an fcontact record for this person?
3268 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3269 dbesc($fsugg['url']),
3270 dbesc($fsugg['name']),
3271 dbesc($fsugg['request'])
3276 // OK, we do. Do we already have an introduction for this person ?
3277 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3278 intval($fsugg['uid']),
3285 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3286 dbesc($fsugg['name']),
3287 dbesc($fsugg['url']),
3288 dbesc($fsugg['photo']),
3289 dbesc($fsugg['request'])
3291 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3292 dbesc($fsugg['url']),
3293 dbesc($fsugg['name']),
3294 dbesc($fsugg['request'])
3299 // database record did not get created. Quietly give up.
3304 $hash = random_string();
3306 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3307 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3308 intval($fsugg['uid']),
3310 intval($fsugg['cid']),
3311 dbesc($fsugg['body']),
3313 dbesc(datetime_convert()),
3318 'type' => NOTIFY_SUGGEST,
3319 'notify_flags' => $importer['notify-flags'],
3320 'language' => $importer['language'],
3321 'to_name' => $importer['username'],
3322 'to_email' => $importer['email'],
3323 'uid' => $importer['importer_uid'],
3325 'link' => $a->get_baseurl() . '/notifications/intros',
3326 'source_name' => $importer['name'],
3327 'source_link' => $importer['url'],
3328 'source_photo' => $importer['photo'],
3329 'verb' => ACTIVITY_REQ_FRIEND,
3338 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3339 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3341 logger('local_delivery: private message received');
3344 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3347 $msg['uid'] = $importer['importer_uid'];
3348 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3349 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3350 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3351 $msg['contact-id'] = $importer['id'];
3352 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3353 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3355 $msg['replied'] = 0;
3356 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3357 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3358 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3362 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3363 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3365 // send notifications.
3367 require_once('include/enotify.php');
3369 $notif_params = array(
3370 'type' => NOTIFY_MAIL,
3371 'notify_flags' => $importer['notify-flags'],
3372 'language' => $importer['language'],
3373 'to_name' => $importer['username'],
3374 'to_email' => $importer['email'],
3375 'uid' => $importer['importer_uid'],
3377 'source_name' => $msg['from-name'],
3378 'source_link' => $importer['url'],
3379 'source_photo' => $importer['thumb'],
3380 'verb' => ACTIVITY_POST,
3384 notification($notif_params);
3390 $community_page = 0;
3391 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3393 $community_page = intval($rawtags[0]['data']);
3395 if(intval($importer['forum']) != $community_page) {
3396 q("update contact set forum = %d where id = %d",
3397 intval($community_page),
3398 intval($importer['id'])
3400 $importer['forum'] = (string) $community_page;
3403 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3405 // process any deleted entries
3407 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3408 if(is_array($del_entries) && count($del_entries)) {
3409 foreach($del_entries as $dentry) {
3411 if(isset($dentry['attribs']['']['ref'])) {
3412 $uri = $dentry['attribs']['']['ref'];
3414 if(isset($dentry['attribs']['']['when'])) {
3415 $when = $dentry['attribs']['']['when'];
3416 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3419 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3423 // check for relayed deletes to our conversation
3426 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3428 intval($importer['importer_uid'])
3431 $parent_uri = $r[0]['parent-uri'];
3432 if($r[0]['id'] != $r[0]['parent'])
3439 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3442 logger('local_delivery: possible community delete');
3445 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3447 // was the top-level post for this reply written by somebody on this site?
3448 // Specifically, the recipient?
3450 $is_a_remote_delete = false;
3452 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3453 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3454 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3455 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3456 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3457 AND `item`.`uid` = %d
3463 intval($importer['importer_uid'])
3466 $is_a_remote_delete = true;
3468 // Does this have the characteristics of a community or private group comment?
3469 // If it's a reply to a wall post on a community/prvgroup page it's a
3470 // valid community comment. Also forum_mode makes it valid for sure.
3471 // If neither, it's not.
3473 if($is_a_remote_delete && $community) {
3474 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3475 $is_a_remote_delete = false;
3476 logger('local_delivery: not a community delete');
3480 if($is_a_remote_delete) {
3481 logger('local_delivery: received remote delete');
3485 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3486 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3488 intval($importer['importer_uid']),
3489 intval($importer['id'])
3495 if($item['deleted'])
3498 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3500 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
3501 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
3502 event_delete($item['event-id']);
3505 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3506 $xo = parse_xml_string($item['object'],false);
3507 $xt = parse_xml_string($item['target'],false);
3509 if($xt->type === ACTIVITY_OBJ_NOTE) {
3510 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3512 intval($importer['importer_uid'])
3516 // For tags, the owner cannot remove the tag on the author's copy of the post.
3518 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3519 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3520 $author_copy = (($item['origin']) ? true : false);
3522 if($owner_remove && $author_copy)
3524 if($author_remove || $owner_remove) {
3525 $tags = explode(',',$i[0]['tag']);
3528 foreach($tags as $tag)
3529 if(trim($tag) !== trim($xo->body))
3530 $newtags[] = trim($tag);
3532 q("update item set tag = '%s' where id = %d",
3533 dbesc(implode(',',$newtags)),
3536 create_tags_from_item($i[0]['id']);
3542 if($item['uri'] == $item['parent-uri']) {
3543 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3544 `body` = '', `title` = ''
3545 WHERE `parent-uri` = '%s' AND `uid` = %d",
3547 dbesc(datetime_convert()),
3548 dbesc($item['uri']),
3549 intval($importer['importer_uid'])
3551 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3552 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3553 update_thread_uri($item['uri'], $importer['importer_uid']);
3556 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3557 `body` = '', `title` = ''
3558 WHERE `uri` = '%s' AND `uid` = %d",
3560 dbesc(datetime_convert()),
3562 intval($importer['importer_uid'])
3564 create_tags_from_itemuri($uri, $importer['importer_uid']);
3565 create_files_from_itemuri($uri, $importer['importer_uid']);
3566 update_thread_uri($uri, $importer['importer_uid']);
3567 if($item['last-child']) {
3568 // ensure that last-child is set in case the comment that had it just got wiped.
3569 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3570 dbesc(datetime_convert()),
3571 dbesc($item['parent-uri']),
3572 intval($item['uid'])
3574 // who is the last child now?
3575 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3576 ORDER BY `created` DESC LIMIT 1",
3577 dbesc($item['parent-uri']),
3578 intval($importer['importer_uid'])
3581 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3586 // if this is a relayed delete, propagate it to other recipients
3588 if($is_a_remote_delete)
3589 proc_run('php',"include/notifier.php","drop",$item['id']);
3597 foreach($feed->get_items() as $item) {
3600 $item_id = $item->get_id();
3601 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3602 if(isset($rawthread[0]['attribs']['']['ref'])) {
3604 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3610 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3613 logger('local_delivery: possible community reply');
3616 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3618 // was the top-level post for this reply written by somebody on this site?
3619 // Specifically, the recipient?
3621 $is_a_remote_comment = false;
3622 $top_uri = $parent_uri;
3624 $r = q("select `item`.`parent-uri` from `item`
3625 WHERE `item`.`uri` = '%s'
3629 if($r && count($r)) {
3630 $top_uri = $r[0]['parent-uri'];
3632 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3633 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3634 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3635 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3636 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3637 AND `item`.`uid` = %d
3643 intval($importer['importer_uid'])
3646 $is_a_remote_comment = true;
3649 // Does this have the characteristics of a community or private group comment?
3650 // If it's a reply to a wall post on a community/prvgroup page it's a
3651 // valid community comment. Also forum_mode makes it valid for sure.
3652 // If neither, it's not.
3654 if($is_a_remote_comment && $community) {
3655 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3656 $is_a_remote_comment = false;
3657 logger('local_delivery: not a community reply');
3661 if($is_a_remote_comment) {
3662 logger('local_delivery: received remote comment');
3664 // remote reply to our post. Import and then notify everybody else.
3666 $datarray = get_atom_elements($feed, $item);
3668 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3670 intval($importer['importer_uid'])
3673 // Update content if 'updated' changes
3677 if (edited_timestamp_is_newer($r[0], $datarray)) {
3679 // do not accept (ignore) an earlier edit than one we currently have.
3680 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3683 logger('received updated comment' , LOGGER_DEBUG);
3684 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3685 dbesc($datarray['title']),
3686 dbesc($datarray['body']),
3687 dbesc($datarray['tag']),
3688 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3689 dbesc(datetime_convert()),
3691 intval($importer['importer_uid'])
3693 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3695 proc_run('php',"include/notifier.php","comment-import",$iid);
3704 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3705 intval($importer['importer_uid'])
3709 $datarray['type'] = 'remote-comment';
3710 $datarray['wall'] = 1;
3711 $datarray['parent-uri'] = $parent_uri;
3712 $datarray['uid'] = $importer['importer_uid'];
3713 $datarray['owner-name'] = $own[0]['name'];
3714 $datarray['owner-link'] = $own[0]['url'];
3715 $datarray['owner-avatar'] = $own[0]['thumb'];
3716 $datarray['contact-id'] = $importer['id'];
3718 if(($datarray['verb'] === ACTIVITY_LIKE)
3719 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3720 || ($datarray['verb'] === ACTIVITY_ATTEND)
3721 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3722 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3724 $datarray['type'] = 'activity';
3725 $datarray['gravity'] = GRAVITY_LIKE;
3726 $datarray['last-child'] = 0;
3727 // only one like or dislike per person
3728 // splitted into two queries for performance issues
3729 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3730 intval($datarray['uid']),
3731 dbesc($datarray['author-link']),
3732 dbesc($datarray['verb']),
3733 dbesc($datarray['parent-uri'])
3738 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3739 intval($datarray['uid']),
3740 dbesc($datarray['author-link']),
3741 dbesc($datarray['verb']),
3742 dbesc($datarray['parent-uri'])
3749 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3751 $xo = parse_xml_string($datarray['object'],false);
3752 $xt = parse_xml_string($datarray['target'],false);
3754 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3756 // fetch the parent item
3758 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3760 intval($importer['importer_uid'])
3765 // extract tag, if not duplicate, and this user allows tags, add to parent item
3767 if($xo->id && $xo->content) {
3768 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3769 if(! (stristr($tagp[0]['tag'],$newtag))) {
3770 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3771 intval($importer['importer_uid'])
3773 if(count($i) && ! intval($i[0]['blocktags'])) {
3774 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3775 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3776 intval($tagp[0]['id']),
3777 dbesc(datetime_convert()),
3778 dbesc(datetime_convert())
3780 create_tags_from_item($tagp[0]['id']);
3788 $posted_id = item_store($datarray);
3793 $datarray["id"] = $posted_id;
3795 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3797 intval($importer['importer_uid'])
3800 $parent = $r[0]['parent'];
3801 $parent_uri = $r[0]['parent-uri'];
3805 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3806 dbesc(datetime_convert()),
3807 intval($importer['importer_uid']),
3808 intval($r[0]['parent'])
3811 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3812 dbesc(datetime_convert()),
3813 intval($importer['importer_uid']),
3818 if($posted_id && $parent) {
3820 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3822 if((! $is_like) && (! $importer['self'])) {
3824 require_once('include/enotify.php');
3827 'type' => NOTIFY_COMMENT,
3828 'notify_flags' => $importer['notify-flags'],
3829 'language' => $importer['language'],
3830 'to_name' => $importer['username'],
3831 'to_email' => $importer['email'],
3832 'uid' => $importer['importer_uid'],
3833 'item' => $datarray,
3834 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3835 'source_name' => stripslashes($datarray['author-name']),
3836 'source_link' => $datarray['author-link'],
3837 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3838 ? $importer['thumb'] : $datarray['author-avatar']),
3839 'verb' => ACTIVITY_POST,
3841 'parent' => $parent,
3842 'parent_uri' => $parent_uri,
3854 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3856 $item_id = $item->get_id();
3857 $datarray = get_atom_elements($feed,$item);
3859 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3862 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3864 intval($importer['importer_uid'])
3867 // Update content if 'updated' changes
3870 if (edited_timestamp_is_newer($r[0], $datarray)) {
3872 // do not accept (ignore) an earlier edit than one we currently have.
3873 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3876 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3877 dbesc($datarray['title']),
3878 dbesc($datarray['body']),
3879 dbesc($datarray['tag']),
3880 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3881 dbesc(datetime_convert()),
3883 intval($importer['importer_uid'])
3885 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3888 // update last-child if it changes
3890 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3891 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3892 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3893 dbesc(datetime_convert()),
3895 intval($importer['importer_uid'])
3897 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3898 intval($allow[0]['data']),
3899 dbesc(datetime_convert()),
3901 intval($importer['importer_uid'])
3907 $datarray['parent-uri'] = $parent_uri;
3908 $datarray['uid'] = $importer['importer_uid'];
3909 $datarray['contact-id'] = $importer['id'];
3910 if(($datarray['verb'] === ACTIVITY_LIKE)
3911 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3912 || ($datarray['verb'] === ACTIVITY_ATTEND)
3913 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3914 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3915 $datarray['type'] = 'activity';
3916 $datarray['gravity'] = GRAVITY_LIKE;
3917 // only one like or dislike per person
3918 // splitted into two queries for performance issues
3919 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3920 intval($datarray['uid']),
3921 dbesc($datarray['author-link']),
3922 dbesc($datarray['verb']),
3923 dbesc($datarray['parent-uri'])
3928 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3929 intval($datarray['uid']),
3930 dbesc($datarray['author-link']),
3931 dbesc($datarray['verb']),
3932 dbesc($datarray['parent-uri'])
3939 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3941 $xo = parse_xml_string($datarray['object'],false);
3942 $xt = parse_xml_string($datarray['target'],false);
3944 if($xt->type == ACTIVITY_OBJ_NOTE) {
3945 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3947 intval($importer['importer_uid'])
3952 // extract tag, if not duplicate, add to parent item
3954 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3955 q("UPDATE item SET tag = '%s' WHERE id = %d",
3956 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3959 create_tags_from_item($r[0]['id']);
3965 $posted_id = item_store($datarray);
3967 // find out if our user is involved in this conversation and wants to be notified.
3969 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3971 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3973 intval($importer['importer_uid'])
3976 if(count($myconv)) {
3977 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3979 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3980 if(! link_compare($datarray['author-link'],$importer_url)) {
3983 foreach($myconv as $conv) {
3985 // now if we find a match, it means we're in this conversation
3987 if(! link_compare($conv['author-link'],$importer_url))
3990 require_once('include/enotify.php');
3992 $conv_parent = $conv['parent'];
3995 'type' => NOTIFY_COMMENT,
3996 'notify_flags' => $importer['notify-flags'],
3997 'language' => $importer['language'],
3998 'to_name' => $importer['username'],
3999 'to_email' => $importer['email'],
4000 'uid' => $importer['importer_uid'],
4001 'item' => $datarray,
4002 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4003 'source_name' => stripslashes($datarray['author-name']),
4004 'source_link' => $datarray['author-link'],
4005 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4006 ? $importer['thumb'] : $datarray['author-avatar']),
4007 'verb' => ACTIVITY_POST,
4009 'parent' => $conv_parent,
4010 'parent_uri' => $parent_uri
4014 // only send one notification
4026 // Head post of a conversation. Have we seen it? If not, import it.
4029 $item_id = $item->get_id();
4030 $datarray = get_atom_elements($feed,$item);
4032 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4033 $ev = bbtoevent($datarray['body']);
4034 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
4035 $ev['cid'] = $importer['id'];
4036 $ev['uid'] = $importer['uid'];
4037 $ev['uri'] = $item_id;
4038 $ev['edited'] = $datarray['edited'];
4039 $ev['private'] = $datarray['private'];
4040 $ev['guid'] = $datarray['guid'];
4042 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4044 intval($importer['uid'])
4047 $ev['id'] = $r[0]['id'];
4048 $xyz = event_store($ev);
4053 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4055 intval($importer['importer_uid'])
4058 // Update content if 'updated' changes
4061 if (edited_timestamp_is_newer($r[0], $datarray)) {
4063 // do not accept (ignore) an earlier edit than one we currently have.
4064 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4067 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4068 dbesc($datarray['title']),
4069 dbesc($datarray['body']),
4070 dbesc($datarray['tag']),
4071 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4072 dbesc(datetime_convert()),
4074 intval($importer['importer_uid'])
4076 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4077 update_thread_uri($item_id, $importer['importer_uid']);
4080 // update last-child if it changes
4082 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4083 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4084 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4085 intval($allow[0]['data']),
4086 dbesc(datetime_convert()),
4088 intval($importer['importer_uid'])
4094 $datarray['parent-uri'] = $item_id;
4095 $datarray['uid'] = $importer['importer_uid'];
4096 $datarray['contact-id'] = $importer['id'];
4099 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4100 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4101 // but otherwise there's a possible data mixup on the sender's system.
4102 // the tgroup delivery code called from item_store will correct it if it's a forum,
4103 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4104 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4105 $datarray['owner-name'] = $importer['senderName'];
4106 $datarray['owner-link'] = $importer['url'];
4107 $datarray['owner-avatar'] = $importer['thumb'];
4110 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4113 // This is my contact on another system, but it's really me.
4114 // Turn this into a wall post.
4115 $notify = item_is_remote_self($importer, $datarray);
4117 $posted_id = item_store($datarray, false, $notify);
4119 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4120 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4123 $xo = parse_xml_string($datarray['object'],false);
4125 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4127 // somebody was poked/prodded. Was it me?
4129 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4131 foreach($links->link as $l) {
4132 $atts = $l->attributes();
4133 switch($atts['rel']) {
4135 $Blink = $atts['href'];
4141 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4143 // send a notification
4144 require_once('include/enotify.php');
4147 'type' => NOTIFY_POKE,
4148 'notify_flags' => $importer['notify-flags'],
4149 'language' => $importer['language'],
4150 'to_name' => $importer['username'],
4151 'to_email' => $importer['email'],
4152 'uid' => $importer['importer_uid'],
4153 'item' => $datarray,
4154 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4155 'source_name' => stripslashes($datarray['author-name']),
4156 'source_link' => $datarray['author-link'],
4157 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4158 ? $importer['thumb'] : $datarray['author-avatar']),
4159 'verb' => $datarray['verb'],
4160 'otype' => 'person',
4161 'activity' => $verb,
4162 'parent' => $datarray['parent']
4178 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4179 $url = notags(trim($datarray['author-link']));
4180 $name = notags(trim($datarray['author-name']));
4181 $photo = notags(trim($datarray['author-avatar']));
4183 if (is_object($item)) {
4184 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4185 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4186 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4190 if(is_array($contact)) {
4191 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4192 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4193 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4194 intval(CONTACT_IS_FRIEND),
4195 intval($contact['id']),
4196 intval($importer['uid'])
4199 // send email notification to owner?
4202 // create contact record
4204 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4205 `blocked`, `readonly`, `pending`, `writable`)
4206 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
4207 intval($importer['uid']),
4208 dbesc(datetime_convert()),
4210 dbesc(normalise_link($url)),
4214 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4215 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4217 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4218 intval($importer['uid']),
4222 $contact_record = $r[0];
4224 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
4226 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
4230 intval($contact_record["id"])
4235 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4236 intval($importer['uid'])
4239 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4241 // create notification
4242 $hash = random_string();
4244 if(is_array($contact_record)) {
4245 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4246 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4247 intval($importer['uid']),
4248 intval($contact_record['id']),
4250 dbesc(datetime_convert())
4254 if(intval($r[0]['def_gid'])) {
4255 require_once('include/group.php');
4256 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4259 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4260 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
4263 'type' => NOTIFY_INTRO,
4264 'notify_flags' => $r[0]['notify-flags'],
4265 'language' => $r[0]['language'],
4266 'to_name' => $r[0]['username'],
4267 'to_email' => $r[0]['email'],
4268 'uid' => $r[0]['uid'],
4269 'link' => $a->get_baseurl() . '/notifications/intro',
4270 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4271 'source_link' => $contact_record['url'],
4272 'source_photo' => $contact_record['photo'],
4273 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4278 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4279 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
4280 intval($importer['uid']),
4288 function lose_follower($importer,$contact,$datarray,$item) {
4290 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4291 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4292 intval(CONTACT_IS_SHARING),
4293 intval($contact['id'])
4297 contact_remove($contact['id']);
4301 function lose_sharer($importer,$contact,$datarray,$item) {
4303 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4304 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4305 intval(CONTACT_IS_FOLLOWER),
4306 intval($contact['id'])
4310 contact_remove($contact['id']);
4315 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4319 if(is_array($importer)) {
4320 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4321 intval($importer['uid'])
4325 // Diaspora has different message-ids in feeds than they do
4326 // through the direct Diaspora protocol. If we try and use
4327 // the feed, we'll get duplicates. So don't.
4329 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4332 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4334 // Use a single verify token, even if multiple hubs
4336 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4338 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4340 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4342 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4343 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4344 dbesc($verify_token),
4345 intval($contact['id'])
4349 post_url($url,$params);
4351 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4358 function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
4362 $name = xmlify($name);
4363 $uri = xmlify($uri);
4366 $photo = xmlify($photo);
4370 $o .= "\t<name>$name</name>\r\n";
4371 $o .= "\t<uri>$uri</uri>\r\n";
4372 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4373 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4375 if ($tag == "author") {
4378 $o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
4380 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4381 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4382 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4383 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4384 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4385 WHERE `profile`.`is-default` AND `contact`.`self` AND
4386 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4387 dbesc(normalise_link($uri)));
4390 if($r[0]['locality'])
4391 $location .= $r[0]['locality'];
4392 if($r[0]['region']) {
4395 $location .= $r[0]['region'];
4397 if($r[0]['country-name']) {
4400 $location .= $r[0]['country-name'];
4403 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4404 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4405 $o .= "\t<poco:note>".xmlify(bbcode($r[0]["about"]))."</poco:note>\r\n";
4406 $o .= "\t<poco:address>\r\n";
4407 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4408 $o .= "\t</poco:address>\r\n";
4409 $o .= "\t<poco:urls>\r\n";
4410 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4411 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4412 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4413 $o .= "\t</poco:urls>\r\n";
4417 call_hooks('atom_author', $o);
4419 $o .= "</$tag>\r\n";
4423 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4427 if(! $item['parent'])
4430 if($item['deleted'])
4431 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4434 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4435 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4437 $body = $item['body'];
4440 $o = "\r\n\r\n<entry>\r\n";
4442 if(is_array($author))
4443 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
4445 $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']), $item['coord']);
4446 if(strlen($item['owner-name']))
4447 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
4449 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4450 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4451 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4452 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4457 if ($item['title'] != "")
4458 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4460 $htmlbody = bbcode($htmlbody, false, false, 7);
4462 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4463 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4464 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4465 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4466 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4467 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4468 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4470 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4473 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4475 if($item['location']) {
4476 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4477 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4481 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4483 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4484 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4487 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4488 if($item['bookmark'])
4489 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4492 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4495 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4497 if($item['signed_text']) {
4498 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4499 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4502 $verb = construct_verb($item);
4503 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4504 $actobj = construct_activity_object($item);
4507 $actarg = construct_activity_target($item);
4511 $tags = item_getfeedtags($item);
4513 foreach($tags as $t)
4514 if (($type != 'html') OR ($t[0] != "@"))
4515 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4519 /// To support these elements, the API needs to be enhanced
4520 /// $o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4521 /// $o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4522 /// $o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4524 // Deactivated since it was meant only for OStatus
4525 //$o .= item_get_attachment($item);
4527 $o .= item_getfeedattach($item);
4529 $mentioned = get_mentions($item);
4533 call_hooks('atom_entry', $o);
4535 $o .= '</entry>' . "\r\n";
4540 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4542 if(get_config('system','disable_embedded'))
4547 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4548 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4553 $img_start = strpos($orig_body, '[img');
4554 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4555 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4556 while( ($img_st_close !== false) && ($img_len !== false) ) {
4558 $img_st_close++; // make it point to AFTER the closing bracket
4559 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4561 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4564 if(stristr($image , $site . '/photo/')) {
4565 // Only embed locally hosted photos
4567 $i = basename($image);
4568 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4569 $x = strpos($i,'-');
4572 $res = substr($i,$x+1);
4573 $i = substr($i,0,$x);
4574 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4581 // Check to see if we should replace this photo link with an embedded image
4582 // 1. No need to do so if the photo is public
4583 // 2. If there's a contact-id provided, see if they're in the access list
4584 // for the photo. If so, embed it.
4585 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4586 // permissions, regardless of order but first check to see if they're an exact
4587 // match to save some processing overhead.
4589 if(has_permissions($r[0])) {
4591 $recips = enumerate_permissions($r[0]);
4592 if(in_array($cid, $recips)) {
4597 if(compare_permissions($item,$r[0]))
4602 $data = $r[0]['data'];
4603 $type = $r[0]['type'];
4605 // If a custom width and height were specified, apply before embedding
4606 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4607 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4609 $width = intval($match[1]);
4610 $height = intval($match[2]);
4612 $ph = new Photo($data, $type);
4613 if($ph->is_valid()) {
4614 $ph->scaleImage(max($width, $height));
4615 $data = $ph->imageString();
4616 $type = $ph->getType();
4620 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4621 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4622 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4628 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4629 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4630 if($orig_body === false)
4633 $img_start = strpos($orig_body, '[img');
4634 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4635 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4638 $new_body = $new_body . $orig_body;
4644 function has_permissions($obj) {
4645 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4650 function compare_permissions($obj1,$obj2) {
4651 // first part is easy. Check that these are exactly the same.
4652 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4653 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4654 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4655 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4658 // This is harder. Parse all the permissions and compare the resulting set.
4660 $recipients1 = enumerate_permissions($obj1);
4661 $recipients2 = enumerate_permissions($obj2);
4664 if($recipients1 == $recipients2)
4669 // returns an array of contact-ids that are allowed to see this object
4671 function enumerate_permissions($obj) {
4672 require_once('include/group.php');
4673 $allow_people = expand_acl($obj['allow_cid']);
4674 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4675 $deny_people = expand_acl($obj['deny_cid']);
4676 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4677 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4678 $deny = array_unique(array_merge($deny_people,$deny_groups));
4679 $recipients = array_diff($recipients,$deny);
4683 function item_getfeedtags($item) {
4686 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4688 for($x = 0; $x < $cnt; $x ++) {
4690 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
4694 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4696 for($x = 0; $x < $cnt; $x ++) {
4698 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4704 function item_get_attachment($item) {
4706 $siteinfo = get_attached_data($item["body"]);
4708 switch($siteinfo["type"]) {
4710 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4713 $imgdata = get_photo_info($siteinfo["image"]);
4714 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4717 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4726 function item_getfeedattach($item) {
4728 $arr = explode('[/attach],',$item['attach']);
4730 foreach($arr as $r) {
4732 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4734 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4735 if(intval($matches[2]))
4736 $ret .= 'length="' . intval($matches[2]) . '" ';
4737 if($matches[4] !== ' ')
4738 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4739 $ret .= ' />' . "\r\n";
4748 function item_expire($uid, $days, $network = "", $force = false) {
4750 if((! $uid) || ($days < 1))
4753 // $expire_network_only = save your own wall posts
4754 // and just expire conversations started by others
4756 $expire_network_only = get_pconfig($uid,'expire','network_only');
4757 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4759 if ($network != "") {
4760 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4761 // There is an index "uid_network_received" but not "uid_network_created"
4762 // This avoids the creation of another index just for one purpose.
4763 // And it doesn't really matter wether to look at "received" or "created"
4764 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4766 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4768 $r = q("SELECT * FROM `item`
4769 WHERE `uid` = %d $range
4780 $expire_items = get_pconfig($uid, 'expire','items');
4781 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4783 // Forcing expiring of items - but not notes and marked items
4785 $expire_items = true;
4787 $expire_notes = get_pconfig($uid, 'expire','notes');
4788 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4790 $expire_starred = get_pconfig($uid, 'expire','starred');
4791 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4793 $expire_photos = get_pconfig($uid, 'expire','photos');
4794 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4796 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4798 foreach($r as $item) {
4800 // don't expire filed items
4802 if(strpos($item['file'],'[') !== false)
4805 // Only expire posts, not photos and photo comments
4807 if($expire_photos==0 && strlen($item['resource-id']))
4809 if($expire_starred==0 && intval($item['starred']))
4811 if($expire_notes==0 && $item['type']=='note')
4813 if($expire_items==0 && $item['type']!='note')
4816 drop_item($item['id'],false);
4819 proc_run('php',"include/notifier.php","expire","$uid");
4824 function drop_items($items) {
4827 if(! local_user() && ! remote_user())
4831 foreach($items as $item) {
4832 $owner = drop_item($item,false);
4833 if($owner && ! $uid)
4838 // multiple threads may have been deleted, send an expire notification
4841 proc_run('php',"include/notifier.php","expire","$uid");
4845 function drop_item($id,$interactive = true) {
4849 // locate item to be deleted
4851 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4858 notice( t('Item not found.') . EOL);
4859 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4864 $owner = $item['uid'];
4868 // check if logged in user is either the author or owner of this item
4870 if(is_array($_SESSION['remote'])) {
4871 foreach($_SESSION['remote'] as $visitor) {
4872 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4873 $cid = $visitor['cid'];
4880 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4882 // Check if we should do HTML-based delete confirmation
4883 if($_REQUEST['confirm']) {
4884 // <form> can't take arguments in its "action" parameter
4885 // so add any arguments as hidden inputs
4886 $query = explode_querystring($a->query_string);
4888 foreach($query['args'] as $arg) {
4889 if(strpos($arg, 'confirm=') === false) {
4890 $arg_parts = explode('=', $arg);
4891 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4895 return replace_macros(get_markup_template('confirm.tpl'), array(
4897 '$message' => t('Do you really want to delete this item?'),
4898 '$extra_inputs' => $inputs,
4899 '$confirm' => t('Yes'),
4900 '$confirm_url' => $query['base'],
4901 '$confirm_name' => 'confirmed',
4902 '$cancel' => t('Cancel'),
4905 // Now check how the user responded to the confirmation query
4906 if($_REQUEST['canceled']) {
4907 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4910 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4913 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4914 dbesc(datetime_convert()),
4915 dbesc(datetime_convert()),
4918 create_tags_from_item($item['id']);
4919 create_files_from_item($item['id']);
4920 delete_thread($item['id'], $item['parent-uri']);
4922 // clean up categories and tags so they don't end up as orphans
4925 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4927 foreach($matches as $mtch) {
4928 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4934 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4936 foreach($matches as $mtch) {
4937 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4941 // If item is a link to a photo resource, nuke all the associated photos
4942 // (visitors will not have photo resources)
4943 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4944 // generate a resource-id and therefore aren't intimately linked to the item.
4946 if(strlen($item['resource-id'])) {
4947 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4948 dbesc($item['resource-id']),
4949 intval($item['uid'])
4951 // ignore the result
4954 // If item is a link to an event, nuke the event record.
4956 if(intval($item['event-id'])) {
4957 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4958 intval($item['event-id']),
4959 intval($item['uid'])
4961 // ignore the result
4964 // If item has attachments, drop them
4966 foreach(explode(",",$item['attach']) as $attach){
4967 preg_match("|attach/(\d+)|", $attach, $matches);
4968 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4969 intval($matches[1]),
4972 // ignore the result
4976 // clean up item_id and sign meta-data tables
4979 // Old code - caused very long queries and warning entries in the mysql logfiles:
4981 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4982 intval($item['id']),
4983 intval($item['uid'])
4986 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4987 intval($item['id']),
4988 intval($item['uid'])
4992 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4994 // Creating list of parents
4995 $r = q("select id from item where parent = %d and uid = %d",
4996 intval($item['id']),
4997 intval($item['uid'])
5002 foreach ($r AS $row) {
5003 if ($parentid != "")
5006 $parentid .= $row["id"];
5010 if ($parentid != "") {
5011 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5013 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5016 // If it's the parent of a comment thread, kill all the kids
5018 if($item['uri'] == $item['parent-uri']) {
5019 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5020 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5021 dbesc(datetime_convert()),
5022 dbesc(datetime_convert()),
5023 dbesc($item['parent-uri']),
5024 intval($item['uid'])
5026 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5027 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5028 delete_thread_uri($item['parent-uri'], $item['uid']);
5029 // ignore the result
5032 // ensure that last-child is set in case the comment that had it just got wiped.
5033 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5034 dbesc(datetime_convert()),
5035 dbesc($item['parent-uri']),
5036 intval($item['uid'])
5038 // who is the last child now?
5039 $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",
5040 dbesc($item['parent-uri']),
5041 intval($item['uid'])
5044 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5049 // Add a relayable_retraction signature for Diaspora.
5050 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5053 $drop_id = intval($item['id']);
5055 // send the notification upstream/downstream as the case may be
5057 proc_run('php',"include/notifier.php","drop","$drop_id");
5061 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5067 notice( t('Permission denied.') . EOL);
5068 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5075 function first_post_date($uid,$wall = false) {
5076 $r = q("select id, created from item
5077 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5079 order by created asc limit 1",
5081 intval($wall ? 1 : 0)
5084 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5085 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5090 /* modified posted_dates() {below} to arrange the list in years */
5091 function list_post_dates($uid, $wall) {
5092 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5094 $dthen = first_post_date($uid, $wall);
5098 // Set the start and end date to the beginning of the month
5099 $dnow = substr($dnow,0,8).'01';
5100 $dthen = substr($dthen,0,8).'01';
5104 // Starting with the current month, get the first and last days of every
5105 // month down to and including the month of the first post
5106 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5107 $dyear = intval(substr($dnow,0,4));
5108 $dstart = substr($dnow,0,8) . '01';
5109 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5110 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5111 $end_month = datetime_convert('','',$dend,'Y-m-d');
5112 $str = day_translate(datetime_convert('','',$dnow,'F'));
5114 $ret[$dyear] = array();
5115 $ret[$dyear][] = array($str,$end_month,$start_month);
5116 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5121 function posted_dates($uid,$wall) {
5122 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5124 $dthen = first_post_date($uid,$wall);
5128 // Set the start and end date to the beginning of the month
5129 $dnow = substr($dnow,0,8).'01';
5130 $dthen = substr($dthen,0,8).'01';
5133 // Starting with the current month, get the first and last days of every
5134 // month down to and including the month of the first post
5135 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5136 $dstart = substr($dnow,0,8) . '01';
5137 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5138 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5139 $end_month = datetime_convert('','',$dend,'Y-m-d');
5140 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5141 $ret[] = array($str,$end_month,$start_month);
5142 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5148 function posted_date_widget($url,$uid,$wall) {
5151 if(! feature_enabled($uid,'archives'))
5154 // For former Facebook folks that left because of "timeline"
5156 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5159 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5160 if(! $visible_years)
5163 $ret = list_post_dates($uid,$wall);
5168 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5169 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5171 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5172 '$title' => t('Archives'),
5173 '$size' => $visible_years,
5174 '$cutoff_year' => $cutoff_year,
5175 '$cutoff' => $cutoff,
5178 '$showmore' => t('show more')
5184 function store_diaspora_retract_sig($item, $user, $baseurl) {
5185 // Note that we can't add a target_author_signature
5186 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5187 // the comment, that means we're the home of the post, and Diaspora will only
5188 // check the parent_author_signature of retractions that it doesn't have to relay further
5190 // I don't think this function gets called for an "unlike," but I'll check anyway
5192 $enabled = intval(get_config('system','diaspora_enabled'));
5194 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5198 logger('drop_item: storing diaspora retraction signature');
5200 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5202 if(local_user() == $item['uid']) {
5204 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5205 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5208 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5209 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5212 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5213 // only handles DFRN deletes
5214 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5215 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5216 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5222 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5223 intval($item['id']),
5224 dbesc($signed_text),