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, $forpubsub = false) {
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 // Include answers to status.net posts in pubsub feeds
125 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
126 LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
127 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
128 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
129 $date_field = "`received`";
130 $sql_order = "`item`.`received` DESC";
132 $date_field = "`changed`";
133 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
136 if(! strlen($last_update))
137 $last_update = 'now -30 days';
139 if(isset($category)) {
140 $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` ",
141 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
142 //$sql_extra .= file_tag_file_query('item',$category,'category');
147 $sql_extra .= " AND `contact`.`self` = 1 ";
150 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
152 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
153 // dbesc($check_date),
155 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
156 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
157 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
158 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
159 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
160 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
161 FROM `item` $sql_post_table
162 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
163 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
164 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
165 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
166 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
168 ORDER BY $sql_order LIMIT 0, 300",
174 // Will check further below if this actually returned results.
175 // We will provide an empty feed if that is the case.
179 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
183 $hubxml = feed_hublinks();
185 $salmon = feed_salmonlinks($owner_nick);
187 $alternatelink = $owner['url'];
190 $alternatelink .= "/category/".$category;
192 $atom .= replace_macros($feed_template, array(
193 '$version' => xmlify(FRIENDICA_VERSION),
194 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
195 '$feed_title' => xmlify($owner['name']),
196 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
198 '$salmon' => $salmon,
199 '$alternatelink' => xmlify($alternatelink),
200 '$name' => xmlify($owner['name']),
201 '$profile_page' => xmlify($owner['url']),
202 '$photo' => xmlify($owner['photo']),
203 '$thumb' => xmlify($owner['thumb']),
204 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
205 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
206 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
207 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
208 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
211 call_hooks('atom_feed', $atom);
213 if(! count($items)) {
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
221 foreach($items as $item) {
223 // prevent private email from leaking.
224 if($item['network'] === NETWORK_MAIL)
227 // public feeds get html, our own nodes use bbcode
231 // catch any email that's in a public conversation and make sure it doesn't leak
239 $atom .= atom_entry($item,$type,null,$owner,true);
242 call_hooks('atom_feed_end', $atom);
244 $atom .= '</feed>' . "\r\n";
250 function construct_verb($item) {
252 return $item['verb'];
253 return ACTIVITY_POST;
256 function construct_activity_object($item) {
258 if($item['object']) {
259 $o = '<as:object>' . "\r\n";
260 $r = parse_xml_string($item['object'],false);
266 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
268 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
270 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
272 if(substr($r->link,0,1) === '<') {
273 // patch up some facebook "like" activity objects that got stored incorrectly
274 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
275 // we can probably remove this hack here and in the following function in a few months time.
276 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
277 $r->link = str_replace('&','&', $r->link);
278 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
282 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
285 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
286 $o .= '</as:object>' . "\r\n";
293 function construct_activity_target($item) {
295 if($item['target']) {
296 $o = '<as:target>' . "\r\n";
297 $r = parse_xml_string($item['target'],false);
301 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
303 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
305 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
307 if(substr($r->link,0,1) === '<') {
308 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
309 $r->link = str_replace('&','&', $r->link);
310 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
314 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
317 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
318 $o .= '</as:target>' . "\r\n";
327 * The purpose of this function is to apply system message length limits to
328 * imported messages without including any embedded photos in the length
330 if(! function_exists('limit_body_size')) {
331 function limit_body_size($body) {
333 // logger('limit_body_size: start', LOGGER_DEBUG);
335 $maxlen = get_max_import_size();
337 // If the length of the body, including the embedded images, is smaller
338 // than the maximum, then don't waste time looking for the images
339 if($maxlen && (strlen($body) > $maxlen)) {
341 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
348 $img_start = strpos($orig_body, '[img');
349 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
350 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
351 while(($img_st_close !== false) && ($img_end !== false)) {
353 $img_st_close++; // make it point to AFTER the closing bracket
354 $img_end += $img_start;
355 $img_end += strlen('[/img]');
357 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
358 // This is an embedded image
360 if( ($textlen + $img_start) > $maxlen ) {
361 if($textlen < $maxlen) {
362 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
363 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
368 $new_body = $new_body . substr($orig_body, 0, $img_start);
369 $textlen += $img_start;
372 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
376 if( ($textlen + $img_end) > $maxlen ) {
377 if($textlen < $maxlen) {
378 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
379 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
384 $new_body = $new_body . substr($orig_body, 0, $img_end);
385 $textlen += $img_end;
388 $orig_body = substr($orig_body, $img_end);
390 if($orig_body === false) // in case the body ends on a closing image tag
393 $img_start = strpos($orig_body, '[img');
394 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
395 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
398 if( ($textlen + strlen($orig_body)) > $maxlen) {
399 if($textlen < $maxlen) {
400 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
401 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
406 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
407 $new_body = $new_body . $orig_body;
408 $textlen += strlen($orig_body);
417 function title_is_body($title, $body) {
419 $title = strip_tags($title);
420 $title = trim($title);
421 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
422 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
424 $body = strip_tags($body);
426 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
427 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
429 if (strlen($title) < strlen($body))
430 $body = substr($body, 0, strlen($title));
432 if (($title != $body) and (substr($title, -3) == "...")) {
433 $pos = strrpos($title, "...");
435 $title = substr($title, 0, $pos);
436 $body = substr($body, 0, $pos);
440 return($title == $body);
445 function get_atom_elements($feed, $item, $contact = array()) {
447 require_once('library/HTMLPurifier.auto.php');
448 require_once('include/html2bbcode.php');
450 $best_photo = array();
454 $author = $item->get_author();
456 $res['author-name'] = unxmlify($author->get_name());
457 $res['author-link'] = unxmlify($author->get_link());
460 $res['author-name'] = unxmlify($feed->get_title());
461 $res['author-link'] = unxmlify($feed->get_permalink());
463 $res['uri'] = unxmlify($item->get_id());
464 $res['title'] = unxmlify($item->get_title());
465 $res['body'] = unxmlify($item->get_content());
466 $res['plink'] = unxmlify($item->get_link(0));
468 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
469 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
471 $res['body'] = nl2br($res['body']);
474 // removing the content of the title if its identically to the body
475 // This helps with auto generated titles e.g. from tumblr
476 if (title_is_body($res["title"], $res["body"]))
480 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
484 // look for a photo. We should check media size and find the best one,
485 // but for now let's just find any author photo
486 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
488 $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"];
489 if (is_array($authorlinks)) {
490 foreach ($authorlinks as $link) {
491 $linkdata = array_shift($link["attribs"]);
493 if ($linkdata["rel"] == "alternate")
494 $res["author-link"] = $linkdata["href"];
498 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate')
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
506 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
507 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
508 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
513 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
515 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
516 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
529 // No photo/profile-link on the item - look at the feed level
531 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
532 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
533 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
534 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
535 foreach($base as $link) {
536 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
537 $res['author-link'] = unxmlify($link['attribs']['']['href']);
538 if(! $res['author-avatar']) {
539 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
540 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
545 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
547 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
548 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
550 if($base && count($base)) {
551 foreach($base as $link) {
552 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
553 $res['author-link'] = unxmlify($link['attribs']['']['href']);
554 if(! (x($res,'author-avatar'))) {
555 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
556 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
563 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
564 if($apps && $apps[0]['attribs']['']['source']) {
565 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
566 if($res['app'] === 'web')
567 $res['app'] = 'OStatus';
570 // base64 encoded json structure representing Diaspora signature
572 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
574 $res['dsprsig'] = unxmlify($dsig[0]['data']);
577 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
579 $res['guid'] = unxmlify($dguid[0]['data']);
581 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
583 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
587 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
590 $have_real_body = false;
592 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
594 $have_real_body = true;
595 $res['body'] = $rawenv[0]['data'];
596 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
597 // make sure nobody is trying to sneak some html tags by us
598 $res['body'] = notags(base64url_decode($res['body']));
602 $res['body'] = limit_body_size($res['body']);
604 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
605 // the content type. Our own network only emits text normally, though it might have been converted to
606 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
607 // have to assume it is all html and needs to be purified.
609 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
610 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
611 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
614 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
616 $res['body'] = reltoabs($res['body'],$base_url);
618 $res['body'] = html2bb_video($res['body']);
620 $res['body'] = oembed_html2bbcode($res['body']);
622 $config = HTMLPurifier_Config::createDefault();
623 $config->set('Cache.DefinitionImpl', null);
625 // we shouldn't need a whitelist, because the bbcode converter
626 // will strip out any unsupported tags.
628 $purifier = new HTMLPurifier($config);
629 $res['body'] = $purifier->purify($res['body']);
631 $res['body'] = @html2bbcode($res['body']);
635 elseif(! $have_real_body) {
637 // it's not one of our messages and it has no tags
638 // so it's probably just text. We'll escape it just to be safe.
640 $res['body'] = escape_tags($res['body']);
644 // this tag is obsolete but we keep it for really old sites
646 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
647 if($allow && $allow[0]['data'] == 1)
648 $res['last-child'] = 1;
650 $res['last-child'] = 0;
652 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
653 if($private && intval($private[0]['data']) > 0)
654 $res['private'] = intval($private[0]['data']);
658 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
659 if($extid && $extid[0]['data'])
660 $res['extid'] = $extid[0]['data'];
662 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
664 $res['location'] = unxmlify($rawlocation[0]['data']);
667 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
669 $res['created'] = unxmlify($rawcreated[0]['data']);
672 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
674 $res['edited'] = unxmlify($rawedited[0]['data']);
676 if((x($res,'edited')) && (! (x($res,'created'))))
677 $res['created'] = $res['edited'];
679 if(! $res['created'])
680 $res['created'] = $item->get_date('c');
683 $res['edited'] = $item->get_date('c');
686 // Disallow time travelling posts
688 $d1 = strtotime($res['created']);
689 $d2 = strtotime($res['edited']);
690 $d3 = strtotime('now');
693 $res['created'] = datetime_convert();
695 $res['edited'] = datetime_convert();
697 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
698 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
699 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
700 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
701 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
702 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
703 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
704 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
705 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
707 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
708 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
710 foreach($base as $link) {
711 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
712 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
713 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
718 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
720 $res['coord'] = unxmlify($rawgeo[0]['data']);
722 if ($contact["network"] == NETWORK_FEED) {
723 $res['verb'] = ACTIVITY_POST;
724 $res['object-type'] = ACTIVITY_OBJ_NOTE;
727 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
729 // select between supported verbs
732 $res['verb'] = unxmlify($rawverb[0]['data']);
735 // translate OStatus unfollow to activity streams if it happened to get selected
737 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
738 $res['verb'] = ACTIVITY_UNFOLLOW;
740 $cats = $item->get_categories();
743 foreach($cats as $cat) {
744 $term = $cat->get_term();
746 $term = $cat->get_label();
747 $scheme = $cat->get_scheme();
748 if($scheme && $term && stristr($scheme,'X-DFRN:'))
749 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
751 $tag_arr[] = notags(trim($term));
753 $res['tag'] = implode(',', $tag_arr);
756 $attach = $item->get_enclosures();
759 foreach($attach as $att) {
760 $len = intval($att->get_length());
761 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
762 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
763 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
764 if(strpos($type,';'))
765 $type = substr($type,0,strpos($type,';'));
766 if((! $link) || (strpos($link,'http') !== 0))
772 $type = 'application/octet-stream';
774 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
776 $res['attach'] = implode(',', $att_arr);
779 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
782 $res['object'] = '<object>' . "\n";
783 $child = $rawobj[0]['child'];
784 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
785 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
786 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
789 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
791 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
793 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
794 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
795 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
797 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
798 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
799 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
800 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
802 $body = html2bb_video($body);
804 $config = HTMLPurifier_Config::createDefault();
805 $config->set('Cache.DefinitionImpl', null);
807 $purifier = new HTMLPurifier($config);
808 $body = $purifier->purify($body);
809 $body = html2bbcode($body);
812 $res['object'] .= '<content>' . $body . '</content>' . "\n";
815 $res['object'] .= '</object>' . "\n";
818 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
821 $res['target'] = '<target>' . "\n";
822 $child = $rawobj[0]['child'];
823 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
824 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
827 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
829 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
830 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
831 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
832 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
833 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
835 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
836 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
837 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
838 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
840 $body = html2bb_video($body);
842 $config = HTMLPurifier_Config::createDefault();
843 $config->set('Cache.DefinitionImpl', null);
845 $purifier = new HTMLPurifier($config);
846 $body = $purifier->purify($body);
847 $body = html2bbcode($body);
850 $res['target'] .= '<content>' . $body . '</content>' . "\n";
853 $res['target'] .= '</target>' . "\n";
856 // This is some experimental stuff. By now retweets are shown with "RT:"
857 // But: There is data so that the message could be shown similar to native retweets
858 // There is some better way to parse this array - but it didn't worked for me.
859 $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"];
860 if (is_array($child)) {
861 logger('get_atom_elements: Looking for status.net repeated message');
863 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
864 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
865 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
866 $uri = $author["uri"][0]["data"];
867 $name = $author["name"][0]["data"];
868 $avatar = @array_shift($author["link"][2]["attribs"]);
869 $avatar = $avatar["href"];
871 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
872 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
874 if (!intval(get_config('system','wall-to-wall_share'))) {
875 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
877 $res["body"] = $prefix.html2bbcode($message)."[/share]";
879 $res["owner-name"] = $res["author-name"];
880 $res["owner-link"] = $res["author-link"];
881 $res["owner-avatar"] = $res["author-avatar"];
883 $res["author-name"] = $name;
884 $res["author-link"] = $uri;
885 $res["author-avatar"] = $avatar;
887 $res["body"] = html2bbcode($message);
892 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
895 // Handle enclosures and treat them as preview picture
897 foreach ($attach AS $attachment)
898 if ($attachment->type == "image/jpeg")
899 $preview = $attachment->link;
901 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
902 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
904 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
905 unset($res["attach"]);
906 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
907 $res["body"] = add_page_info_to_body($res["body"]);
908 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
909 $res["body"] = add_page_info_to_body($res["body"]);
912 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
914 call_hooks('parse_atom', $arr);
919 function add_page_info_data($data) {
920 call_hooks('page_info_data', $data);
922 // It maybe is a rich content, but if it does have everything that a link has,
923 // then treat it that way
924 if (($data["type"] == "rich") AND is_string($data["title"]) AND
925 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
926 $data["type"] = "link";
928 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
931 if ($no_photos AND ($data["type"] == "photo"))
934 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
935 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
936 require_once("include/network.php");
937 $data["url"] = short_link($data["url"]);
940 if (($data["type"] != "photo") AND is_string($data["title"]))
941 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
943 if (($data["type"] != "video") AND ($photo != ""))
944 $text .= '[img]'.$photo.'[/img]';
945 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
946 $imagedata = $data["images"][0];
947 $text .= '[img]'.$imagedata["src"].'[/img]';
950 if (($data["type"] != "photo") AND is_string($data["text"]))
951 $text .= "[quote]".$data["text"]."[/quote]";
954 if (isset($data["keywords"]) AND count($data["keywords"])) {
957 foreach ($data["keywords"] AS $keyword) {
958 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
959 array("","", "", "", "", ""), $keyword);
960 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
964 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
967 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
968 require_once("mod/parse_url.php");
970 $data = Cache::get("parse_url:".$url);
972 $data = parseurl_getsiteinfo($url, true);
973 Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
975 $data = unserialize($data);
978 $data["images"][0]["src"] = $photo;
980 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
982 if (!$keywords AND isset($data["keywords"]))
983 unset($data["keywords"]);
985 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
986 $list = explode(",", $keyword_blacklist);
987 foreach ($list AS $keyword) {
988 $keyword = trim($keyword);
989 $index = array_search($keyword, $data["keywords"]);
990 if ($index !== false)
991 unset($data["keywords"][$index]);
998 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
999 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 if (isset($data["keywords"]) AND count($data["keywords"])) {
1004 foreach ($data["keywords"] AS $keyword) {
1005 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1006 array("","", "", "", "", ""), $keyword);
1011 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1018 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1019 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1021 $text = add_page_info_data($data);
1026 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1028 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1030 $URLSearchString = "^\[\]";
1032 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1033 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1036 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1038 // Convert urls without bbcode elements
1039 if (!$matches AND $texturl) {
1040 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1042 // Yeah, a hack. I really hate regular expressions :)
1044 $matches[1] = $matches[2];
1048 $footer = add_page_info($matches[1], $no_photos);
1050 // Remove the link from the body if the link is attached at the end of the post
1051 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1052 $removedlink = trim(str_replace($matches[1], "", $body));
1053 if (($removedlink == "") OR strstr($body, $removedlink))
1054 $body = $removedlink;
1056 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1057 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1058 if (($removedlink == "") OR strstr($body, $removedlink))
1059 $body = $removedlink;
1062 // Add the page information to the bottom
1063 if (isset($footer) AND (trim($footer) != ""))
1069 function encode_rel_links($links) {
1071 if(! ((is_array($links)) && (count($links))))
1073 foreach($links as $link) {
1075 if($link['attribs']['']['rel'])
1076 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1077 if($link['attribs']['']['type'])
1078 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1079 if($link['attribs']['']['href'])
1080 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1081 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1082 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1083 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1084 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1085 $o .= ' />' . "\n" ;
1090 function add_guid($item) {
1091 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1095 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1096 dbesc($item["guid"]), dbesc($item["plink"]),
1097 dbesc($item["uri"]), dbesc($item["network"]));
1100 // Adds a "lang" specification in a "postopts" element of given $arr,
1101 // if possible and not already present.
1102 // Expects "body" element to exist in $arr.
1103 // TODO: add a parameter to request forcing override
1104 function item_add_language_opt(&$arr) {
1106 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1108 if ( x($arr, 'postopts') )
1110 if ( strstr($arr['postopts'], 'lang=') )
1113 // TODO: add parameter to request overriding
1116 $postopts = $arr['postopts'];
1123 require_once('library/langdet/Text/LanguageDetect.php');
1124 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1125 $l = new Text_LanguageDetect;
1126 //$lng = $l->detectConfidence($naked_body);
1127 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1128 $lng = $l->detect($naked_body, 3);
1130 if (sizeof($lng) > 0) {
1131 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
1132 $postopts .= 'lang=';
1134 foreach ($lng as $language => $score) {
1135 $postopts .= $sep . $language.";".$score;
1138 $arr['postopts'] = $postopts;
1142 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1144 // If it is a posting where users should get notifications, then define it as wall posting
1147 $arr['type'] = 'wall';
1149 $arr['last-child'] = 1;
1150 $arr['network'] = NETWORK_DFRN;
1153 // If a Diaspora signature structure was passed in, pull it out of the
1154 // item array and set it aside for later storage.
1157 if(x($arr,'dsprsig')) {
1158 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1159 unset($arr['dsprsig']);
1162 // Converting the plink
1163 if ($arr['network'] == NETWORK_OSTATUS) {
1164 if (isset($arr['plink']))
1165 $arr['plink'] = ostatus_convert_href($arr['plink']);
1166 elseif (isset($arr['uri']))
1167 $arr['plink'] = ostatus_convert_href($arr['uri']);
1170 if(x($arr, 'gravity'))
1171 $arr['gravity'] = intval($arr['gravity']);
1172 elseif($arr['parent-uri'] === $arr['uri'])
1173 $arr['gravity'] = 0;
1174 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1175 $arr['gravity'] = 6;
1177 $arr['gravity'] = 6; // extensible catchall
1179 if(! x($arr,'type'))
1180 $arr['type'] = 'remote';
1184 /* check for create date and expire time */
1185 $uid = intval($arr['uid']);
1186 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1188 $expire_interval = $r[0]['expire'];
1189 if ($expire_interval>0) {
1190 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1191 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1192 if ($created_date < $expire_date) {
1193 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1199 // If there is no guid then take the same guid that was taken before for the same uri
1200 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1201 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1202 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1203 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1206 $arr['guid'] = $r[0]["guid"];
1207 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1211 // If there is no guid then take the same guid that was taken before for the same plink
1212 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1213 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1214 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1215 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1218 $arr['guid'] = $r[0]["guid"];
1219 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1221 if ($r[0]["uri"] != $arr['uri'])
1222 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1226 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1227 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1228 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1229 // $arr['body'] = strip_tags($arr['body']);
1231 item_add_language_opt($arr);
1236 $guid_prefix = $arr['network'];
1238 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1239 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1240 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1241 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1242 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1243 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1244 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1245 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1246 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1247 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1248 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1249 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1250 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1251 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1252 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1253 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1254 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1255 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1256 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1257 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1258 $arr['deleted'] = 0;
1259 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1260 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1261 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1262 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1263 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1264 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1265 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1266 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1267 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1268 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1269 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1270 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1271 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1272 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1273 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1274 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1275 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1276 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1277 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1278 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1279 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1280 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1281 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1282 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1284 if ($arr['plink'] == "") {
1286 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1289 if ($arr['network'] == "") {
1290 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1291 intval($arr['contact-id']),
1296 $arr['network'] = $r[0]["network"];
1298 // Fallback to friendica (why is it empty in some cases?)
1299 if ($arr['network'] == "")
1300 $arr['network'] = NETWORK_DFRN;
1302 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1305 if ($arr['guid'] != "") {
1306 // Checking if there is already an item with the same guid
1307 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1308 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1309 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1312 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1317 // Check for hashtags in the body and repair or add hashtag links
1318 item_body_set_hashtags($arr);
1320 $arr['thr-parent'] = $arr['parent-uri'];
1321 if($arr['parent-uri'] === $arr['uri']) {
1323 $parent_deleted = 0;
1324 $allow_cid = $arr['allow_cid'];
1325 $allow_gid = $arr['allow_gid'];
1326 $deny_cid = $arr['deny_cid'];
1327 $deny_gid = $arr['deny_gid'];
1328 $notify_type = 'wall-new';
1332 // find the parent and snarf the item id and ACLs
1333 // and anything else we need to inherit
1335 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1336 dbesc($arr['parent-uri']),
1342 // is the new message multi-level threaded?
1343 // even though we don't support it now, preserve the info
1344 // and re-attach to the conversation parent.
1346 if($r[0]['uri'] != $r[0]['parent-uri']) {
1347 $arr['parent-uri'] = $r[0]['parent-uri'];
1348 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1349 ORDER BY `id` ASC LIMIT 1",
1350 dbesc($r[0]['parent-uri']),
1351 dbesc($r[0]['parent-uri']),
1358 $parent_id = $r[0]['id'];
1359 $parent_deleted = $r[0]['deleted'];
1360 $allow_cid = $r[0]['allow_cid'];
1361 $allow_gid = $r[0]['allow_gid'];
1362 $deny_cid = $r[0]['deny_cid'];
1363 $deny_gid = $r[0]['deny_gid'];
1364 $arr['wall'] = $r[0]['wall'];
1365 $notify_type = 'comment-new';
1367 // if the parent is private, force privacy for the entire conversation
1368 // This differs from the above settings as it subtly allows comments from
1369 // email correspondents to be private even if the overall thread is not.
1371 if($r[0]['private'])
1372 $arr['private'] = $r[0]['private'];
1374 // Edge case. We host a public forum that was originally posted to privately.
1375 // The original author commented, but as this is a comment, the permissions
1376 // weren't fixed up so it will still show the comment as private unless we fix it here.
1378 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1379 $arr['private'] = 0;
1382 // If its a post from myself then tag the thread as "mention"
1383 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1384 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1387 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1388 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1389 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1390 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1391 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1397 // Allow one to see reply tweets from status.net even when
1398 // we don't have or can't see the original post.
1401 logger('item_store: $force_parent=true, reply converted to top-level post.');
1403 $arr['parent-uri'] = $arr['uri'];
1404 $arr['gravity'] = 0;
1407 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1411 $parent_deleted = 0;
1415 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1417 dbesc($arr['network']),
1420 if($r && count($r)) {
1421 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1425 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1426 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1427 dbesc($arr['body']),
1428 dbesc($arr['network']),
1429 dbesc($arr['created']),
1430 intval($arr['contact-id']),
1433 if($r && count($r)) {
1434 logger('duplicated item with the same body found. ' . print_r($arr,true));
1438 // Is this item available in the global items (with uid=0)?
1439 if ($arr["uid"] == 0) {
1440 $arr["global"] = true;
1442 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1444 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1446 $arr["global"] = (count($isglobal) > 0);
1449 // Fill the cache field
1450 put_item_in_cache($arr);
1453 call_hooks('post_local',$arr);
1455 call_hooks('post_remote',$arr);
1457 if(x($arr,'cancel')) {
1458 logger('item_store: post cancelled by plugin.');
1462 // Store the unescaped version
1467 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1469 $r = dbq("INSERT INTO `item` (`"
1470 . implode("`, `", array_keys($arr))
1472 . implode("', '", array_values($arr))
1478 // find the item we just created
1479 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1486 // Store the guid and other relevant data
1489 $current_post = $r[0]['id'];
1490 logger('item_store: created item ' . $current_post);
1492 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1493 // This can be used to filter for inactive contacts.
1494 // Only do this for public postings to avoid privacy problems, since poco data is public.
1495 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1497 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1499 // Is it a forum? Then we don't care about the rules from above
1500 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1501 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1502 intval($arr['contact-id']));
1508 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1509 dbesc($arr['received']),
1510 dbesc($arr['received']),
1511 intval($arr['contact-id'])
1514 logger('item_store: could not locate created item');
1518 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1519 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1521 intval($arr['uid']),
1522 intval($current_post)
1526 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1527 $parent_id = $current_post;
1529 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1532 $private = $arr['private'];
1534 // Set parent id - and also make sure to inherit the parent's ACLs.
1536 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1537 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1544 intval($parent_deleted),
1545 intval($current_post)
1548 $arr['id'] = $current_post;
1549 $arr['parent'] = $parent_id;
1550 $arr['allow_cid'] = $allow_cid;
1551 $arr['allow_gid'] = $allow_gid;
1552 $arr['deny_cid'] = $deny_cid;
1553 $arr['deny_gid'] = $deny_gid;
1554 $arr['private'] = $private;
1555 $arr['deleted'] = $parent_deleted;
1557 // update the commented timestamp on the parent
1558 // Only update "commented" if it is really a comment
1559 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1560 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1561 dbesc(datetime_convert()),
1562 dbesc(datetime_convert()),
1566 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1567 dbesc(datetime_convert()),
1572 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1573 intval($current_post),
1574 dbesc($dsprsig->signed_text),
1575 dbesc($dsprsig->signature),
1576 dbesc($dsprsig->signer)
1582 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1585 if($arr['last-child']) {
1586 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1588 intval($arr['uid']),
1589 intval($current_post)
1593 $deleted = tag_deliver($arr['uid'],$current_post);
1595 // current post can be deleted if is for a community page and no mention are
1597 if (!$deleted AND !$dontcache) {
1599 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1600 if (count($r) == 1) {
1602 call_hooks('post_local_end', $r[0]);
1604 call_hooks('post_remote_end', $r[0]);
1606 logger('item_store: new item not found in DB, id ' . $current_post);
1609 // Add every contact of the post to the global contact table
1612 create_tags_from_item($current_post);
1613 create_files_from_item($current_post);
1615 // Only check for notifications on start posts
1616 if ($arr['parent-uri'] === $arr['uri']) {
1617 add_thread($current_post);
1618 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1620 // Send a notification for every new post?
1621 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1622 intval($arr['contact-id']),
1625 $send_notification = count($r);
1627 if (!$send_notification) {
1628 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1629 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1632 foreach ($tags AS $tag) {
1633 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1634 normalise_link($tag["url"]), intval($arr['uid']));
1636 $send_notification = true;
1641 if ($send_notification) {
1642 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1643 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1644 intval($arr['uid']));
1646 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1647 intval($current_post),
1653 require_once('include/enotify.php');
1655 'type' => NOTIFY_SHARE,
1656 'notify_flags' => $u[0]['notify-flags'],
1657 'language' => $u[0]['language'],
1658 'to_name' => $u[0]['username'],
1659 'to_email' => $u[0]['email'],
1660 'uid' => $u[0]['uid'],
1662 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1663 'source_name' => $item[0]['author-name'],
1664 'source_link' => $item[0]['author-link'],
1665 'source_photo' => $item[0]['author-avatar'],
1666 'verb' => ACTIVITY_TAG,
1668 'parent' => $arr['parent']
1670 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1673 update_thread($parent_id);
1674 add_shadow_entry($arr);
1678 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1680 return $current_post;
1683 function item_body_set_hashtags(&$item) {
1685 $tags = get_tags($item["body"]);
1691 // This sorting is important when there are hashtags that are part of other hashtags
1692 // Otherwise there could be problems with hashtags like #test and #test2
1697 $URLSearchString = "^\[\]";
1699 // All hashtags should point to the home server
1700 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1701 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1703 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1704 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1706 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1707 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1709 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1712 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1714 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1717 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1719 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1722 // Repair recursive urls
1723 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1724 "#$2", $item["body"]);
1727 foreach($tags as $tag) {
1728 if(strpos($tag,'#') !== 0)
1731 if(strpos($tag,'[url='))
1734 $basetag = str_replace('_',' ',substr($tag,1));
1736 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1738 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1740 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1741 if(strlen($item["tag"]))
1742 $item["tag"] = ','.$item["tag"];
1743 $item["tag"] = $newtag.$item["tag"];
1747 // Convert back the masked hashtags
1748 $item["body"] = str_replace("#", "#", $item["body"]);
1751 function get_item_guid($id) {
1752 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1754 return($r[0]["guid"]);
1759 function get_item_id($guid, $uid = 0) {
1765 $uid == local_user();
1767 // Does the given user have this item?
1769 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1770 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1771 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1774 $nick = $r[0]["nickname"];
1778 // Or is it anywhere on the server?
1780 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1781 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1782 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1783 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1784 AND `item`.`private` = 0 AND `item`.`wall` = 1
1785 AND `item`.`guid` = '%s'", dbesc($guid));
1788 $nick = $r[0]["nickname"];
1791 return(array("nick" => $nick, "id" => $id));
1795 function get_item_contact($item,$contacts) {
1796 if(! count($contacts) || (! is_array($item)))
1798 foreach($contacts as $contact) {
1799 if($contact['id'] == $item['contact-id']) {
1801 break; // NOTREACHED
1808 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1810 * @param int $item_id
1811 * @return bool true if item was deleted, else false
1813 function tag_deliver($uid,$item_id) {
1821 $u = q("select * from user where uid = %d limit 1",
1827 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1828 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1831 $i = q("select * from item where id = %d and uid = %d limit 1",
1840 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1842 // Diaspora uses their own hardwired link URL in @-tags
1843 // instead of the one we supply with webfinger
1845 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1847 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1849 foreach($matches as $mtch) {
1850 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1852 logger('tag_deliver: mention found: ' . $mtch[2]);
1858 if ( ($community_page || $prvgroup) &&
1859 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1860 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1862 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1863 q("DELETE FROM item WHERE id = %d and uid = %d",
1873 // send a notification
1875 // use a local photo if we have one
1877 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1878 intval($u[0]['uid']),
1879 dbesc(normalise_link($item['author-link']))
1881 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1884 require_once('include/enotify.php');
1886 'type' => NOTIFY_TAGSELF,
1887 'notify_flags' => $u[0]['notify-flags'],
1888 'language' => $u[0]['language'],
1889 'to_name' => $u[0]['username'],
1890 'to_email' => $u[0]['email'],
1891 'uid' => $u[0]['uid'],
1893 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1894 'source_name' => $item['author-name'],
1895 'source_link' => $item['author-link'],
1896 'source_photo' => $photo,
1897 'verb' => ACTIVITY_TAG,
1899 'parent' => $item['parent']
1903 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1905 call_hooks('tagged', $arr);
1907 if((! $community_page) && (! $prvgroup))
1911 // tgroup delivery - setup a second delivery chain
1912 // prevent delivery looping - only proceed
1913 // if the message originated elsewhere and is a top-level post
1915 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1918 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1921 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1922 intval($u[0]['uid'])
1927 // also reset all the privacy bits to the forum default permissions
1929 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1931 $forum_mode = (($prvgroup) ? 2 : 1);
1933 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1934 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1935 intval($forum_mode),
1936 dbesc($c[0]['name']),
1937 dbesc($c[0]['url']),
1938 dbesc($c[0]['thumb']),
1940 dbesc($u[0]['allow_cid']),
1941 dbesc($u[0]['allow_gid']),
1942 dbesc($u[0]['deny_cid']),
1943 dbesc($u[0]['deny_gid']),
1946 update_thread($item_id);
1948 proc_run('php','include/notifier.php','tgroup',$item_id);
1954 function tgroup_check($uid,$item) {
1960 // check that the message originated elsewhere and is a top-level post
1962 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1966 $u = q("select * from user where uid = %d limit 1",
1972 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1973 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1976 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1978 // Diaspora uses their own hardwired link URL in @-tags
1979 // instead of the one we supply with webfinger
1981 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1983 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1985 foreach($matches as $mtch) {
1986 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1988 logger('tgroup_check: mention found: ' . $mtch[2]);
1996 if((! $community_page) && (! $prvgroup))
2010 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2014 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2016 if($contact['duplex'] && $contact['dfrn-id'])
2017 $idtosend = '0:' . $orig_id;
2018 if($contact['duplex'] && $contact['issued-id'])
2019 $idtosend = '1:' . $orig_id;
2022 $rino = get_config('system','rino_encrypt');
2023 $rino = intval($rino);
2024 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2025 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2027 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2029 $ssl_val = intval(get_config('system','ssl_policy'));
2033 case SSL_POLICY_FULL:
2034 $ssl_policy = 'full';
2036 case SSL_POLICY_SELFSIGN:
2037 $ssl_policy = 'self';
2039 case SSL_POLICY_NONE:
2041 $ssl_policy = 'none';
2045 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2047 logger('dfrn_deliver: ' . $url);
2049 $xml = fetch_url($url);
2051 $curl_stat = $a->get_curl_code();
2053 return(-1); // timed out
2055 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2060 if(strpos($xml,'<?xml') === false) {
2061 logger('dfrn_deliver: no valid XML returned');
2062 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2066 $res = parse_xml_string($xml);
2068 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2069 return (($res->status) ? $res->status : 3);
2071 $postvars = array();
2072 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2073 $challenge = hex2bin((string) $res->challenge);
2074 $perm = (($res->perm) ? $res->perm : null);
2075 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2076 $rino_remote_version = intval($res->rino);
2077 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2079 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2081 if($owner['page-flags'] == PAGE_PRVGROUP)
2084 $final_dfrn_id = '';
2087 if((($perm == 'rw') && (! intval($contact['writable'])))
2088 || (($perm == 'r') && (intval($contact['writable'])))) {
2089 q("update contact set writable = %d where id = %d",
2090 intval(($perm == 'rw') ? 1 : 0),
2091 intval($contact['id'])
2093 $contact['writable'] = (string) 1 - intval($contact['writable']);
2097 if(($contact['duplex'] && strlen($contact['pubkey']))
2098 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2099 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2100 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2101 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2104 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2105 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2108 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2110 if(strpos($final_dfrn_id,':') == 1)
2111 $final_dfrn_id = substr($final_dfrn_id,2);
2113 if($final_dfrn_id != $orig_id) {
2114 logger('dfrn_deliver: wrong dfrn_id.');
2115 // did not decode properly - cannot trust this site
2119 $postvars['dfrn_id'] = $idtosend;
2120 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2122 $postvars['dissolve'] = '1';
2125 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2126 $postvars['data'] = $atom;
2127 $postvars['perm'] = 'rw';
2130 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2131 $postvars['perm'] = 'r';
2134 $postvars['ssl_policy'] = $ssl_policy;
2137 $postvars['page'] = $page;
2140 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2141 logger('rino version: '. $rino_remote_version);
2143 switch($rino_remote_version) {
2145 // Deprecated rino version!
2146 $key = substr(random_string(),0,16);
2147 $data = aes_encrypt($postvars['data'],$key);
2150 // RINO 2 based on php-encryption
2152 $key = Crypto::createNewRandomKey();
2153 } catch (CryptoTestFailed $ex) {
2154 logger('Cannot safely create a key');
2156 } catch (CannotPerformOperation $ex) {
2157 logger('Cannot safely create a key');
2161 $data = Crypto::encrypt($postvars['data'], $key);
2162 } catch (CryptoTestFailed $ex) {
2163 logger('Cannot safely perform encryption');
2165 } catch (CannotPerformOperation $ex) {
2166 logger('Cannot safely perform encryption');
2171 logger("rino: invalid requested verision '$rino_remote_version'");
2175 $postvars['rino'] = $rino_remote_version;
2176 $postvars['data'] = bin2hex($data);
2178 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2181 if($dfrn_version >= 2.1) {
2182 if(($contact['duplex'] && strlen($contact['pubkey']))
2183 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2184 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2186 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2189 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2193 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2194 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2197 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2201 logger('md5 rawkey ' . md5($postvars['key']));
2203 $postvars['key'] = bin2hex($postvars['key']);
2207 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2209 $xml = post_url($contact['notify'],$postvars);
2211 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2213 $curl_stat = $a->get_curl_code();
2214 if((! $curl_stat) || (! strlen($xml)))
2215 return(-1); // timed out
2217 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2220 if(strpos($xml,'<?xml') === false) {
2221 logger('dfrn_deliver: phase 2: no valid XML returned');
2222 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2226 if($contact['term-date'] != '0000-00-00 00:00:00') {
2227 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2228 require_once('include/Contact.php');
2229 unmark_for_death($contact);
2232 $res = parse_xml_string($xml);
2234 return $res->status;
2239 This function returns true if $update has an edited timestamp newer
2240 than $existing, i.e. $update contains new data which should override
2241 what's already there. If there is no timestamp yet, the update is
2242 assumed to be newer. If the update has no timestamp, the existing
2243 item is assumed to be up-to-date. If the timestamps are equal it
2244 assumes the update has been seen before and should be ignored.
2246 function edited_timestamp_is_newer($existing, $update) {
2247 if (!x($existing,'edited') || !$existing['edited']) {
2250 if (!x($update,'edited') || !$update['edited']) {
2253 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2254 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2255 return (strcmp($existing_edited, $update_edited) < 0);
2260 * consume_feed - process atom feed and update anything/everything we might need to update
2262 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2264 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2265 * It is this person's stuff that is going to be updated.
2266 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2267 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2268 * have a contact record.
2269 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2270 * might not) try and subscribe to it.
2271 * $datedir sorts in reverse order
2272 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2273 * imported prior to its children being seen in the stream unless we are certain
2274 * of how the feed is arranged/ordered.
2275 * With $pass = 1, we only pull parent items out of the stream.
2276 * With $pass = 2, we only pull children (comments/likes).
2278 * So running this twice, first with pass 1 and then with pass 2 will do the right
2279 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2280 * model where comments can have sub-threads. That would require some massive sorting
2281 * to get all the feed items into a mostly linear ordering, and might still require
2285 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2286 if ($contact['network'] === NETWORK_OSTATUS) {
2288 logger("Consume OStatus messages ", LOGGER_DEBUG);
2289 ostatus_import($xml,$importer,$contact, $hub);
2294 if ($contact['network'] === NETWORK_FEED) {
2296 logger("Consume feeds", LOGGER_DEBUG);
2297 feed_import($xml,$importer,$contact, $hub);
2302 require_once('library/simplepie/simplepie.inc');
2303 require_once('include/contact_selectors.php');
2305 if(! strlen($xml)) {
2306 logger('consume_feed: empty input');
2310 $feed = new SimplePie();
2311 $feed->set_raw_data($xml);
2313 $feed->enable_order_by_date(true);
2315 $feed->enable_order_by_date(false);
2319 logger('consume_feed: Error parsing XML: ' . $feed->error());
2321 $permalink = $feed->get_permalink();
2323 // Check at the feed level for updated contact name and/or photo
2327 $photo_timestamp = '';
2330 $contact_updated = '';
2332 $hubs = $feed->get_links('hub');
2333 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2336 $hub = implode(',', $hubs);
2338 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2340 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2342 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2343 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2344 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2345 $new_name = $elems['name'][0]['data'];
2347 // Manually checking for changed contact names
2348 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2349 $name_updated = date("c");
2350 $photo_timestamp = date("c");
2353 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2354 if ($photo_timestamp == "")
2355 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2356 $photo_url = $elems['link'][0]['attribs']['']['href'];
2359 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2360 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2364 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2365 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2367 $contact_updated = $photo_timestamp;
2369 require_once("include/Photo.php");
2370 $photo_failure = false;
2371 $have_photo = false;
2373 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2374 intval($contact['id']),
2375 intval($contact['uid'])
2378 $resource_id = $r[0]['resource-id'];
2382 $resource_id = photo_new_resource();
2385 $img_str = fetch_url($photo_url,true);
2386 // guess mimetype from headers or filename
2387 $type = guess_image_type($photo_url,true);
2390 $img = new Photo($img_str, $type);
2391 if($img->is_valid()) {
2393 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2394 dbesc($resource_id),
2395 intval($contact['id']),
2396 intval($contact['uid'])
2400 $img->scaleImageSquare(175);
2402 $hash = $resource_id;
2403 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2405 $img->scaleImage(80);
2406 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2408 $img->scaleImage(48);
2409 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2413 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2414 WHERE `uid` = %d AND `id` = %d",
2415 dbesc(datetime_convert()),
2416 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2417 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2418 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2419 intval($contact['uid']),
2420 intval($contact['id'])
2425 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2426 if ($name_updated > $contact_updated)
2427 $contact_updated = $name_updated;
2429 $r = q("select * from contact where uid = %d and id = %d limit 1",
2430 intval($contact['uid']),
2431 intval($contact['id'])
2434 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2435 dbesc(notags(trim($new_name))),
2436 dbesc(datetime_convert()),
2437 intval($contact['uid']),
2438 intval($contact['id'])
2441 // do our best to update the name on content items
2444 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2445 dbesc(notags(trim($new_name))),
2446 dbesc($r[0]['name']),
2447 dbesc($r[0]['url']),
2448 intval($contact['uid'])
2453 if ($contact_updated AND $new_name AND $photo_url)
2454 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2456 if(strlen($birthday)) {
2457 if(substr($birthday,0,4) != $contact['bdyear']) {
2458 logger('consume_feed: updating birthday: ' . $birthday);
2462 * Add new birthday event for this person
2464 * $bdtext is just a readable placeholder in case the event is shared
2465 * with others. We will replace it during presentation to our $importer
2466 * to contain a sparkle link and perhaps a photo.
2470 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2471 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2474 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2475 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2476 intval($contact['uid']),
2477 intval($contact['id']),
2478 dbesc(datetime_convert()),
2479 dbesc(datetime_convert()),
2480 dbesc(datetime_convert('UTC','UTC', $birthday)),
2481 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2490 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2491 dbesc(substr($birthday,0,4)),
2492 intval($contact['uid']),
2493 intval($contact['id'])
2496 // This function is called twice without reloading the contact
2497 // Make sure we only create one event. This is why &$contact
2498 // is a reference var in this function
2500 $contact['bdyear'] = substr($birthday,0,4);
2504 $community_page = 0;
2505 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2507 $community_page = intval($rawtags[0]['data']);
2509 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2510 q("update contact set forum = %d where id = %d",
2511 intval($community_page),
2512 intval($contact['id'])
2514 $contact['forum'] = (string) $community_page;
2518 // process any deleted entries
2520 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2521 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2522 foreach($del_entries as $dentry) {
2524 if(isset($dentry['attribs']['']['ref'])) {
2525 $uri = $dentry['attribs']['']['ref'];
2527 if(isset($dentry['attribs']['']['when'])) {
2528 $when = $dentry['attribs']['']['when'];
2529 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2532 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2534 if($deleted && is_array($contact)) {
2535 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2536 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2538 intval($importer['uid']),
2539 intval($contact['id'])
2544 if(! $item['deleted'])
2545 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2547 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2548 $xo = parse_xml_string($item['object'],false);
2549 $xt = parse_xml_string($item['target'],false);
2550 if($xt->type === ACTIVITY_OBJ_NOTE) {
2551 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2553 intval($importer['importer_uid'])
2557 // For tags, the owner cannot remove the tag on the author's copy of the post.
2559 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2560 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2561 $author_copy = (($item['origin']) ? true : false);
2563 if($owner_remove && $author_copy)
2565 if($author_remove || $owner_remove) {
2566 $tags = explode(',',$i[0]['tag']);
2569 foreach($tags as $tag)
2570 if(trim($tag) !== trim($xo->body))
2571 $newtags[] = trim($tag);
2573 q("update item set tag = '%s' where id = %d",
2574 dbesc(implode(',',$newtags)),
2577 create_tags_from_item($i[0]['id']);
2583 if($item['uri'] == $item['parent-uri']) {
2584 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2585 `body` = '', `title` = ''
2586 WHERE `parent-uri` = '%s' AND `uid` = %d",
2588 dbesc(datetime_convert()),
2589 dbesc($item['uri']),
2590 intval($importer['uid'])
2592 create_tags_from_itemuri($item['uri'], $importer['uid']);
2593 create_files_from_itemuri($item['uri'], $importer['uid']);
2594 update_thread_uri($item['uri'], $importer['uid']);
2597 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2598 `body` = '', `title` = ''
2599 WHERE `uri` = '%s' AND `uid` = %d",
2601 dbesc(datetime_convert()),
2603 intval($importer['uid'])
2605 create_tags_from_itemuri($uri, $importer['uid']);
2606 create_files_from_itemuri($uri, $importer['uid']);
2607 if($item['last-child']) {
2608 // ensure that last-child is set in case the comment that had it just got wiped.
2609 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2610 dbesc(datetime_convert()),
2611 dbesc($item['parent-uri']),
2612 intval($item['uid'])
2614 // who is the last child now?
2615 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2616 ORDER BY `created` DESC LIMIT 1",
2617 dbesc($item['parent-uri']),
2618 intval($importer['uid'])
2621 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2632 // Now process the feed
2634 if($feed->get_item_quantity()) {
2636 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2638 // in inverse date order
2640 $items = array_reverse($feed->get_items());
2642 $items = $feed->get_items();
2645 foreach($items as $item) {
2648 $item_id = $item->get_id();
2649 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2650 if(isset($rawthread[0]['attribs']['']['ref'])) {
2652 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2655 if(($is_reply) && is_array($contact)) {
2660 // not allowed to post
2662 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2666 // Have we seen it? If not, import it.
2668 $item_id = $item->get_id();
2669 $datarray = get_atom_elements($feed, $item, $contact);
2671 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2672 $datarray['author-name'] = $contact['name'];
2673 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2674 $datarray['author-link'] = $contact['url'];
2675 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2676 $datarray['author-avatar'] = $contact['thumb'];
2678 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2679 logger('consume_feed: no author information! ' . print_r($datarray,true));
2683 $force_parent = false;
2684 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2685 if($contact['network'] === NETWORK_OSTATUS)
2686 $force_parent = true;
2687 if(strlen($datarray['title']))
2688 unset($datarray['title']);
2689 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2690 dbesc(datetime_convert()),
2692 intval($importer['uid'])
2694 $datarray['last-child'] = 1;
2695 update_thread_uri($parent_uri, $importer['uid']);
2699 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2701 intval($importer['uid'])
2704 // Update content if 'updated' changes
2707 if (edited_timestamp_is_newer($r[0], $datarray)) {
2709 // do not accept (ignore) an earlier edit than one we currently have.
2710 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2713 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2714 dbesc($datarray['title']),
2715 dbesc($datarray['body']),
2716 dbesc($datarray['tag']),
2717 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2718 dbesc(datetime_convert()),
2720 intval($importer['uid'])
2722 create_tags_from_itemuri($item_id, $importer['uid']);
2723 update_thread_uri($item_id, $importer['uid']);
2726 // update last-child if it changes
2728 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2729 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2730 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2731 dbesc(datetime_convert()),
2733 intval($importer['uid'])
2735 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2736 intval($allow[0]['data']),
2737 dbesc(datetime_convert()),
2739 intval($importer['uid'])
2741 update_thread_uri($item_id, $importer['uid']);
2747 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2748 // one way feed - no remote comment ability
2749 $datarray['last-child'] = 0;
2751 $datarray['parent-uri'] = $parent_uri;
2752 $datarray['uid'] = $importer['uid'];
2753 $datarray['contact-id'] = $contact['id'];
2754 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2755 $datarray['type'] = 'activity';
2756 $datarray['gravity'] = GRAVITY_LIKE;
2757 // only one like or dislike per person
2758 // splitted into two queries for performance issues
2759 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2760 intval($datarray['uid']),
2761 intval($datarray['contact-id']),
2762 dbesc($datarray['verb']),
2768 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2769 intval($datarray['uid']),
2770 intval($datarray['contact-id']),
2771 dbesc($datarray['verb']),
2778 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2779 $xo = parse_xml_string($datarray['object'],false);
2780 $xt = parse_xml_string($datarray['target'],false);
2782 if($xt->type == ACTIVITY_OBJ_NOTE) {
2783 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2785 intval($importer['importer_uid'])
2790 // extract tag, if not duplicate, add to parent item
2791 if($xo->id && $xo->content) {
2792 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2793 if(! (stristr($r[0]['tag'],$newtag))) {
2794 q("UPDATE item SET tag = '%s' WHERE id = %d",
2795 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2798 create_tags_from_item($r[0]['id']);
2804 $r = item_store($datarray,$force_parent);
2810 // Head post of a conversation. Have we seen it? If not, import it.
2812 $item_id = $item->get_id();
2814 $datarray = get_atom_elements($feed, $item, $contact);
2816 if(is_array($contact)) {
2817 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2818 $datarray['author-name'] = $contact['name'];
2819 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2820 $datarray['author-link'] = $contact['url'];
2821 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2822 $datarray['author-avatar'] = $contact['thumb'];
2825 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2826 logger('consume_feed: no author information! ' . print_r($datarray,true));
2830 // special handling for events
2832 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2833 $ev = bbtoevent($datarray['body']);
2834 if(x($ev,'desc') && x($ev,'start')) {
2835 $ev['uid'] = $importer['uid'];
2836 $ev['uri'] = $item_id;
2837 $ev['edited'] = $datarray['edited'];
2838 $ev['private'] = $datarray['private'];
2840 if(is_array($contact))
2841 $ev['cid'] = $contact['id'];
2842 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2844 intval($importer['uid'])
2847 $ev['id'] = $r[0]['id'];
2848 $xyz = event_store($ev);
2853 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2854 if(strlen($datarray['title']))
2855 unset($datarray['title']);
2856 $datarray['last-child'] = 1;
2860 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2862 intval($importer['uid'])
2865 // Update content if 'updated' changes
2868 if (edited_timestamp_is_newer($r[0], $datarray)) {
2870 // do not accept (ignore) an earlier edit than one we currently have.
2871 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2874 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2875 dbesc($datarray['title']),
2876 dbesc($datarray['body']),
2877 dbesc($datarray['tag']),
2878 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2879 dbesc(datetime_convert()),
2881 intval($importer['uid'])
2883 create_tags_from_itemuri($item_id, $importer['uid']);
2884 update_thread_uri($item_id, $importer['uid']);
2887 // update last-child if it changes
2889 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2890 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2891 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2892 intval($allow[0]['data']),
2893 dbesc(datetime_convert()),
2895 intval($importer['uid'])
2897 update_thread_uri($item_id, $importer['uid']);
2902 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2903 logger('consume-feed: New follower');
2904 new_follower($importer,$contact,$datarray,$item);
2907 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2908 lose_follower($importer,$contact,$datarray,$item);
2912 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2913 logger('consume-feed: New friend request');
2914 new_follower($importer,$contact,$datarray,$item,true);
2917 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2918 lose_sharer($importer,$contact,$datarray,$item);
2923 if(! is_array($contact))
2927 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2928 // one way feed - no remote comment ability
2929 $datarray['last-child'] = 0;
2931 if($contact['network'] === NETWORK_FEED)
2932 $datarray['private'] = 2;
2934 $datarray['parent-uri'] = $item_id;
2935 $datarray['uid'] = $importer['uid'];
2936 $datarray['contact-id'] = $contact['id'];
2938 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2939 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2940 // but otherwise there's a possible data mixup on the sender's system.
2941 // the tgroup delivery code called from item_store will correct it if it's a forum,
2942 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2943 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2944 $datarray['owner-name'] = $contact['name'];
2945 $datarray['owner-link'] = $contact['url'];
2946 $datarray['owner-avatar'] = $contact['thumb'];
2949 // We've allowed "followers" to reach this point so we can decide if they are
2950 // posting an @-tag delivery, which followers are allowed to do for certain
2951 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2953 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2956 // This is my contact on another system, but it's really me.
2957 // Turn this into a wall post.
2958 $notify = item_is_remote_self($contact, $datarray);
2960 $r = item_store($datarray, false, $notify);
2961 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2969 function item_is_remote_self($contact, &$datarray) {
2972 if (!$contact['remote_self'])
2975 // Prevent the forwarding of posts that are forwarded
2976 if ($datarray["extid"] == NETWORK_DFRN)
2979 // Prevent to forward already forwarded posts
2980 if ($datarray["app"] == $a->get_hostname())
2983 // Only forward posts
2984 if ($datarray["verb"] != ACTIVITY_POST)
2987 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2990 $datarray2 = $datarray;
2991 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2992 if ($contact['remote_self'] == 2) {
2993 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2994 intval($contact['uid']));
2996 $datarray['contact-id'] = $r[0]["id"];
2998 $datarray['owner-name'] = $r[0]["name"];
2999 $datarray['owner-link'] = $r[0]["url"];
3000 $datarray['owner-avatar'] = $r[0]["thumb"];
3002 $datarray['author-name'] = $datarray['owner-name'];
3003 $datarray['author-link'] = $datarray['owner-link'];
3004 $datarray['author-avatar'] = $datarray['owner-avatar'];
3007 if ($contact['network'] != NETWORK_FEED) {
3008 $datarray["guid"] = get_guid(32);
3009 unset($datarray["plink"]);
3010 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3011 $datarray["parent-uri"] = $datarray["uri"];
3012 $datarray["extid"] = $contact['network'];
3013 $urlpart = parse_url($datarray2['author-link']);
3014 $datarray["app"] = $urlpart["host"];
3016 $datarray['private'] = 0;
3019 if ($contact['network'] != NETWORK_FEED) {
3020 // Store the original post
3021 $r = item_store($datarray2, false, false);
3022 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3024 $datarray["app"] = "Feed";
3029 function local_delivery($importer,$data) {
3032 logger(__function__, LOGGER_TRACE);
3034 if($importer['readonly']) {
3035 // We aren't receiving stuff from this person. But we will quietly ignore them
3036 // rather than a blatant "go away" message.
3037 logger('local_delivery: ignoring');
3042 // Consume notification feed. This may differ from consuming a public feed in several ways
3043 // - might contain email or friend suggestions
3044 // - might contain remote followup to our message
3045 // - in which case we need to accept it and then notify other conversants
3046 // - we may need to send various email notifications
3048 $feed = new SimplePie();
3049 $feed->set_raw_data($data);
3050 $feed->enable_order_by_date(false);
3055 logger('local_delivery: Error parsing XML: ' . $feed->error());
3058 // Check at the feed level for updated contact name and/or photo
3062 $photo_timestamp = '';
3064 $contact_updated = '';
3067 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3069 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3071 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3074 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3075 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3076 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3077 $new_name = $elems['name'][0]['data'];
3079 // Manually checking for changed contact names
3080 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3081 $name_updated = date("c");
3082 $photo_timestamp = date("c");
3085 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3086 if ($photo_timestamp == "")
3087 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3088 $photo_url = $elems['link'][0]['attribs']['']['href'];
3092 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3094 $contact_updated = $photo_timestamp;
3096 logger('local_delivery: Updating photo for ' . $importer['name']);
3097 require_once("include/Photo.php");
3098 $photo_failure = false;
3099 $have_photo = false;
3101 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3102 intval($importer['id']),
3103 intval($importer['importer_uid'])
3106 $resource_id = $r[0]['resource-id'];
3110 $resource_id = photo_new_resource();
3113 $img_str = fetch_url($photo_url,true);
3114 // guess mimetype from headers or filename
3115 $type = guess_image_type($photo_url,true);
3118 $img = new Photo($img_str, $type);
3119 if($img->is_valid()) {
3121 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3122 dbesc($resource_id),
3123 intval($importer['id']),
3124 intval($importer['importer_uid'])
3128 $img->scaleImageSquare(175);
3130 $hash = $resource_id;
3131 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3133 $img->scaleImage(80);
3134 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3136 $img->scaleImage(48);
3137 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3141 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3142 WHERE `uid` = %d AND `id` = %d",
3143 dbesc(datetime_convert()),
3144 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3145 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3146 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3147 intval($importer['importer_uid']),
3148 intval($importer['id'])
3153 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3154 if ($name_updated > $contact_updated)
3155 $contact_updated = $name_updated;
3157 $r = q("select * from contact where uid = %d and id = %d limit 1",
3158 intval($importer['importer_uid']),
3159 intval($importer['id'])
3162 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3163 dbesc(notags(trim($new_name))),
3164 dbesc(datetime_convert()),
3165 intval($importer['importer_uid']),
3166 intval($importer['id'])
3169 // do our best to update the name on content items
3172 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3173 dbesc(notags(trim($new_name))),
3174 dbesc($r[0]['name']),
3175 dbesc($r[0]['url']),
3176 intval($importer['importer_uid'])
3181 if ($contact_updated AND $new_name AND $photo_url)
3182 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3184 // Currently unsupported - needs a lot of work
3185 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3186 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3187 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3189 $newloc['uid'] = $importer['importer_uid'];
3190 $newloc['cid'] = $importer['id'];
3191 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3192 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3193 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3194 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3195 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3196 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3197 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3198 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3199 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3200 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3201 /** relocated user must have original key pair */
3202 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3203 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3205 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3208 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3209 intval($importer['id']),
3210 intval($importer['importer_uid']));
3215 $x = q("UPDATE contact SET
3226 `site-pubkey` = '%s'
3227 WHERE id=%d AND uid=%d;",
3228 dbesc($newloc['name']),
3229 dbesc($newloc['photo']),
3230 dbesc($newloc['thumb']),
3231 dbesc($newloc['micro']),
3232 dbesc($newloc['url']),
3233 dbesc(normalise_link($newloc['url'])),
3234 dbesc($newloc['request']),
3235 dbesc($newloc['confirm']),
3236 dbesc($newloc['notify']),
3237 dbesc($newloc['poll']),
3238 dbesc($newloc['sitepubkey']),
3239 intval($importer['id']),
3240 intval($importer['importer_uid']));
3246 'owner-link' => array($old['url'], $newloc['url']),
3247 'author-link' => array($old['url'], $newloc['url']),
3248 'owner-avatar' => array($old['photo'], $newloc['photo']),
3249 'author-avatar' => array($old['photo'], $newloc['photo']),
3251 foreach ($fields as $n=>$f){
3252 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3255 intval($importer['importer_uid']));
3261 // merge with current record, current contents have priority
3262 // update record, set url-updated
3263 // update profile photos
3269 // handle friend suggestion notification
3271 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3272 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3273 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3275 $fsugg['uid'] = $importer['importer_uid'];
3276 $fsugg['cid'] = $importer['id'];
3277 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3278 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3279 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3280 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3281 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3283 // Does our member already have a friend matching this description?
3285 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3286 dbesc($fsugg['name']),
3287 dbesc(normalise_link($fsugg['url'])),
3288 intval($fsugg['uid'])
3293 // Do we already have an fcontact record for this person?
3296 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3297 dbesc($fsugg['url']),
3298 dbesc($fsugg['name']),
3299 dbesc($fsugg['request'])
3304 // OK, we do. Do we already have an introduction for this person ?
3305 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3306 intval($fsugg['uid']),
3313 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3314 dbesc($fsugg['name']),
3315 dbesc($fsugg['url']),
3316 dbesc($fsugg['photo']),
3317 dbesc($fsugg['request'])
3319 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3320 dbesc($fsugg['url']),
3321 dbesc($fsugg['name']),
3322 dbesc($fsugg['request'])
3327 // database record did not get created. Quietly give up.
3332 $hash = random_string();
3334 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3335 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3336 intval($fsugg['uid']),
3338 intval($fsugg['cid']),
3339 dbesc($fsugg['body']),
3341 dbesc(datetime_convert()),
3346 'type' => NOTIFY_SUGGEST,
3347 'notify_flags' => $importer['notify-flags'],
3348 'language' => $importer['language'],
3349 'to_name' => $importer['username'],
3350 'to_email' => $importer['email'],
3351 'uid' => $importer['importer_uid'],
3353 'link' => $a->get_baseurl() . '/notifications/intros',
3354 'source_name' => $importer['name'],
3355 'source_link' => $importer['url'],
3356 'source_photo' => $importer['photo'],
3357 'verb' => ACTIVITY_REQ_FRIEND,
3366 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3367 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3369 logger('local_delivery: private message received');
3372 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3375 $msg['uid'] = $importer['importer_uid'];
3376 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3377 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3378 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3379 $msg['contact-id'] = $importer['id'];
3380 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3381 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3383 $msg['replied'] = 0;
3384 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3385 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3386 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3390 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3391 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3393 // send notifications.
3395 require_once('include/enotify.php');
3397 $notif_params = array(
3398 'type' => NOTIFY_MAIL,
3399 'notify_flags' => $importer['notify-flags'],
3400 'language' => $importer['language'],
3401 'to_name' => $importer['username'],
3402 'to_email' => $importer['email'],
3403 'uid' => $importer['importer_uid'],
3405 'source_name' => $msg['from-name'],
3406 'source_link' => $importer['url'],
3407 'source_photo' => $importer['thumb'],
3408 'verb' => ACTIVITY_POST,
3412 notification($notif_params);
3418 $community_page = 0;
3419 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3421 $community_page = intval($rawtags[0]['data']);
3423 if(intval($importer['forum']) != $community_page) {
3424 q("update contact set forum = %d where id = %d",
3425 intval($community_page),
3426 intval($importer['id'])
3428 $importer['forum'] = (string) $community_page;
3431 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3433 // process any deleted entries
3435 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3436 if(is_array($del_entries) && count($del_entries)) {
3437 foreach($del_entries as $dentry) {
3439 if(isset($dentry['attribs']['']['ref'])) {
3440 $uri = $dentry['attribs']['']['ref'];
3442 if(isset($dentry['attribs']['']['when'])) {
3443 $when = $dentry['attribs']['']['when'];
3444 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3447 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3451 // check for relayed deletes to our conversation
3454 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3456 intval($importer['importer_uid'])
3459 $parent_uri = $r[0]['parent-uri'];
3460 if($r[0]['id'] != $r[0]['parent'])
3467 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3470 logger('local_delivery: possible community delete');
3473 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3475 // was the top-level post for this reply written by somebody on this site?
3476 // Specifically, the recipient?
3478 $is_a_remote_delete = false;
3480 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3481 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3482 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3483 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3484 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3485 AND `item`.`uid` = %d
3491 intval($importer['importer_uid'])
3494 $is_a_remote_delete = true;
3496 // Does this have the characteristics of a community or private group comment?
3497 // If it's a reply to a wall post on a community/prvgroup page it's a
3498 // valid community comment. Also forum_mode makes it valid for sure.
3499 // If neither, it's not.
3501 if($is_a_remote_delete && $community) {
3502 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3503 $is_a_remote_delete = false;
3504 logger('local_delivery: not a community delete');
3508 if($is_a_remote_delete) {
3509 logger('local_delivery: received remote delete');
3513 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3514 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3516 intval($importer['importer_uid']),
3517 intval($importer['id'])
3523 if($item['deleted'])
3526 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3528 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3529 $xo = parse_xml_string($item['object'],false);
3530 $xt = parse_xml_string($item['target'],false);
3532 if($xt->type === ACTIVITY_OBJ_NOTE) {
3533 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3535 intval($importer['importer_uid'])
3539 // For tags, the owner cannot remove the tag on the author's copy of the post.
3541 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3542 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3543 $author_copy = (($item['origin']) ? true : false);
3545 if($owner_remove && $author_copy)
3547 if($author_remove || $owner_remove) {
3548 $tags = explode(',',$i[0]['tag']);
3551 foreach($tags as $tag)
3552 if(trim($tag) !== trim($xo->body))
3553 $newtags[] = trim($tag);
3555 q("update item set tag = '%s' where id = %d",
3556 dbesc(implode(',',$newtags)),
3559 create_tags_from_item($i[0]['id']);
3565 if($item['uri'] == $item['parent-uri']) {
3566 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3567 `body` = '', `title` = ''
3568 WHERE `parent-uri` = '%s' AND `uid` = %d",
3570 dbesc(datetime_convert()),
3571 dbesc($item['uri']),
3572 intval($importer['importer_uid'])
3574 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3575 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3576 update_thread_uri($item['uri'], $importer['importer_uid']);
3579 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3580 `body` = '', `title` = ''
3581 WHERE `uri` = '%s' AND `uid` = %d",
3583 dbesc(datetime_convert()),
3585 intval($importer['importer_uid'])
3587 create_tags_from_itemuri($uri, $importer['importer_uid']);
3588 create_files_from_itemuri($uri, $importer['importer_uid']);
3589 update_thread_uri($uri, $importer['importer_uid']);
3590 if($item['last-child']) {
3591 // ensure that last-child is set in case the comment that had it just got wiped.
3592 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3593 dbesc(datetime_convert()),
3594 dbesc($item['parent-uri']),
3595 intval($item['uid'])
3597 // who is the last child now?
3598 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3599 ORDER BY `created` DESC LIMIT 1",
3600 dbesc($item['parent-uri']),
3601 intval($importer['importer_uid'])
3604 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3609 // if this is a relayed delete, propagate it to other recipients
3611 if($is_a_remote_delete)
3612 proc_run('php',"include/notifier.php","drop",$item['id']);
3620 foreach($feed->get_items() as $item) {
3623 $item_id = $item->get_id();
3624 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3625 if(isset($rawthread[0]['attribs']['']['ref'])) {
3627 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3633 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3636 logger('local_delivery: possible community reply');
3639 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3641 // was the top-level post for this reply written by somebody on this site?
3642 // Specifically, the recipient?
3644 $is_a_remote_comment = false;
3645 $top_uri = $parent_uri;
3647 $r = q("select `item`.`parent-uri` from `item`
3648 WHERE `item`.`uri` = '%s'
3652 if($r && count($r)) {
3653 $top_uri = $r[0]['parent-uri'];
3655 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3656 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3657 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3658 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3659 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3660 AND `item`.`uid` = %d
3666 intval($importer['importer_uid'])
3669 $is_a_remote_comment = true;
3672 // Does this have the characteristics of a community or private group comment?
3673 // If it's a reply to a wall post on a community/prvgroup page it's a
3674 // valid community comment. Also forum_mode makes it valid for sure.
3675 // If neither, it's not.
3677 if($is_a_remote_comment && $community) {
3678 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3679 $is_a_remote_comment = false;
3680 logger('local_delivery: not a community reply');
3684 if($is_a_remote_comment) {
3685 logger('local_delivery: received remote comment');
3687 // remote reply to our post. Import and then notify everybody else.
3689 $datarray = get_atom_elements($feed, $item);
3691 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3693 intval($importer['importer_uid'])
3696 // Update content if 'updated' changes
3700 if (edited_timestamp_is_newer($r[0], $datarray)) {
3702 // do not accept (ignore) an earlier edit than one we currently have.
3703 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3706 logger('received updated comment' , LOGGER_DEBUG);
3707 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3708 dbesc($datarray['title']),
3709 dbesc($datarray['body']),
3710 dbesc($datarray['tag']),
3711 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3712 dbesc(datetime_convert()),
3714 intval($importer['importer_uid'])
3716 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3718 proc_run('php',"include/notifier.php","comment-import",$iid);
3727 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3728 intval($importer['importer_uid'])
3732 $datarray['type'] = 'remote-comment';
3733 $datarray['wall'] = 1;
3734 $datarray['parent-uri'] = $parent_uri;
3735 $datarray['uid'] = $importer['importer_uid'];
3736 $datarray['owner-name'] = $own[0]['name'];
3737 $datarray['owner-link'] = $own[0]['url'];
3738 $datarray['owner-avatar'] = $own[0]['thumb'];
3739 $datarray['contact-id'] = $importer['id'];
3741 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3743 $datarray['type'] = 'activity';
3744 $datarray['gravity'] = GRAVITY_LIKE;
3745 $datarray['last-child'] = 0;
3746 // only one like or dislike per person
3747 // splitted into two queries for performance issues
3748 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3749 intval($datarray['uid']),
3750 intval($datarray['contact-id']),
3751 dbesc($datarray['verb']),
3752 dbesc($datarray['parent-uri'])
3758 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3759 intval($datarray['uid']),
3760 intval($datarray['contact-id']),
3761 dbesc($datarray['verb']),
3762 dbesc($datarray['parent-uri'])
3769 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3771 $xo = parse_xml_string($datarray['object'],false);
3772 $xt = parse_xml_string($datarray['target'],false);
3774 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3776 // fetch the parent item
3778 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3780 intval($importer['importer_uid'])
3785 // extract tag, if not duplicate, and this user allows tags, add to parent item
3787 if($xo->id && $xo->content) {
3788 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3789 if(! (stristr($tagp[0]['tag'],$newtag))) {
3790 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3791 intval($importer['importer_uid'])
3793 if(count($i) && ! intval($i[0]['blocktags'])) {
3794 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3795 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3796 intval($tagp[0]['id']),
3797 dbesc(datetime_convert()),
3798 dbesc(datetime_convert())
3800 create_tags_from_item($tagp[0]['id']);
3808 $posted_id = item_store($datarray);
3813 $datarray["id"] = $posted_id;
3815 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3817 intval($importer['importer_uid'])
3820 $parent = $r[0]['parent'];
3821 $parent_uri = $r[0]['parent-uri'];
3825 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3826 dbesc(datetime_convert()),
3827 intval($importer['importer_uid']),
3828 intval($r[0]['parent'])
3831 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3832 dbesc(datetime_convert()),
3833 intval($importer['importer_uid']),
3838 if($posted_id && $parent) {
3840 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3842 if((! $is_like) && (! $importer['self'])) {
3844 require_once('include/enotify.php');
3847 'type' => NOTIFY_COMMENT,
3848 'notify_flags' => $importer['notify-flags'],
3849 'language' => $importer['language'],
3850 'to_name' => $importer['username'],
3851 'to_email' => $importer['email'],
3852 'uid' => $importer['importer_uid'],
3853 'item' => $datarray,
3854 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3855 'source_name' => stripslashes($datarray['author-name']),
3856 'source_link' => $datarray['author-link'],
3857 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3858 ? $importer['thumb'] : $datarray['author-avatar']),
3859 'verb' => ACTIVITY_POST,
3861 'parent' => $parent,
3862 'parent_uri' => $parent_uri,
3874 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3876 $item_id = $item->get_id();
3877 $datarray = get_atom_elements($feed,$item);
3879 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3882 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3884 intval($importer['importer_uid'])
3887 // Update content if 'updated' changes
3890 if (edited_timestamp_is_newer($r[0], $datarray)) {
3892 // do not accept (ignore) an earlier edit than one we currently have.
3893 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3896 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3897 dbesc($datarray['title']),
3898 dbesc($datarray['body']),
3899 dbesc($datarray['tag']),
3900 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3901 dbesc(datetime_convert()),
3903 intval($importer['importer_uid'])
3905 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3908 // update last-child if it changes
3910 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3911 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3912 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3913 dbesc(datetime_convert()),
3915 intval($importer['importer_uid'])
3917 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3918 intval($allow[0]['data']),
3919 dbesc(datetime_convert()),
3921 intval($importer['importer_uid'])
3927 $datarray['parent-uri'] = $parent_uri;
3928 $datarray['uid'] = $importer['importer_uid'];
3929 $datarray['contact-id'] = $importer['id'];
3930 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3931 $datarray['type'] = 'activity';
3932 $datarray['gravity'] = GRAVITY_LIKE;
3933 // only one like or dislike per person
3934 // splitted into two queries for performance issues
3935 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3936 intval($datarray['uid']),
3937 intval($datarray['contact-id']),
3938 dbesc($datarray['verb']),
3944 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3945 intval($datarray['uid']),
3946 intval($datarray['contact-id']),
3947 dbesc($datarray['verb']),
3955 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3957 $xo = parse_xml_string($datarray['object'],false);
3958 $xt = parse_xml_string($datarray['target'],false);
3960 if($xt->type == ACTIVITY_OBJ_NOTE) {
3961 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3963 intval($importer['importer_uid'])
3968 // extract tag, if not duplicate, add to parent item
3970 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3971 q("UPDATE item SET tag = '%s' WHERE id = %d",
3972 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3975 create_tags_from_item($r[0]['id']);
3981 $posted_id = item_store($datarray);
3983 // find out if our user is involved in this conversation and wants to be notified.
3985 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3987 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3989 intval($importer['importer_uid'])
3992 if(count($myconv)) {
3993 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3995 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3996 if(! link_compare($datarray['author-link'],$importer_url)) {
3999 foreach($myconv as $conv) {
4001 // now if we find a match, it means we're in this conversation
4003 if(! link_compare($conv['author-link'],$importer_url))
4006 require_once('include/enotify.php');
4008 $conv_parent = $conv['parent'];
4011 'type' => NOTIFY_COMMENT,
4012 'notify_flags' => $importer['notify-flags'],
4013 'language' => $importer['language'],
4014 'to_name' => $importer['username'],
4015 'to_email' => $importer['email'],
4016 'uid' => $importer['importer_uid'],
4017 'item' => $datarray,
4018 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4019 'source_name' => stripslashes($datarray['author-name']),
4020 'source_link' => $datarray['author-link'],
4021 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4022 ? $importer['thumb'] : $datarray['author-avatar']),
4023 'verb' => ACTIVITY_POST,
4025 'parent' => $conv_parent,
4026 'parent_uri' => $parent_uri
4030 // only send one notification
4042 // Head post of a conversation. Have we seen it? If not, import it.
4045 $item_id = $item->get_id();
4046 $datarray = get_atom_elements($feed,$item);
4048 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4049 $ev = bbtoevent($datarray['body']);
4050 if(x($ev,'desc') && x($ev,'start')) {
4051 $ev['cid'] = $importer['id'];
4052 $ev['uid'] = $importer['uid'];
4053 $ev['uri'] = $item_id;
4054 $ev['edited'] = $datarray['edited'];
4055 $ev['private'] = $datarray['private'];
4057 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4059 intval($importer['uid'])
4062 $ev['id'] = $r[0]['id'];
4063 $xyz = event_store($ev);
4068 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4070 intval($importer['importer_uid'])
4073 // Update content if 'updated' changes
4076 if (edited_timestamp_is_newer($r[0], $datarray)) {
4078 // do not accept (ignore) an earlier edit than one we currently have.
4079 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4082 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4083 dbesc($datarray['title']),
4084 dbesc($datarray['body']),
4085 dbesc($datarray['tag']),
4086 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4087 dbesc(datetime_convert()),
4089 intval($importer['importer_uid'])
4091 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4092 update_thread_uri($item_id, $importer['importer_uid']);
4095 // update last-child if it changes
4097 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4098 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4099 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4100 intval($allow[0]['data']),
4101 dbesc(datetime_convert()),
4103 intval($importer['importer_uid'])
4109 $datarray['parent-uri'] = $item_id;
4110 $datarray['uid'] = $importer['importer_uid'];
4111 $datarray['contact-id'] = $importer['id'];
4114 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4115 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4116 // but otherwise there's a possible data mixup on the sender's system.
4117 // the tgroup delivery code called from item_store will correct it if it's a forum,
4118 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4119 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4120 $datarray['owner-name'] = $importer['senderName'];
4121 $datarray['owner-link'] = $importer['url'];
4122 $datarray['owner-avatar'] = $importer['thumb'];
4125 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4128 // This is my contact on another system, but it's really me.
4129 // Turn this into a wall post.
4130 $notify = item_is_remote_self($importer, $datarray);
4132 $posted_id = item_store($datarray, false, $notify);
4134 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4135 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4138 $xo = parse_xml_string($datarray['object'],false);
4140 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4142 // somebody was poked/prodded. Was it me?
4144 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4146 foreach($links->link as $l) {
4147 $atts = $l->attributes();
4148 switch($atts['rel']) {
4150 $Blink = $atts['href'];
4156 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4158 // send a notification
4159 require_once('include/enotify.php');
4162 'type' => NOTIFY_POKE,
4163 'notify_flags' => $importer['notify-flags'],
4164 'language' => $importer['language'],
4165 'to_name' => $importer['username'],
4166 'to_email' => $importer['email'],
4167 'uid' => $importer['importer_uid'],
4168 'item' => $datarray,
4169 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4170 'source_name' => stripslashes($datarray['author-name']),
4171 'source_link' => $datarray['author-link'],
4172 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4173 ? $importer['thumb'] : $datarray['author-avatar']),
4174 'verb' => $datarray['verb'],
4175 'otype' => 'person',
4176 'activity' => $verb,
4177 'parent' => $datarray['parent']
4193 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4194 $url = notags(trim($datarray['author-link']));
4195 $name = notags(trim($datarray['author-name']));
4196 $photo = notags(trim($datarray['author-avatar']));
4198 if (is_object($item)) {
4199 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4200 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4201 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4205 if(is_array($contact)) {
4206 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4207 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4208 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4209 intval(CONTACT_IS_FRIEND),
4210 intval($contact['id']),
4211 intval($importer['uid'])
4214 // send email notification to owner?
4218 // create contact record
4220 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4221 `blocked`, `readonly`, `pending`, `writable` )
4222 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4223 intval($importer['uid']),
4224 dbesc(datetime_convert()),
4226 dbesc(normalise_link($url)),
4230 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4231 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4233 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4234 intval($importer['uid']),
4238 $contact_record = $r[0];
4240 // create notification
4241 $hash = random_string();
4243 if(is_array($contact_record)) {
4244 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4245 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4246 intval($importer['uid']),
4247 intval($contact_record['id']),
4249 dbesc(datetime_convert())
4253 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4254 intval($importer['uid'])
4259 if(intval($r[0]['def_gid'])) {
4260 require_once('include/group.php');
4261 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4264 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4265 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4268 'type' => NOTIFY_INTRO,
4269 'notify_flags' => $r[0]['notify-flags'],
4270 'language' => $r[0]['language'],
4271 'to_name' => $r[0]['username'],
4272 'to_email' => $r[0]['email'],
4273 'uid' => $r[0]['uid'],
4274 'link' => $a->get_baseurl() . '/notifications/intro',
4275 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4276 'source_link' => $contact_record['url'],
4277 'source_photo' => $contact_record['photo'],
4278 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4287 function lose_follower($importer,$contact,$datarray,$item) {
4289 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4290 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4291 intval(CONTACT_IS_SHARING),
4292 intval($contact['id'])
4296 contact_remove($contact['id']);
4300 function lose_sharer($importer,$contact,$datarray,$item) {
4302 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4303 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4304 intval(CONTACT_IS_FOLLOWER),
4305 intval($contact['id'])
4309 contact_remove($contact['id']);
4314 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4318 if(is_array($importer)) {
4319 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4320 intval($importer['uid'])
4324 // Diaspora has different message-ids in feeds than they do
4325 // through the direct Diaspora protocol. If we try and use
4326 // the feed, we'll get duplicates. So don't.
4328 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4331 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4333 // Use a single verify token, even if multiple hubs
4335 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4337 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4339 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4341 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4342 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4343 dbesc($verify_token),
4344 intval($contact['id'])
4348 post_url($url,$params);
4350 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4357 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4361 $name = xmlify($name);
4362 $uri = xmlify($uri);
4365 $photo = xmlify($photo);
4369 $o .= "\t<name>$name</name>\r\n";
4370 $o .= "\t<uri>$uri</uri>\r\n";
4371 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4372 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4374 if ($tag == "author") {
4375 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4376 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4377 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4378 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4379 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4380 WHERE `profile`.`is-default` AND `contact`.`self` AND
4381 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4382 dbesc(normalise_link($uri)));
4385 if($r[0]['locality'])
4386 $location .= $r[0]['locality'];
4387 if($r[0]['region']) {
4390 $location .= $r[0]['region'];
4392 if($r[0]['country-name']) {
4395 $location .= $r[0]['country-name'];
4398 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4399 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4400 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4401 $o .= "\t<poco:address>\r\n";
4402 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4403 $o .= "\t</poco:address>\r\n";
4404 $o .= "\t<poco:urls>\r\n";
4405 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4406 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4407 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4408 $o .= "\t</poco:urls>\r\n";
4412 call_hooks('atom_author', $o);
4414 $o .= "</$tag>\r\n";
4418 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4422 if(! $item['parent'])
4425 if($item['deleted'])
4426 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4429 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4430 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4432 $body = $item['body'];
4435 $o = "\r\n\r\n<entry>\r\n";
4437 if(is_array($author))
4438 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4440 $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']));
4441 if(strlen($item['owner-name']))
4442 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4444 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4445 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4446 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4447 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4452 if ($item['title'] != "")
4453 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4455 $htmlbody = bbcode($htmlbody, false, false, 7);
4457 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4458 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4459 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4460 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4461 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4462 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4463 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4465 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4468 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4470 if($item['location']) {
4471 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4472 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4476 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4478 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4479 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4482 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4483 if($item['bookmark'])
4484 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4487 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4490 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4492 if($item['signed_text']) {
4493 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4494 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4497 $verb = construct_verb($item);
4498 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4499 $actobj = construct_activity_object($item);
4502 $actarg = construct_activity_target($item);
4506 $tags = item_getfeedtags($item);
4508 foreach($tags as $t)
4509 if (($type != 'html') OR ($t[0] != "@"))
4510 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4514 // To support these elements, the API needs to be enhanced
4515 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4516 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4517 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4519 $o .= item_get_attachment($item);
4521 $o .= item_getfeedattach($item);
4523 $mentioned = get_mentions($item);
4527 call_hooks('atom_entry', $o);
4529 $o .= '</entry>' . "\r\n";
4534 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4536 if(get_config('system','disable_embedded'))
4541 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4542 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4547 $img_start = strpos($orig_body, '[img');
4548 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4549 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4550 while( ($img_st_close !== false) && ($img_len !== false) ) {
4552 $img_st_close++; // make it point to AFTER the closing bracket
4553 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4555 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4558 if(stristr($image , $site . '/photo/')) {
4559 // Only embed locally hosted photos
4561 $i = basename($image);
4562 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4563 $x = strpos($i,'-');
4566 $res = substr($i,$x+1);
4567 $i = substr($i,0,$x);
4568 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4575 // Check to see if we should replace this photo link with an embedded image
4576 // 1. No need to do so if the photo is public
4577 // 2. If there's a contact-id provided, see if they're in the access list
4578 // for the photo. If so, embed it.
4579 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4580 // permissions, regardless of order but first check to see if they're an exact
4581 // match to save some processing overhead.
4583 if(has_permissions($r[0])) {
4585 $recips = enumerate_permissions($r[0]);
4586 if(in_array($cid, $recips)) {
4591 if(compare_permissions($item,$r[0]))
4596 $data = $r[0]['data'];
4597 $type = $r[0]['type'];
4599 // If a custom width and height were specified, apply before embedding
4600 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4601 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4603 $width = intval($match[1]);
4604 $height = intval($match[2]);
4606 $ph = new Photo($data, $type);
4607 if($ph->is_valid()) {
4608 $ph->scaleImage(max($width, $height));
4609 $data = $ph->imageString();
4610 $type = $ph->getType();
4614 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4615 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4616 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4622 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4623 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4624 if($orig_body === false)
4627 $img_start = strpos($orig_body, '[img');
4628 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4629 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4632 $new_body = $new_body . $orig_body;
4638 function has_permissions($obj) {
4639 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4644 function compare_permissions($obj1,$obj2) {
4645 // first part is easy. Check that these are exactly the same.
4646 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4647 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4648 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4649 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4652 // This is harder. Parse all the permissions and compare the resulting set.
4654 $recipients1 = enumerate_permissions($obj1);
4655 $recipients2 = enumerate_permissions($obj2);
4658 if($recipients1 == $recipients2)
4663 // returns an array of contact-ids that are allowed to see this object
4665 function enumerate_permissions($obj) {
4666 require_once('include/group.php');
4667 $allow_people = expand_acl($obj['allow_cid']);
4668 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4669 $deny_people = expand_acl($obj['deny_cid']);
4670 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4671 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4672 $deny = array_unique(array_merge($deny_people,$deny_groups));
4673 $recipients = array_diff($recipients,$deny);
4677 function item_getfeedtags($item) {
4680 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4682 for($x = 0; $x < $cnt; $x ++) {
4684 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4688 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4690 for($x = 0; $x < $cnt; $x ++) {
4692 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4698 function item_get_attachment($item) {
4700 $siteinfo = get_attached_data($item["body"]);
4702 switch($siteinfo["type"]) {
4704 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4707 $imgdata = get_photo_info($siteinfo["image"]);
4708 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4711 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4720 function item_getfeedattach($item) {
4722 $arr = explode('[/attach],',$item['attach']);
4724 foreach($arr as $r) {
4726 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4728 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4729 if(intval($matches[2]))
4730 $ret .= 'length="' . intval($matches[2]) . '" ';
4731 if($matches[4] !== ' ')
4732 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4733 $ret .= ' />' . "\r\n";
4742 function item_expire($uid, $days, $network = "", $force = false) {
4744 if((! $uid) || ($days < 1))
4747 // $expire_network_only = save your own wall posts
4748 // and just expire conversations started by others
4750 $expire_network_only = get_pconfig($uid,'expire','network_only');
4751 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4753 if ($network != "") {
4754 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4755 // There is an index "uid_network_received" but not "uid_network_created"
4756 // This avoids the creation of another index just for one purpose.
4757 // And it doesn't really matter wether to look at "received" or "created"
4758 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4760 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4762 $r = q("SELECT * FROM `item`
4763 WHERE `uid` = %d $range
4774 $expire_items = get_pconfig($uid, 'expire','items');
4775 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4777 // Forcing expiring of items - but not notes and marked items
4779 $expire_items = true;
4781 $expire_notes = get_pconfig($uid, 'expire','notes');
4782 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4784 $expire_starred = get_pconfig($uid, 'expire','starred');
4785 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4787 $expire_photos = get_pconfig($uid, 'expire','photos');
4788 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4790 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4792 foreach($r as $item) {
4794 // don't expire filed items
4796 if(strpos($item['file'],'[') !== false)
4799 // Only expire posts, not photos and photo comments
4801 if($expire_photos==0 && strlen($item['resource-id']))
4803 if($expire_starred==0 && intval($item['starred']))
4805 if($expire_notes==0 && $item['type']=='note')
4807 if($expire_items==0 && $item['type']!='note')
4810 drop_item($item['id'],false);
4813 proc_run('php',"include/notifier.php","expire","$uid");
4818 function drop_items($items) {
4821 if(! local_user() && ! remote_user())
4825 foreach($items as $item) {
4826 $owner = drop_item($item,false);
4827 if($owner && ! $uid)
4832 // multiple threads may have been deleted, send an expire notification
4835 proc_run('php',"include/notifier.php","expire","$uid");
4839 function drop_item($id,$interactive = true) {
4843 // locate item to be deleted
4845 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4852 notice( t('Item not found.') . EOL);
4853 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4858 $owner = $item['uid'];
4862 // check if logged in user is either the author or owner of this item
4864 if(is_array($_SESSION['remote'])) {
4865 foreach($_SESSION['remote'] as $visitor) {
4866 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4867 $cid = $visitor['cid'];
4874 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4876 // Check if we should do HTML-based delete confirmation
4877 if($_REQUEST['confirm']) {
4878 // <form> can't take arguments in its "action" parameter
4879 // so add any arguments as hidden inputs
4880 $query = explode_querystring($a->query_string);
4882 foreach($query['args'] as $arg) {
4883 if(strpos($arg, 'confirm=') === false) {
4884 $arg_parts = explode('=', $arg);
4885 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4889 return replace_macros(get_markup_template('confirm.tpl'), array(
4891 '$message' => t('Do you really want to delete this item?'),
4892 '$extra_inputs' => $inputs,
4893 '$confirm' => t('Yes'),
4894 '$confirm_url' => $query['base'],
4895 '$confirm_name' => 'confirmed',
4896 '$cancel' => t('Cancel'),
4899 // Now check how the user responded to the confirmation query
4900 if($_REQUEST['canceled']) {
4901 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4904 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4907 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4908 dbesc(datetime_convert()),
4909 dbesc(datetime_convert()),
4912 create_tags_from_item($item['id']);
4913 create_files_from_item($item['id']);
4914 delete_thread($item['id'], $item['parent-uri']);
4916 // clean up categories and tags so they don't end up as orphans
4919 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4921 foreach($matches as $mtch) {
4922 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4928 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4930 foreach($matches as $mtch) {
4931 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4935 // If item is a link to a photo resource, nuke all the associated photos
4936 // (visitors will not have photo resources)
4937 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4938 // generate a resource-id and therefore aren't intimately linked to the item.
4940 if(strlen($item['resource-id'])) {
4941 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4942 dbesc($item['resource-id']),
4943 intval($item['uid'])
4945 // ignore the result
4948 // If item is a link to an event, nuke the event record.
4950 if(intval($item['event-id'])) {
4951 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4952 intval($item['event-id']),
4953 intval($item['uid'])
4955 // ignore the result
4958 // If item has attachments, drop them
4960 foreach(explode(",",$item['attach']) as $attach){
4961 preg_match("|attach/(\d+)|", $attach, $matches);
4962 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4963 intval($matches[1]),
4966 // ignore the result
4970 // clean up item_id and sign meta-data tables
4973 // Old code - caused very long queries and warning entries in the mysql logfiles:
4975 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4976 intval($item['id']),
4977 intval($item['uid'])
4980 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4981 intval($item['id']),
4982 intval($item['uid'])
4986 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4988 // Creating list of parents
4989 $r = q("select id from item where parent = %d and uid = %d",
4990 intval($item['id']),
4991 intval($item['uid'])
4996 foreach ($r AS $row) {
4997 if ($parentid != "")
5000 $parentid .= $row["id"];
5004 if ($parentid != "") {
5005 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5007 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5010 // If it's the parent of a comment thread, kill all the kids
5012 if($item['uri'] == $item['parent-uri']) {
5013 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5014 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5015 dbesc(datetime_convert()),
5016 dbesc(datetime_convert()),
5017 dbesc($item['parent-uri']),
5018 intval($item['uid'])
5020 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5021 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5022 delete_thread_uri($item['parent-uri'], $item['uid']);
5023 // ignore the result
5026 // ensure that last-child is set in case the comment that had it just got wiped.
5027 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5028 dbesc(datetime_convert()),
5029 dbesc($item['parent-uri']),
5030 intval($item['uid'])
5032 // who is the last child now?
5033 $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",
5034 dbesc($item['parent-uri']),
5035 intval($item['uid'])
5038 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5043 // Add a relayable_retraction signature for Diaspora.
5044 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5047 $drop_id = intval($item['id']);
5049 // send the notification upstream/downstream as the case may be
5051 proc_run('php',"include/notifier.php","drop","$drop_id");
5055 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5061 notice( t('Permission denied.') . EOL);
5062 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5069 function first_post_date($uid,$wall = false) {
5070 $r = q("select id, created from item
5071 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5073 order by created asc limit 1",
5075 intval($wall ? 1 : 0)
5078 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5079 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5084 /* modified posted_dates() {below} to arrange the list in years */
5085 function list_post_dates($uid, $wall) {
5086 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5088 $dthen = first_post_date($uid, $wall);
5092 // Set the start and end date to the beginning of the month
5093 $dnow = substr($dnow,0,8).'01';
5094 $dthen = substr($dthen,0,8).'01';
5098 // Starting with the current month, get the first and last days of every
5099 // month down to and including the month of the first post
5100 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5101 $dyear = intval(substr($dnow,0,4));
5102 $dstart = substr($dnow,0,8) . '01';
5103 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5104 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5105 $end_month = datetime_convert('','',$dend,'Y-m-d');
5106 $str = day_translate(datetime_convert('','',$dnow,'F'));
5108 $ret[$dyear] = array();
5109 $ret[$dyear][] = array($str,$end_month,$start_month);
5110 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5115 function posted_dates($uid,$wall) {
5116 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5118 $dthen = first_post_date($uid,$wall);
5122 // Set the start and end date to the beginning of the month
5123 $dnow = substr($dnow,0,8).'01';
5124 $dthen = substr($dthen,0,8).'01';
5127 // Starting with the current month, get the first and last days of every
5128 // month down to and including the month of the first post
5129 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5130 $dstart = substr($dnow,0,8) . '01';
5131 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5132 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5133 $end_month = datetime_convert('','',$dend,'Y-m-d');
5134 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5135 $ret[] = array($str,$end_month,$start_month);
5136 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5142 function posted_date_widget($url,$uid,$wall) {
5145 if(! feature_enabled($uid,'archives'))
5148 // For former Facebook folks that left because of "timeline"
5150 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5153 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5154 if(! $visible_years)
5157 $ret = list_post_dates($uid,$wall);
5162 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5163 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5165 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5166 '$title' => t('Archives'),
5167 '$size' => $visible_years,
5168 '$cutoff_year' => $cutoff_year,
5169 '$cutoff' => $cutoff,
5172 '$showmore' => t('show more')
5178 function store_diaspora_retract_sig($item, $user, $baseurl) {
5179 // Note that we can't add a target_author_signature
5180 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5181 // the comment, that means we're the home of the post, and Diaspora will only
5182 // check the parent_author_signature of retractions that it doesn't have to relay further
5184 // I don't think this function gets called for an "unlike," but I'll check anyway
5186 $enabled = intval(get_config('system','diaspora_enabled'));
5188 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5192 logger('drop_item: storing diaspora retraction signature');
5194 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5196 if(local_user() == $item['uid']) {
5198 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5199 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5202 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5203 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5206 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5207 // only handles DFRN deletes
5208 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5209 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5210 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5216 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5217 intval($item['id']),
5218 dbesc($signed_text),