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"] = $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 // if an OStatus conversation url was passed in, it is stored and then
1094 // removed from the array.
1095 $ostatus_conversation = null;
1097 if (isset($arr["ostatus_conversation"])) {
1098 $ostatus_conversation = $arr["ostatus_conversation"];
1099 unset($arr["ostatus_conversation"]);
1102 if(x($arr, 'gravity'))
1103 $arr['gravity'] = intval($arr['gravity']);
1104 elseif($arr['parent-uri'] === $arr['uri'])
1105 $arr['gravity'] = 0;
1106 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1107 $arr['gravity'] = 6;
1109 $arr['gravity'] = 6; // extensible catchall
1111 if(! x($arr,'type'))
1112 $arr['type'] = 'remote';
1116 /* check for create date and expire time */
1117 $uid = intval($arr['uid']);
1118 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1120 $expire_interval = $r[0]['expire'];
1121 if ($expire_interval>0) {
1122 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1123 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1124 if ($created_date < $expire_date) {
1125 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1131 // If there is no guid then take the same guid that was taken before for the same uri
1132 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1133 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1134 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1135 dbesc(trim($arr['uri']))
1139 $arr['guid'] = $r[0]["guid"];
1140 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1144 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1145 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1146 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1147 // $arr['body'] = strip_tags($arr['body']);
1150 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1151 require_once('library/langdet/Text/LanguageDetect.php');
1152 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1153 $l = new Text_LanguageDetect;
1154 //$lng = $l->detectConfidence($naked_body);
1155 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1156 $lng = $l->detect($naked_body, 3);
1158 if (sizeof($lng) > 0) {
1161 foreach ($lng as $language => $score) {
1162 if ($postopts == "")
1163 $postopts = "lang=";
1167 $postopts .= $language.";".$score;
1169 $arr['postopts'] = $postopts;
1173 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1174 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1175 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1176 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1177 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1178 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1179 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1180 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1181 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1182 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1183 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1184 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1185 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1186 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1187 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1188 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1189 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1190 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1191 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1192 $arr['deleted'] = 0;
1193 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1194 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1195 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1196 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1197 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1198 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1199 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1200 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1201 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1202 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1203 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1204 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1205 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1206 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1207 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1208 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1209 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1210 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1211 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1212 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1213 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1214 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1215 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1216 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1217 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1219 if ($arr['plink'] == "") {
1221 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1224 if ($arr['network'] == "") {
1225 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1226 intval($arr['contact-id']),
1231 $arr['network'] = $r[0]["network"];
1233 // Fallback to friendica (why is it empty in some cases?)
1234 if ($arr['network'] == "")
1235 $arr['network'] = NETWORK_DFRN;
1237 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1240 if ($arr['guid'] != "") {
1241 // Checking if there is already an item with the same guid
1242 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1243 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1244 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1247 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1252 // Check for hashtags in the body and repair or add hashtag links
1253 item_body_set_hashtags($arr);
1255 $arr['thr-parent'] = $arr['parent-uri'];
1256 if($arr['parent-uri'] === $arr['uri']) {
1258 $parent_deleted = 0;
1259 $allow_cid = $arr['allow_cid'];
1260 $allow_gid = $arr['allow_gid'];
1261 $deny_cid = $arr['deny_cid'];
1262 $deny_gid = $arr['deny_gid'];
1263 $notify_type = 'wall-new';
1267 // find the parent and snarf the item id and ACLs
1268 // and anything else we need to inherit
1270 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1271 dbesc($arr['parent-uri']),
1277 // is the new message multi-level threaded?
1278 // even though we don't support it now, preserve the info
1279 // and re-attach to the conversation parent.
1281 if($r[0]['uri'] != $r[0]['parent-uri']) {
1282 $arr['parent-uri'] = $r[0]['parent-uri'];
1283 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1284 ORDER BY `id` ASC LIMIT 1",
1285 dbesc($r[0]['parent-uri']),
1286 dbesc($r[0]['parent-uri']),
1293 $parent_id = $r[0]['id'];
1294 $parent_deleted = $r[0]['deleted'];
1295 $allow_cid = $r[0]['allow_cid'];
1296 $allow_gid = $r[0]['allow_gid'];
1297 $deny_cid = $r[0]['deny_cid'];
1298 $deny_gid = $r[0]['deny_gid'];
1299 $arr['wall'] = $r[0]['wall'];
1300 $notify_type = 'comment-new';
1302 // if the parent is private, force privacy for the entire conversation
1303 // This differs from the above settings as it subtly allows comments from
1304 // email correspondents to be private even if the overall thread is not.
1306 if($r[0]['private'])
1307 $arr['private'] = $r[0]['private'];
1309 // Edge case. We host a public forum that was originally posted to privately.
1310 // The original author commented, but as this is a comment, the permissions
1311 // weren't fixed up so it will still show the comment as private unless we fix it here.
1313 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1314 $arr['private'] = 0;
1317 // If its a post from myself then tag the thread as "mention"
1318 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1319 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1322 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1323 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1324 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1325 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1326 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1332 // Allow one to see reply tweets from status.net even when
1333 // we don't have or can't see the original post.
1336 logger('item_store: $force_parent=true, reply converted to top-level post.');
1338 $arr['parent-uri'] = $arr['uri'];
1339 $arr['gravity'] = 0;
1342 logger('item_store: item parent was not found - ignoring item');
1346 $parent_deleted = 0;
1350 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1355 if($r && count($r)) {
1356 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1359 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1360 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1361 dbesc($arr['body']),
1362 dbesc($arr['created']),
1363 intval($arr['contact-id']),
1366 if($r && count($r)) {
1367 logger('duplicated item with the same body found. ' . print_r($arr,true));
1372 // Is this item available in the global items (with uid=0)?
1373 if ($arr["uid"] == 0) {
1374 $arr["global"] = true;
1376 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1378 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1380 $arr["global"] = (count($isglobal) > 0);
1383 // Fill the cache field
1384 put_item_in_cache($arr);
1386 call_hooks('post_remote',$arr);
1388 if(x($arr,'cancel')) {
1389 logger('item_store: post cancelled by plugin.');
1393 // Store the unescaped version
1398 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1400 $r = dbq("INSERT INTO `item` (`"
1401 . implode("`, `", array_keys($arr))
1403 . implode("', '", array_values($arr))
1409 // find the item we just created
1410 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1416 $current_post = $r[0]['id'];
1417 logger('item_store: created item ' . $current_post);
1419 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1420 // This can be used to filter for inactive contacts.
1421 // Only do this for public postings to avoid privacy problems, since poco data is public.
1422 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1424 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1426 // Is it a forum? Then we don't care about the rules from above
1427 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1428 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1429 intval($arr['contact-id']));
1435 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1436 dbesc($arr['received']),
1437 dbesc($arr['received']),
1438 intval($arr['contact-id'])
1441 logger('item_store: could not locate created item');
1445 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1446 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1448 intval($arr['uid']),
1449 intval($current_post)
1453 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1454 $parent_id = $current_post;
1456 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1459 $private = $arr['private'];
1461 // Set parent id - and also make sure to inherit the parent's ACLs.
1463 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1464 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1471 intval($parent_deleted),
1472 intval($current_post)
1475 // Complete ostatus threads
1476 if ($ostatus_conversation)
1477 complete_conversation($current_post, $ostatus_conversation);
1479 $arr['id'] = $current_post;
1480 $arr['parent'] = $parent_id;
1481 $arr['allow_cid'] = $allow_cid;
1482 $arr['allow_gid'] = $allow_gid;
1483 $arr['deny_cid'] = $deny_cid;
1484 $arr['deny_gid'] = $deny_gid;
1485 $arr['private'] = $private;
1486 $arr['deleted'] = $parent_deleted;
1488 // update the commented timestamp on the parent
1489 // Only update "commented" if it is really a comment
1490 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1491 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1492 dbesc(datetime_convert()),
1493 dbesc(datetime_convert()),
1497 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1498 dbesc(datetime_convert()),
1503 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1504 intval($current_post),
1505 dbesc($dsprsig->signed_text),
1506 dbesc($dsprsig->signature),
1507 dbesc($dsprsig->signer)
1513 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1516 if($arr['last-child']) {
1517 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1519 intval($arr['uid']),
1520 intval($current_post)
1524 $deleted = tag_deliver($arr['uid'],$current_post);
1526 // current post can be deleted if is for a community page and no mention are
1528 if (!$deleted AND !$dontcache) {
1530 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1531 if (count($r) == 1) {
1532 call_hooks('post_remote_end', $r[0]);
1534 logger('item_store: new item not found in DB, id ' . $current_post);
1537 // Add every contact of the post to the global contact table
1540 create_tags_from_item($current_post);
1541 create_files_from_item($current_post);
1543 // Only check for notifications on start posts
1544 if ($arr['parent-uri'] === $arr['uri']) {
1545 add_thread($current_post);
1546 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1548 // Send a notification for every new post?
1549 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1550 intval($arr['contact-id']),
1553 $send_notification = count($r);
1555 if (!$send_notification) {
1556 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1557 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1560 foreach ($tags AS $tag) {
1561 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1562 normalise_link($tag["url"]), intval($arr['uid']));
1564 $send_notification = true;
1569 if ($send_notification) {
1570 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1571 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1572 intval($arr['uid']));
1574 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1575 intval($current_post),
1581 require_once('include/enotify.php');
1583 'type' => NOTIFY_SHARE,
1584 'notify_flags' => $u[0]['notify-flags'],
1585 'language' => $u[0]['language'],
1586 'to_name' => $u[0]['username'],
1587 'to_email' => $u[0]['email'],
1588 'uid' => $u[0]['uid'],
1590 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1591 'source_name' => $item[0]['author-name'],
1592 'source_link' => $item[0]['author-link'],
1593 'source_photo' => $item[0]['author-avatar'],
1594 'verb' => ACTIVITY_TAG,
1596 'parent' => $arr['parent']
1598 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1601 update_thread($parent_id);
1602 add_shadow_entry($arr);
1606 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1608 return $current_post;
1611 function item_body_set_hashtags(&$item) {
1613 $tags = get_tags($item["body"]);
1619 // This sorting is important when there are hashtags that are part of other hashtags
1620 // Otherwise there could be problems with hashtags like #test and #test2
1625 $URLSearchString = "^\[\]";
1627 // All hashtags should point to the home server
1628 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1629 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1631 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1632 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1634 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1635 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1637 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1640 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1642 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1645 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1647 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1650 // Repair recursive urls
1651 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1652 "#$2", $item["body"]);
1654 foreach($tags as $tag) {
1655 if(strpos($tag,'#') !== 0)
1658 if(strpos($tag,'[url='))
1661 $basetag = str_replace('_',' ',substr($tag,1));
1663 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1665 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1667 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1668 if(strlen($item["tag"]))
1669 $item["tag"] = ','.$item["tag"];
1670 $item["tag"] = $newtag.$item["tag"];
1674 // Convert back the masked hashtags
1675 $item["body"] = str_replace("#", "#", $item["body"]);
1678 function get_item_guid($id) {
1679 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1681 return($r[0]["guid"]);
1686 function get_item_id($guid, $uid = 0) {
1692 $uid == local_user();
1694 // Does the given user have this item?
1696 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1697 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1698 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1701 $nick = $r[0]["nickname"];
1705 // Or is it anywhere on the server?
1707 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1708 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1709 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1710 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1711 AND `item`.`private` = 0 AND `item`.`wall` = 1
1712 AND `item`.`guid` = '%s'", dbesc($guid));
1715 $nick = $r[0]["nickname"];
1718 return(array("nick" => $nick, "id" => $id));
1722 function get_item_contact($item,$contacts) {
1723 if(! count($contacts) || (! is_array($item)))
1725 foreach($contacts as $contact) {
1726 if($contact['id'] == $item['contact-id']) {
1728 break; // NOTREACHED
1735 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1737 * @param int $item_id
1738 * @return bool true if item was deleted, else false
1740 function tag_deliver($uid,$item_id) {
1748 $u = q("select * from user where uid = %d limit 1",
1754 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1755 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1758 $i = q("select * from item where id = %d and uid = %d limit 1",
1767 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1769 // Diaspora uses their own hardwired link URL in @-tags
1770 // instead of the one we supply with webfinger
1772 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1774 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1776 foreach($matches as $mtch) {
1777 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1779 logger('tag_deliver: mention found: ' . $mtch[2]);
1785 if ( ($community_page || $prvgroup) &&
1786 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1787 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1789 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1790 q("DELETE FROM item WHERE id = %d and uid = %d",
1800 // send a notification
1802 // use a local photo if we have one
1804 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1805 intval($u[0]['uid']),
1806 dbesc(normalise_link($item['author-link']))
1808 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1811 require_once('include/enotify.php');
1813 'type' => NOTIFY_TAGSELF,
1814 'notify_flags' => $u[0]['notify-flags'],
1815 'language' => $u[0]['language'],
1816 'to_name' => $u[0]['username'],
1817 'to_email' => $u[0]['email'],
1818 'uid' => $u[0]['uid'],
1820 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1821 'source_name' => $item['author-name'],
1822 'source_link' => $item['author-link'],
1823 'source_photo' => $photo,
1824 'verb' => ACTIVITY_TAG,
1826 'parent' => $item['parent']
1830 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1832 call_hooks('tagged', $arr);
1834 if((! $community_page) && (! $prvgroup))
1838 // tgroup delivery - setup a second delivery chain
1839 // prevent delivery looping - only proceed
1840 // if the message originated elsewhere and is a top-level post
1842 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1845 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1848 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1849 intval($u[0]['uid'])
1854 // also reset all the privacy bits to the forum default permissions
1856 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1858 $forum_mode = (($prvgroup) ? 2 : 1);
1860 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1861 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1862 intval($forum_mode),
1863 dbesc($c[0]['name']),
1864 dbesc($c[0]['url']),
1865 dbesc($c[0]['thumb']),
1867 dbesc($u[0]['allow_cid']),
1868 dbesc($u[0]['allow_gid']),
1869 dbesc($u[0]['deny_cid']),
1870 dbesc($u[0]['deny_gid']),
1873 update_thread($item_id);
1875 proc_run('php','include/notifier.php','tgroup',$item_id);
1881 function tgroup_check($uid,$item) {
1887 // check that the message originated elsewhere and is a top-level post
1889 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1893 $u = q("select * from user where uid = %d limit 1",
1899 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1900 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1903 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1905 // Diaspora uses their own hardwired link URL in @-tags
1906 // instead of the one we supply with webfinger
1908 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1910 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1912 foreach($matches as $mtch) {
1913 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1915 logger('tgroup_check: mention found: ' . $mtch[2]);
1923 if((! $community_page) && (! $prvgroup))
1937 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1941 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1943 if($contact['duplex'] && $contact['dfrn-id'])
1944 $idtosend = '0:' . $orig_id;
1945 if($contact['duplex'] && $contact['issued-id'])
1946 $idtosend = '1:' . $orig_id;
1948 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1950 $rino_enable = get_config('system','rino_encrypt');
1955 $ssl_val = intval(get_config('system','ssl_policy'));
1959 case SSL_POLICY_FULL:
1960 $ssl_policy = 'full';
1962 case SSL_POLICY_SELFSIGN:
1963 $ssl_policy = 'self';
1965 case SSL_POLICY_NONE:
1967 $ssl_policy = 'none';
1971 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1973 logger('dfrn_deliver: ' . $url);
1975 $xml = fetch_url($url);
1977 $curl_stat = $a->get_curl_code();
1979 return(-1); // timed out
1981 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1986 if(strpos($xml,'<?xml') === false) {
1987 logger('dfrn_deliver: no valid XML returned');
1988 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1992 $res = parse_xml_string($xml);
1994 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1995 return (($res->status) ? $res->status : 3);
1997 $postvars = array();
1998 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1999 $challenge = hex2bin((string) $res->challenge);
2000 $perm = (($res->perm) ? $res->perm : null);
2001 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2002 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2003 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2005 if($owner['page-flags'] == PAGE_PRVGROUP)
2008 $final_dfrn_id = '';
2011 if((($perm == 'rw') && (! intval($contact['writable'])))
2012 || (($perm == 'r') && (intval($contact['writable'])))) {
2013 q("update contact set writable = %d where id = %d",
2014 intval(($perm == 'rw') ? 1 : 0),
2015 intval($contact['id'])
2017 $contact['writable'] = (string) 1 - intval($contact['writable']);
2021 if(($contact['duplex'] && strlen($contact['pubkey']))
2022 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2023 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2024 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2025 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2028 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2029 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2032 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2034 if(strpos($final_dfrn_id,':') == 1)
2035 $final_dfrn_id = substr($final_dfrn_id,2);
2037 if($final_dfrn_id != $orig_id) {
2038 logger('dfrn_deliver: wrong dfrn_id.');
2039 // did not decode properly - cannot trust this site
2043 $postvars['dfrn_id'] = $idtosend;
2044 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2046 $postvars['dissolve'] = '1';
2049 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2050 $postvars['data'] = $atom;
2051 $postvars['perm'] = 'rw';
2054 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2055 $postvars['perm'] = 'r';
2058 $postvars['ssl_policy'] = $ssl_policy;
2061 $postvars['page'] = $page;
2063 if($rino && $rino_allowed && (! $dissolve)) {
2064 $key = substr(random_string(),0,16);
2065 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2066 $postvars['data'] = $data;
2067 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2070 if($dfrn_version >= 2.1) {
2071 if(($contact['duplex'] && strlen($contact['pubkey']))
2072 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2073 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2075 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2078 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2082 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2083 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2086 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2090 logger('md5 rawkey ' . md5($postvars['key']));
2092 $postvars['key'] = bin2hex($postvars['key']);
2095 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2097 $xml = post_url($contact['notify'],$postvars);
2099 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2101 $curl_stat = $a->get_curl_code();
2102 if((! $curl_stat) || (! strlen($xml)))
2103 return(-1); // timed out
2105 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2108 if(strpos($xml,'<?xml') === false) {
2109 logger('dfrn_deliver: phase 2: no valid XML returned');
2110 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2114 if($contact['term-date'] != '0000-00-00 00:00:00') {
2115 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2116 require_once('include/Contact.php');
2117 unmark_for_death($contact);
2120 $res = parse_xml_string($xml);
2122 return $res->status;
2127 This function returns true if $update has an edited timestamp newer
2128 than $existing, i.e. $update contains new data which should override
2129 what's already there. If there is no timestamp yet, the update is
2130 assumed to be newer. If the update has no timestamp, the existing
2131 item is assumed to be up-to-date. If the timestamps are equal it
2132 assumes the update has been seen before and should be ignored.
2134 function edited_timestamp_is_newer($existing, $update) {
2135 if (!x($existing,'edited') || !$existing['edited']) {
2138 if (!x($update,'edited') || !$update['edited']) {
2141 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2142 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2143 return (strcmp($existing_edited, $update_edited) < 0);
2148 * consume_feed - process atom feed and update anything/everything we might need to update
2150 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2152 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2153 * It is this person's stuff that is going to be updated.
2154 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2155 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2156 * have a contact record.
2157 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2158 * might not) try and subscribe to it.
2159 * $datedir sorts in reverse order
2160 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2161 * imported prior to its children being seen in the stream unless we are certain
2162 * of how the feed is arranged/ordered.
2163 * With $pass = 1, we only pull parent items out of the stream.
2164 * With $pass = 2, we only pull children (comments/likes).
2166 * So running this twice, first with pass 1 and then with pass 2 will do the right
2167 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2168 * model where comments can have sub-threads. That would require some massive sorting
2169 * to get all the feed items into a mostly linear ordering, and might still require
2173 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2175 require_once('library/simplepie/simplepie.inc');
2176 require_once('include/contact_selectors.php');
2178 if(! strlen($xml)) {
2179 logger('consume_feed: empty input');
2183 $feed = new SimplePie();
2184 $feed->set_raw_data($xml);
2186 $feed->enable_order_by_date(true);
2188 $feed->enable_order_by_date(false);
2192 logger('consume_feed: Error parsing XML: ' . $feed->error());
2194 $permalink = $feed->get_permalink();
2196 // Check at the feed level for updated contact name and/or photo
2200 $photo_timestamp = '';
2203 $contact_updated = '';
2205 $hubs = $feed->get_links('hub');
2206 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2209 $hub = implode(',', $hubs);
2211 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2213 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2215 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2216 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2217 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2218 $new_name = $elems['name'][0]['data'];
2220 // Manually checking for changed contact names
2221 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2222 $name_updated = date("c");
2223 $photo_timestamp = date("c");
2226 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2227 if ($photo_timestamp == "")
2228 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2229 $photo_url = $elems['link'][0]['attribs']['']['href'];
2232 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2233 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2237 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2238 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2240 $contact_updated = $photo_timestamp;
2242 require_once("include/Photo.php");
2243 $photo_failure = false;
2244 $have_photo = false;
2246 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2247 intval($contact['id']),
2248 intval($contact['uid'])
2251 $resource_id = $r[0]['resource-id'];
2255 $resource_id = photo_new_resource();
2258 $img_str = fetch_url($photo_url,true);
2259 // guess mimetype from headers or filename
2260 $type = guess_image_type($photo_url,true);
2263 $img = new Photo($img_str, $type);
2264 if($img->is_valid()) {
2266 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2267 dbesc($resource_id),
2268 intval($contact['id']),
2269 intval($contact['uid'])
2273 $img->scaleImageSquare(175);
2275 $hash = $resource_id;
2276 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2278 $img->scaleImage(80);
2279 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2281 $img->scaleImage(48);
2282 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2286 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2287 WHERE `uid` = %d AND `id` = %d",
2288 dbesc(datetime_convert()),
2289 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2290 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2291 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2292 intval($contact['uid']),
2293 intval($contact['id'])
2298 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2299 if ($name_updated > $contact_updated)
2300 $contact_updated = $name_updated;
2302 $r = q("select * from contact where uid = %d and id = %d limit 1",
2303 intval($contact['uid']),
2304 intval($contact['id'])
2307 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2308 dbesc(notags(trim($new_name))),
2309 dbesc(datetime_convert()),
2310 intval($contact['uid']),
2311 intval($contact['id'])
2314 // do our best to update the name on content items
2317 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2318 dbesc(notags(trim($new_name))),
2319 dbesc($r[0]['name']),
2320 dbesc($r[0]['url']),
2321 intval($contact['uid'])
2326 if ($contact_updated AND $new_name AND $photo_url)
2327 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2329 if(strlen($birthday)) {
2330 if(substr($birthday,0,4) != $contact['bdyear']) {
2331 logger('consume_feed: updating birthday: ' . $birthday);
2335 * Add new birthday event for this person
2337 * $bdtext is just a readable placeholder in case the event is shared
2338 * with others. We will replace it during presentation to our $importer
2339 * to contain a sparkle link and perhaps a photo.
2343 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2344 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2347 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2348 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2349 intval($contact['uid']),
2350 intval($contact['id']),
2351 dbesc(datetime_convert()),
2352 dbesc(datetime_convert()),
2353 dbesc(datetime_convert('UTC','UTC', $birthday)),
2354 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2363 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2364 dbesc(substr($birthday,0,4)),
2365 intval($contact['uid']),
2366 intval($contact['id'])
2369 // This function is called twice without reloading the contact
2370 // Make sure we only create one event. This is why &$contact
2371 // is a reference var in this function
2373 $contact['bdyear'] = substr($birthday,0,4);
2377 $community_page = 0;
2378 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2380 $community_page = intval($rawtags[0]['data']);
2382 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2383 q("update contact set forum = %d where id = %d",
2384 intval($community_page),
2385 intval($contact['id'])
2387 $contact['forum'] = (string) $community_page;
2391 // process any deleted entries
2393 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2394 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2395 foreach($del_entries as $dentry) {
2397 if(isset($dentry['attribs']['']['ref'])) {
2398 $uri = $dentry['attribs']['']['ref'];
2400 if(isset($dentry['attribs']['']['when'])) {
2401 $when = $dentry['attribs']['']['when'];
2402 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2405 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2407 if($deleted && is_array($contact)) {
2408 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2409 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2411 intval($importer['uid']),
2412 intval($contact['id'])
2417 if(! $item['deleted'])
2418 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2420 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2421 $xo = parse_xml_string($item['object'],false);
2422 $xt = parse_xml_string($item['target'],false);
2423 if($xt->type === ACTIVITY_OBJ_NOTE) {
2424 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2426 intval($importer['importer_uid'])
2430 // For tags, the owner cannot remove the tag on the author's copy of the post.
2432 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2433 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2434 $author_copy = (($item['origin']) ? true : false);
2436 if($owner_remove && $author_copy)
2438 if($author_remove || $owner_remove) {
2439 $tags = explode(',',$i[0]['tag']);
2442 foreach($tags as $tag)
2443 if(trim($tag) !== trim($xo->body))
2444 $newtags[] = trim($tag);
2446 q("update item set tag = '%s' where id = %d",
2447 dbesc(implode(',',$newtags)),
2450 create_tags_from_item($i[0]['id']);
2456 if($item['uri'] == $item['parent-uri']) {
2457 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2458 `body` = '', `title` = ''
2459 WHERE `parent-uri` = '%s' AND `uid` = %d",
2461 dbesc(datetime_convert()),
2462 dbesc($item['uri']),
2463 intval($importer['uid'])
2465 create_tags_from_itemuri($item['uri'], $importer['uid']);
2466 create_files_from_itemuri($item['uri'], $importer['uid']);
2467 update_thread_uri($item['uri'], $importer['uid']);
2470 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2471 `body` = '', `title` = ''
2472 WHERE `uri` = '%s' AND `uid` = %d",
2474 dbesc(datetime_convert()),
2476 intval($importer['uid'])
2478 create_tags_from_itemuri($uri, $importer['uid']);
2479 create_files_from_itemuri($uri, $importer['uid']);
2480 if($item['last-child']) {
2481 // ensure that last-child is set in case the comment that had it just got wiped.
2482 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2483 dbesc(datetime_convert()),
2484 dbesc($item['parent-uri']),
2485 intval($item['uid'])
2487 // who is the last child now?
2488 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2489 ORDER BY `created` DESC LIMIT 1",
2490 dbesc($item['parent-uri']),
2491 intval($importer['uid'])
2494 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2505 // Now process the feed
2507 if($feed->get_item_quantity()) {
2509 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2511 // in inverse date order
2513 $items = array_reverse($feed->get_items());
2515 $items = $feed->get_items();
2518 foreach($items as $item) {
2521 $item_id = $item->get_id();
2522 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2523 if(isset($rawthread[0]['attribs']['']['ref'])) {
2525 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2528 if(($is_reply) && is_array($contact)) {
2533 // not allowed to post
2535 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2539 // Have we seen it? If not, import it.
2541 $item_id = $item->get_id();
2542 $datarray = get_atom_elements($feed, $item, $contact);
2544 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2545 $datarray['author-name'] = $contact['name'];
2546 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2547 $datarray['author-link'] = $contact['url'];
2548 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2549 $datarray['author-avatar'] = $contact['thumb'];
2551 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2552 logger('consume_feed: no author information! ' . print_r($datarray,true));
2556 $force_parent = false;
2557 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2558 if($contact['network'] === NETWORK_OSTATUS)
2559 $force_parent = true;
2560 if(strlen($datarray['title']))
2561 unset($datarray['title']);
2562 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2563 dbesc(datetime_convert()),
2565 intval($importer['uid'])
2567 $datarray['last-child'] = 1;
2568 update_thread_uri($parent_uri, $importer['uid']);
2572 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2574 intval($importer['uid'])
2577 // Update content if 'updated' changes
2580 if (edited_timestamp_is_newer($r[0], $datarray)) {
2582 // do not accept (ignore) an earlier edit than one we currently have.
2583 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2586 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2587 dbesc($datarray['title']),
2588 dbesc($datarray['body']),
2589 dbesc($datarray['tag']),
2590 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2591 dbesc(datetime_convert()),
2593 intval($importer['uid'])
2595 create_tags_from_itemuri($item_id, $importer['uid']);
2596 update_thread_uri($item_id, $importer['uid']);
2599 // update last-child if it changes
2601 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2602 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2603 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2604 dbesc(datetime_convert()),
2606 intval($importer['uid'])
2608 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2609 intval($allow[0]['data']),
2610 dbesc(datetime_convert()),
2612 intval($importer['uid'])
2614 update_thread_uri($item_id, $importer['uid']);
2620 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2621 // one way feed - no remote comment ability
2622 $datarray['last-child'] = 0;
2624 $datarray['parent-uri'] = $parent_uri;
2625 $datarray['uid'] = $importer['uid'];
2626 $datarray['contact-id'] = $contact['id'];
2627 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2628 $datarray['type'] = 'activity';
2629 $datarray['gravity'] = GRAVITY_LIKE;
2630 // only one like or dislike per person
2631 // splitted into two queries for performance issues
2632 $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",
2633 intval($datarray['uid']),
2634 intval($datarray['contact-id']),
2635 dbesc($datarray['verb']),
2641 $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",
2642 intval($datarray['uid']),
2643 intval($datarray['contact-id']),
2644 dbesc($datarray['verb']),
2651 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2652 $xo = parse_xml_string($datarray['object'],false);
2653 $xt = parse_xml_string($datarray['target'],false);
2655 if($xt->type == ACTIVITY_OBJ_NOTE) {
2656 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2658 intval($importer['importer_uid'])
2663 // extract tag, if not duplicate, add to parent item
2664 if($xo->id && $xo->content) {
2665 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2666 if(! (stristr($r[0]['tag'],$newtag))) {
2667 q("UPDATE item SET tag = '%s' WHERE id = %d",
2668 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2671 create_tags_from_item($r[0]['id']);
2677 $r = item_store($datarray,$force_parent);
2683 // Head post of a conversation. Have we seen it? If not, import it.
2685 $item_id = $item->get_id();
2687 $datarray = get_atom_elements($feed, $item, $contact);
2689 if(is_array($contact)) {
2690 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2691 $datarray['author-name'] = $contact['name'];
2692 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2693 $datarray['author-link'] = $contact['url'];
2694 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2695 $datarray['author-avatar'] = $contact['thumb'];
2698 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2699 logger('consume_feed: no author information! ' . print_r($datarray,true));
2703 // special handling for events
2705 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2706 $ev = bbtoevent($datarray['body']);
2707 if(x($ev,'desc') && x($ev,'start')) {
2708 $ev['uid'] = $importer['uid'];
2709 $ev['uri'] = $item_id;
2710 $ev['edited'] = $datarray['edited'];
2711 $ev['private'] = $datarray['private'];
2713 if(is_array($contact))
2714 $ev['cid'] = $contact['id'];
2715 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2717 intval($importer['uid'])
2720 $ev['id'] = $r[0]['id'];
2721 $xyz = event_store($ev);
2726 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2727 if(strlen($datarray['title']))
2728 unset($datarray['title']);
2729 $datarray['last-child'] = 1;
2733 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2735 intval($importer['uid'])
2738 // Update content if 'updated' changes
2741 if (edited_timestamp_is_newer($r[0], $datarray)) {
2743 // do not accept (ignore) an earlier edit than one we currently have.
2744 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2747 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2748 dbesc($datarray['title']),
2749 dbesc($datarray['body']),
2750 dbesc($datarray['tag']),
2751 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2752 dbesc(datetime_convert()),
2754 intval($importer['uid'])
2756 create_tags_from_itemuri($item_id, $importer['uid']);
2757 update_thread_uri($item_id, $importer['uid']);
2760 // update last-child if it changes
2762 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2763 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2764 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2765 intval($allow[0]['data']),
2766 dbesc(datetime_convert()),
2768 intval($importer['uid'])
2770 update_thread_uri($item_id, $importer['uid']);
2775 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2776 logger('consume-feed: New follower');
2777 new_follower($importer,$contact,$datarray,$item);
2780 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2781 lose_follower($importer,$contact,$datarray,$item);
2785 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2786 logger('consume-feed: New friend request');
2787 new_follower($importer,$contact,$datarray,$item,true);
2790 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2791 lose_sharer($importer,$contact,$datarray,$item);
2796 if(! is_array($contact))
2800 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2801 // one way feed - no remote comment ability
2802 $datarray['last-child'] = 0;
2804 if($contact['network'] === NETWORK_FEED)
2805 $datarray['private'] = 2;
2807 $datarray['parent-uri'] = $item_id;
2808 $datarray['uid'] = $importer['uid'];
2809 $datarray['contact-id'] = $contact['id'];
2811 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2812 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2813 // but otherwise there's a possible data mixup on the sender's system.
2814 // the tgroup delivery code called from item_store will correct it if it's a forum,
2815 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2816 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2817 $datarray['owner-name'] = $contact['name'];
2818 $datarray['owner-link'] = $contact['url'];
2819 $datarray['owner-avatar'] = $contact['thumb'];
2822 // We've allowed "followers" to reach this point so we can decide if they are
2823 // posting an @-tag delivery, which followers are allowed to do for certain
2824 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2826 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2829 // This is my contact on another system, but it's really me.
2830 // Turn this into a wall post.
2831 $notify = item_is_remote_self($contact, $datarray);
2833 $r = item_store($datarray, false, $notify);
2834 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2842 function item_is_remote_self($contact, &$datarray) {
2845 if (!$contact['remote_self'])
2848 // Prevent the forwarding of posts that are forwarded
2849 if ($datarray["extid"] == NETWORK_DFRN)
2852 // Prevent to forward already forwarded posts
2853 if ($datarray["app"] == $a->get_hostname())
2856 // Only forward posts
2857 if ($datarray["verb"] != ACTIVITY_POST)
2860 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2863 $datarray2 = $datarray;
2864 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2865 if ($contact['remote_self'] == 2) {
2866 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2867 intval($contact['uid']));
2869 $datarray['contact-id'] = $r[0]["id"];
2871 $datarray['owner-name'] = $r[0]["name"];
2872 $datarray['owner-link'] = $r[0]["url"];
2873 $datarray['owner-avatar'] = $r[0]["thumb"];
2875 $datarray['author-name'] = $datarray['owner-name'];
2876 $datarray['author-link'] = $datarray['owner-link'];
2877 $datarray['author-avatar'] = $datarray['owner-avatar'];
2880 if ($contact['network'] != NETWORK_FEED) {
2881 $datarray["guid"] = get_guid(32);
2882 unset($datarray["plink"]);
2883 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2884 $datarray["parent-uri"] = $datarray["uri"];
2885 $datarray["extid"] = $contact['network'];
2886 $urlpart = parse_url($datarray2['author-link']);
2887 $datarray["app"] = $urlpart["host"];
2889 $datarray['private'] = 0;
2892 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2893 // $datarray["app"] = network_to_name($contact['network']);
2895 if ($contact['network'] != NETWORK_FEED) {
2896 // Store the original post
2897 $r = item_store($datarray2, false, false);
2898 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2900 $datarray["app"] = "Feed";
2905 function local_delivery($importer,$data) {
2908 logger(__function__, LOGGER_TRACE);
2910 if($importer['readonly']) {
2911 // We aren't receiving stuff from this person. But we will quietly ignore them
2912 // rather than a blatant "go away" message.
2913 logger('local_delivery: ignoring');
2918 // Consume notification feed. This may differ from consuming a public feed in several ways
2919 // - might contain email or friend suggestions
2920 // - might contain remote followup to our message
2921 // - in which case we need to accept it and then notify other conversants
2922 // - we may need to send various email notifications
2924 $feed = new SimplePie();
2925 $feed->set_raw_data($data);
2926 $feed->enable_order_by_date(false);
2931 logger('local_delivery: Error parsing XML: ' . $feed->error());
2934 // Check at the feed level for updated contact name and/or photo
2938 $photo_timestamp = '';
2940 $contact_updated = '';
2943 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2945 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2947 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2950 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2951 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2952 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2953 $new_name = $elems['name'][0]['data'];
2955 // Manually checking for changed contact names
2956 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2957 $name_updated = date("c");
2958 $photo_timestamp = date("c");
2961 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2962 if ($photo_timestamp == "")
2963 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2964 $photo_url = $elems['link'][0]['attribs']['']['href'];
2968 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2970 $contact_updated = $photo_timestamp;
2972 logger('local_delivery: Updating photo for ' . $importer['name']);
2973 require_once("include/Photo.php");
2974 $photo_failure = false;
2975 $have_photo = false;
2977 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2978 intval($importer['id']),
2979 intval($importer['importer_uid'])
2982 $resource_id = $r[0]['resource-id'];
2986 $resource_id = photo_new_resource();
2989 $img_str = fetch_url($photo_url,true);
2990 // guess mimetype from headers or filename
2991 $type = guess_image_type($photo_url,true);
2994 $img = new Photo($img_str, $type);
2995 if($img->is_valid()) {
2997 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2998 dbesc($resource_id),
2999 intval($importer['id']),
3000 intval($importer['importer_uid'])
3004 $img->scaleImageSquare(175);
3006 $hash = $resource_id;
3007 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3009 $img->scaleImage(80);
3010 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3012 $img->scaleImage(48);
3013 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3017 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3018 WHERE `uid` = %d AND `id` = %d",
3019 dbesc(datetime_convert()),
3020 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3021 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3022 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3023 intval($importer['importer_uid']),
3024 intval($importer['id'])
3029 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3030 if ($name_updated > $contact_updated)
3031 $contact_updated = $name_updated;
3033 $r = q("select * from contact where uid = %d and id = %d limit 1",
3034 intval($importer['importer_uid']),
3035 intval($importer['id'])
3038 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3039 dbesc(notags(trim($new_name))),
3040 dbesc(datetime_convert()),
3041 intval($importer['importer_uid']),
3042 intval($importer['id'])
3045 // do our best to update the name on content items
3048 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3049 dbesc(notags(trim($new_name))),
3050 dbesc($r[0]['name']),
3051 dbesc($r[0]['url']),
3052 intval($importer['importer_uid'])
3057 if ($contact_updated AND $new_name AND $photo_url)
3058 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3060 // Currently unsupported - needs a lot of work
3061 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3062 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3063 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3065 $newloc['uid'] = $importer['importer_uid'];
3066 $newloc['cid'] = $importer['id'];
3067 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3068 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3069 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3070 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3071 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3072 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3073 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3074 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3075 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3076 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3077 /** relocated user must have original key pair */
3078 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3079 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3081 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3084 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3085 intval($importer['id']),
3086 intval($importer['importer_uid']));
3091 $x = q("UPDATE contact SET
3102 `site-pubkey` = '%s'
3103 WHERE id=%d AND uid=%d;",
3104 dbesc($newloc['name']),
3105 dbesc($newloc['photo']),
3106 dbesc($newloc['thumb']),
3107 dbesc($newloc['micro']),
3108 dbesc($newloc['url']),
3109 dbesc(normalise_link($newloc['url'])),
3110 dbesc($newloc['request']),
3111 dbesc($newloc['confirm']),
3112 dbesc($newloc['notify']),
3113 dbesc($newloc['poll']),
3114 dbesc($newloc['sitepubkey']),
3115 intval($importer['id']),
3116 intval($importer['importer_uid']));
3122 'owner-link' => array($old['url'], $newloc['url']),
3123 'author-link' => array($old['url'], $newloc['url']),
3124 'owner-avatar' => array($old['photo'], $newloc['photo']),
3125 'author-avatar' => array($old['photo'], $newloc['photo']),
3127 foreach ($fields as $n=>$f){
3128 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3131 intval($importer['importer_uid']));
3137 // merge with current record, current contents have priority
3138 // update record, set url-updated
3139 // update profile photos
3145 // handle friend suggestion notification
3147 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3148 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3149 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3151 $fsugg['uid'] = $importer['importer_uid'];
3152 $fsugg['cid'] = $importer['id'];
3153 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3154 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3155 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3156 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3157 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3159 // Does our member already have a friend matching this description?
3161 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3162 dbesc($fsugg['name']),
3163 dbesc(normalise_link($fsugg['url'])),
3164 intval($fsugg['uid'])
3169 // Do we already have an fcontact record for this person?
3172 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3173 dbesc($fsugg['url']),
3174 dbesc($fsugg['name']),
3175 dbesc($fsugg['request'])
3180 // OK, we do. Do we already have an introduction for this person ?
3181 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3182 intval($fsugg['uid']),
3189 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3190 dbesc($fsugg['name']),
3191 dbesc($fsugg['url']),
3192 dbesc($fsugg['photo']),
3193 dbesc($fsugg['request'])
3195 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3196 dbesc($fsugg['url']),
3197 dbesc($fsugg['name']),
3198 dbesc($fsugg['request'])
3203 // database record did not get created. Quietly give up.
3208 $hash = random_string();
3210 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3211 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3212 intval($fsugg['uid']),
3214 intval($fsugg['cid']),
3215 dbesc($fsugg['body']),
3217 dbesc(datetime_convert()),
3222 'type' => NOTIFY_SUGGEST,
3223 'notify_flags' => $importer['notify-flags'],
3224 'language' => $importer['language'],
3225 'to_name' => $importer['username'],
3226 'to_email' => $importer['email'],
3227 'uid' => $importer['importer_uid'],
3229 'link' => $a->get_baseurl() . '/notifications/intros',
3230 'source_name' => $importer['name'],
3231 'source_link' => $importer['url'],
3232 'source_photo' => $importer['photo'],
3233 'verb' => ACTIVITY_REQ_FRIEND,
3242 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3243 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3245 logger('local_delivery: private message received');
3248 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3251 $msg['uid'] = $importer['importer_uid'];
3252 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3253 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3254 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3255 $msg['contact-id'] = $importer['id'];
3256 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3257 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3259 $msg['replied'] = 0;
3260 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3261 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3262 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3266 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3267 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3269 // send notifications.
3271 require_once('include/enotify.php');
3273 $notif_params = array(
3274 'type' => NOTIFY_MAIL,
3275 'notify_flags' => $importer['notify-flags'],
3276 'language' => $importer['language'],
3277 'to_name' => $importer['username'],
3278 'to_email' => $importer['email'],
3279 'uid' => $importer['importer_uid'],
3281 'source_name' => $msg['from-name'],
3282 'source_link' => $importer['url'],
3283 'source_photo' => $importer['thumb'],
3284 'verb' => ACTIVITY_POST,
3288 notification($notif_params);
3294 $community_page = 0;
3295 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3297 $community_page = intval($rawtags[0]['data']);
3299 if(intval($importer['forum']) != $community_page) {
3300 q("update contact set forum = %d where id = %d",
3301 intval($community_page),
3302 intval($importer['id'])
3304 $importer['forum'] = (string) $community_page;
3307 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3309 // process any deleted entries
3311 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3312 if(is_array($del_entries) && count($del_entries)) {
3313 foreach($del_entries as $dentry) {
3315 if(isset($dentry['attribs']['']['ref'])) {
3316 $uri = $dentry['attribs']['']['ref'];
3318 if(isset($dentry['attribs']['']['when'])) {
3319 $when = $dentry['attribs']['']['when'];
3320 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3323 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3327 // check for relayed deletes to our conversation
3330 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3332 intval($importer['importer_uid'])
3335 $parent_uri = $r[0]['parent-uri'];
3336 if($r[0]['id'] != $r[0]['parent'])
3343 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3346 logger('local_delivery: possible community delete');
3349 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3351 // was the top-level post for this reply written by somebody on this site?
3352 // Specifically, the recipient?
3354 $is_a_remote_delete = false;
3356 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3357 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3358 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3359 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3360 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3361 AND `item`.`uid` = %d
3367 intval($importer['importer_uid'])
3370 $is_a_remote_delete = true;
3372 // Does this have the characteristics of a community or private group comment?
3373 // If it's a reply to a wall post on a community/prvgroup page it's a
3374 // valid community comment. Also forum_mode makes it valid for sure.
3375 // If neither, it's not.
3377 if($is_a_remote_delete && $community) {
3378 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3379 $is_a_remote_delete = false;
3380 logger('local_delivery: not a community delete');
3384 if($is_a_remote_delete) {
3385 logger('local_delivery: received remote delete');
3389 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3390 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3392 intval($importer['importer_uid']),
3393 intval($importer['id'])
3399 if($item['deleted'])
3402 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3404 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3405 $xo = parse_xml_string($item['object'],false);
3406 $xt = parse_xml_string($item['target'],false);
3408 if($xt->type === ACTIVITY_OBJ_NOTE) {
3409 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3411 intval($importer['importer_uid'])
3415 // For tags, the owner cannot remove the tag on the author's copy of the post.
3417 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3418 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3419 $author_copy = (($item['origin']) ? true : false);
3421 if($owner_remove && $author_copy)
3423 if($author_remove || $owner_remove) {
3424 $tags = explode(',',$i[0]['tag']);
3427 foreach($tags as $tag)
3428 if(trim($tag) !== trim($xo->body))
3429 $newtags[] = trim($tag);
3431 q("update item set tag = '%s' where id = %d",
3432 dbesc(implode(',',$newtags)),
3435 create_tags_from_item($i[0]['id']);
3441 if($item['uri'] == $item['parent-uri']) {
3442 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3443 `body` = '', `title` = ''
3444 WHERE `parent-uri` = '%s' AND `uid` = %d",
3446 dbesc(datetime_convert()),
3447 dbesc($item['uri']),
3448 intval($importer['importer_uid'])
3450 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3451 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3452 update_thread_uri($item['uri'], $importer['importer_uid']);
3455 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3456 `body` = '', `title` = ''
3457 WHERE `uri` = '%s' AND `uid` = %d",
3459 dbesc(datetime_convert()),
3461 intval($importer['importer_uid'])
3463 create_tags_from_itemuri($uri, $importer['importer_uid']);
3464 create_files_from_itemuri($uri, $importer['importer_uid']);
3465 update_thread_uri($uri, $importer['importer_uid']);
3466 if($item['last-child']) {
3467 // ensure that last-child is set in case the comment that had it just got wiped.
3468 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3469 dbesc(datetime_convert()),
3470 dbesc($item['parent-uri']),
3471 intval($item['uid'])
3473 // who is the last child now?
3474 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3475 ORDER BY `created` DESC LIMIT 1",
3476 dbesc($item['parent-uri']),
3477 intval($importer['importer_uid'])
3480 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3485 // if this is a relayed delete, propagate it to other recipients
3487 if($is_a_remote_delete)
3488 proc_run('php',"include/notifier.php","drop",$item['id']);
3496 foreach($feed->get_items() as $item) {
3499 $item_id = $item->get_id();
3500 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3501 if(isset($rawthread[0]['attribs']['']['ref'])) {
3503 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3509 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3512 logger('local_delivery: possible community reply');
3515 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3517 // was the top-level post for this reply written by somebody on this site?
3518 // Specifically, the recipient?
3520 $is_a_remote_comment = false;
3521 $top_uri = $parent_uri;
3523 $r = q("select `item`.`parent-uri` from `item`
3524 WHERE `item`.`uri` = '%s'
3528 if($r && count($r)) {
3529 $top_uri = $r[0]['parent-uri'];
3531 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3532 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3533 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3534 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3535 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3536 AND `item`.`uid` = %d
3542 intval($importer['importer_uid'])
3545 $is_a_remote_comment = true;
3548 // Does this have the characteristics of a community or private group comment?
3549 // If it's a reply to a wall post on a community/prvgroup page it's a
3550 // valid community comment. Also forum_mode makes it valid for sure.
3551 // If neither, it's not.
3553 if($is_a_remote_comment && $community) {
3554 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3555 $is_a_remote_comment = false;
3556 logger('local_delivery: not a community reply');
3560 if($is_a_remote_comment) {
3561 logger('local_delivery: received remote comment');
3563 // remote reply to our post. Import and then notify everybody else.
3565 $datarray = get_atom_elements($feed, $item);
3567 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3569 intval($importer['importer_uid'])
3572 // Update content if 'updated' changes
3576 if (edited_timestamp_is_newer($r[0], $datarray)) {
3578 // do not accept (ignore) an earlier edit than one we currently have.
3579 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3582 logger('received updated comment' , LOGGER_DEBUG);
3583 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3584 dbesc($datarray['title']),
3585 dbesc($datarray['body']),
3586 dbesc($datarray['tag']),
3587 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3588 dbesc(datetime_convert()),
3590 intval($importer['importer_uid'])
3592 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3594 proc_run('php',"include/notifier.php","comment-import",$iid);
3603 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3604 intval($importer['importer_uid'])
3608 $datarray['type'] = 'remote-comment';
3609 $datarray['wall'] = 1;
3610 $datarray['parent-uri'] = $parent_uri;
3611 $datarray['uid'] = $importer['importer_uid'];
3612 $datarray['owner-name'] = $own[0]['name'];
3613 $datarray['owner-link'] = $own[0]['url'];
3614 $datarray['owner-avatar'] = $own[0]['thumb'];
3615 $datarray['contact-id'] = $importer['id'];
3617 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3619 $datarray['type'] = 'activity';
3620 $datarray['gravity'] = GRAVITY_LIKE;
3621 $datarray['last-child'] = 0;
3622 // only one like or dislike per person
3623 // splitted into two queries for performance issues
3624 $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",
3625 intval($datarray['uid']),
3626 intval($datarray['contact-id']),
3627 dbesc($datarray['verb']),
3628 dbesc($datarray['parent-uri'])
3634 $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",
3635 intval($datarray['uid']),
3636 intval($datarray['contact-id']),
3637 dbesc($datarray['verb']),
3638 dbesc($datarray['parent-uri'])
3645 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3647 $xo = parse_xml_string($datarray['object'],false);
3648 $xt = parse_xml_string($datarray['target'],false);
3650 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3652 // fetch the parent item
3654 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3656 intval($importer['importer_uid'])
3661 // extract tag, if not duplicate, and this user allows tags, add to parent item
3663 if($xo->id && $xo->content) {
3664 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3665 if(! (stristr($tagp[0]['tag'],$newtag))) {
3666 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3667 intval($importer['importer_uid'])
3669 if(count($i) && ! intval($i[0]['blocktags'])) {
3670 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3671 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3672 intval($tagp[0]['id']),
3673 dbesc(datetime_convert()),
3674 dbesc(datetime_convert())
3676 create_tags_from_item($tagp[0]['id']);
3684 $posted_id = item_store($datarray);
3689 $datarray["id"] = $posted_id;
3691 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3693 intval($importer['importer_uid'])
3696 $parent = $r[0]['parent'];
3697 $parent_uri = $r[0]['parent-uri'];
3701 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3702 dbesc(datetime_convert()),
3703 intval($importer['importer_uid']),
3704 intval($r[0]['parent'])
3707 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3708 dbesc(datetime_convert()),
3709 intval($importer['importer_uid']),
3714 if($posted_id && $parent) {
3716 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3718 if((! $is_like) && (! $importer['self'])) {
3720 require_once('include/enotify.php');
3723 'type' => NOTIFY_COMMENT,
3724 'notify_flags' => $importer['notify-flags'],
3725 'language' => $importer['language'],
3726 'to_name' => $importer['username'],
3727 'to_email' => $importer['email'],
3728 'uid' => $importer['importer_uid'],
3729 'item' => $datarray,
3730 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3731 'source_name' => stripslashes($datarray['author-name']),
3732 'source_link' => $datarray['author-link'],
3733 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3734 ? $importer['thumb'] : $datarray['author-avatar']),
3735 'verb' => ACTIVITY_POST,
3737 'parent' => $parent,
3738 'parent_uri' => $parent_uri,
3750 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3752 $item_id = $item->get_id();
3753 $datarray = get_atom_elements($feed,$item);
3755 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3758 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3760 intval($importer['importer_uid'])
3763 // Update content if 'updated' changes
3766 if (edited_timestamp_is_newer($r[0], $datarray)) {
3768 // do not accept (ignore) an earlier edit than one we currently have.
3769 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3772 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3773 dbesc($datarray['title']),
3774 dbesc($datarray['body']),
3775 dbesc($datarray['tag']),
3776 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3777 dbesc(datetime_convert()),
3779 intval($importer['importer_uid'])
3781 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3784 // update last-child if it changes
3786 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3787 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3788 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3789 dbesc(datetime_convert()),
3791 intval($importer['importer_uid'])
3793 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3794 intval($allow[0]['data']),
3795 dbesc(datetime_convert()),
3797 intval($importer['importer_uid'])
3803 $datarray['parent-uri'] = $parent_uri;
3804 $datarray['uid'] = $importer['importer_uid'];
3805 $datarray['contact-id'] = $importer['id'];
3806 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3807 $datarray['type'] = 'activity';
3808 $datarray['gravity'] = GRAVITY_LIKE;
3809 // only one like or dislike per person
3810 // splitted into two queries for performance issues
3811 $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",
3812 intval($datarray['uid']),
3813 intval($datarray['contact-id']),
3814 dbesc($datarray['verb']),
3820 $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",
3821 intval($datarray['uid']),
3822 intval($datarray['contact-id']),
3823 dbesc($datarray['verb']),
3831 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3833 $xo = parse_xml_string($datarray['object'],false);
3834 $xt = parse_xml_string($datarray['target'],false);
3836 if($xt->type == ACTIVITY_OBJ_NOTE) {
3837 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3839 intval($importer['importer_uid'])
3844 // extract tag, if not duplicate, add to parent item
3846 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3847 q("UPDATE item SET tag = '%s' WHERE id = %d",
3848 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3851 create_tags_from_item($r[0]['id']);
3857 $posted_id = item_store($datarray);
3859 // find out if our user is involved in this conversation and wants to be notified.
3861 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3863 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3865 intval($importer['importer_uid'])
3868 if(count($myconv)) {
3869 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3871 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3872 if(! link_compare($datarray['author-link'],$importer_url)) {
3875 foreach($myconv as $conv) {
3877 // now if we find a match, it means we're in this conversation
3879 if(! link_compare($conv['author-link'],$importer_url))
3882 require_once('include/enotify.php');
3884 $conv_parent = $conv['parent'];
3887 'type' => NOTIFY_COMMENT,
3888 'notify_flags' => $importer['notify-flags'],
3889 'language' => $importer['language'],
3890 'to_name' => $importer['username'],
3891 'to_email' => $importer['email'],
3892 'uid' => $importer['importer_uid'],
3893 'item' => $datarray,
3894 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3895 'source_name' => stripslashes($datarray['author-name']),
3896 'source_link' => $datarray['author-link'],
3897 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3898 ? $importer['thumb'] : $datarray['author-avatar']),
3899 'verb' => ACTIVITY_POST,
3901 'parent' => $conv_parent,
3902 'parent_uri' => $parent_uri
3906 // only send one notification
3918 // Head post of a conversation. Have we seen it? If not, import it.
3921 $item_id = $item->get_id();
3922 $datarray = get_atom_elements($feed,$item);
3924 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3925 $ev = bbtoevent($datarray['body']);
3926 if(x($ev,'desc') && x($ev,'start')) {
3927 $ev['cid'] = $importer['id'];
3928 $ev['uid'] = $importer['uid'];
3929 $ev['uri'] = $item_id;
3930 $ev['edited'] = $datarray['edited'];
3931 $ev['private'] = $datarray['private'];
3933 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3935 intval($importer['uid'])
3938 $ev['id'] = $r[0]['id'];
3939 $xyz = event_store($ev);
3944 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3946 intval($importer['importer_uid'])
3949 // Update content if 'updated' changes
3952 if (edited_timestamp_is_newer($r[0], $datarray)) {
3954 // do not accept (ignore) an earlier edit than one we currently have.
3955 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3958 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3959 dbesc($datarray['title']),
3960 dbesc($datarray['body']),
3961 dbesc($datarray['tag']),
3962 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3963 dbesc(datetime_convert()),
3965 intval($importer['importer_uid'])
3967 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3968 update_thread_uri($item_id, $importer['importer_uid']);
3971 // update last-child if it changes
3973 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3974 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3975 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3976 intval($allow[0]['data']),
3977 dbesc(datetime_convert()),
3979 intval($importer['importer_uid'])
3985 $datarray['parent-uri'] = $item_id;
3986 $datarray['uid'] = $importer['importer_uid'];
3987 $datarray['contact-id'] = $importer['id'];
3990 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3991 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3992 // but otherwise there's a possible data mixup on the sender's system.
3993 // the tgroup delivery code called from item_store will correct it if it's a forum,
3994 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3995 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3996 $datarray['owner-name'] = $importer['senderName'];
3997 $datarray['owner-link'] = $importer['url'];
3998 $datarray['owner-avatar'] = $importer['thumb'];
4001 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4004 // This is my contact on another system, but it's really me.
4005 // Turn this into a wall post.
4006 $notify = item_is_remote_self($importer, $datarray);
4008 $posted_id = item_store($datarray, false, $notify);
4010 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4011 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4014 $xo = parse_xml_string($datarray['object'],false);
4016 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4018 // somebody was poked/prodded. Was it me?
4020 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4022 foreach($links->link as $l) {
4023 $atts = $l->attributes();
4024 switch($atts['rel']) {
4026 $Blink = $atts['href'];
4032 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4034 // send a notification
4035 require_once('include/enotify.php');
4038 'type' => NOTIFY_POKE,
4039 'notify_flags' => $importer['notify-flags'],
4040 'language' => $importer['language'],
4041 'to_name' => $importer['username'],
4042 'to_email' => $importer['email'],
4043 'uid' => $importer['importer_uid'],
4044 'item' => $datarray,
4045 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4046 'source_name' => stripslashes($datarray['author-name']),
4047 'source_link' => $datarray['author-link'],
4048 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4049 ? $importer['thumb'] : $datarray['author-avatar']),
4050 'verb' => $datarray['verb'],
4051 'otype' => 'person',
4052 'activity' => $verb,
4053 'parent' => $datarray['parent']
4069 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4070 $url = notags(trim($datarray['author-link']));
4071 $name = notags(trim($datarray['author-name']));
4072 $photo = notags(trim($datarray['author-avatar']));
4074 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4075 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4076 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4078 if(is_array($contact)) {
4079 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4080 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4081 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4082 intval(CONTACT_IS_FRIEND),
4083 intval($contact['id']),
4084 intval($importer['uid'])
4087 // send email notification to owner?
4091 // create contact record
4093 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4094 `blocked`, `readonly`, `pending`, `writable` )
4095 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4096 intval($importer['uid']),
4097 dbesc(datetime_convert()),
4099 dbesc(normalise_link($url)),
4103 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4104 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4106 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4107 intval($importer['uid']),
4111 $contact_record = $r[0];
4113 // create notification
4114 $hash = random_string();
4116 if(is_array($contact_record)) {
4117 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4118 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4119 intval($importer['uid']),
4120 intval($contact_record['id']),
4122 dbesc(datetime_convert())
4126 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4127 intval($importer['uid'])
4132 if(intval($r[0]['def_gid'])) {
4133 require_once('include/group.php');
4134 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4137 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4138 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4141 'type' => NOTIFY_INTRO,
4142 'notify_flags' => $r[0]['notify-flags'],
4143 'language' => $r[0]['language'],
4144 'to_name' => $r[0]['username'],
4145 'to_email' => $r[0]['email'],
4146 'uid' => $r[0]['uid'],
4147 'link' => $a->get_baseurl() . '/notifications/intro',
4148 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4149 'source_link' => $contact_record['url'],
4150 'source_photo' => $contact_record['photo'],
4151 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4160 function lose_follower($importer,$contact,$datarray,$item) {
4162 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4163 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4164 intval(CONTACT_IS_SHARING),
4165 intval($contact['id'])
4169 contact_remove($contact['id']);
4173 function lose_sharer($importer,$contact,$datarray,$item) {
4175 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4176 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4177 intval(CONTACT_IS_FOLLOWER),
4178 intval($contact['id'])
4182 contact_remove($contact['id']);
4187 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4191 if(is_array($importer)) {
4192 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4193 intval($importer['uid'])
4197 // Diaspora has different message-ids in feeds than they do
4198 // through the direct Diaspora protocol. If we try and use
4199 // the feed, we'll get duplicates. So don't.
4201 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4204 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4206 // Use a single verify token, even if multiple hubs
4208 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4210 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4212 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4214 if(! strlen($contact['hub-verify'])) {
4215 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4216 dbesc($verify_token),
4217 intval($contact['id'])
4221 post_url($url,$params);
4223 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4230 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4234 $name = xmlify($name);
4235 $uri = xmlify($uri);
4238 $photo = xmlify($photo);
4242 $o .= "<name>$name</name>\r\n";
4243 $o .= "<uri>$uri</uri>\r\n";
4244 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4245 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4247 call_hooks('atom_author', $o);
4249 $o .= "</$tag>\r\n";
4253 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4257 if(! $item['parent'])
4260 if($item['deleted'])
4261 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4264 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4265 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4267 $body = $item['body'];
4270 $o = "\r\n\r\n<entry>\r\n";
4272 if(is_array($author))
4273 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4275 $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']));
4276 if(strlen($item['owner-name']))
4277 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4279 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4280 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4281 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4286 if ($item['title'] != "")
4287 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4289 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4291 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4292 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4293 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4294 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4295 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4296 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4297 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4301 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4303 if($item['location']) {
4304 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4305 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4309 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4311 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4312 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4315 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4316 if($item['bookmark'])
4317 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4320 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4323 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4325 if($item['signed_text']) {
4326 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4327 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4330 $verb = construct_verb($item);
4331 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4332 $actobj = construct_activity_object($item);
4335 $actarg = construct_activity_target($item);
4339 $tags = item_getfeedtags($item);
4341 foreach($tags as $t) {
4342 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4346 $o .= item_getfeedattach($item);
4348 $mentioned = get_mentions($item);
4352 call_hooks('atom_entry', $o);
4354 $o .= '</entry>' . "\r\n";
4359 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4361 if(get_config('system','disable_embedded'))
4366 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4367 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4372 $img_start = strpos($orig_body, '[img');
4373 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4374 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4375 while( ($img_st_close !== false) && ($img_len !== false) ) {
4377 $img_st_close++; // make it point to AFTER the closing bracket
4378 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4380 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4383 if(stristr($image , $site . '/photo/')) {
4384 // Only embed locally hosted photos
4386 $i = basename($image);
4387 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4388 $x = strpos($i,'-');
4391 $res = substr($i,$x+1);
4392 $i = substr($i,0,$x);
4393 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4400 // Check to see if we should replace this photo link with an embedded image
4401 // 1. No need to do so if the photo is public
4402 // 2. If there's a contact-id provided, see if they're in the access list
4403 // for the photo. If so, embed it.
4404 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4405 // permissions, regardless of order but first check to see if they're an exact
4406 // match to save some processing overhead.
4408 if(has_permissions($r[0])) {
4410 $recips = enumerate_permissions($r[0]);
4411 if(in_array($cid, $recips)) {
4416 if(compare_permissions($item,$r[0]))
4421 $data = $r[0]['data'];
4422 $type = $r[0]['type'];
4424 // If a custom width and height were specified, apply before embedding
4425 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4426 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4428 $width = intval($match[1]);
4429 $height = intval($match[2]);
4431 $ph = new Photo($data, $type);
4432 if($ph->is_valid()) {
4433 $ph->scaleImage(max($width, $height));
4434 $data = $ph->imageString();
4435 $type = $ph->getType();
4439 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4440 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4441 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4447 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4448 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4449 if($orig_body === false)
4452 $img_start = strpos($orig_body, '[img');
4453 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4454 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4457 $new_body = $new_body . $orig_body;
4463 function has_permissions($obj) {
4464 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4469 function compare_permissions($obj1,$obj2) {
4470 // first part is easy. Check that these are exactly the same.
4471 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4472 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4473 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4474 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4477 // This is harder. Parse all the permissions and compare the resulting set.
4479 $recipients1 = enumerate_permissions($obj1);
4480 $recipients2 = enumerate_permissions($obj2);
4483 if($recipients1 == $recipients2)
4488 // returns an array of contact-ids that are allowed to see this object
4490 function enumerate_permissions($obj) {
4491 require_once('include/group.php');
4492 $allow_people = expand_acl($obj['allow_cid']);
4493 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4494 $deny_people = expand_acl($obj['deny_cid']);
4495 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4496 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4497 $deny = array_unique(array_merge($deny_people,$deny_groups));
4498 $recipients = array_diff($recipients,$deny);
4502 function item_getfeedtags($item) {
4505 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4507 for($x = 0; $x < $cnt; $x ++) {
4509 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4513 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4515 for($x = 0; $x < $cnt; $x ++) {
4517 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4523 function item_getfeedattach($item) {
4525 $arr = explode('[/attach],',$item['attach']);
4527 foreach($arr as $r) {
4529 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4531 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4532 if(intval($matches[2]))
4533 $ret .= 'length="' . intval($matches[2]) . '" ';
4534 if($matches[4] !== ' ')
4535 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4536 $ret .= ' />' . "\r\n";
4545 function item_expire($uid, $days, $network = "", $force = false) {
4547 if((! $uid) || ($days < 1))
4550 // $expire_network_only = save your own wall posts
4551 // and just expire conversations started by others
4553 $expire_network_only = get_pconfig($uid,'expire','network_only');
4554 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4556 if ($network != "") {
4557 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4558 // There is an index "uid_network_received" but not "uid_network_created"
4559 // This avoids the creation of another index just for one purpose.
4560 // And it doesn't really matter wether to look at "received" or "created"
4561 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4563 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4565 $r = q("SELECT * FROM `item`
4566 WHERE `uid` = %d $range
4577 $expire_items = get_pconfig($uid, 'expire','items');
4578 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4580 // Forcing expiring of items - but not notes and marked items
4582 $expire_items = true;
4584 $expire_notes = get_pconfig($uid, 'expire','notes');
4585 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4587 $expire_starred = get_pconfig($uid, 'expire','starred');
4588 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4590 $expire_photos = get_pconfig($uid, 'expire','photos');
4591 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4593 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4595 foreach($r as $item) {
4597 // don't expire filed items
4599 if(strpos($item['file'],'[') !== false)
4602 // Only expire posts, not photos and photo comments
4604 if($expire_photos==0 && strlen($item['resource-id']))
4606 if($expire_starred==0 && intval($item['starred']))
4608 if($expire_notes==0 && $item['type']=='note')
4610 if($expire_items==0 && $item['type']!='note')
4613 drop_item($item['id'],false);
4616 proc_run('php',"include/notifier.php","expire","$uid");
4621 function drop_items($items) {
4624 if(! local_user() && ! remote_user())
4628 foreach($items as $item) {
4629 $owner = drop_item($item,false);
4630 if($owner && ! $uid)
4635 // multiple threads may have been deleted, send an expire notification
4638 proc_run('php',"include/notifier.php","expire","$uid");
4642 function drop_item($id,$interactive = true) {
4646 // locate item to be deleted
4648 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4655 notice( t('Item not found.') . EOL);
4656 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4661 $owner = $item['uid'];
4665 // check if logged in user is either the author or owner of this item
4667 if(is_array($_SESSION['remote'])) {
4668 foreach($_SESSION['remote'] as $visitor) {
4669 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4670 $cid = $visitor['cid'];
4677 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4679 // Check if we should do HTML-based delete confirmation
4680 if($_REQUEST['confirm']) {
4681 // <form> can't take arguments in its "action" parameter
4682 // so add any arguments as hidden inputs
4683 $query = explode_querystring($a->query_string);
4685 foreach($query['args'] as $arg) {
4686 if(strpos($arg, 'confirm=') === false) {
4687 $arg_parts = explode('=', $arg);
4688 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4692 return replace_macros(get_markup_template('confirm.tpl'), array(
4694 '$message' => t('Do you really want to delete this item?'),
4695 '$extra_inputs' => $inputs,
4696 '$confirm' => t('Yes'),
4697 '$confirm_url' => $query['base'],
4698 '$confirm_name' => 'confirmed',
4699 '$cancel' => t('Cancel'),
4702 // Now check how the user responded to the confirmation query
4703 if($_REQUEST['canceled']) {
4704 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4707 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4710 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4711 dbesc(datetime_convert()),
4712 dbesc(datetime_convert()),
4715 create_tags_from_item($item['id']);
4716 create_files_from_item($item['id']);
4717 delete_thread($item['id'], $item['parent-uri']);
4719 // clean up categories and tags so they don't end up as orphans
4722 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4724 foreach($matches as $mtch) {
4725 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4731 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4733 foreach($matches as $mtch) {
4734 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4738 // If item is a link to a photo resource, nuke all the associated photos
4739 // (visitors will not have photo resources)
4740 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4741 // generate a resource-id and therefore aren't intimately linked to the item.
4743 if(strlen($item['resource-id'])) {
4744 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4745 dbesc($item['resource-id']),
4746 intval($item['uid'])
4748 // ignore the result
4751 // If item is a link to an event, nuke the event record.
4753 if(intval($item['event-id'])) {
4754 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4755 intval($item['event-id']),
4756 intval($item['uid'])
4758 // ignore the result
4761 // clean up item_id and sign meta-data tables
4764 // Old code - caused very long queries and warning entries in the mysql logfiles:
4766 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4767 intval($item['id']),
4768 intval($item['uid'])
4771 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4772 intval($item['id']),
4773 intval($item['uid'])
4777 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4779 // Creating list of parents
4780 $r = q("select id from item where parent = %d and uid = %d",
4781 intval($item['id']),
4782 intval($item['uid'])
4787 foreach ($r AS $row) {
4788 if ($parentid != "")
4791 $parentid .= $row["id"];
4795 if ($parentid != "") {
4796 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4798 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4801 // If it's the parent of a comment thread, kill all the kids
4803 if($item['uri'] == $item['parent-uri']) {
4804 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4805 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4806 dbesc(datetime_convert()),
4807 dbesc(datetime_convert()),
4808 dbesc($item['parent-uri']),
4809 intval($item['uid'])
4811 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4812 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4813 delete_thread_uri($item['parent-uri'], $item['uid']);
4814 // ignore the result
4817 // ensure that last-child is set in case the comment that had it just got wiped.
4818 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4819 dbesc(datetime_convert()),
4820 dbesc($item['parent-uri']),
4821 intval($item['uid'])
4823 // who is the last child now?
4824 $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",
4825 dbesc($item['parent-uri']),
4826 intval($item['uid'])
4829 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4834 // Add a relayable_retraction signature for Diaspora.
4835 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4837 $drop_id = intval($item['id']);
4839 // send the notification upstream/downstream as the case may be
4841 proc_run('php',"include/notifier.php","drop","$drop_id");
4845 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4851 notice( t('Permission denied.') . EOL);
4852 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4859 function first_post_date($uid,$wall = false) {
4860 $r = q("select id, created from item
4861 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4863 order by created asc limit 1",
4865 intval($wall ? 1 : 0)
4868 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4869 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4874 /* modified posted_dates() {below} to arrange the list in years */
4875 function list_post_dates($uid, $wall) {
4876 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4878 $dthen = first_post_date($uid, $wall);
4882 // Set the start and end date to the beginning of the month
4883 $dnow = substr($dnow,0,8).'01';
4884 $dthen = substr($dthen,0,8).'01';
4888 // Starting with the current month, get the first and last days of every
4889 // month down to and including the month of the first post
4890 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4891 $dyear = intval(substr($dnow,0,4));
4892 $dstart = substr($dnow,0,8) . '01';
4893 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4894 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4895 $end_month = datetime_convert('','',$dend,'Y-m-d');
4896 $str = day_translate(datetime_convert('','',$dnow,'F'));
4898 $ret[$dyear] = array();
4899 $ret[$dyear][] = array($str,$end_month,$start_month);
4900 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4905 function posted_dates($uid,$wall) {
4906 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4908 $dthen = first_post_date($uid,$wall);
4912 // Set the start and end date to the beginning of the month
4913 $dnow = substr($dnow,0,8).'01';
4914 $dthen = substr($dthen,0,8).'01';
4917 // Starting with the current month, get the first and last days of every
4918 // month down to and including the month of the first post
4919 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4920 $dstart = substr($dnow,0,8) . '01';
4921 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4922 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4923 $end_month = datetime_convert('','',$dend,'Y-m-d');
4924 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4925 $ret[] = array($str,$end_month,$start_month);
4926 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4932 function posted_date_widget($url,$uid,$wall) {
4935 if(! feature_enabled($uid,'archives'))
4938 // For former Facebook folks that left because of "timeline"
4940 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4943 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4944 if(! $visible_years)
4947 $ret = list_post_dates($uid,$wall);
4952 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4953 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4955 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4956 '$title' => t('Archives'),
4957 '$size' => $visible_years,
4958 '$cutoff_year' => $cutoff_year,
4959 '$cutoff' => $cutoff,
4962 '$showmore' => t('show more')
4968 function store_diaspora_retract_sig($item, $user, $baseurl) {
4969 // Note that we can't add a target_author_signature
4970 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4971 // the comment, that means we're the home of the post, and Diaspora will only
4972 // check the parent_author_signature of retractions that it doesn't have to relay further
4974 // I don't think this function gets called for an "unlike," but I'll check anyway
4976 $enabled = intval(get_config('system','diaspora_enabled'));
4978 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4982 logger('drop_item: storing diaspora retraction signature');
4984 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4986 if(local_user() == $item['uid']) {
4988 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4989 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4992 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4993 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4996 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4997 // only handles DFRN deletes
4998 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4999 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5000 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5006 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5007 intval($item['id']),
5008 dbesc($signed_text),