3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15 require_once('mod/share.php');
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
20 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21 $public_feed = (($dfrn_id) ? false : true);
22 $starred = false; // not yet implemented, possible security issues
25 if($public_feed && $a->argc > 2) {
26 for($x = 2; $x < $a->argc; $x++) {
27 if($a->argv[$x] == 'converse')
29 if($a->argv[$x] == 'starred')
31 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32 $category = $a->argv[$x+1];
38 // default permissions - anonymous user
40 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
42 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
52 $owner_id = $owner['user_uid'];
53 $owner_nick = $owner['nickname'];
55 $birthday = feed_birthday($owner_id,$owner['timezone']);
64 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
68 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '1:' . $dfrn_id;
72 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
73 $my_id = '0:' . $dfrn_id;
80 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
88 require_once('include/security.php');
89 $groups = init_groups_visitor($contact['id']);
92 for($x = 0; $x < count($groups); $x ++)
93 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
94 $gs = implode('|', $groups);
97 $gs = '<<>>' ; // Impossible to match
99 $sql_extra = sprintf("
100 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
101 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
102 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
103 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
105 intval($contact['id']),
106 intval($contact['id']),
117 if(! strlen($last_update))
118 $last_update = 'now -30 days';
120 if(isset($category)) {
121 $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` ",
122 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
123 //$sql_extra .= file_tag_file_query('item',$category,'category');
128 $sql_extra .= " AND `contact`.`self` = 1 ";
131 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
133 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
134 // dbesc($check_date),
136 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
137 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
138 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
139 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
140 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
141 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
142 FROM `item` $sql_post_table
143 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
144 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
145 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
146 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
147 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
149 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
155 // Will check further below if this actually returned results.
156 // We will provide an empty feed if that is the case.
160 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
164 $hubxml = feed_hublinks();
166 $salmon = feed_salmonlinks($owner_nick);
168 $alternatelink = $owner['url'];
171 $alternatelink .= "/category/".$category;
173 $atom .= replace_macros($feed_template, array(
174 '$version' => xmlify(FRIENDICA_VERSION),
175 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
176 '$feed_title' => xmlify($owner['name']),
177 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
179 '$salmon' => $salmon,
180 '$alternatelink' => xmlify($alternatelink),
181 '$name' => xmlify($owner['name']),
182 '$profile_page' => xmlify($owner['url']),
183 '$photo' => xmlify($owner['photo']),
184 '$thumb' => xmlify($owner['thumb']),
185 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
186 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
187 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
188 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
189 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
192 call_hooks('atom_feed', $atom);
194 if(! count($items)) {
196 call_hooks('atom_feed_end', $atom);
198 $atom .= '</feed>' . "\r\n";
202 foreach($items as $item) {
204 // prevent private email from leaking.
205 if($item['network'] === NETWORK_MAIL)
208 // public feeds get html, our own nodes use bbcode
212 // catch any email that's in a public conversation and make sure it doesn't leak
220 $atom .= atom_entry($item,$type,null,$owner,true);
223 call_hooks('atom_feed_end', $atom);
225 $atom .= '</feed>' . "\r\n";
231 function construct_verb($item) {
233 return $item['verb'];
234 return ACTIVITY_POST;
237 function construct_activity_object($item) {
239 if($item['object']) {
240 $o = '<as:object>' . "\r\n";
241 $r = parse_xml_string($item['object'],false);
247 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
249 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
251 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
253 if(substr($r->link,0,1) === '<') {
254 // patch up some facebook "like" activity objects that got stored incorrectly
255 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
256 // we can probably remove this hack here and in the following function in a few months time.
257 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
258 $r->link = str_replace('&','&', $r->link);
259 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
263 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
266 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
267 $o .= '</as:object>' . "\r\n";
274 function construct_activity_target($item) {
276 if($item['target']) {
277 $o = '<as:target>' . "\r\n";
278 $r = parse_xml_string($item['target'],false);
282 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
284 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
286 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
288 if(substr($r->link,0,1) === '<') {
289 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
290 $r->link = str_replace('&','&', $r->link);
291 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
295 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
298 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
299 $o .= '</as:target>' . "\r\n";
308 * The purpose of this function is to apply system message length limits to
309 * imported messages without including any embedded photos in the length
311 if(! function_exists('limit_body_size')) {
312 function limit_body_size($body) {
314 // logger('limit_body_size: start', LOGGER_DEBUG);
316 $maxlen = get_max_import_size();
318 // If the length of the body, including the embedded images, is smaller
319 // than the maximum, then don't waste time looking for the images
320 if($maxlen && (strlen($body) > $maxlen)) {
322 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
329 $img_start = strpos($orig_body, '[img');
330 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
331 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
332 while(($img_st_close !== false) && ($img_end !== false)) {
334 $img_st_close++; // make it point to AFTER the closing bracket
335 $img_end += $img_start;
336 $img_end += strlen('[/img]');
338 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
339 // This is an embedded image
341 if( ($textlen + $img_start) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_start);
350 $textlen += $img_start;
353 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
357 if( ($textlen + $img_end) > $maxlen ) {
358 if($textlen < $maxlen) {
359 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
360 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
365 $new_body = $new_body . substr($orig_body, 0, $img_end);
366 $textlen += $img_end;
369 $orig_body = substr($orig_body, $img_end);
371 if($orig_body === false) // in case the body ends on a closing image tag
374 $img_start = strpos($orig_body, '[img');
375 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
376 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
379 if( ($textlen + strlen($orig_body)) > $maxlen) {
380 if($textlen < $maxlen) {
381 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
382 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
387 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
388 $new_body = $new_body . $orig_body;
389 $textlen += strlen($orig_body);
398 function title_is_body($title, $body) {
400 $title = strip_tags($title);
401 $title = trim($title);
402 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
403 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
405 $body = strip_tags($body);
407 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
408 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
410 if (strlen($title) < strlen($body))
411 $body = substr($body, 0, strlen($title));
413 if (($title != $body) and (substr($title, -3) == "...")) {
414 $pos = strrpos($title, "...");
416 $title = substr($title, 0, $pos);
417 $body = substr($body, 0, $pos);
421 return($title == $body);
426 function get_atom_elements($feed, $item, $contact = array()) {
428 require_once('library/HTMLPurifier.auto.php');
429 require_once('include/html2bbcode.php');
431 $best_photo = array();
435 $author = $item->get_author();
437 $res['author-name'] = unxmlify($author->get_name());
438 $res['author-link'] = unxmlify($author->get_link());
441 $res['author-name'] = unxmlify($feed->get_title());
442 $res['author-link'] = unxmlify($feed->get_permalink());
444 $res['uri'] = unxmlify($item->get_id());
445 $res['title'] = unxmlify($item->get_title());
446 $res['body'] = unxmlify($item->get_content());
447 $res['plink'] = unxmlify($item->get_link(0));
449 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
450 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
452 $res['body'] = nl2br($res['body']);
455 // removing the content of the title if its identically to the body
456 // This helps with auto generated titles e.g. from tumblr
457 if (title_is_body($res["title"], $res["body"]))
461 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
465 // look for a photo. We should check media size and find the best one,
466 // but for now let's just find any author photo
468 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
470 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
471 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
472 foreach($base as $link) {
473 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
474 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
475 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
480 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
482 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
483 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 if($base && count($base)) {
485 foreach($base as $link) {
486 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
487 $res['author-link'] = unxmlify($link['attribs']['']['href']);
488 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
489 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
490 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
496 // No photo/profile-link on the item - look at the feed level
498 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
499 $rawauthor = $feed->get_feed_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' && (! $res['author-link']))
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(! $res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $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'))) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
530 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
531 if($apps && $apps[0]['attribs']['']['source']) {
532 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
533 if($res['app'] === 'web')
534 $res['app'] = 'OStatus';
537 // base64 encoded json structure representing Diaspora signature
539 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
541 $res['dsprsig'] = unxmlify($dsig[0]['data']);
544 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
546 $res['guid'] = unxmlify($dguid[0]['data']);
548 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
550 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
554 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
557 $have_real_body = false;
559 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
561 $have_real_body = true;
562 $res['body'] = $rawenv[0]['data'];
563 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
564 // make sure nobody is trying to sneak some html tags by us
565 $res['body'] = notags(base64url_decode($res['body']));
569 $res['body'] = limit_body_size($res['body']);
571 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
572 // the content type. Our own network only emits text normally, though it might have been converted to
573 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
574 // have to assume it is all html and needs to be purified.
576 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
577 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
578 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
581 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
583 $res['body'] = reltoabs($res['body'],$base_url);
585 $res['body'] = html2bb_video($res['body']);
587 $res['body'] = oembed_html2bbcode($res['body']);
589 $config = HTMLPurifier_Config::createDefault();
590 $config->set('Cache.DefinitionImpl', null);
592 // we shouldn't need a whitelist, because the bbcode converter
593 // will strip out any unsupported tags.
595 $purifier = new HTMLPurifier($config);
596 $res['body'] = $purifier->purify($res['body']);
598 $res['body'] = @html2bbcode($res['body']);
602 elseif(! $have_real_body) {
604 // it's not one of our messages and it has no tags
605 // so it's probably just text. We'll escape it just to be safe.
607 $res['body'] = escape_tags($res['body']);
611 // this tag is obsolete but we keep it for really old sites
613 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
614 if($allow && $allow[0]['data'] == 1)
615 $res['last-child'] = 1;
617 $res['last-child'] = 0;
619 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
620 if($private && intval($private[0]['data']) > 0)
621 $res['private'] = intval($private[0]['data']);
625 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
626 if($extid && $extid[0]['data'])
627 $res['extid'] = $extid[0]['data'];
629 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
631 $res['location'] = unxmlify($rawlocation[0]['data']);
634 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
636 $res['created'] = unxmlify($rawcreated[0]['data']);
639 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
641 $res['edited'] = unxmlify($rawedited[0]['data']);
643 if((x($res,'edited')) && (! (x($res,'created'))))
644 $res['created'] = $res['edited'];
646 if(! $res['created'])
647 $res['created'] = $item->get_date('c');
650 $res['edited'] = $item->get_date('c');
653 // Disallow time travelling posts
655 $d1 = strtotime($res['created']);
656 $d2 = strtotime($res['edited']);
657 $d3 = strtotime('now');
660 $res['created'] = datetime_convert();
662 $res['edited'] = datetime_convert();
664 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
665 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
667 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
668 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
669 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
671 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
672 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
674 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
675 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
677 foreach($base as $link) {
678 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
679 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
680 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
685 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
687 $res['coord'] = unxmlify($rawgeo[0]['data']);
689 if ($contact["network"] == NETWORK_FEED) {
690 $res['verb'] = ACTIVITY_POST;
691 $res['object-type'] = ACTIVITY_OBJ_NOTE;
694 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
696 // select between supported verbs
699 $res['verb'] = unxmlify($rawverb[0]['data']);
702 // translate OStatus unfollow to activity streams if it happened to get selected
704 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
705 $res['verb'] = ACTIVITY_UNFOLLOW;
707 $cats = $item->get_categories();
710 foreach($cats as $cat) {
711 $term = $cat->get_term();
713 $term = $cat->get_label();
714 $scheme = $cat->get_scheme();
715 if($scheme && $term && stristr($scheme,'X-DFRN:'))
716 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
718 $tag_arr[] = notags(trim($term));
720 $res['tag'] = implode(',', $tag_arr);
723 $attach = $item->get_enclosures();
726 foreach($attach as $att) {
727 $len = intval($att->get_length());
728 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
729 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
730 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
731 if(strpos($type,';'))
732 $type = substr($type,0,strpos($type,';'));
733 if((! $link) || (strpos($link,'http') !== 0))
739 $type = 'application/octet-stream';
741 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
743 $res['attach'] = implode(',', $att_arr);
746 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
749 $res['object'] = '<object>' . "\n";
750 $child = $rawobj[0]['child'];
751 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
752 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
753 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
756 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
758 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
760 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
761 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
764 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
765 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
766 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
767 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
769 $body = html2bb_video($body);
771 $config = HTMLPurifier_Config::createDefault();
772 $config->set('Cache.DefinitionImpl', null);
774 $purifier = new HTMLPurifier($config);
775 $body = $purifier->purify($body);
776 $body = html2bbcode($body);
779 $res['object'] .= '<content>' . $body . '</content>' . "\n";
782 $res['object'] .= '</object>' . "\n";
785 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
788 $res['target'] = '<target>' . "\n";
789 $child = $rawobj[0]['child'];
790 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
791 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
794 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
796 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
798 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
799 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
802 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
803 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
804 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
805 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
807 $body = html2bb_video($body);
809 $config = HTMLPurifier_Config::createDefault();
810 $config->set('Cache.DefinitionImpl', null);
812 $purifier = new HTMLPurifier($config);
813 $body = $purifier->purify($body);
814 $body = html2bbcode($body);
817 $res['target'] .= '<content>' . $body . '</content>' . "\n";
820 $res['target'] .= '</target>' . "\n";
823 // This is some experimental stuff. By now retweets are shown with "RT:"
824 // But: There is data so that the message could be shown similar to native retweets
825 // There is some better way to parse this array - but it didn't worked for me.
826 $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"];
827 if (is_array($child)) {
828 logger('get_atom_elements: Looking for status.net repeated message');
830 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
831 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
832 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
833 $uri = $author["uri"][0]["data"];
834 $name = $author["name"][0]["data"];
835 $avatar = @array_shift($author["link"][2]["attribs"]);
836 $avatar = $avatar["href"];
838 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
839 logger('get_atom_elements: fixing sender of repeated message.');
841 if (!intval(get_config('system','wall-to-wall_share'))) {
842 $prefix = share_header($name, $uri, $avatar, "", "", $orig_uri);
844 $res["body"] = $prefix.html2bbcode($message)."[/share]";
846 $res["owner-name"] = $res["author-name"];
847 $res["owner-link"] = $res["author-link"];
848 $res["owner-avatar"] = $res["author-avatar"];
850 $res["author-name"] = $name;
851 $res["author-link"] = $uri;
852 $res["author-avatar"] = $avatar;
854 $res["body"] = html2bbcode($message);
859 // Search for ostatus conversation url
860 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
862 if (is_array($links)) {
863 foreach ($links as $link) {
864 $conversation = array_shift($link["attribs"]);
866 if ($conversation["rel"] == "ostatus:conversation") {
867 $res["ostatus_conversation"] = ostatus_convert_href($conversation["href"]);
868 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
873 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
876 // Handle enclosures and treat them as preview picture
878 foreach ($attach AS $attachment)
879 if ($attachment->type == "image/jpeg")
880 $preview = $attachment->link;
882 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
883 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
885 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
886 unset($res["attach"]);
887 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
888 $res["body"] = add_page_info_to_body($res["body"]);
889 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
890 $res["body"] = add_page_info_to_body($res["body"]);
893 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
895 call_hooks('parse_atom', $arr);
900 function add_page_info_data($data) {
901 call_hooks('page_info_data', $data);
903 // It maybe is a rich content, but if it does have everything that a link has,
904 // then treat it that way
905 if (($data["type"] == "rich") AND is_string($data["title"]) AND
906 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
907 $data["type"] = "link";
909 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
912 if ($no_photos AND ($data["type"] == "photo"))
915 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
916 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
917 require_once("include/network.php");
918 $data["url"] = short_link($data["url"]);
921 if (($data["type"] != "photo") AND is_string($data["title"]))
922 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
924 if (($data["type"] != "video") AND ($photo != ""))
925 $text .= '[img]'.$photo.'[/img]';
926 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
927 $imagedata = $data["images"][0];
928 $text .= '[img]'.$imagedata["src"].'[/img]';
931 if (($data["type"] != "photo") AND is_string($data["text"]))
932 $text .= "[quote]".$data["text"]."[/quote]";
935 if (isset($data["keywords"]) AND count($data["keywords"])) {
938 foreach ($data["keywords"] AS $keyword) {
939 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
940 array("","", "", "", "", ""), $keyword);
941 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
945 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
948 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
949 require_once("mod/parse_url.php");
951 $data = Cache::get("parse_url:".$url);
953 $data = parseurl_getsiteinfo($url, true);
954 Cache::set("parse_url:".$url,serialize($data));
956 $data = unserialize($data);
959 $data["images"][0]["src"] = $photo;
961 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
963 if (!$keywords AND isset($data["keywords"]))
964 unset($data["keywords"]);
966 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
967 $list = explode(",", $keyword_blacklist);
968 foreach ($list AS $keyword) {
969 $keyword = trim($keyword);
970 $index = array_search($keyword, $data["keywords"]);
971 if ($index !== false)
972 unset($data["keywords"][$index]);
979 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
980 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
983 if (isset($data["keywords"]) AND count($data["keywords"])) {
985 foreach ($data["keywords"] AS $keyword) {
986 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
987 array("","", "", "", "", ""), $keyword);
992 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
999 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1000 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 $text = add_page_info_data($data);
1007 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1009 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1011 $URLSearchString = "^\[\]";
1013 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1014 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1017 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1019 // Convert urls without bbcode elements
1020 if (!$matches AND $texturl) {
1021 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1023 // Yeah, a hack. I really hate regular expressions :)
1025 $matches[1] = $matches[2];
1029 $footer = add_page_info($matches[1], $no_photos);
1031 // Remove the link from the body if the link is attached at the end of the post
1032 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1033 $removedlink = trim(str_replace($matches[1], "", $body));
1034 if (($removedlink == "") OR strstr($body, $removedlink))
1035 $body = $removedlink;
1037 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1038 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1039 if (($removedlink == "") OR strstr($body, $removedlink))
1040 $body = $removedlink;
1043 // Add the page information to the bottom
1044 if (isset($footer) AND (trim($footer) != ""))
1050 function encode_rel_links($links) {
1052 if(! ((is_array($links)) && (count($links))))
1054 foreach($links as $link) {
1056 if($link['attribs']['']['rel'])
1057 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1058 if($link['attribs']['']['type'])
1059 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1060 if($link['attribs']['']['href'])
1061 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1062 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1063 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1064 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1065 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1066 $o .= ' />' . "\n" ;
1073 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1075 // If it is a posting where users should get notifications, then define it as wall posting
1078 $arr['type'] = 'wall';
1080 $arr['last-child'] = 1;
1081 $arr['network'] = NETWORK_DFRN;
1084 // If a Diaspora signature structure was passed in, pull it out of the
1085 // item array and set it aside for later storage.
1088 if(x($arr,'dsprsig')) {
1089 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1090 unset($arr['dsprsig']);
1093 // Converting the plink
1094 if ($arr['network'] == NETWORK_OSTATUS) {
1095 if (isset($arr['plink']))
1096 $arr['plink'] = ostatus_convert_href($arr['plink']);
1097 elseif (isset($arr['uri']))
1098 $arr['plink'] = ostatus_convert_href($arr['uri']);
1101 // if an OStatus conversation url was passed in, it is stored and then
1102 // removed from the array.
1103 $ostatus_conversation = null;
1105 if (isset($arr["ostatus_conversation"])) {
1106 $ostatus_conversation = $arr["ostatus_conversation"];
1107 unset($arr["ostatus_conversation"]);
1110 if(x($arr, 'gravity'))
1111 $arr['gravity'] = intval($arr['gravity']);
1112 elseif($arr['parent-uri'] === $arr['uri'])
1113 $arr['gravity'] = 0;
1114 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1115 $arr['gravity'] = 6;
1117 $arr['gravity'] = 6; // extensible catchall
1119 if(! x($arr,'type'))
1120 $arr['type'] = 'remote';
1124 /* check for create date and expire time */
1125 $uid = intval($arr['uid']);
1126 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1128 $expire_interval = $r[0]['expire'];
1129 if ($expire_interval>0) {
1130 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1131 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1132 if ($created_date < $expire_date) {
1133 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1139 // If there is no guid then take the same guid that was taken before for the same uri
1140 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1141 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1142 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1143 dbesc(trim($arr['uri']))
1147 $arr['guid'] = $r[0]["guid"];
1148 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1152 // If there is no guid then take the same guid that was taken before for the same plink
1153 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "")) {
1154 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1155 $r = q("SELECT `guid` FROM `item` WHERE `plink` = '%s' AND `guid` != '' LIMIT 1",
1156 dbesc(trim($arr['plink']))
1160 $arr['guid'] = $r[0]["guid"];
1161 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1165 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1166 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1167 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1168 // $arr['body'] = strip_tags($arr['body']);
1171 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1172 require_once('library/langdet/Text/LanguageDetect.php');
1173 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1174 $l = new Text_LanguageDetect;
1175 //$lng = $l->detectConfidence($naked_body);
1176 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1177 $lng = $l->detect($naked_body, 3);
1179 if (sizeof($lng) > 0) {
1182 foreach ($lng as $language => $score) {
1183 if ($postopts == "")
1184 $postopts = "lang=";
1188 $postopts .= $language.";".$score;
1190 $arr['postopts'] = $postopts;
1194 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1195 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1196 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1197 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1198 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1199 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1200 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1201 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1202 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1203 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1204 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1205 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1206 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1207 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1208 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1209 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1210 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1211 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1212 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1213 $arr['deleted'] = 0;
1214 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1215 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1216 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1217 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1218 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1219 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1220 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1221 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1222 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1223 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1224 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1225 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1226 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1227 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1228 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1229 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1230 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1231 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1232 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1233 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1234 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1235 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1236 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1237 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1238 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1240 if ($arr['plink'] == "") {
1242 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1245 if ($arr['network'] == "") {
1246 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1247 intval($arr['contact-id']),
1252 $arr['network'] = $r[0]["network"];
1254 // Fallback to friendica (why is it empty in some cases?)
1255 if ($arr['network'] == "")
1256 $arr['network'] = NETWORK_DFRN;
1258 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1261 if ($arr['guid'] != "") {
1262 // Checking if there is already an item with the same guid
1263 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1264 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1265 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1268 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1273 // Check for hashtags in the body and repair or add hashtag links
1274 item_body_set_hashtags($arr);
1276 $arr['thr-parent'] = $arr['parent-uri'];
1277 if($arr['parent-uri'] === $arr['uri']) {
1279 $parent_deleted = 0;
1280 $allow_cid = $arr['allow_cid'];
1281 $allow_gid = $arr['allow_gid'];
1282 $deny_cid = $arr['deny_cid'];
1283 $deny_gid = $arr['deny_gid'];
1284 $notify_type = 'wall-new';
1288 // find the parent and snarf the item id and ACLs
1289 // and anything else we need to inherit
1291 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1292 dbesc($arr['parent-uri']),
1298 // is the new message multi-level threaded?
1299 // even though we don't support it now, preserve the info
1300 // and re-attach to the conversation parent.
1302 if($r[0]['uri'] != $r[0]['parent-uri']) {
1303 $arr['parent-uri'] = $r[0]['parent-uri'];
1304 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1305 ORDER BY `id` ASC LIMIT 1",
1306 dbesc($r[0]['parent-uri']),
1307 dbesc($r[0]['parent-uri']),
1314 $parent_id = $r[0]['id'];
1315 $parent_deleted = $r[0]['deleted'];
1316 $allow_cid = $r[0]['allow_cid'];
1317 $allow_gid = $r[0]['allow_gid'];
1318 $deny_cid = $r[0]['deny_cid'];
1319 $deny_gid = $r[0]['deny_gid'];
1320 $arr['wall'] = $r[0]['wall'];
1321 $notify_type = 'comment-new';
1323 // if the parent is private, force privacy for the entire conversation
1324 // This differs from the above settings as it subtly allows comments from
1325 // email correspondents to be private even if the overall thread is not.
1327 if($r[0]['private'])
1328 $arr['private'] = $r[0]['private'];
1330 // Edge case. We host a public forum that was originally posted to privately.
1331 // The original author commented, but as this is a comment, the permissions
1332 // weren't fixed up so it will still show the comment as private unless we fix it here.
1334 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1335 $arr['private'] = 0;
1338 // If its a post from myself then tag the thread as "mention"
1339 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1340 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1343 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1344 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1345 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1346 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1347 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1353 // Allow one to see reply tweets from status.net even when
1354 // we don't have or can't see the original post.
1357 logger('item_store: $force_parent=true, reply converted to top-level post.');
1359 $arr['parent-uri'] = $arr['uri'];
1360 $arr['gravity'] = 0;
1363 logger('item_store: item parent was not found - ignoring item');
1367 $parent_deleted = 0;
1371 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1373 dbesc($arr['network']),
1376 if($r && count($r)) {
1377 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1381 $r = q("SELECT `id` FROM `item` WHERE `plink` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1382 dbesc($arr['plink']),
1383 dbesc($arr['network']),
1386 if($r && count($r)) {
1387 logger('duplicated item with the same plink found. ' . print_r($arr,true));
1391 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1392 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1393 dbesc($arr['body']),
1394 dbesc($arr['network']),
1395 dbesc($arr['created']),
1396 intval($arr['contact-id']),
1399 if($r && count($r)) {
1400 logger('duplicated item with the same body found. ' . print_r($arr,true));
1404 // Is this item available in the global items (with uid=0)?
1405 if ($arr["uid"] == 0) {
1406 $arr["global"] = true;
1408 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1410 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1412 $arr["global"] = (count($isglobal) > 0);
1415 // Fill the cache field
1416 put_item_in_cache($arr);
1418 call_hooks('post_remote',$arr);
1420 if(x($arr,'cancel')) {
1421 logger('item_store: post cancelled by plugin.');
1425 // Store the unescaped version
1430 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1432 $r = dbq("INSERT INTO `item` (`"
1433 . implode("`, `", array_keys($arr))
1435 . implode("', '", array_values($arr))
1441 // find the item we just created
1442 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1448 $current_post = $r[0]['id'];
1449 logger('item_store: created item ' . $current_post);
1451 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1452 // This can be used to filter for inactive contacts.
1453 // Only do this for public postings to avoid privacy problems, since poco data is public.
1454 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1456 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1458 // Is it a forum? Then we don't care about the rules from above
1459 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1460 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1461 intval($arr['contact-id']));
1467 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1468 dbesc($arr['received']),
1469 dbesc($arr['received']),
1470 intval($arr['contact-id'])
1473 logger('item_store: could not locate created item');
1477 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1478 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1480 intval($arr['uid']),
1481 intval($current_post)
1485 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1486 $parent_id = $current_post;
1488 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1491 $private = $arr['private'];
1493 // Set parent id - and also make sure to inherit the parent's ACLs.
1495 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1496 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1503 intval($parent_deleted),
1504 intval($current_post)
1507 // Complete ostatus threads
1508 if ($ostatus_conversation)
1509 complete_conversation($current_post, $ostatus_conversation);
1511 $arr['id'] = $current_post;
1512 $arr['parent'] = $parent_id;
1513 $arr['allow_cid'] = $allow_cid;
1514 $arr['allow_gid'] = $allow_gid;
1515 $arr['deny_cid'] = $deny_cid;
1516 $arr['deny_gid'] = $deny_gid;
1517 $arr['private'] = $private;
1518 $arr['deleted'] = $parent_deleted;
1520 // update the commented timestamp on the parent
1521 // Only update "commented" if it is really a comment
1522 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1523 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1524 dbesc(datetime_convert()),
1525 dbesc(datetime_convert()),
1529 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1530 dbesc(datetime_convert()),
1535 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1536 intval($current_post),
1537 dbesc($dsprsig->signed_text),
1538 dbesc($dsprsig->signature),
1539 dbesc($dsprsig->signer)
1545 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1548 if($arr['last-child']) {
1549 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1551 intval($arr['uid']),
1552 intval($current_post)
1556 $deleted = tag_deliver($arr['uid'],$current_post);
1558 // current post can be deleted if is for a community page and no mention are
1560 if (!$deleted AND !$dontcache) {
1562 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1563 if (count($r) == 1) {
1564 call_hooks('post_remote_end', $r[0]);
1566 logger('item_store: new item not found in DB, id ' . $current_post);
1569 // Add every contact of the post to the global contact table
1572 create_tags_from_item($current_post);
1573 create_files_from_item($current_post);
1575 // Only check for notifications on start posts
1576 if ($arr['parent-uri'] === $arr['uri']) {
1577 add_thread($current_post);
1578 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1580 // Send a notification for every new post?
1581 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1582 intval($arr['contact-id']),
1585 $send_notification = count($r);
1587 if (!$send_notification) {
1588 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1589 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1592 foreach ($tags AS $tag) {
1593 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1594 normalise_link($tag["url"]), intval($arr['uid']));
1596 $send_notification = true;
1601 if ($send_notification) {
1602 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1603 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1604 intval($arr['uid']));
1606 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1607 intval($current_post),
1613 require_once('include/enotify.php');
1615 'type' => NOTIFY_SHARE,
1616 'notify_flags' => $u[0]['notify-flags'],
1617 'language' => $u[0]['language'],
1618 'to_name' => $u[0]['username'],
1619 'to_email' => $u[0]['email'],
1620 'uid' => $u[0]['uid'],
1622 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1623 'source_name' => $item[0]['author-name'],
1624 'source_link' => $item[0]['author-link'],
1625 'source_photo' => $item[0]['author-avatar'],
1626 'verb' => ACTIVITY_TAG,
1628 'parent' => $arr['parent']
1630 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1633 update_thread($parent_id);
1634 add_shadow_entry($arr);
1638 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1640 return $current_post;
1643 function item_body_set_hashtags(&$item) {
1645 $tags = get_tags($item["body"]);
1651 // This sorting is important when there are hashtags that are part of other hashtags
1652 // Otherwise there could be problems with hashtags like #test and #test2
1657 $URLSearchString = "^\[\]";
1659 // All hashtags should point to the home server
1660 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1661 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1663 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1664 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1666 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1667 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1669 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1672 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1674 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1677 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1679 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1682 // Repair recursive urls
1683 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1684 "#$2", $item["body"]);
1686 foreach($tags as $tag) {
1687 if(strpos($tag,'#') !== 0)
1690 if(strpos($tag,'[url='))
1693 $basetag = str_replace('_',' ',substr($tag,1));
1695 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1697 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1699 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1700 if(strlen($item["tag"]))
1701 $item["tag"] = ','.$item["tag"];
1702 $item["tag"] = $newtag.$item["tag"];
1706 // Convert back the masked hashtags
1707 $item["body"] = str_replace("#", "#", $item["body"]);
1710 function get_item_guid($id) {
1711 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1713 return($r[0]["guid"]);
1718 function get_item_id($guid, $uid = 0) {
1724 $uid == local_user();
1726 // Does the given user have this item?
1728 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1729 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1730 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1733 $nick = $r[0]["nickname"];
1737 // Or is it anywhere on the server?
1739 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1740 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1741 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1742 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1743 AND `item`.`private` = 0 AND `item`.`wall` = 1
1744 AND `item`.`guid` = '%s'", dbesc($guid));
1747 $nick = $r[0]["nickname"];
1750 return(array("nick" => $nick, "id" => $id));
1754 function get_item_contact($item,$contacts) {
1755 if(! count($contacts) || (! is_array($item)))
1757 foreach($contacts as $contact) {
1758 if($contact['id'] == $item['contact-id']) {
1760 break; // NOTREACHED
1767 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1769 * @param int $item_id
1770 * @return bool true if item was deleted, else false
1772 function tag_deliver($uid,$item_id) {
1780 $u = q("select * from user where uid = %d limit 1",
1786 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1787 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1790 $i = q("select * from item where id = %d and uid = %d limit 1",
1799 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1801 // Diaspora uses their own hardwired link URL in @-tags
1802 // instead of the one we supply with webfinger
1804 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1806 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1808 foreach($matches as $mtch) {
1809 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1811 logger('tag_deliver: mention found: ' . $mtch[2]);
1817 if ( ($community_page || $prvgroup) &&
1818 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1819 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1821 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1822 q("DELETE FROM item WHERE id = %d and uid = %d",
1832 // send a notification
1834 // use a local photo if we have one
1836 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1837 intval($u[0]['uid']),
1838 dbesc(normalise_link($item['author-link']))
1840 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1843 require_once('include/enotify.php');
1845 'type' => NOTIFY_TAGSELF,
1846 'notify_flags' => $u[0]['notify-flags'],
1847 'language' => $u[0]['language'],
1848 'to_name' => $u[0]['username'],
1849 'to_email' => $u[0]['email'],
1850 'uid' => $u[0]['uid'],
1852 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1853 'source_name' => $item['author-name'],
1854 'source_link' => $item['author-link'],
1855 'source_photo' => $photo,
1856 'verb' => ACTIVITY_TAG,
1858 'parent' => $item['parent']
1862 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1864 call_hooks('tagged', $arr);
1866 if((! $community_page) && (! $prvgroup))
1870 // tgroup delivery - setup a second delivery chain
1871 // prevent delivery looping - only proceed
1872 // if the message originated elsewhere and is a top-level post
1874 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1877 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1880 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1881 intval($u[0]['uid'])
1886 // also reset all the privacy bits to the forum default permissions
1888 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1890 $forum_mode = (($prvgroup) ? 2 : 1);
1892 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1893 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1894 intval($forum_mode),
1895 dbesc($c[0]['name']),
1896 dbesc($c[0]['url']),
1897 dbesc($c[0]['thumb']),
1899 dbesc($u[0]['allow_cid']),
1900 dbesc($u[0]['allow_gid']),
1901 dbesc($u[0]['deny_cid']),
1902 dbesc($u[0]['deny_gid']),
1905 update_thread($item_id);
1907 proc_run('php','include/notifier.php','tgroup',$item_id);
1913 function tgroup_check($uid,$item) {
1919 // check that the message originated elsewhere and is a top-level post
1921 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1925 $u = q("select * from user where uid = %d limit 1",
1931 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1932 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1935 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1937 // Diaspora uses their own hardwired link URL in @-tags
1938 // instead of the one we supply with webfinger
1940 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1942 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1944 foreach($matches as $mtch) {
1945 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1947 logger('tgroup_check: mention found: ' . $mtch[2]);
1955 if((! $community_page) && (! $prvgroup))
1969 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1973 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1975 if($contact['duplex'] && $contact['dfrn-id'])
1976 $idtosend = '0:' . $orig_id;
1977 if($contact['duplex'] && $contact['issued-id'])
1978 $idtosend = '1:' . $orig_id;
1980 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1982 $rino_enable = get_config('system','rino_encrypt');
1987 $ssl_val = intval(get_config('system','ssl_policy'));
1991 case SSL_POLICY_FULL:
1992 $ssl_policy = 'full';
1994 case SSL_POLICY_SELFSIGN:
1995 $ssl_policy = 'self';
1997 case SSL_POLICY_NONE:
1999 $ssl_policy = 'none';
2003 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
2005 logger('dfrn_deliver: ' . $url);
2007 $xml = fetch_url($url);
2009 $curl_stat = $a->get_curl_code();
2011 return(-1); // timed out
2013 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2018 if(strpos($xml,'<?xml') === false) {
2019 logger('dfrn_deliver: no valid XML returned');
2020 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2024 $res = parse_xml_string($xml);
2026 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2027 return (($res->status) ? $res->status : 3);
2029 $postvars = array();
2030 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2031 $challenge = hex2bin((string) $res->challenge);
2032 $perm = (($res->perm) ? $res->perm : null);
2033 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2034 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2035 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2037 if($owner['page-flags'] == PAGE_PRVGROUP)
2040 $final_dfrn_id = '';
2043 if((($perm == 'rw') && (! intval($contact['writable'])))
2044 || (($perm == 'r') && (intval($contact['writable'])))) {
2045 q("update contact set writable = %d where id = %d",
2046 intval(($perm == 'rw') ? 1 : 0),
2047 intval($contact['id'])
2049 $contact['writable'] = (string) 1 - intval($contact['writable']);
2053 if(($contact['duplex'] && strlen($contact['pubkey']))
2054 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2055 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2056 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2057 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2060 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2061 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2064 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2066 if(strpos($final_dfrn_id,':') == 1)
2067 $final_dfrn_id = substr($final_dfrn_id,2);
2069 if($final_dfrn_id != $orig_id) {
2070 logger('dfrn_deliver: wrong dfrn_id.');
2071 // did not decode properly - cannot trust this site
2075 $postvars['dfrn_id'] = $idtosend;
2076 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2078 $postvars['dissolve'] = '1';
2081 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2082 $postvars['data'] = $atom;
2083 $postvars['perm'] = 'rw';
2086 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2087 $postvars['perm'] = 'r';
2090 $postvars['ssl_policy'] = $ssl_policy;
2093 $postvars['page'] = $page;
2095 if($rino && $rino_allowed && (! $dissolve)) {
2096 $key = substr(random_string(),0,16);
2097 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2098 $postvars['data'] = $data;
2099 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2102 if($dfrn_version >= 2.1) {
2103 if(($contact['duplex'] && strlen($contact['pubkey']))
2104 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2105 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2107 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2110 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2114 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2115 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2118 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2122 logger('md5 rawkey ' . md5($postvars['key']));
2124 $postvars['key'] = bin2hex($postvars['key']);
2127 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2129 $xml = post_url($contact['notify'],$postvars);
2131 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2133 $curl_stat = $a->get_curl_code();
2134 if((! $curl_stat) || (! strlen($xml)))
2135 return(-1); // timed out
2137 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2140 if(strpos($xml,'<?xml') === false) {
2141 logger('dfrn_deliver: phase 2: no valid XML returned');
2142 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2146 if($contact['term-date'] != '0000-00-00 00:00:00') {
2147 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2148 require_once('include/Contact.php');
2149 unmark_for_death($contact);
2152 $res = parse_xml_string($xml);
2154 return $res->status;
2159 This function returns true if $update has an edited timestamp newer
2160 than $existing, i.e. $update contains new data which should override
2161 what's already there. If there is no timestamp yet, the update is
2162 assumed to be newer. If the update has no timestamp, the existing
2163 item is assumed to be up-to-date. If the timestamps are equal it
2164 assumes the update has been seen before and should be ignored.
2166 function edited_timestamp_is_newer($existing, $update) {
2167 if (!x($existing,'edited') || !$existing['edited']) {
2170 if (!x($update,'edited') || !$update['edited']) {
2173 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2174 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2175 return (strcmp($existing_edited, $update_edited) < 0);
2180 * consume_feed - process atom feed and update anything/everything we might need to update
2182 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2184 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2185 * It is this person's stuff that is going to be updated.
2186 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2187 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2188 * have a contact record.
2189 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2190 * might not) try and subscribe to it.
2191 * $datedir sorts in reverse order
2192 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2193 * imported prior to its children being seen in the stream unless we are certain
2194 * of how the feed is arranged/ordered.
2195 * With $pass = 1, we only pull parent items out of the stream.
2196 * With $pass = 2, we only pull children (comments/likes).
2198 * So running this twice, first with pass 1 and then with pass 2 will do the right
2199 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2200 * model where comments can have sub-threads. That would require some massive sorting
2201 * to get all the feed items into a mostly linear ordering, and might still require
2205 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2207 require_once('library/simplepie/simplepie.inc');
2208 require_once('include/contact_selectors.php');
2210 if(! strlen($xml)) {
2211 logger('consume_feed: empty input');
2215 $feed = new SimplePie();
2216 $feed->set_raw_data($xml);
2218 $feed->enable_order_by_date(true);
2220 $feed->enable_order_by_date(false);
2224 logger('consume_feed: Error parsing XML: ' . $feed->error());
2226 $permalink = $feed->get_permalink();
2228 // Check at the feed level for updated contact name and/or photo
2232 $photo_timestamp = '';
2235 $contact_updated = '';
2237 $hubs = $feed->get_links('hub');
2238 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2241 $hub = implode(',', $hubs);
2243 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2245 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2247 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2248 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2249 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2250 $new_name = $elems['name'][0]['data'];
2252 // Manually checking for changed contact names
2253 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2254 $name_updated = date("c");
2255 $photo_timestamp = date("c");
2258 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2259 if ($photo_timestamp == "")
2260 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2261 $photo_url = $elems['link'][0]['attribs']['']['href'];
2264 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2265 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2269 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2270 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2272 $contact_updated = $photo_timestamp;
2274 require_once("include/Photo.php");
2275 $photo_failure = false;
2276 $have_photo = false;
2278 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2279 intval($contact['id']),
2280 intval($contact['uid'])
2283 $resource_id = $r[0]['resource-id'];
2287 $resource_id = photo_new_resource();
2290 $img_str = fetch_url($photo_url,true);
2291 // guess mimetype from headers or filename
2292 $type = guess_image_type($photo_url,true);
2295 $img = new Photo($img_str, $type);
2296 if($img->is_valid()) {
2298 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2299 dbesc($resource_id),
2300 intval($contact['id']),
2301 intval($contact['uid'])
2305 $img->scaleImageSquare(175);
2307 $hash = $resource_id;
2308 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2310 $img->scaleImage(80);
2311 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2313 $img->scaleImage(48);
2314 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2318 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2319 WHERE `uid` = %d AND `id` = %d",
2320 dbesc(datetime_convert()),
2321 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2322 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2323 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2324 intval($contact['uid']),
2325 intval($contact['id'])
2330 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2331 if ($name_updated > $contact_updated)
2332 $contact_updated = $name_updated;
2334 $r = q("select * from contact where uid = %d and id = %d limit 1",
2335 intval($contact['uid']),
2336 intval($contact['id'])
2339 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2340 dbesc(notags(trim($new_name))),
2341 dbesc(datetime_convert()),
2342 intval($contact['uid']),
2343 intval($contact['id'])
2346 // do our best to update the name on content items
2349 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2350 dbesc(notags(trim($new_name))),
2351 dbesc($r[0]['name']),
2352 dbesc($r[0]['url']),
2353 intval($contact['uid'])
2358 if ($contact_updated AND $new_name AND $photo_url)
2359 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2361 if(strlen($birthday)) {
2362 if(substr($birthday,0,4) != $contact['bdyear']) {
2363 logger('consume_feed: updating birthday: ' . $birthday);
2367 * Add new birthday event for this person
2369 * $bdtext is just a readable placeholder in case the event is shared
2370 * with others. We will replace it during presentation to our $importer
2371 * to contain a sparkle link and perhaps a photo.
2375 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2376 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2379 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2380 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2381 intval($contact['uid']),
2382 intval($contact['id']),
2383 dbesc(datetime_convert()),
2384 dbesc(datetime_convert()),
2385 dbesc(datetime_convert('UTC','UTC', $birthday)),
2386 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2395 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2396 dbesc(substr($birthday,0,4)),
2397 intval($contact['uid']),
2398 intval($contact['id'])
2401 // This function is called twice without reloading the contact
2402 // Make sure we only create one event. This is why &$contact
2403 // is a reference var in this function
2405 $contact['bdyear'] = substr($birthday,0,4);
2409 $community_page = 0;
2410 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2412 $community_page = intval($rawtags[0]['data']);
2414 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2415 q("update contact set forum = %d where id = %d",
2416 intval($community_page),
2417 intval($contact['id'])
2419 $contact['forum'] = (string) $community_page;
2423 // process any deleted entries
2425 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2426 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2427 foreach($del_entries as $dentry) {
2429 if(isset($dentry['attribs']['']['ref'])) {
2430 $uri = $dentry['attribs']['']['ref'];
2432 if(isset($dentry['attribs']['']['when'])) {
2433 $when = $dentry['attribs']['']['when'];
2434 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2437 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2439 if($deleted && is_array($contact)) {
2440 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2441 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2443 intval($importer['uid']),
2444 intval($contact['id'])
2449 if(! $item['deleted'])
2450 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2452 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2453 $xo = parse_xml_string($item['object'],false);
2454 $xt = parse_xml_string($item['target'],false);
2455 if($xt->type === ACTIVITY_OBJ_NOTE) {
2456 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2458 intval($importer['importer_uid'])
2462 // For tags, the owner cannot remove the tag on the author's copy of the post.
2464 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2465 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2466 $author_copy = (($item['origin']) ? true : false);
2468 if($owner_remove && $author_copy)
2470 if($author_remove || $owner_remove) {
2471 $tags = explode(',',$i[0]['tag']);
2474 foreach($tags as $tag)
2475 if(trim($tag) !== trim($xo->body))
2476 $newtags[] = trim($tag);
2478 q("update item set tag = '%s' where id = %d",
2479 dbesc(implode(',',$newtags)),
2482 create_tags_from_item($i[0]['id']);
2488 if($item['uri'] == $item['parent-uri']) {
2489 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2490 `body` = '', `title` = ''
2491 WHERE `parent-uri` = '%s' AND `uid` = %d",
2493 dbesc(datetime_convert()),
2494 dbesc($item['uri']),
2495 intval($importer['uid'])
2497 create_tags_from_itemuri($item['uri'], $importer['uid']);
2498 create_files_from_itemuri($item['uri'], $importer['uid']);
2499 update_thread_uri($item['uri'], $importer['uid']);
2502 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2503 `body` = '', `title` = ''
2504 WHERE `uri` = '%s' AND `uid` = %d",
2506 dbesc(datetime_convert()),
2508 intval($importer['uid'])
2510 create_tags_from_itemuri($uri, $importer['uid']);
2511 create_files_from_itemuri($uri, $importer['uid']);
2512 if($item['last-child']) {
2513 // ensure that last-child is set in case the comment that had it just got wiped.
2514 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2515 dbesc(datetime_convert()),
2516 dbesc($item['parent-uri']),
2517 intval($item['uid'])
2519 // who is the last child now?
2520 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2521 ORDER BY `created` DESC LIMIT 1",
2522 dbesc($item['parent-uri']),
2523 intval($importer['uid'])
2526 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2537 // Now process the feed
2539 if($feed->get_item_quantity()) {
2541 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2543 // in inverse date order
2545 $items = array_reverse($feed->get_items());
2547 $items = $feed->get_items();
2550 foreach($items as $item) {
2553 $item_id = $item->get_id();
2554 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2555 if(isset($rawthread[0]['attribs']['']['ref'])) {
2557 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2560 if(($is_reply) && is_array($contact)) {
2565 // not allowed to post
2567 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2571 // Have we seen it? If not, import it.
2573 $item_id = $item->get_id();
2574 $datarray = get_atom_elements($feed, $item, $contact);
2576 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2577 $datarray['author-name'] = $contact['name'];
2578 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2579 $datarray['author-link'] = $contact['url'];
2580 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2581 $datarray['author-avatar'] = $contact['thumb'];
2583 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2584 logger('consume_feed: no author information! ' . print_r($datarray,true));
2588 $force_parent = false;
2589 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2590 if($contact['network'] === NETWORK_OSTATUS)
2591 $force_parent = true;
2592 if(strlen($datarray['title']))
2593 unset($datarray['title']);
2594 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2595 dbesc(datetime_convert()),
2597 intval($importer['uid'])
2599 $datarray['last-child'] = 1;
2600 update_thread_uri($parent_uri, $importer['uid']);
2604 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2606 intval($importer['uid'])
2609 // Update content if 'updated' changes
2612 if (edited_timestamp_is_newer($r[0], $datarray)) {
2614 // do not accept (ignore) an earlier edit than one we currently have.
2615 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2618 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2619 dbesc($datarray['title']),
2620 dbesc($datarray['body']),
2621 dbesc($datarray['tag']),
2622 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2623 dbesc(datetime_convert()),
2625 intval($importer['uid'])
2627 create_tags_from_itemuri($item_id, $importer['uid']);
2628 update_thread_uri($item_id, $importer['uid']);
2631 // update last-child if it changes
2633 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2634 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2635 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2636 dbesc(datetime_convert()),
2638 intval($importer['uid'])
2640 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2641 intval($allow[0]['data']),
2642 dbesc(datetime_convert()),
2644 intval($importer['uid'])
2646 update_thread_uri($item_id, $importer['uid']);
2652 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2653 // one way feed - no remote comment ability
2654 $datarray['last-child'] = 0;
2656 $datarray['parent-uri'] = $parent_uri;
2657 $datarray['uid'] = $importer['uid'];
2658 $datarray['contact-id'] = $contact['id'];
2659 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2660 $datarray['type'] = 'activity';
2661 $datarray['gravity'] = GRAVITY_LIKE;
2662 // only one like or dislike per person
2663 // splitted into two queries for performance issues
2664 $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",
2665 intval($datarray['uid']),
2666 intval($datarray['contact-id']),
2667 dbesc($datarray['verb']),
2673 $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",
2674 intval($datarray['uid']),
2675 intval($datarray['contact-id']),
2676 dbesc($datarray['verb']),
2683 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2684 $xo = parse_xml_string($datarray['object'],false);
2685 $xt = parse_xml_string($datarray['target'],false);
2687 if($xt->type == ACTIVITY_OBJ_NOTE) {
2688 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2690 intval($importer['importer_uid'])
2695 // extract tag, if not duplicate, add to parent item
2696 if($xo->id && $xo->content) {
2697 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2698 if(! (stristr($r[0]['tag'],$newtag))) {
2699 q("UPDATE item SET tag = '%s' WHERE id = %d",
2700 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2703 create_tags_from_item($r[0]['id']);
2709 $r = item_store($datarray,$force_parent);
2715 // Head post of a conversation. Have we seen it? If not, import it.
2717 $item_id = $item->get_id();
2719 $datarray = get_atom_elements($feed, $item, $contact);
2721 if(is_array($contact)) {
2722 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2723 $datarray['author-name'] = $contact['name'];
2724 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2725 $datarray['author-link'] = $contact['url'];
2726 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2727 $datarray['author-avatar'] = $contact['thumb'];
2730 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2731 logger('consume_feed: no author information! ' . print_r($datarray,true));
2735 // special handling for events
2737 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2738 $ev = bbtoevent($datarray['body']);
2739 if(x($ev,'desc') && x($ev,'start')) {
2740 $ev['uid'] = $importer['uid'];
2741 $ev['uri'] = $item_id;
2742 $ev['edited'] = $datarray['edited'];
2743 $ev['private'] = $datarray['private'];
2745 if(is_array($contact))
2746 $ev['cid'] = $contact['id'];
2747 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2749 intval($importer['uid'])
2752 $ev['id'] = $r[0]['id'];
2753 $xyz = event_store($ev);
2758 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2759 if(strlen($datarray['title']))
2760 unset($datarray['title']);
2761 $datarray['last-child'] = 1;
2765 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2767 intval($importer['uid'])
2770 // Update content if 'updated' changes
2773 if (edited_timestamp_is_newer($r[0], $datarray)) {
2775 // do not accept (ignore) an earlier edit than one we currently have.
2776 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2779 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2780 dbesc($datarray['title']),
2781 dbesc($datarray['body']),
2782 dbesc($datarray['tag']),
2783 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2784 dbesc(datetime_convert()),
2786 intval($importer['uid'])
2788 create_tags_from_itemuri($item_id, $importer['uid']);
2789 update_thread_uri($item_id, $importer['uid']);
2792 // update last-child if it changes
2794 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2795 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2796 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2797 intval($allow[0]['data']),
2798 dbesc(datetime_convert()),
2800 intval($importer['uid'])
2802 update_thread_uri($item_id, $importer['uid']);
2807 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2808 logger('consume-feed: New follower');
2809 new_follower($importer,$contact,$datarray,$item);
2812 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2813 lose_follower($importer,$contact,$datarray,$item);
2817 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2818 logger('consume-feed: New friend request');
2819 new_follower($importer,$contact,$datarray,$item,true);
2822 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2823 lose_sharer($importer,$contact,$datarray,$item);
2828 if(! is_array($contact))
2832 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2833 // one way feed - no remote comment ability
2834 $datarray['last-child'] = 0;
2836 if($contact['network'] === NETWORK_FEED)
2837 $datarray['private'] = 2;
2839 $datarray['parent-uri'] = $item_id;
2840 $datarray['uid'] = $importer['uid'];
2841 $datarray['contact-id'] = $contact['id'];
2843 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2844 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2845 // but otherwise there's a possible data mixup on the sender's system.
2846 // the tgroup delivery code called from item_store will correct it if it's a forum,
2847 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2848 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2849 $datarray['owner-name'] = $contact['name'];
2850 $datarray['owner-link'] = $contact['url'];
2851 $datarray['owner-avatar'] = $contact['thumb'];
2854 // We've allowed "followers" to reach this point so we can decide if they are
2855 // posting an @-tag delivery, which followers are allowed to do for certain
2856 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2858 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2861 // This is my contact on another system, but it's really me.
2862 // Turn this into a wall post.
2863 $notify = item_is_remote_self($contact, $datarray);
2865 $r = item_store($datarray, false, $notify);
2866 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2874 function item_is_remote_self($contact, &$datarray) {
2877 if (!$contact['remote_self'])
2880 // Prevent the forwarding of posts that are forwarded
2881 if ($datarray["extid"] == NETWORK_DFRN)
2884 // Prevent to forward already forwarded posts
2885 if ($datarray["app"] == $a->get_hostname())
2888 // Only forward posts
2889 if ($datarray["verb"] != ACTIVITY_POST)
2892 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2895 $datarray2 = $datarray;
2896 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2897 if ($contact['remote_self'] == 2) {
2898 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2899 intval($contact['uid']));
2901 $datarray['contact-id'] = $r[0]["id"];
2903 $datarray['owner-name'] = $r[0]["name"];
2904 $datarray['owner-link'] = $r[0]["url"];
2905 $datarray['owner-avatar'] = $r[0]["thumb"];
2907 $datarray['author-name'] = $datarray['owner-name'];
2908 $datarray['author-link'] = $datarray['owner-link'];
2909 $datarray['author-avatar'] = $datarray['owner-avatar'];
2912 if ($contact['network'] != NETWORK_FEED) {
2913 $datarray["guid"] = get_guid(32);
2914 unset($datarray["plink"]);
2915 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2916 $datarray["parent-uri"] = $datarray["uri"];
2917 $datarray["extid"] = $contact['network'];
2918 $urlpart = parse_url($datarray2['author-link']);
2919 $datarray["app"] = $urlpart["host"];
2921 $datarray['private'] = 0;
2924 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2925 // $datarray["app"] = network_to_name($contact['network']);
2927 if ($contact['network'] != NETWORK_FEED) {
2928 // Store the original post
2929 $r = item_store($datarray2, false, false);
2930 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2932 $datarray["app"] = "Feed";
2937 function local_delivery($importer,$data) {
2940 logger(__function__, LOGGER_TRACE);
2942 if($importer['readonly']) {
2943 // We aren't receiving stuff from this person. But we will quietly ignore them
2944 // rather than a blatant "go away" message.
2945 logger('local_delivery: ignoring');
2950 // Consume notification feed. This may differ from consuming a public feed in several ways
2951 // - might contain email or friend suggestions
2952 // - might contain remote followup to our message
2953 // - in which case we need to accept it and then notify other conversants
2954 // - we may need to send various email notifications
2956 $feed = new SimplePie();
2957 $feed->set_raw_data($data);
2958 $feed->enable_order_by_date(false);
2963 logger('local_delivery: Error parsing XML: ' . $feed->error());
2966 // Check at the feed level for updated contact name and/or photo
2970 $photo_timestamp = '';
2972 $contact_updated = '';
2975 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2977 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2979 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2982 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2983 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2984 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2985 $new_name = $elems['name'][0]['data'];
2987 // Manually checking for changed contact names
2988 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2989 $name_updated = date("c");
2990 $photo_timestamp = date("c");
2993 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2994 if ($photo_timestamp == "")
2995 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2996 $photo_url = $elems['link'][0]['attribs']['']['href'];
3000 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3002 $contact_updated = $photo_timestamp;
3004 logger('local_delivery: Updating photo for ' . $importer['name']);
3005 require_once("include/Photo.php");
3006 $photo_failure = false;
3007 $have_photo = false;
3009 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3010 intval($importer['id']),
3011 intval($importer['importer_uid'])
3014 $resource_id = $r[0]['resource-id'];
3018 $resource_id = photo_new_resource();
3021 $img_str = fetch_url($photo_url,true);
3022 // guess mimetype from headers or filename
3023 $type = guess_image_type($photo_url,true);
3026 $img = new Photo($img_str, $type);
3027 if($img->is_valid()) {
3029 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3030 dbesc($resource_id),
3031 intval($importer['id']),
3032 intval($importer['importer_uid'])
3036 $img->scaleImageSquare(175);
3038 $hash = $resource_id;
3039 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3041 $img->scaleImage(80);
3042 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3044 $img->scaleImage(48);
3045 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3049 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3050 WHERE `uid` = %d AND `id` = %d",
3051 dbesc(datetime_convert()),
3052 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3053 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3054 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3055 intval($importer['importer_uid']),
3056 intval($importer['id'])
3061 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3062 if ($name_updated > $contact_updated)
3063 $contact_updated = $name_updated;
3065 $r = q("select * from contact where uid = %d and id = %d limit 1",
3066 intval($importer['importer_uid']),
3067 intval($importer['id'])
3070 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3071 dbesc(notags(trim($new_name))),
3072 dbesc(datetime_convert()),
3073 intval($importer['importer_uid']),
3074 intval($importer['id'])
3077 // do our best to update the name on content items
3080 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3081 dbesc(notags(trim($new_name))),
3082 dbesc($r[0]['name']),
3083 dbesc($r[0]['url']),
3084 intval($importer['importer_uid'])
3089 if ($contact_updated AND $new_name AND $photo_url)
3090 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3092 // Currently unsupported - needs a lot of work
3093 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3094 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3095 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3097 $newloc['uid'] = $importer['importer_uid'];
3098 $newloc['cid'] = $importer['id'];
3099 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3100 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3101 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3102 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3103 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3104 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3105 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3106 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3107 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3108 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3109 /** relocated user must have original key pair */
3110 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3111 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3113 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3116 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3117 intval($importer['id']),
3118 intval($importer['importer_uid']));
3123 $x = q("UPDATE contact SET
3134 `site-pubkey` = '%s'
3135 WHERE id=%d AND uid=%d;",
3136 dbesc($newloc['name']),
3137 dbesc($newloc['photo']),
3138 dbesc($newloc['thumb']),
3139 dbesc($newloc['micro']),
3140 dbesc($newloc['url']),
3141 dbesc(normalise_link($newloc['url'])),
3142 dbesc($newloc['request']),
3143 dbesc($newloc['confirm']),
3144 dbesc($newloc['notify']),
3145 dbesc($newloc['poll']),
3146 dbesc($newloc['sitepubkey']),
3147 intval($importer['id']),
3148 intval($importer['importer_uid']));
3154 'owner-link' => array($old['url'], $newloc['url']),
3155 'author-link' => array($old['url'], $newloc['url']),
3156 'owner-avatar' => array($old['photo'], $newloc['photo']),
3157 'author-avatar' => array($old['photo'], $newloc['photo']),
3159 foreach ($fields as $n=>$f){
3160 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3163 intval($importer['importer_uid']));
3169 // merge with current record, current contents have priority
3170 // update record, set url-updated
3171 // update profile photos
3177 // handle friend suggestion notification
3179 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3180 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3181 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3183 $fsugg['uid'] = $importer['importer_uid'];
3184 $fsugg['cid'] = $importer['id'];
3185 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3186 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3187 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3188 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3189 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3191 // Does our member already have a friend matching this description?
3193 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3194 dbesc($fsugg['name']),
3195 dbesc(normalise_link($fsugg['url'])),
3196 intval($fsugg['uid'])
3201 // Do we already have an fcontact record for this person?
3204 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3205 dbesc($fsugg['url']),
3206 dbesc($fsugg['name']),
3207 dbesc($fsugg['request'])
3212 // OK, we do. Do we already have an introduction for this person ?
3213 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3214 intval($fsugg['uid']),
3221 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3222 dbesc($fsugg['name']),
3223 dbesc($fsugg['url']),
3224 dbesc($fsugg['photo']),
3225 dbesc($fsugg['request'])
3227 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3228 dbesc($fsugg['url']),
3229 dbesc($fsugg['name']),
3230 dbesc($fsugg['request'])
3235 // database record did not get created. Quietly give up.
3240 $hash = random_string();
3242 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3243 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3244 intval($fsugg['uid']),
3246 intval($fsugg['cid']),
3247 dbesc($fsugg['body']),
3249 dbesc(datetime_convert()),
3254 'type' => NOTIFY_SUGGEST,
3255 'notify_flags' => $importer['notify-flags'],
3256 'language' => $importer['language'],
3257 'to_name' => $importer['username'],
3258 'to_email' => $importer['email'],
3259 'uid' => $importer['importer_uid'],
3261 'link' => $a->get_baseurl() . '/notifications/intros',
3262 'source_name' => $importer['name'],
3263 'source_link' => $importer['url'],
3264 'source_photo' => $importer['photo'],
3265 'verb' => ACTIVITY_REQ_FRIEND,
3274 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3275 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3277 logger('local_delivery: private message received');
3280 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3283 $msg['uid'] = $importer['importer_uid'];
3284 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3285 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3286 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3287 $msg['contact-id'] = $importer['id'];
3288 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3289 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3291 $msg['replied'] = 0;
3292 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3293 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3294 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3298 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3299 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3301 // send notifications.
3303 require_once('include/enotify.php');
3305 $notif_params = array(
3306 'type' => NOTIFY_MAIL,
3307 'notify_flags' => $importer['notify-flags'],
3308 'language' => $importer['language'],
3309 'to_name' => $importer['username'],
3310 'to_email' => $importer['email'],
3311 'uid' => $importer['importer_uid'],
3313 'source_name' => $msg['from-name'],
3314 'source_link' => $importer['url'],
3315 'source_photo' => $importer['thumb'],
3316 'verb' => ACTIVITY_POST,
3320 notification($notif_params);
3326 $community_page = 0;
3327 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3329 $community_page = intval($rawtags[0]['data']);
3331 if(intval($importer['forum']) != $community_page) {
3332 q("update contact set forum = %d where id = %d",
3333 intval($community_page),
3334 intval($importer['id'])
3336 $importer['forum'] = (string) $community_page;
3339 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3341 // process any deleted entries
3343 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3344 if(is_array($del_entries) && count($del_entries)) {
3345 foreach($del_entries as $dentry) {
3347 if(isset($dentry['attribs']['']['ref'])) {
3348 $uri = $dentry['attribs']['']['ref'];
3350 if(isset($dentry['attribs']['']['when'])) {
3351 $when = $dentry['attribs']['']['when'];
3352 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3355 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3359 // check for relayed deletes to our conversation
3362 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3364 intval($importer['importer_uid'])
3367 $parent_uri = $r[0]['parent-uri'];
3368 if($r[0]['id'] != $r[0]['parent'])
3375 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3378 logger('local_delivery: possible community delete');
3381 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3383 // was the top-level post for this reply written by somebody on this site?
3384 // Specifically, the recipient?
3386 $is_a_remote_delete = false;
3388 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3389 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3390 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3391 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3392 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3393 AND `item`.`uid` = %d
3399 intval($importer['importer_uid'])
3402 $is_a_remote_delete = true;
3404 // Does this have the characteristics of a community or private group comment?
3405 // If it's a reply to a wall post on a community/prvgroup page it's a
3406 // valid community comment. Also forum_mode makes it valid for sure.
3407 // If neither, it's not.
3409 if($is_a_remote_delete && $community) {
3410 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3411 $is_a_remote_delete = false;
3412 logger('local_delivery: not a community delete');
3416 if($is_a_remote_delete) {
3417 logger('local_delivery: received remote delete');
3421 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3422 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3424 intval($importer['importer_uid']),
3425 intval($importer['id'])
3431 if($item['deleted'])
3434 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3436 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3437 $xo = parse_xml_string($item['object'],false);
3438 $xt = parse_xml_string($item['target'],false);
3440 if($xt->type === ACTIVITY_OBJ_NOTE) {
3441 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3443 intval($importer['importer_uid'])
3447 // For tags, the owner cannot remove the tag on the author's copy of the post.
3449 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3450 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3451 $author_copy = (($item['origin']) ? true : false);
3453 if($owner_remove && $author_copy)
3455 if($author_remove || $owner_remove) {
3456 $tags = explode(',',$i[0]['tag']);
3459 foreach($tags as $tag)
3460 if(trim($tag) !== trim($xo->body))
3461 $newtags[] = trim($tag);
3463 q("update item set tag = '%s' where id = %d",
3464 dbesc(implode(',',$newtags)),
3467 create_tags_from_item($i[0]['id']);
3473 if($item['uri'] == $item['parent-uri']) {
3474 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3475 `body` = '', `title` = ''
3476 WHERE `parent-uri` = '%s' AND `uid` = %d",
3478 dbesc(datetime_convert()),
3479 dbesc($item['uri']),
3480 intval($importer['importer_uid'])
3482 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3483 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3484 update_thread_uri($item['uri'], $importer['importer_uid']);
3487 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3488 `body` = '', `title` = ''
3489 WHERE `uri` = '%s' AND `uid` = %d",
3491 dbesc(datetime_convert()),
3493 intval($importer['importer_uid'])
3495 create_tags_from_itemuri($uri, $importer['importer_uid']);
3496 create_files_from_itemuri($uri, $importer['importer_uid']);
3497 update_thread_uri($uri, $importer['importer_uid']);
3498 if($item['last-child']) {
3499 // ensure that last-child is set in case the comment that had it just got wiped.
3500 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3501 dbesc(datetime_convert()),
3502 dbesc($item['parent-uri']),
3503 intval($item['uid'])
3505 // who is the last child now?
3506 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3507 ORDER BY `created` DESC LIMIT 1",
3508 dbesc($item['parent-uri']),
3509 intval($importer['importer_uid'])
3512 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3517 // if this is a relayed delete, propagate it to other recipients
3519 if($is_a_remote_delete)
3520 proc_run('php',"include/notifier.php","drop",$item['id']);
3528 foreach($feed->get_items() as $item) {
3531 $item_id = $item->get_id();
3532 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3533 if(isset($rawthread[0]['attribs']['']['ref'])) {
3535 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3541 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3544 logger('local_delivery: possible community reply');
3547 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3549 // was the top-level post for this reply written by somebody on this site?
3550 // Specifically, the recipient?
3552 $is_a_remote_comment = false;
3553 $top_uri = $parent_uri;
3555 $r = q("select `item`.`parent-uri` from `item`
3556 WHERE `item`.`uri` = '%s'
3560 if($r && count($r)) {
3561 $top_uri = $r[0]['parent-uri'];
3563 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3564 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3565 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3566 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3567 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3568 AND `item`.`uid` = %d
3574 intval($importer['importer_uid'])
3577 $is_a_remote_comment = true;
3580 // Does this have the characteristics of a community or private group comment?
3581 // If it's a reply to a wall post on a community/prvgroup page it's a
3582 // valid community comment. Also forum_mode makes it valid for sure.
3583 // If neither, it's not.
3585 if($is_a_remote_comment && $community) {
3586 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3587 $is_a_remote_comment = false;
3588 logger('local_delivery: not a community reply');
3592 if($is_a_remote_comment) {
3593 logger('local_delivery: received remote comment');
3595 // remote reply to our post. Import and then notify everybody else.
3597 $datarray = get_atom_elements($feed, $item);
3599 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3601 intval($importer['importer_uid'])
3604 // Update content if 'updated' changes
3608 if (edited_timestamp_is_newer($r[0], $datarray)) {
3610 // do not accept (ignore) an earlier edit than one we currently have.
3611 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3614 logger('received updated comment' , LOGGER_DEBUG);
3615 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3616 dbesc($datarray['title']),
3617 dbesc($datarray['body']),
3618 dbesc($datarray['tag']),
3619 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3620 dbesc(datetime_convert()),
3622 intval($importer['importer_uid'])
3624 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3626 proc_run('php',"include/notifier.php","comment-import",$iid);
3635 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3636 intval($importer['importer_uid'])
3640 $datarray['type'] = 'remote-comment';
3641 $datarray['wall'] = 1;
3642 $datarray['parent-uri'] = $parent_uri;
3643 $datarray['uid'] = $importer['importer_uid'];
3644 $datarray['owner-name'] = $own[0]['name'];
3645 $datarray['owner-link'] = $own[0]['url'];
3646 $datarray['owner-avatar'] = $own[0]['thumb'];
3647 $datarray['contact-id'] = $importer['id'];
3649 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3651 $datarray['type'] = 'activity';
3652 $datarray['gravity'] = GRAVITY_LIKE;
3653 $datarray['last-child'] = 0;
3654 // only one like or dislike per person
3655 // splitted into two queries for performance issues
3656 $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",
3657 intval($datarray['uid']),
3658 intval($datarray['contact-id']),
3659 dbesc($datarray['verb']),
3660 dbesc($datarray['parent-uri'])
3666 $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",
3667 intval($datarray['uid']),
3668 intval($datarray['contact-id']),
3669 dbesc($datarray['verb']),
3670 dbesc($datarray['parent-uri'])
3677 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3679 $xo = parse_xml_string($datarray['object'],false);
3680 $xt = parse_xml_string($datarray['target'],false);
3682 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3684 // fetch the parent item
3686 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3688 intval($importer['importer_uid'])
3693 // extract tag, if not duplicate, and this user allows tags, add to parent item
3695 if($xo->id && $xo->content) {
3696 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3697 if(! (stristr($tagp[0]['tag'],$newtag))) {
3698 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3699 intval($importer['importer_uid'])
3701 if(count($i) && ! intval($i[0]['blocktags'])) {
3702 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3703 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3704 intval($tagp[0]['id']),
3705 dbesc(datetime_convert()),
3706 dbesc(datetime_convert())
3708 create_tags_from_item($tagp[0]['id']);
3716 $posted_id = item_store($datarray);
3721 $datarray["id"] = $posted_id;
3723 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3725 intval($importer['importer_uid'])
3728 $parent = $r[0]['parent'];
3729 $parent_uri = $r[0]['parent-uri'];
3733 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3734 dbesc(datetime_convert()),
3735 intval($importer['importer_uid']),
3736 intval($r[0]['parent'])
3739 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3740 dbesc(datetime_convert()),
3741 intval($importer['importer_uid']),
3746 if($posted_id && $parent) {
3748 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3750 if((! $is_like) && (! $importer['self'])) {
3752 require_once('include/enotify.php');
3755 'type' => NOTIFY_COMMENT,
3756 'notify_flags' => $importer['notify-flags'],
3757 'language' => $importer['language'],
3758 'to_name' => $importer['username'],
3759 'to_email' => $importer['email'],
3760 'uid' => $importer['importer_uid'],
3761 'item' => $datarray,
3762 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3763 'source_name' => stripslashes($datarray['author-name']),
3764 'source_link' => $datarray['author-link'],
3765 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3766 ? $importer['thumb'] : $datarray['author-avatar']),
3767 'verb' => ACTIVITY_POST,
3769 'parent' => $parent,
3770 'parent_uri' => $parent_uri,
3782 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3784 $item_id = $item->get_id();
3785 $datarray = get_atom_elements($feed,$item);
3787 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3790 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3792 intval($importer['importer_uid'])
3795 // Update content if 'updated' changes
3798 if (edited_timestamp_is_newer($r[0], $datarray)) {
3800 // do not accept (ignore) an earlier edit than one we currently have.
3801 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3804 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3805 dbesc($datarray['title']),
3806 dbesc($datarray['body']),
3807 dbesc($datarray['tag']),
3808 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3809 dbesc(datetime_convert()),
3811 intval($importer['importer_uid'])
3813 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3816 // update last-child if it changes
3818 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3819 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3820 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3821 dbesc(datetime_convert()),
3823 intval($importer['importer_uid'])
3825 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3826 intval($allow[0]['data']),
3827 dbesc(datetime_convert()),
3829 intval($importer['importer_uid'])
3835 $datarray['parent-uri'] = $parent_uri;
3836 $datarray['uid'] = $importer['importer_uid'];
3837 $datarray['contact-id'] = $importer['id'];
3838 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3839 $datarray['type'] = 'activity';
3840 $datarray['gravity'] = GRAVITY_LIKE;
3841 // only one like or dislike per person
3842 // splitted into two queries for performance issues
3843 $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",
3844 intval($datarray['uid']),
3845 intval($datarray['contact-id']),
3846 dbesc($datarray['verb']),
3852 $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",
3853 intval($datarray['uid']),
3854 intval($datarray['contact-id']),
3855 dbesc($datarray['verb']),
3863 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3865 $xo = parse_xml_string($datarray['object'],false);
3866 $xt = parse_xml_string($datarray['target'],false);
3868 if($xt->type == ACTIVITY_OBJ_NOTE) {
3869 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3871 intval($importer['importer_uid'])
3876 // extract tag, if not duplicate, add to parent item
3878 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3879 q("UPDATE item SET tag = '%s' WHERE id = %d",
3880 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3883 create_tags_from_item($r[0]['id']);
3889 $posted_id = item_store($datarray);
3891 // find out if our user is involved in this conversation and wants to be notified.
3893 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3895 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3897 intval($importer['importer_uid'])
3900 if(count($myconv)) {
3901 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3903 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3904 if(! link_compare($datarray['author-link'],$importer_url)) {
3907 foreach($myconv as $conv) {
3909 // now if we find a match, it means we're in this conversation
3911 if(! link_compare($conv['author-link'],$importer_url))
3914 require_once('include/enotify.php');
3916 $conv_parent = $conv['parent'];
3919 'type' => NOTIFY_COMMENT,
3920 'notify_flags' => $importer['notify-flags'],
3921 'language' => $importer['language'],
3922 'to_name' => $importer['username'],
3923 'to_email' => $importer['email'],
3924 'uid' => $importer['importer_uid'],
3925 'item' => $datarray,
3926 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3927 'source_name' => stripslashes($datarray['author-name']),
3928 'source_link' => $datarray['author-link'],
3929 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3930 ? $importer['thumb'] : $datarray['author-avatar']),
3931 'verb' => ACTIVITY_POST,
3933 'parent' => $conv_parent,
3934 'parent_uri' => $parent_uri
3938 // only send one notification
3950 // Head post of a conversation. Have we seen it? If not, import it.
3953 $item_id = $item->get_id();
3954 $datarray = get_atom_elements($feed,$item);
3956 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3957 $ev = bbtoevent($datarray['body']);
3958 if(x($ev,'desc') && x($ev,'start')) {
3959 $ev['cid'] = $importer['id'];
3960 $ev['uid'] = $importer['uid'];
3961 $ev['uri'] = $item_id;
3962 $ev['edited'] = $datarray['edited'];
3963 $ev['private'] = $datarray['private'];
3965 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3967 intval($importer['uid'])
3970 $ev['id'] = $r[0]['id'];
3971 $xyz = event_store($ev);
3976 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3978 intval($importer['importer_uid'])
3981 // Update content if 'updated' changes
3984 if (edited_timestamp_is_newer($r[0], $datarray)) {
3986 // do not accept (ignore) an earlier edit than one we currently have.
3987 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3990 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3991 dbesc($datarray['title']),
3992 dbesc($datarray['body']),
3993 dbesc($datarray['tag']),
3994 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3995 dbesc(datetime_convert()),
3997 intval($importer['importer_uid'])
3999 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4000 update_thread_uri($item_id, $importer['importer_uid']);
4003 // update last-child if it changes
4005 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4006 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4007 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4008 intval($allow[0]['data']),
4009 dbesc(datetime_convert()),
4011 intval($importer['importer_uid'])
4017 $datarray['parent-uri'] = $item_id;
4018 $datarray['uid'] = $importer['importer_uid'];
4019 $datarray['contact-id'] = $importer['id'];
4022 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4023 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4024 // but otherwise there's a possible data mixup on the sender's system.
4025 // the tgroup delivery code called from item_store will correct it if it's a forum,
4026 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4027 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4028 $datarray['owner-name'] = $importer['senderName'];
4029 $datarray['owner-link'] = $importer['url'];
4030 $datarray['owner-avatar'] = $importer['thumb'];
4033 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4036 // This is my contact on another system, but it's really me.
4037 // Turn this into a wall post.
4038 $notify = item_is_remote_self($importer, $datarray);
4040 $posted_id = item_store($datarray, false, $notify);
4042 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4043 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4046 $xo = parse_xml_string($datarray['object'],false);
4048 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4050 // somebody was poked/prodded. Was it me?
4052 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4054 foreach($links->link as $l) {
4055 $atts = $l->attributes();
4056 switch($atts['rel']) {
4058 $Blink = $atts['href'];
4064 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4066 // send a notification
4067 require_once('include/enotify.php');
4070 'type' => NOTIFY_POKE,
4071 'notify_flags' => $importer['notify-flags'],
4072 'language' => $importer['language'],
4073 'to_name' => $importer['username'],
4074 'to_email' => $importer['email'],
4075 'uid' => $importer['importer_uid'],
4076 'item' => $datarray,
4077 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4078 'source_name' => stripslashes($datarray['author-name']),
4079 'source_link' => $datarray['author-link'],
4080 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4081 ? $importer['thumb'] : $datarray['author-avatar']),
4082 'verb' => $datarray['verb'],
4083 'otype' => 'person',
4084 'activity' => $verb,
4085 'parent' => $datarray['parent']
4101 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4102 $url = notags(trim($datarray['author-link']));
4103 $name = notags(trim($datarray['author-name']));
4104 $photo = notags(trim($datarray['author-avatar']));
4106 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4107 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4108 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4110 if(is_array($contact)) {
4111 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4112 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4113 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4114 intval(CONTACT_IS_FRIEND),
4115 intval($contact['id']),
4116 intval($importer['uid'])
4119 // send email notification to owner?
4123 // create contact record
4125 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4126 `blocked`, `readonly`, `pending`, `writable` )
4127 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4128 intval($importer['uid']),
4129 dbesc(datetime_convert()),
4131 dbesc(normalise_link($url)),
4135 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4136 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4138 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4139 intval($importer['uid']),
4143 $contact_record = $r[0];
4145 // create notification
4146 $hash = random_string();
4148 if(is_array($contact_record)) {
4149 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4150 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4151 intval($importer['uid']),
4152 intval($contact_record['id']),
4154 dbesc(datetime_convert())
4158 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4159 intval($importer['uid'])
4164 if(intval($r[0]['def_gid'])) {
4165 require_once('include/group.php');
4166 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4169 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4170 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4173 'type' => NOTIFY_INTRO,
4174 'notify_flags' => $r[0]['notify-flags'],
4175 'language' => $r[0]['language'],
4176 'to_name' => $r[0]['username'],
4177 'to_email' => $r[0]['email'],
4178 'uid' => $r[0]['uid'],
4179 'link' => $a->get_baseurl() . '/notifications/intro',
4180 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4181 'source_link' => $contact_record['url'],
4182 'source_photo' => $contact_record['photo'],
4183 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4192 function lose_follower($importer,$contact,$datarray,$item) {
4194 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4195 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4196 intval(CONTACT_IS_SHARING),
4197 intval($contact['id'])
4201 contact_remove($contact['id']);
4205 function lose_sharer($importer,$contact,$datarray,$item) {
4207 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4208 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4209 intval(CONTACT_IS_FOLLOWER),
4210 intval($contact['id'])
4214 contact_remove($contact['id']);
4219 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4223 if(is_array($importer)) {
4224 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4225 intval($importer['uid'])
4229 // Diaspora has different message-ids in feeds than they do
4230 // through the direct Diaspora protocol. If we try and use
4231 // the feed, we'll get duplicates. So don't.
4233 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4236 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4238 // Use a single verify token, even if multiple hubs
4240 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4242 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4244 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4246 if(! strlen($contact['hub-verify'])) {
4247 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4248 dbesc($verify_token),
4249 intval($contact['id'])
4253 post_url($url,$params);
4255 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4262 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4266 $name = xmlify($name);
4267 $uri = xmlify($uri);
4270 $photo = xmlify($photo);
4274 $o .= "<name>$name</name>\r\n";
4275 $o .= "<uri>$uri</uri>\r\n";
4276 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4277 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4279 call_hooks('atom_author', $o);
4281 $o .= "</$tag>\r\n";
4285 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4289 if(! $item['parent'])
4292 if($item['deleted'])
4293 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4296 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4297 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4299 $body = $item['body'];
4302 $o = "\r\n\r\n<entry>\r\n";
4304 if(is_array($author))
4305 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4307 $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']));
4308 if(strlen($item['owner-name']))
4309 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4311 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4312 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4313 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4318 if ($item['title'] != "")
4319 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4321 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4323 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4324 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4325 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4326 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4327 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4328 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4329 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4333 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4335 if($item['location']) {
4336 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4337 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4341 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4343 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4344 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4347 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4348 if($item['bookmark'])
4349 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4352 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4355 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4357 if($item['signed_text']) {
4358 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4359 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4362 $verb = construct_verb($item);
4363 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4364 $actobj = construct_activity_object($item);
4367 $actarg = construct_activity_target($item);
4371 $tags = item_getfeedtags($item);
4373 foreach($tags as $t) {
4374 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4378 $o .= item_getfeedattach($item);
4380 $mentioned = get_mentions($item);
4384 call_hooks('atom_entry', $o);
4386 $o .= '</entry>' . "\r\n";
4391 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4393 if(get_config('system','disable_embedded'))
4398 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4399 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4404 $img_start = strpos($orig_body, '[img');
4405 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4406 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4407 while( ($img_st_close !== false) && ($img_len !== false) ) {
4409 $img_st_close++; // make it point to AFTER the closing bracket
4410 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4412 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4415 if(stristr($image , $site . '/photo/')) {
4416 // Only embed locally hosted photos
4418 $i = basename($image);
4419 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4420 $x = strpos($i,'-');
4423 $res = substr($i,$x+1);
4424 $i = substr($i,0,$x);
4425 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4432 // Check to see if we should replace this photo link with an embedded image
4433 // 1. No need to do so if the photo is public
4434 // 2. If there's a contact-id provided, see if they're in the access list
4435 // for the photo. If so, embed it.
4436 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4437 // permissions, regardless of order but first check to see if they're an exact
4438 // match to save some processing overhead.
4440 if(has_permissions($r[0])) {
4442 $recips = enumerate_permissions($r[0]);
4443 if(in_array($cid, $recips)) {
4448 if(compare_permissions($item,$r[0]))
4453 $data = $r[0]['data'];
4454 $type = $r[0]['type'];
4456 // If a custom width and height were specified, apply before embedding
4457 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4458 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4460 $width = intval($match[1]);
4461 $height = intval($match[2]);
4463 $ph = new Photo($data, $type);
4464 if($ph->is_valid()) {
4465 $ph->scaleImage(max($width, $height));
4466 $data = $ph->imageString();
4467 $type = $ph->getType();
4471 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4472 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4473 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4479 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4480 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4481 if($orig_body === false)
4484 $img_start = strpos($orig_body, '[img');
4485 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4486 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4489 $new_body = $new_body . $orig_body;
4495 function has_permissions($obj) {
4496 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4501 function compare_permissions($obj1,$obj2) {
4502 // first part is easy. Check that these are exactly the same.
4503 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4504 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4505 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4506 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4509 // This is harder. Parse all the permissions and compare the resulting set.
4511 $recipients1 = enumerate_permissions($obj1);
4512 $recipients2 = enumerate_permissions($obj2);
4515 if($recipients1 == $recipients2)
4520 // returns an array of contact-ids that are allowed to see this object
4522 function enumerate_permissions($obj) {
4523 require_once('include/group.php');
4524 $allow_people = expand_acl($obj['allow_cid']);
4525 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4526 $deny_people = expand_acl($obj['deny_cid']);
4527 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4528 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4529 $deny = array_unique(array_merge($deny_people,$deny_groups));
4530 $recipients = array_diff($recipients,$deny);
4534 function item_getfeedtags($item) {
4537 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4539 for($x = 0; $x < $cnt; $x ++) {
4541 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4545 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4547 for($x = 0; $x < $cnt; $x ++) {
4549 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4555 function item_getfeedattach($item) {
4557 $arr = explode('[/attach],',$item['attach']);
4559 foreach($arr as $r) {
4561 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4563 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4564 if(intval($matches[2]))
4565 $ret .= 'length="' . intval($matches[2]) . '" ';
4566 if($matches[4] !== ' ')
4567 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4568 $ret .= ' />' . "\r\n";
4577 function item_expire($uid, $days, $network = "", $force = false) {
4579 if((! $uid) || ($days < 1))
4582 // $expire_network_only = save your own wall posts
4583 // and just expire conversations started by others
4585 $expire_network_only = get_pconfig($uid,'expire','network_only');
4586 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4588 if ($network != "") {
4589 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4590 // There is an index "uid_network_received" but not "uid_network_created"
4591 // This avoids the creation of another index just for one purpose.
4592 // And it doesn't really matter wether to look at "received" or "created"
4593 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4595 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4597 $r = q("SELECT * FROM `item`
4598 WHERE `uid` = %d $range
4609 $expire_items = get_pconfig($uid, 'expire','items');
4610 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4612 // Forcing expiring of items - but not notes and marked items
4614 $expire_items = true;
4616 $expire_notes = get_pconfig($uid, 'expire','notes');
4617 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4619 $expire_starred = get_pconfig($uid, 'expire','starred');
4620 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4622 $expire_photos = get_pconfig($uid, 'expire','photos');
4623 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4625 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4627 foreach($r as $item) {
4629 // don't expire filed items
4631 if(strpos($item['file'],'[') !== false)
4634 // Only expire posts, not photos and photo comments
4636 if($expire_photos==0 && strlen($item['resource-id']))
4638 if($expire_starred==0 && intval($item['starred']))
4640 if($expire_notes==0 && $item['type']=='note')
4642 if($expire_items==0 && $item['type']!='note')
4645 drop_item($item['id'],false);
4648 proc_run('php',"include/notifier.php","expire","$uid");
4653 function drop_items($items) {
4656 if(! local_user() && ! remote_user())
4660 foreach($items as $item) {
4661 $owner = drop_item($item,false);
4662 if($owner && ! $uid)
4667 // multiple threads may have been deleted, send an expire notification
4670 proc_run('php',"include/notifier.php","expire","$uid");
4674 function drop_item($id,$interactive = true) {
4678 // locate item to be deleted
4680 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4687 notice( t('Item not found.') . EOL);
4688 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4693 $owner = $item['uid'];
4697 // check if logged in user is either the author or owner of this item
4699 if(is_array($_SESSION['remote'])) {
4700 foreach($_SESSION['remote'] as $visitor) {
4701 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4702 $cid = $visitor['cid'];
4709 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4711 // Check if we should do HTML-based delete confirmation
4712 if($_REQUEST['confirm']) {
4713 // <form> can't take arguments in its "action" parameter
4714 // so add any arguments as hidden inputs
4715 $query = explode_querystring($a->query_string);
4717 foreach($query['args'] as $arg) {
4718 if(strpos($arg, 'confirm=') === false) {
4719 $arg_parts = explode('=', $arg);
4720 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4724 return replace_macros(get_markup_template('confirm.tpl'), array(
4726 '$message' => t('Do you really want to delete this item?'),
4727 '$extra_inputs' => $inputs,
4728 '$confirm' => t('Yes'),
4729 '$confirm_url' => $query['base'],
4730 '$confirm_name' => 'confirmed',
4731 '$cancel' => t('Cancel'),
4734 // Now check how the user responded to the confirmation query
4735 if($_REQUEST['canceled']) {
4736 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4739 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4742 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4743 dbesc(datetime_convert()),
4744 dbesc(datetime_convert()),
4747 create_tags_from_item($item['id']);
4748 create_files_from_item($item['id']);
4749 delete_thread($item['id'], $item['parent-uri']);
4751 // clean up categories and tags so they don't end up as orphans
4754 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4756 foreach($matches as $mtch) {
4757 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4763 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4765 foreach($matches as $mtch) {
4766 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4770 // If item is a link to a photo resource, nuke all the associated photos
4771 // (visitors will not have photo resources)
4772 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4773 // generate a resource-id and therefore aren't intimately linked to the item.
4775 if(strlen($item['resource-id'])) {
4776 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4777 dbesc($item['resource-id']),
4778 intval($item['uid'])
4780 // ignore the result
4783 // If item is a link to an event, nuke the event record.
4785 if(intval($item['event-id'])) {
4786 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4787 intval($item['event-id']),
4788 intval($item['uid'])
4790 // ignore the result
4793 // clean up item_id and sign meta-data tables
4796 // Old code - caused very long queries and warning entries in the mysql logfiles:
4798 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4799 intval($item['id']),
4800 intval($item['uid'])
4803 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4804 intval($item['id']),
4805 intval($item['uid'])
4809 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4811 // Creating list of parents
4812 $r = q("select id from item where parent = %d and uid = %d",
4813 intval($item['id']),
4814 intval($item['uid'])
4819 foreach ($r AS $row) {
4820 if ($parentid != "")
4823 $parentid .= $row["id"];
4827 if ($parentid != "") {
4828 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4830 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4833 // If it's the parent of a comment thread, kill all the kids
4835 if($item['uri'] == $item['parent-uri']) {
4836 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4837 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4838 dbesc(datetime_convert()),
4839 dbesc(datetime_convert()),
4840 dbesc($item['parent-uri']),
4841 intval($item['uid'])
4843 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4844 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4845 delete_thread_uri($item['parent-uri'], $item['uid']);
4846 // ignore the result
4849 // ensure that last-child is set in case the comment that had it just got wiped.
4850 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4851 dbesc(datetime_convert()),
4852 dbesc($item['parent-uri']),
4853 intval($item['uid'])
4855 // who is the last child now?
4856 $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",
4857 dbesc($item['parent-uri']),
4858 intval($item['uid'])
4861 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4866 // Add a relayable_retraction signature for Diaspora.
4867 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4869 $drop_id = intval($item['id']);
4871 // send the notification upstream/downstream as the case may be
4873 proc_run('php',"include/notifier.php","drop","$drop_id");
4877 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4883 notice( t('Permission denied.') . EOL);
4884 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4891 function first_post_date($uid,$wall = false) {
4892 $r = q("select id, created from item
4893 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4895 order by created asc limit 1",
4897 intval($wall ? 1 : 0)
4900 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4901 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4906 /* modified posted_dates() {below} to arrange the list in years */
4907 function list_post_dates($uid, $wall) {
4908 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4910 $dthen = first_post_date($uid, $wall);
4914 // Set the start and end date to the beginning of the month
4915 $dnow = substr($dnow,0,8).'01';
4916 $dthen = substr($dthen,0,8).'01';
4920 // Starting with the current month, get the first and last days of every
4921 // month down to and including the month of the first post
4922 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4923 $dyear = intval(substr($dnow,0,4));
4924 $dstart = substr($dnow,0,8) . '01';
4925 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4926 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4927 $end_month = datetime_convert('','',$dend,'Y-m-d');
4928 $str = day_translate(datetime_convert('','',$dnow,'F'));
4930 $ret[$dyear] = array();
4931 $ret[$dyear][] = array($str,$end_month,$start_month);
4932 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4937 function posted_dates($uid,$wall) {
4938 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4940 $dthen = first_post_date($uid,$wall);
4944 // Set the start and end date to the beginning of the month
4945 $dnow = substr($dnow,0,8).'01';
4946 $dthen = substr($dthen,0,8).'01';
4949 // Starting with the current month, get the first and last days of every
4950 // month down to and including the month of the first post
4951 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4952 $dstart = substr($dnow,0,8) . '01';
4953 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4954 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4955 $end_month = datetime_convert('','',$dend,'Y-m-d');
4956 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4957 $ret[] = array($str,$end_month,$start_month);
4958 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4964 function posted_date_widget($url,$uid,$wall) {
4967 if(! feature_enabled($uid,'archives'))
4970 // For former Facebook folks that left because of "timeline"
4972 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4975 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4976 if(! $visible_years)
4979 $ret = list_post_dates($uid,$wall);
4984 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4985 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4987 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4988 '$title' => t('Archives'),
4989 '$size' => $visible_years,
4990 '$cutoff_year' => $cutoff_year,
4991 '$cutoff' => $cutoff,
4994 '$showmore' => t('show more')
5000 function store_diaspora_retract_sig($item, $user, $baseurl) {
5001 // Note that we can't add a target_author_signature
5002 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5003 // the comment, that means we're the home of the post, and Diaspora will only
5004 // check the parent_author_signature of retractions that it doesn't have to relay further
5006 // I don't think this function gets called for an "unlike," but I'll check anyway
5008 $enabled = intval(get_config('system','diaspora_enabled'));
5010 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5014 logger('drop_item: storing diaspora retraction signature');
5016 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5018 if(local_user() == $item['uid']) {
5020 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5021 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5024 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5025 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5028 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5029 // only handles DFRN deletes
5030 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5031 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5032 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5038 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5039 intval($item['id']),
5040 dbesc($signed_text),