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 // Check for hashtags in the body and repair or add hashtag links
1241 item_body_set_hashtags($arr);
1243 $arr['thr-parent'] = $arr['parent-uri'];
1244 if($arr['parent-uri'] === $arr['uri']) {
1246 $parent_deleted = 0;
1247 $allow_cid = $arr['allow_cid'];
1248 $allow_gid = $arr['allow_gid'];
1249 $deny_cid = $arr['deny_cid'];
1250 $deny_gid = $arr['deny_gid'];
1251 $notify_type = 'wall-new';
1255 // find the parent and snarf the item id and ACLs
1256 // and anything else we need to inherit
1258 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1259 dbesc($arr['parent-uri']),
1265 // is the new message multi-level threaded?
1266 // even though we don't support it now, preserve the info
1267 // and re-attach to the conversation parent.
1269 if($r[0]['uri'] != $r[0]['parent-uri']) {
1270 $arr['parent-uri'] = $r[0]['parent-uri'];
1271 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1272 ORDER BY `id` ASC LIMIT 1",
1273 dbesc($r[0]['parent-uri']),
1274 dbesc($r[0]['parent-uri']),
1281 $parent_id = $r[0]['id'];
1282 $parent_deleted = $r[0]['deleted'];
1283 $allow_cid = $r[0]['allow_cid'];
1284 $allow_gid = $r[0]['allow_gid'];
1285 $deny_cid = $r[0]['deny_cid'];
1286 $deny_gid = $r[0]['deny_gid'];
1287 $arr['wall'] = $r[0]['wall'];
1288 $notify_type = 'comment-new';
1290 // if the parent is private, force privacy for the entire conversation
1291 // This differs from the above settings as it subtly allows comments from
1292 // email correspondents to be private even if the overall thread is not.
1294 if($r[0]['private'])
1295 $arr['private'] = $r[0]['private'];
1297 // Edge case. We host a public forum that was originally posted to privately.
1298 // The original author commented, but as this is a comment, the permissions
1299 // weren't fixed up so it will still show the comment as private unless we fix it here.
1301 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1302 $arr['private'] = 0;
1305 // If its a post from myself then tag the thread as "mention"
1306 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1307 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1310 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1311 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1312 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1313 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1314 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1320 // Allow one to see reply tweets from status.net even when
1321 // we don't have or can't see the original post.
1324 logger('item_store: $force_parent=true, reply converted to top-level post.');
1326 $arr['parent-uri'] = $arr['uri'];
1327 $arr['gravity'] = 0;
1330 logger('item_store: item parent was not found - ignoring item');
1334 $parent_deleted = 0;
1338 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1342 if($r && count($r)) {
1343 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1347 // Is this item available in the global items (with uid=0)?
1348 if ($arr["uid"] == 0) {
1349 $arr["global"] = true;
1351 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1353 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1355 $arr["global"] = (count($isglobal) > 0);
1358 // Fill the cache field
1359 put_item_in_cache($arr);
1361 call_hooks('post_remote',$arr);
1363 if(x($arr,'cancel')) {
1364 logger('item_store: post cancelled by plugin.');
1368 // Store the unescaped version
1373 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1375 $r = dbq("INSERT INTO `item` (`"
1376 . implode("`, `", array_keys($arr))
1378 . implode("', '", array_values($arr))
1384 // find the item we just created
1385 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1391 $current_post = $r[0]['id'];
1392 logger('item_store: created item ' . $current_post);
1394 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1395 // This can be used to filter for inactive contacts.
1396 // Only do this for public postings to avoid privacy problems, since poco data is public.
1397 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1399 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1401 // Is it a forum? Then we don't care about the rules from above
1402 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1403 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1404 intval($arr['contact-id']));
1410 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1411 dbesc($arr['received']),
1412 dbesc($arr['received']),
1413 intval($arr['contact-id'])
1416 logger('item_store: could not locate created item');
1420 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1421 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1423 intval($arr['uid']),
1424 intval($current_post)
1428 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1429 $parent_id = $current_post;
1431 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1434 $private = $arr['private'];
1436 // Set parent id - and also make sure to inherit the parent's ACLs.
1438 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1439 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1446 intval($parent_deleted),
1447 intval($current_post)
1450 // Complete ostatus threads
1451 if ($ostatus_conversation)
1452 complete_conversation($current_post, $ostatus_conversation);
1454 $arr['id'] = $current_post;
1455 $arr['parent'] = $parent_id;
1456 $arr['allow_cid'] = $allow_cid;
1457 $arr['allow_gid'] = $allow_gid;
1458 $arr['deny_cid'] = $deny_cid;
1459 $arr['deny_gid'] = $deny_gid;
1460 $arr['private'] = $private;
1461 $arr['deleted'] = $parent_deleted;
1463 // update the commented timestamp on the parent
1464 // Only update "commented" if it is really a comment
1465 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1466 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1467 dbesc(datetime_convert()),
1468 dbesc(datetime_convert()),
1472 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1473 dbesc(datetime_convert()),
1478 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1479 intval($current_post),
1480 dbesc($dsprsig->signed_text),
1481 dbesc($dsprsig->signature),
1482 dbesc($dsprsig->signer)
1488 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1491 if($arr['last-child']) {
1492 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1494 intval($arr['uid']),
1495 intval($current_post)
1499 $deleted = tag_deliver($arr['uid'],$current_post);
1501 // current post can be deleted if is for a community page and no mention are
1503 if (!$deleted AND !$dontcache) {
1505 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1506 if (count($r) == 1) {
1507 call_hooks('post_remote_end', $r[0]);
1509 logger('item_store: new item not found in DB, id ' . $current_post);
1512 // Add every contact of the post to the global contact table
1515 create_tags_from_item($current_post);
1516 create_files_from_item($current_post);
1518 // Only check for notifications on start posts
1519 if ($arr['parent-uri'] === $arr['uri']) {
1520 add_thread($current_post);
1521 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1523 // Send a notification for every new post?
1524 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1525 intval($arr['contact-id']),
1528 $send_notification = count($r);
1530 if (!$send_notification) {
1531 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1532 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1535 foreach ($tags AS $tag) {
1536 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1537 normalise_link($tag["url"]), intval($arr['uid']));
1539 $send_notification = true;
1544 if ($send_notification) {
1545 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1546 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1547 intval($arr['uid']));
1549 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1550 intval($current_post),
1556 require_once('include/enotify.php');
1558 'type' => NOTIFY_SHARE,
1559 'notify_flags' => $u[0]['notify-flags'],
1560 'language' => $u[0]['language'],
1561 'to_name' => $u[0]['username'],
1562 'to_email' => $u[0]['email'],
1563 'uid' => $u[0]['uid'],
1565 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1566 'source_name' => $item[0]['author-name'],
1567 'source_link' => $item[0]['author-link'],
1568 'source_photo' => $item[0]['author-avatar'],
1569 'verb' => ACTIVITY_TAG,
1571 'parent' => $arr['parent']
1573 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1576 update_thread($parent_id);
1577 add_shadow_entry($arr);
1581 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1583 return $current_post;
1586 function item_body_set_hashtags(&$item) {
1588 $tags = get_tags($item["body"]);
1594 // This sorting is important when there are hashtags that are part of other hashtags
1595 // Otherwise there could be problems with hashtags like #test and #test2
1600 $URLSearchString = "^\[\]";
1602 // All hashtags should point to the home server
1603 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1604 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1606 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1607 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1609 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1610 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1612 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1615 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1617 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1620 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1622 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1625 // Repair recursive urls
1626 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1627 "#$2", $item["body"]);
1629 foreach($tags as $tag) {
1630 if(strpos($tag,'#') !== 0)
1633 if(strpos($tag,'[url='))
1636 $basetag = str_replace('_',' ',substr($tag,1));
1638 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1640 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1642 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1643 if(strlen($item["tag"]))
1644 $item["tag"] = ','.$item["tag"];
1645 $item["tag"] = $newtag.$item["tag"];
1649 // Convert back the masked hashtags
1650 $item["body"] = str_replace("#", "#", $item["body"]);
1653 function get_item_guid($id) {
1654 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1656 return($r[0]["guid"]);
1661 function get_item_id($guid, $uid = 0) {
1667 $uid == local_user();
1669 // Does the given user have this item?
1671 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1672 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1673 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1676 $nick = $r[0]["nickname"];
1680 // Or is it anywhere on the server?
1682 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1683 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1684 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1685 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1686 AND `item`.`private` = 0 AND `item`.`wall` = 1
1687 AND `item`.`guid` = '%s'", dbesc($guid));
1690 $nick = $r[0]["nickname"];
1693 return(array("nick" => $nick, "id" => $id));
1697 function get_item_contact($item,$contacts) {
1698 if(! count($contacts) || (! is_array($item)))
1700 foreach($contacts as $contact) {
1701 if($contact['id'] == $item['contact-id']) {
1703 break; // NOTREACHED
1710 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1712 * @param int $item_id
1713 * @return bool true if item was deleted, else false
1715 function tag_deliver($uid,$item_id) {
1723 $u = q("select * from user where uid = %d limit 1",
1729 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1730 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1733 $i = q("select * from item where id = %d and uid = %d limit 1",
1742 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1744 // Diaspora uses their own hardwired link URL in @-tags
1745 // instead of the one we supply with webfinger
1747 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1749 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1751 foreach($matches as $mtch) {
1752 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1754 logger('tag_deliver: mention found: ' . $mtch[2]);
1760 if ( ($community_page || $prvgroup) &&
1761 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1762 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1764 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1765 q("DELETE FROM item WHERE id = %d and uid = %d",
1775 // send a notification
1777 // use a local photo if we have one
1779 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1780 intval($u[0]['uid']),
1781 dbesc(normalise_link($item['author-link']))
1783 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1786 require_once('include/enotify.php');
1788 'type' => NOTIFY_TAGSELF,
1789 'notify_flags' => $u[0]['notify-flags'],
1790 'language' => $u[0]['language'],
1791 'to_name' => $u[0]['username'],
1792 'to_email' => $u[0]['email'],
1793 'uid' => $u[0]['uid'],
1795 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1796 'source_name' => $item['author-name'],
1797 'source_link' => $item['author-link'],
1798 'source_photo' => $photo,
1799 'verb' => ACTIVITY_TAG,
1801 'parent' => $item['parent']
1805 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1807 call_hooks('tagged', $arr);
1809 if((! $community_page) && (! $prvgroup))
1813 // tgroup delivery - setup a second delivery chain
1814 // prevent delivery looping - only proceed
1815 // if the message originated elsewhere and is a top-level post
1817 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1820 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1823 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1824 intval($u[0]['uid'])
1829 // also reset all the privacy bits to the forum default permissions
1831 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1833 $forum_mode = (($prvgroup) ? 2 : 1);
1835 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1836 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1837 intval($forum_mode),
1838 dbesc($c[0]['name']),
1839 dbesc($c[0]['url']),
1840 dbesc($c[0]['thumb']),
1842 dbesc($u[0]['allow_cid']),
1843 dbesc($u[0]['allow_gid']),
1844 dbesc($u[0]['deny_cid']),
1845 dbesc($u[0]['deny_gid']),
1848 update_thread($item_id);
1850 proc_run('php','include/notifier.php','tgroup',$item_id);
1856 function tgroup_check($uid,$item) {
1862 // check that the message originated elsewhere and is a top-level post
1864 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1868 $u = q("select * from user where uid = %d limit 1",
1874 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1875 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1878 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1880 // Diaspora uses their own hardwired link URL in @-tags
1881 // instead of the one we supply with webfinger
1883 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1885 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1887 foreach($matches as $mtch) {
1888 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1890 logger('tgroup_check: mention found: ' . $mtch[2]);
1898 if((! $community_page) && (! $prvgroup))
1912 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1916 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1918 if($contact['duplex'] && $contact['dfrn-id'])
1919 $idtosend = '0:' . $orig_id;
1920 if($contact['duplex'] && $contact['issued-id'])
1921 $idtosend = '1:' . $orig_id;
1923 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1925 $rino_enable = get_config('system','rino_encrypt');
1930 $ssl_val = intval(get_config('system','ssl_policy'));
1934 case SSL_POLICY_FULL:
1935 $ssl_policy = 'full';
1937 case SSL_POLICY_SELFSIGN:
1938 $ssl_policy = 'self';
1940 case SSL_POLICY_NONE:
1942 $ssl_policy = 'none';
1946 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1948 logger('dfrn_deliver: ' . $url);
1950 $xml = fetch_url($url);
1952 $curl_stat = $a->get_curl_code();
1954 return(-1); // timed out
1956 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1961 if(strpos($xml,'<?xml') === false) {
1962 logger('dfrn_deliver: no valid XML returned');
1963 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1967 $res = parse_xml_string($xml);
1969 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1970 return (($res->status) ? $res->status : 3);
1972 $postvars = array();
1973 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1974 $challenge = hex2bin((string) $res->challenge);
1975 $perm = (($res->perm) ? $res->perm : null);
1976 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1977 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1978 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1980 if($owner['page-flags'] == PAGE_PRVGROUP)
1983 $final_dfrn_id = '';
1986 if((($perm == 'rw') && (! intval($contact['writable'])))
1987 || (($perm == 'r') && (intval($contact['writable'])))) {
1988 q("update contact set writable = %d where id = %d",
1989 intval(($perm == 'rw') ? 1 : 0),
1990 intval($contact['id'])
1992 $contact['writable'] = (string) 1 - intval($contact['writable']);
1996 if(($contact['duplex'] && strlen($contact['pubkey']))
1997 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1998 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1999 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2000 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2003 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2004 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2007 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2009 if(strpos($final_dfrn_id,':') == 1)
2010 $final_dfrn_id = substr($final_dfrn_id,2);
2012 if($final_dfrn_id != $orig_id) {
2013 logger('dfrn_deliver: wrong dfrn_id.');
2014 // did not decode properly - cannot trust this site
2018 $postvars['dfrn_id'] = $idtosend;
2019 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2021 $postvars['dissolve'] = '1';
2024 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2025 $postvars['data'] = $atom;
2026 $postvars['perm'] = 'rw';
2029 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2030 $postvars['perm'] = 'r';
2033 $postvars['ssl_policy'] = $ssl_policy;
2036 $postvars['page'] = $page;
2038 if($rino && $rino_allowed && (! $dissolve)) {
2039 $key = substr(random_string(),0,16);
2040 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2041 $postvars['data'] = $data;
2042 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2045 if($dfrn_version >= 2.1) {
2046 if(($contact['duplex'] && strlen($contact['pubkey']))
2047 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2048 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2050 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2053 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2057 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2058 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2061 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2065 logger('md5 rawkey ' . md5($postvars['key']));
2067 $postvars['key'] = bin2hex($postvars['key']);
2070 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2072 $xml = post_url($contact['notify'],$postvars);
2074 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2076 $curl_stat = $a->get_curl_code();
2077 if((! $curl_stat) || (! strlen($xml)))
2078 return(-1); // timed out
2080 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2083 if(strpos($xml,'<?xml') === false) {
2084 logger('dfrn_deliver: phase 2: no valid XML returned');
2085 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2089 if($contact['term-date'] != '0000-00-00 00:00:00') {
2090 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2091 require_once('include/Contact.php');
2092 unmark_for_death($contact);
2095 $res = parse_xml_string($xml);
2097 return $res->status;
2102 This function returns true if $update has an edited timestamp newer
2103 than $existing, i.e. $update contains new data which should override
2104 what's already there. If there is no timestamp yet, the update is
2105 assumed to be newer. If the update has no timestamp, the existing
2106 item is assumed to be up-to-date. If the timestamps are equal it
2107 assumes the update has been seen before and should be ignored.
2109 function edited_timestamp_is_newer($existing, $update) {
2110 if (!x($existing,'edited') || !$existing['edited']) {
2113 if (!x($update,'edited') || !$update['edited']) {
2116 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2117 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2118 return (strcmp($existing_edited, $update_edited) < 0);
2123 * consume_feed - process atom feed and update anything/everything we might need to update
2125 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2127 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2128 * It is this person's stuff that is going to be updated.
2129 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2130 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2131 * have a contact record.
2132 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2133 * might not) try and subscribe to it.
2134 * $datedir sorts in reverse order
2135 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2136 * imported prior to its children being seen in the stream unless we are certain
2137 * of how the feed is arranged/ordered.
2138 * With $pass = 1, we only pull parent items out of the stream.
2139 * With $pass = 2, we only pull children (comments/likes).
2141 * So running this twice, first with pass 1 and then with pass 2 will do the right
2142 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2143 * model where comments can have sub-threads. That would require some massive sorting
2144 * to get all the feed items into a mostly linear ordering, and might still require
2148 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2150 require_once('library/simplepie/simplepie.inc');
2151 require_once('include/contact_selectors.php');
2153 if(! strlen($xml)) {
2154 logger('consume_feed: empty input');
2158 $feed = new SimplePie();
2159 $feed->set_raw_data($xml);
2161 $feed->enable_order_by_date(true);
2163 $feed->enable_order_by_date(false);
2167 logger('consume_feed: Error parsing XML: ' . $feed->error());
2169 $permalink = $feed->get_permalink();
2171 // Check at the feed level for updated contact name and/or photo
2175 $photo_timestamp = '';
2178 $contact_updated = '';
2180 $hubs = $feed->get_links('hub');
2181 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2184 $hub = implode(',', $hubs);
2186 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2188 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2190 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2191 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2192 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2193 $new_name = $elems['name'][0]['data'];
2195 // Manually checking for changed contact names
2196 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2197 $name_updated = date("c");
2198 $photo_timestamp = date("c");
2201 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2202 if ($photo_timestamp == "")
2203 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2204 $photo_url = $elems['link'][0]['attribs']['']['href'];
2207 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2208 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2212 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2213 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2215 $contact_updated = $photo_timestamp;
2217 require_once("include/Photo.php");
2218 $photo_failure = false;
2219 $have_photo = false;
2221 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2222 intval($contact['id']),
2223 intval($contact['uid'])
2226 $resource_id = $r[0]['resource-id'];
2230 $resource_id = photo_new_resource();
2233 $img_str = fetch_url($photo_url,true);
2234 // guess mimetype from headers or filename
2235 $type = guess_image_type($photo_url,true);
2238 $img = new Photo($img_str, $type);
2239 if($img->is_valid()) {
2241 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2242 dbesc($resource_id),
2243 intval($contact['id']),
2244 intval($contact['uid'])
2248 $img->scaleImageSquare(175);
2250 $hash = $resource_id;
2251 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2253 $img->scaleImage(80);
2254 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2256 $img->scaleImage(48);
2257 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2261 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2262 WHERE `uid` = %d AND `id` = %d",
2263 dbesc(datetime_convert()),
2264 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2265 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2266 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2267 intval($contact['uid']),
2268 intval($contact['id'])
2273 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2274 if ($name_updated > $contact_updated)
2275 $contact_updated = $name_updated;
2277 $r = q("select * from contact where uid = %d and id = %d limit 1",
2278 intval($contact['uid']),
2279 intval($contact['id'])
2282 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2283 dbesc(notags(trim($new_name))),
2284 dbesc(datetime_convert()),
2285 intval($contact['uid']),
2286 intval($contact['id'])
2289 // do our best to update the name on content items
2292 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2293 dbesc(notags(trim($new_name))),
2294 dbesc($r[0]['name']),
2295 dbesc($r[0]['url']),
2296 intval($contact['uid'])
2301 if ($contact_updated AND $new_name AND $photo_url)
2302 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2304 if(strlen($birthday)) {
2305 if(substr($birthday,0,4) != $contact['bdyear']) {
2306 logger('consume_feed: updating birthday: ' . $birthday);
2310 * Add new birthday event for this person
2312 * $bdtext is just a readable placeholder in case the event is shared
2313 * with others. We will replace it during presentation to our $importer
2314 * to contain a sparkle link and perhaps a photo.
2318 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2319 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2322 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2323 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2324 intval($contact['uid']),
2325 intval($contact['id']),
2326 dbesc(datetime_convert()),
2327 dbesc(datetime_convert()),
2328 dbesc(datetime_convert('UTC','UTC', $birthday)),
2329 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2338 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2339 dbesc(substr($birthday,0,4)),
2340 intval($contact['uid']),
2341 intval($contact['id'])
2344 // This function is called twice without reloading the contact
2345 // Make sure we only create one event. This is why &$contact
2346 // is a reference var in this function
2348 $contact['bdyear'] = substr($birthday,0,4);
2352 $community_page = 0;
2353 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2355 $community_page = intval($rawtags[0]['data']);
2357 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2358 q("update contact set forum = %d where id = %d",
2359 intval($community_page),
2360 intval($contact['id'])
2362 $contact['forum'] = (string) $community_page;
2366 // process any deleted entries
2368 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2369 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2370 foreach($del_entries as $dentry) {
2372 if(isset($dentry['attribs']['']['ref'])) {
2373 $uri = $dentry['attribs']['']['ref'];
2375 if(isset($dentry['attribs']['']['when'])) {
2376 $when = $dentry['attribs']['']['when'];
2377 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2380 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2382 if($deleted && is_array($contact)) {
2383 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2384 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2386 intval($importer['uid']),
2387 intval($contact['id'])
2392 if(! $item['deleted'])
2393 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2395 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2396 $xo = parse_xml_string($item['object'],false);
2397 $xt = parse_xml_string($item['target'],false);
2398 if($xt->type === ACTIVITY_OBJ_NOTE) {
2399 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2401 intval($importer['importer_uid'])
2405 // For tags, the owner cannot remove the tag on the author's copy of the post.
2407 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2408 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2409 $author_copy = (($item['origin']) ? true : false);
2411 if($owner_remove && $author_copy)
2413 if($author_remove || $owner_remove) {
2414 $tags = explode(',',$i[0]['tag']);
2417 foreach($tags as $tag)
2418 if(trim($tag) !== trim($xo->body))
2419 $newtags[] = trim($tag);
2421 q("update item set tag = '%s' where id = %d",
2422 dbesc(implode(',',$newtags)),
2425 create_tags_from_item($i[0]['id']);
2431 if($item['uri'] == $item['parent-uri']) {
2432 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2433 `body` = '', `title` = ''
2434 WHERE `parent-uri` = '%s' AND `uid` = %d",
2436 dbesc(datetime_convert()),
2437 dbesc($item['uri']),
2438 intval($importer['uid'])
2440 create_tags_from_itemuri($item['uri'], $importer['uid']);
2441 create_files_from_itemuri($item['uri'], $importer['uid']);
2442 update_thread_uri($item['uri'], $importer['uid']);
2445 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2446 `body` = '', `title` = ''
2447 WHERE `uri` = '%s' AND `uid` = %d",
2449 dbesc(datetime_convert()),
2451 intval($importer['uid'])
2453 create_tags_from_itemuri($uri, $importer['uid']);
2454 create_files_from_itemuri($uri, $importer['uid']);
2455 if($item['last-child']) {
2456 // ensure that last-child is set in case the comment that had it just got wiped.
2457 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2458 dbesc(datetime_convert()),
2459 dbesc($item['parent-uri']),
2460 intval($item['uid'])
2462 // who is the last child now?
2463 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2464 ORDER BY `created` DESC LIMIT 1",
2465 dbesc($item['parent-uri']),
2466 intval($importer['uid'])
2469 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2480 // Now process the feed
2482 if($feed->get_item_quantity()) {
2484 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2486 // in inverse date order
2488 $items = array_reverse($feed->get_items());
2490 $items = $feed->get_items();
2493 foreach($items as $item) {
2496 $item_id = $item->get_id();
2497 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2498 if(isset($rawthread[0]['attribs']['']['ref'])) {
2500 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2503 if(($is_reply) && is_array($contact)) {
2508 // not allowed to post
2510 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2514 // Have we seen it? If not, import it.
2516 $item_id = $item->get_id();
2517 $datarray = get_atom_elements($feed, $item, $contact);
2519 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2520 $datarray['author-name'] = $contact['name'];
2521 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2522 $datarray['author-link'] = $contact['url'];
2523 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2524 $datarray['author-avatar'] = $contact['thumb'];
2526 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2527 logger('consume_feed: no author information! ' . print_r($datarray,true));
2531 $force_parent = false;
2532 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2533 if($contact['network'] === NETWORK_OSTATUS)
2534 $force_parent = true;
2535 if(strlen($datarray['title']))
2536 unset($datarray['title']);
2537 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2538 dbesc(datetime_convert()),
2540 intval($importer['uid'])
2542 $datarray['last-child'] = 1;
2543 update_thread_uri($parent_uri, $importer['uid']);
2547 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2549 intval($importer['uid'])
2552 // Update content if 'updated' changes
2555 if (edited_timestamp_is_newer($r[0], $datarray)) {
2557 // do not accept (ignore) an earlier edit than one we currently have.
2558 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2561 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2562 dbesc($datarray['title']),
2563 dbesc($datarray['body']),
2564 dbesc($datarray['tag']),
2565 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2566 dbesc(datetime_convert()),
2568 intval($importer['uid'])
2570 create_tags_from_itemuri($item_id, $importer['uid']);
2571 update_thread_uri($item_id, $importer['uid']);
2574 // update last-child if it changes
2576 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2577 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2578 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2579 dbesc(datetime_convert()),
2581 intval($importer['uid'])
2583 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2584 intval($allow[0]['data']),
2585 dbesc(datetime_convert()),
2587 intval($importer['uid'])
2589 update_thread_uri($item_id, $importer['uid']);
2595 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2596 // one way feed - no remote comment ability
2597 $datarray['last-child'] = 0;
2599 $datarray['parent-uri'] = $parent_uri;
2600 $datarray['uid'] = $importer['uid'];
2601 $datarray['contact-id'] = $contact['id'];
2602 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2603 $datarray['type'] = 'activity';
2604 $datarray['gravity'] = GRAVITY_LIKE;
2605 // only one like or dislike per person
2606 // splitted into two queries for performance issues
2607 $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",
2608 intval($datarray['uid']),
2609 intval($datarray['contact-id']),
2610 dbesc($datarray['verb']),
2616 $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",
2617 intval($datarray['uid']),
2618 intval($datarray['contact-id']),
2619 dbesc($datarray['verb']),
2626 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2627 $xo = parse_xml_string($datarray['object'],false);
2628 $xt = parse_xml_string($datarray['target'],false);
2630 if($xt->type == ACTIVITY_OBJ_NOTE) {
2631 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2633 intval($importer['importer_uid'])
2638 // extract tag, if not duplicate, add to parent item
2639 if($xo->id && $xo->content) {
2640 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2641 if(! (stristr($r[0]['tag'],$newtag))) {
2642 q("UPDATE item SET tag = '%s' WHERE id = %d",
2643 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2646 create_tags_from_item($r[0]['id']);
2652 $r = item_store($datarray,$force_parent);
2658 // Head post of a conversation. Have we seen it? If not, import it.
2660 $item_id = $item->get_id();
2662 $datarray = get_atom_elements($feed, $item, $contact);
2664 if(is_array($contact)) {
2665 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2666 $datarray['author-name'] = $contact['name'];
2667 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2668 $datarray['author-link'] = $contact['url'];
2669 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2670 $datarray['author-avatar'] = $contact['thumb'];
2673 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2674 logger('consume_feed: no author information! ' . print_r($datarray,true));
2678 // special handling for events
2680 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2681 $ev = bbtoevent($datarray['body']);
2682 if(x($ev,'desc') && x($ev,'start')) {
2683 $ev['uid'] = $importer['uid'];
2684 $ev['uri'] = $item_id;
2685 $ev['edited'] = $datarray['edited'];
2686 $ev['private'] = $datarray['private'];
2688 if(is_array($contact))
2689 $ev['cid'] = $contact['id'];
2690 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2692 intval($importer['uid'])
2695 $ev['id'] = $r[0]['id'];
2696 $xyz = event_store($ev);
2701 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2702 if(strlen($datarray['title']))
2703 unset($datarray['title']);
2704 $datarray['last-child'] = 1;
2708 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2710 intval($importer['uid'])
2713 // Update content if 'updated' changes
2716 if (edited_timestamp_is_newer($r[0], $datarray)) {
2718 // do not accept (ignore) an earlier edit than one we currently have.
2719 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2722 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2723 dbesc($datarray['title']),
2724 dbesc($datarray['body']),
2725 dbesc($datarray['tag']),
2726 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2727 dbesc(datetime_convert()),
2729 intval($importer['uid'])
2731 create_tags_from_itemuri($item_id, $importer['uid']);
2732 update_thread_uri($item_id, $importer['uid']);
2735 // update last-child if it changes
2737 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2738 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2739 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2740 intval($allow[0]['data']),
2741 dbesc(datetime_convert()),
2743 intval($importer['uid'])
2745 update_thread_uri($item_id, $importer['uid']);
2750 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2751 logger('consume-feed: New follower');
2752 new_follower($importer,$contact,$datarray,$item);
2755 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2756 lose_follower($importer,$contact,$datarray,$item);
2760 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2761 logger('consume-feed: New friend request');
2762 new_follower($importer,$contact,$datarray,$item,true);
2765 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2766 lose_sharer($importer,$contact,$datarray,$item);
2771 if(! is_array($contact))
2775 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2776 // one way feed - no remote comment ability
2777 $datarray['last-child'] = 0;
2779 if($contact['network'] === NETWORK_FEED)
2780 $datarray['private'] = 2;
2782 $datarray['parent-uri'] = $item_id;
2783 $datarray['uid'] = $importer['uid'];
2784 $datarray['contact-id'] = $contact['id'];
2786 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2787 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2788 // but otherwise there's a possible data mixup on the sender's system.
2789 // the tgroup delivery code called from item_store will correct it if it's a forum,
2790 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2791 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2792 $datarray['owner-name'] = $contact['name'];
2793 $datarray['owner-link'] = $contact['url'];
2794 $datarray['owner-avatar'] = $contact['thumb'];
2797 // We've allowed "followers" to reach this point so we can decide if they are
2798 // posting an @-tag delivery, which followers are allowed to do for certain
2799 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2801 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2804 // This is my contact on another system, but it's really me.
2805 // Turn this into a wall post.
2806 $notify = item_is_remote_self($contact, $datarray);
2808 $r = item_store($datarray, false, $notify);
2809 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2817 function item_is_remote_self($contact, &$datarray) {
2820 if (!$contact['remote_self'])
2823 // Prevent the forwarding of posts that are forwarded
2824 if ($datarray["extid"] == NETWORK_DFRN)
2827 // Prevent to forward already forwarded posts
2828 if ($datarray["app"] == $a->get_hostname())
2831 // Only forward posts
2832 if ($datarray["verb"] != ACTIVITY_POST)
2835 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2838 $datarray2 = $datarray;
2839 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2840 if ($contact['remote_self'] == 2) {
2841 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2842 intval($contact['uid']));
2844 $datarray['contact-id'] = $r[0]["id"];
2846 $datarray['owner-name'] = $r[0]["name"];
2847 $datarray['owner-link'] = $r[0]["url"];
2848 $datarray['owner-avatar'] = $r[0]["thumb"];
2850 $datarray['author-name'] = $datarray['owner-name'];
2851 $datarray['author-link'] = $datarray['owner-link'];
2852 $datarray['author-avatar'] = $datarray['owner-avatar'];
2855 if ($contact['network'] != NETWORK_FEED) {
2856 $datarray["guid"] = get_guid(32);
2857 unset($datarray["plink"]);
2858 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2859 $datarray["parent-uri"] = $datarray["uri"];
2860 $datarray["extid"] = $contact['network'];
2861 $urlpart = parse_url($datarray2['author-link']);
2862 $datarray["app"] = $urlpart["host"];
2864 $datarray['private'] = 0;
2867 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2868 // $datarray["app"] = network_to_name($contact['network']);
2870 if ($contact['network'] != NETWORK_FEED) {
2871 // Store the original post
2872 $r = item_store($datarray2, false, false);
2873 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2875 $datarray["app"] = "Feed";
2880 function local_delivery($importer,$data) {
2883 logger(__function__, LOGGER_TRACE);
2885 if($importer['readonly']) {
2886 // We aren't receiving stuff from this person. But we will quietly ignore them
2887 // rather than a blatant "go away" message.
2888 logger('local_delivery: ignoring');
2893 // Consume notification feed. This may differ from consuming a public feed in several ways
2894 // - might contain email or friend suggestions
2895 // - might contain remote followup to our message
2896 // - in which case we need to accept it and then notify other conversants
2897 // - we may need to send various email notifications
2899 $feed = new SimplePie();
2900 $feed->set_raw_data($data);
2901 $feed->enable_order_by_date(false);
2906 logger('local_delivery: Error parsing XML: ' . $feed->error());
2909 // Check at the feed level for updated contact name and/or photo
2913 $photo_timestamp = '';
2915 $contact_updated = '';
2918 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2920 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2922 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2925 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2926 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2927 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2928 $new_name = $elems['name'][0]['data'];
2930 // Manually checking for changed contact names
2931 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2932 $name_updated = date("c");
2933 $photo_timestamp = date("c");
2936 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2937 if ($photo_timestamp == "")
2938 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2939 $photo_url = $elems['link'][0]['attribs']['']['href'];
2943 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2945 $contact_updated = $photo_timestamp;
2947 logger('local_delivery: Updating photo for ' . $importer['name']);
2948 require_once("include/Photo.php");
2949 $photo_failure = false;
2950 $have_photo = false;
2952 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2953 intval($importer['id']),
2954 intval($importer['importer_uid'])
2957 $resource_id = $r[0]['resource-id'];
2961 $resource_id = photo_new_resource();
2964 $img_str = fetch_url($photo_url,true);
2965 // guess mimetype from headers or filename
2966 $type = guess_image_type($photo_url,true);
2969 $img = new Photo($img_str, $type);
2970 if($img->is_valid()) {
2972 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2973 dbesc($resource_id),
2974 intval($importer['id']),
2975 intval($importer['importer_uid'])
2979 $img->scaleImageSquare(175);
2981 $hash = $resource_id;
2982 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2984 $img->scaleImage(80);
2985 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2987 $img->scaleImage(48);
2988 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2992 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2993 WHERE `uid` = %d AND `id` = %d",
2994 dbesc(datetime_convert()),
2995 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2996 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2997 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2998 intval($importer['importer_uid']),
2999 intval($importer['id'])
3004 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3005 if ($name_updated > $contact_updated)
3006 $contact_updated = $name_updated;
3008 $r = q("select * from contact where uid = %d and id = %d limit 1",
3009 intval($importer['importer_uid']),
3010 intval($importer['id'])
3013 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3014 dbesc(notags(trim($new_name))),
3015 dbesc(datetime_convert()),
3016 intval($importer['importer_uid']),
3017 intval($importer['id'])
3020 // do our best to update the name on content items
3023 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3024 dbesc(notags(trim($new_name))),
3025 dbesc($r[0]['name']),
3026 dbesc($r[0]['url']),
3027 intval($importer['importer_uid'])
3032 if ($contact_updated AND $new_name AND $photo_url)
3033 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3035 // Currently unsupported - needs a lot of work
3036 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3037 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3038 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3040 $newloc['uid'] = $importer['importer_uid'];
3041 $newloc['cid'] = $importer['id'];
3042 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3043 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3044 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3045 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3046 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3047 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3048 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3049 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3050 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3051 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3052 /** relocated user must have original key pair */
3053 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3054 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3056 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3059 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3060 intval($importer['id']),
3061 intval($importer['importer_uid']));
3066 $x = q("UPDATE contact SET
3077 `site-pubkey` = '%s'
3078 WHERE id=%d AND uid=%d;",
3079 dbesc($newloc['name']),
3080 dbesc($newloc['photo']),
3081 dbesc($newloc['thumb']),
3082 dbesc($newloc['micro']),
3083 dbesc($newloc['url']),
3084 dbesc(normalise_link($newloc['url'])),
3085 dbesc($newloc['request']),
3086 dbesc($newloc['confirm']),
3087 dbesc($newloc['notify']),
3088 dbesc($newloc['poll']),
3089 dbesc($newloc['sitepubkey']),
3090 intval($importer['id']),
3091 intval($importer['importer_uid']));
3097 'owner-link' => array($old['url'], $newloc['url']),
3098 'author-link' => array($old['url'], $newloc['url']),
3099 'owner-avatar' => array($old['photo'], $newloc['photo']),
3100 'author-avatar' => array($old['photo'], $newloc['photo']),
3102 foreach ($fields as $n=>$f){
3103 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3106 intval($importer['importer_uid']));
3112 // merge with current record, current contents have priority
3113 // update record, set url-updated
3114 // update profile photos
3120 // handle friend suggestion notification
3122 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3123 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3124 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3126 $fsugg['uid'] = $importer['importer_uid'];
3127 $fsugg['cid'] = $importer['id'];
3128 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3129 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3130 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3131 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3132 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3134 // Does our member already have a friend matching this description?
3136 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3137 dbesc($fsugg['name']),
3138 dbesc(normalise_link($fsugg['url'])),
3139 intval($fsugg['uid'])
3144 // Do we already have an fcontact record for this person?
3147 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3148 dbesc($fsugg['url']),
3149 dbesc($fsugg['name']),
3150 dbesc($fsugg['request'])
3155 // OK, we do. Do we already have an introduction for this person ?
3156 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3157 intval($fsugg['uid']),
3164 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3165 dbesc($fsugg['name']),
3166 dbesc($fsugg['url']),
3167 dbesc($fsugg['photo']),
3168 dbesc($fsugg['request'])
3170 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3171 dbesc($fsugg['url']),
3172 dbesc($fsugg['name']),
3173 dbesc($fsugg['request'])
3178 // database record did not get created. Quietly give up.
3183 $hash = random_string();
3185 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3186 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3187 intval($fsugg['uid']),
3189 intval($fsugg['cid']),
3190 dbesc($fsugg['body']),
3192 dbesc(datetime_convert()),
3197 'type' => NOTIFY_SUGGEST,
3198 'notify_flags' => $importer['notify-flags'],
3199 'language' => $importer['language'],
3200 'to_name' => $importer['username'],
3201 'to_email' => $importer['email'],
3202 'uid' => $importer['importer_uid'],
3204 'link' => $a->get_baseurl() . '/notifications/intros',
3205 'source_name' => $importer['name'],
3206 'source_link' => $importer['url'],
3207 'source_photo' => $importer['photo'],
3208 'verb' => ACTIVITY_REQ_FRIEND,
3217 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3218 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3220 logger('local_delivery: private message received');
3223 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3226 $msg['uid'] = $importer['importer_uid'];
3227 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3228 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3229 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3230 $msg['contact-id'] = $importer['id'];
3231 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3232 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3234 $msg['replied'] = 0;
3235 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3236 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3237 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3241 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3242 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3244 // send notifications.
3246 require_once('include/enotify.php');
3248 $notif_params = array(
3249 'type' => NOTIFY_MAIL,
3250 'notify_flags' => $importer['notify-flags'],
3251 'language' => $importer['language'],
3252 'to_name' => $importer['username'],
3253 'to_email' => $importer['email'],
3254 'uid' => $importer['importer_uid'],
3256 'source_name' => $msg['from-name'],
3257 'source_link' => $importer['url'],
3258 'source_photo' => $importer['thumb'],
3259 'verb' => ACTIVITY_POST,
3263 notification($notif_params);
3269 $community_page = 0;
3270 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3272 $community_page = intval($rawtags[0]['data']);
3274 if(intval($importer['forum']) != $community_page) {
3275 q("update contact set forum = %d where id = %d",
3276 intval($community_page),
3277 intval($importer['id'])
3279 $importer['forum'] = (string) $community_page;
3282 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3284 // process any deleted entries
3286 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3287 if(is_array($del_entries) && count($del_entries)) {
3288 foreach($del_entries as $dentry) {
3290 if(isset($dentry['attribs']['']['ref'])) {
3291 $uri = $dentry['attribs']['']['ref'];
3293 if(isset($dentry['attribs']['']['when'])) {
3294 $when = $dentry['attribs']['']['when'];
3295 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3298 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3302 // check for relayed deletes to our conversation
3305 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3307 intval($importer['importer_uid'])
3310 $parent_uri = $r[0]['parent-uri'];
3311 if($r[0]['id'] != $r[0]['parent'])
3318 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3321 logger('local_delivery: possible community delete');
3324 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3326 // was the top-level post for this reply written by somebody on this site?
3327 // Specifically, the recipient?
3329 $is_a_remote_delete = false;
3331 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3332 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3333 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3334 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3335 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3336 AND `item`.`uid` = %d
3342 intval($importer['importer_uid'])
3345 $is_a_remote_delete = true;
3347 // Does this have the characteristics of a community or private group comment?
3348 // If it's a reply to a wall post on a community/prvgroup page it's a
3349 // valid community comment. Also forum_mode makes it valid for sure.
3350 // If neither, it's not.
3352 if($is_a_remote_delete && $community) {
3353 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3354 $is_a_remote_delete = false;
3355 logger('local_delivery: not a community delete');
3359 if($is_a_remote_delete) {
3360 logger('local_delivery: received remote delete');
3364 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3365 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3367 intval($importer['importer_uid']),
3368 intval($importer['id'])
3374 if($item['deleted'])
3377 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3379 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3380 $xo = parse_xml_string($item['object'],false);
3381 $xt = parse_xml_string($item['target'],false);
3383 if($xt->type === ACTIVITY_OBJ_NOTE) {
3384 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3386 intval($importer['importer_uid'])
3390 // For tags, the owner cannot remove the tag on the author's copy of the post.
3392 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3393 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3394 $author_copy = (($item['origin']) ? true : false);
3396 if($owner_remove && $author_copy)
3398 if($author_remove || $owner_remove) {
3399 $tags = explode(',',$i[0]['tag']);
3402 foreach($tags as $tag)
3403 if(trim($tag) !== trim($xo->body))
3404 $newtags[] = trim($tag);
3406 q("update item set tag = '%s' where id = %d",
3407 dbesc(implode(',',$newtags)),
3410 create_tags_from_item($i[0]['id']);
3416 if($item['uri'] == $item['parent-uri']) {
3417 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3418 `body` = '', `title` = ''
3419 WHERE `parent-uri` = '%s' AND `uid` = %d",
3421 dbesc(datetime_convert()),
3422 dbesc($item['uri']),
3423 intval($importer['importer_uid'])
3425 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3426 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3427 update_thread_uri($item['uri'], $importer['importer_uid']);
3430 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3431 `body` = '', `title` = ''
3432 WHERE `uri` = '%s' AND `uid` = %d",
3434 dbesc(datetime_convert()),
3436 intval($importer['importer_uid'])
3438 create_tags_from_itemuri($uri, $importer['importer_uid']);
3439 create_files_from_itemuri($uri, $importer['importer_uid']);
3440 update_thread_uri($uri, $importer['importer_uid']);
3441 if($item['last-child']) {
3442 // ensure that last-child is set in case the comment that had it just got wiped.
3443 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3444 dbesc(datetime_convert()),
3445 dbesc($item['parent-uri']),
3446 intval($item['uid'])
3448 // who is the last child now?
3449 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3450 ORDER BY `created` DESC LIMIT 1",
3451 dbesc($item['parent-uri']),
3452 intval($importer['importer_uid'])
3455 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3460 // if this is a relayed delete, propagate it to other recipients
3462 if($is_a_remote_delete)
3463 proc_run('php',"include/notifier.php","drop",$item['id']);
3471 foreach($feed->get_items() as $item) {
3474 $item_id = $item->get_id();
3475 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3476 if(isset($rawthread[0]['attribs']['']['ref'])) {
3478 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3484 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3487 logger('local_delivery: possible community reply');
3490 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3492 // was the top-level post for this reply written by somebody on this site?
3493 // Specifically, the recipient?
3495 $is_a_remote_comment = false;
3496 $top_uri = $parent_uri;
3498 $r = q("select `item`.`parent-uri` from `item`
3499 WHERE `item`.`uri` = '%s'
3503 if($r && count($r)) {
3504 $top_uri = $r[0]['parent-uri'];
3506 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3507 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3508 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3509 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3510 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3511 AND `item`.`uid` = %d
3517 intval($importer['importer_uid'])
3520 $is_a_remote_comment = true;
3523 // Does this have the characteristics of a community or private group comment?
3524 // If it's a reply to a wall post on a community/prvgroup page it's a
3525 // valid community comment. Also forum_mode makes it valid for sure.
3526 // If neither, it's not.
3528 if($is_a_remote_comment && $community) {
3529 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3530 $is_a_remote_comment = false;
3531 logger('local_delivery: not a community reply');
3535 if($is_a_remote_comment) {
3536 logger('local_delivery: received remote comment');
3538 // remote reply to our post. Import and then notify everybody else.
3540 $datarray = get_atom_elements($feed, $item);
3542 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3544 intval($importer['importer_uid'])
3547 // Update content if 'updated' changes
3551 if (edited_timestamp_is_newer($r[0], $datarray)) {
3553 // do not accept (ignore) an earlier edit than one we currently have.
3554 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3557 logger('received updated comment' , LOGGER_DEBUG);
3558 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3559 dbesc($datarray['title']),
3560 dbesc($datarray['body']),
3561 dbesc($datarray['tag']),
3562 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3563 dbesc(datetime_convert()),
3565 intval($importer['importer_uid'])
3567 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3569 proc_run('php',"include/notifier.php","comment-import",$iid);
3578 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3579 intval($importer['importer_uid'])
3583 $datarray['type'] = 'remote-comment';
3584 $datarray['wall'] = 1;
3585 $datarray['parent-uri'] = $parent_uri;
3586 $datarray['uid'] = $importer['importer_uid'];
3587 $datarray['owner-name'] = $own[0]['name'];
3588 $datarray['owner-link'] = $own[0]['url'];
3589 $datarray['owner-avatar'] = $own[0]['thumb'];
3590 $datarray['contact-id'] = $importer['id'];
3592 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3594 $datarray['type'] = 'activity';
3595 $datarray['gravity'] = GRAVITY_LIKE;
3596 $datarray['last-child'] = 0;
3597 // only one like or dislike per person
3598 // splitted into two queries for performance issues
3599 $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",
3600 intval($datarray['uid']),
3601 intval($datarray['contact-id']),
3602 dbesc($datarray['verb']),
3603 dbesc($datarray['parent-uri'])
3609 $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",
3610 intval($datarray['uid']),
3611 intval($datarray['contact-id']),
3612 dbesc($datarray['verb']),
3613 dbesc($datarray['parent-uri'])
3620 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3622 $xo = parse_xml_string($datarray['object'],false);
3623 $xt = parse_xml_string($datarray['target'],false);
3625 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3627 // fetch the parent item
3629 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3631 intval($importer['importer_uid'])
3636 // extract tag, if not duplicate, and this user allows tags, add to parent item
3638 if($xo->id && $xo->content) {
3639 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3640 if(! (stristr($tagp[0]['tag'],$newtag))) {
3641 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3642 intval($importer['importer_uid'])
3644 if(count($i) && ! intval($i[0]['blocktags'])) {
3645 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3646 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3647 intval($tagp[0]['id']),
3648 dbesc(datetime_convert()),
3649 dbesc(datetime_convert())
3651 create_tags_from_item($tagp[0]['id']);
3659 $posted_id = item_store($datarray);
3664 $datarray["id"] = $posted_id;
3666 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3668 intval($importer['importer_uid'])
3671 $parent = $r[0]['parent'];
3672 $parent_uri = $r[0]['parent-uri'];
3676 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3677 dbesc(datetime_convert()),
3678 intval($importer['importer_uid']),
3679 intval($r[0]['parent'])
3682 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3683 dbesc(datetime_convert()),
3684 intval($importer['importer_uid']),
3689 if($posted_id && $parent) {
3691 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3693 if((! $is_like) && (! $importer['self'])) {
3695 require_once('include/enotify.php');
3698 'type' => NOTIFY_COMMENT,
3699 'notify_flags' => $importer['notify-flags'],
3700 'language' => $importer['language'],
3701 'to_name' => $importer['username'],
3702 'to_email' => $importer['email'],
3703 'uid' => $importer['importer_uid'],
3704 'item' => $datarray,
3705 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3706 'source_name' => stripslashes($datarray['author-name']),
3707 'source_link' => $datarray['author-link'],
3708 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3709 ? $importer['thumb'] : $datarray['author-avatar']),
3710 'verb' => ACTIVITY_POST,
3712 'parent' => $parent,
3713 'parent_uri' => $parent_uri,
3725 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3727 $item_id = $item->get_id();
3728 $datarray = get_atom_elements($feed,$item);
3730 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3733 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3735 intval($importer['importer_uid'])
3738 // Update content if 'updated' changes
3741 if (edited_timestamp_is_newer($r[0], $datarray)) {
3743 // do not accept (ignore) an earlier edit than one we currently have.
3744 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3747 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3748 dbesc($datarray['title']),
3749 dbesc($datarray['body']),
3750 dbesc($datarray['tag']),
3751 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3752 dbesc(datetime_convert()),
3754 intval($importer['importer_uid'])
3756 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3759 // update last-child if it changes
3761 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3762 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3763 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3764 dbesc(datetime_convert()),
3766 intval($importer['importer_uid'])
3768 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3769 intval($allow[0]['data']),
3770 dbesc(datetime_convert()),
3772 intval($importer['importer_uid'])
3778 $datarray['parent-uri'] = $parent_uri;
3779 $datarray['uid'] = $importer['importer_uid'];
3780 $datarray['contact-id'] = $importer['id'];
3781 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3782 $datarray['type'] = 'activity';
3783 $datarray['gravity'] = GRAVITY_LIKE;
3784 // only one like or dislike per person
3785 // splitted into two queries for performance issues
3786 $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",
3787 intval($datarray['uid']),
3788 intval($datarray['contact-id']),
3789 dbesc($datarray['verb']),
3795 $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",
3796 intval($datarray['uid']),
3797 intval($datarray['contact-id']),
3798 dbesc($datarray['verb']),
3806 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3808 $xo = parse_xml_string($datarray['object'],false);
3809 $xt = parse_xml_string($datarray['target'],false);
3811 if($xt->type == ACTIVITY_OBJ_NOTE) {
3812 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3814 intval($importer['importer_uid'])
3819 // extract tag, if not duplicate, add to parent item
3821 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3822 q("UPDATE item SET tag = '%s' WHERE id = %d",
3823 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3826 create_tags_from_item($r[0]['id']);
3832 $posted_id = item_store($datarray);
3834 // find out if our user is involved in this conversation and wants to be notified.
3836 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3838 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3840 intval($importer['importer_uid'])
3843 if(count($myconv)) {
3844 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3846 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3847 if(! link_compare($datarray['author-link'],$importer_url)) {
3850 foreach($myconv as $conv) {
3852 // now if we find a match, it means we're in this conversation
3854 if(! link_compare($conv['author-link'],$importer_url))
3857 require_once('include/enotify.php');
3859 $conv_parent = $conv['parent'];
3862 'type' => NOTIFY_COMMENT,
3863 'notify_flags' => $importer['notify-flags'],
3864 'language' => $importer['language'],
3865 'to_name' => $importer['username'],
3866 'to_email' => $importer['email'],
3867 'uid' => $importer['importer_uid'],
3868 'item' => $datarray,
3869 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3870 'source_name' => stripslashes($datarray['author-name']),
3871 'source_link' => $datarray['author-link'],
3872 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3873 ? $importer['thumb'] : $datarray['author-avatar']),
3874 'verb' => ACTIVITY_POST,
3876 'parent' => $conv_parent,
3877 'parent_uri' => $parent_uri
3881 // only send one notification
3893 // Head post of a conversation. Have we seen it? If not, import it.
3896 $item_id = $item->get_id();
3897 $datarray = get_atom_elements($feed,$item);
3899 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3900 $ev = bbtoevent($datarray['body']);
3901 if(x($ev,'desc') && x($ev,'start')) {
3902 $ev['cid'] = $importer['id'];
3903 $ev['uid'] = $importer['uid'];
3904 $ev['uri'] = $item_id;
3905 $ev['edited'] = $datarray['edited'];
3906 $ev['private'] = $datarray['private'];
3908 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3910 intval($importer['uid'])
3913 $ev['id'] = $r[0]['id'];
3914 $xyz = event_store($ev);
3919 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3921 intval($importer['importer_uid'])
3924 // Update content if 'updated' changes
3927 if (edited_timestamp_is_newer($r[0], $datarray)) {
3929 // do not accept (ignore) an earlier edit than one we currently have.
3930 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3933 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3934 dbesc($datarray['title']),
3935 dbesc($datarray['body']),
3936 dbesc($datarray['tag']),
3937 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3938 dbesc(datetime_convert()),
3940 intval($importer['importer_uid'])
3942 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3943 update_thread_uri($item_id, $importer['importer_uid']);
3946 // update last-child if it changes
3948 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3949 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3950 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3951 intval($allow[0]['data']),
3952 dbesc(datetime_convert()),
3954 intval($importer['importer_uid'])
3960 $datarray['parent-uri'] = $item_id;
3961 $datarray['uid'] = $importer['importer_uid'];
3962 $datarray['contact-id'] = $importer['id'];
3965 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3966 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3967 // but otherwise there's a possible data mixup on the sender's system.
3968 // the tgroup delivery code called from item_store will correct it if it's a forum,
3969 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3970 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3971 $datarray['owner-name'] = $importer['senderName'];
3972 $datarray['owner-link'] = $importer['url'];
3973 $datarray['owner-avatar'] = $importer['thumb'];
3976 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3979 // This is my contact on another system, but it's really me.
3980 // Turn this into a wall post.
3981 $notify = item_is_remote_self($importer, $datarray);
3983 $posted_id = item_store($datarray, false, $notify);
3985 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3986 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3989 $xo = parse_xml_string($datarray['object'],false);
3991 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3993 // somebody was poked/prodded. Was it me?
3995 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3997 foreach($links->link as $l) {
3998 $atts = $l->attributes();
3999 switch($atts['rel']) {
4001 $Blink = $atts['href'];
4007 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4009 // send a notification
4010 require_once('include/enotify.php');
4013 'type' => NOTIFY_POKE,
4014 'notify_flags' => $importer['notify-flags'],
4015 'language' => $importer['language'],
4016 'to_name' => $importer['username'],
4017 'to_email' => $importer['email'],
4018 'uid' => $importer['importer_uid'],
4019 'item' => $datarray,
4020 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4021 'source_name' => stripslashes($datarray['author-name']),
4022 'source_link' => $datarray['author-link'],
4023 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4024 ? $importer['thumb'] : $datarray['author-avatar']),
4025 'verb' => $datarray['verb'],
4026 'otype' => 'person',
4027 'activity' => $verb,
4028 'parent' => $datarray['parent']
4044 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4045 $url = notags(trim($datarray['author-link']));
4046 $name = notags(trim($datarray['author-name']));
4047 $photo = notags(trim($datarray['author-avatar']));
4049 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4050 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4051 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4053 if(is_array($contact)) {
4054 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4055 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4056 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4057 intval(CONTACT_IS_FRIEND),
4058 intval($contact['id']),
4059 intval($importer['uid'])
4062 // send email notification to owner?
4066 // create contact record
4068 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4069 `blocked`, `readonly`, `pending`, `writable` )
4070 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4071 intval($importer['uid']),
4072 dbesc(datetime_convert()),
4074 dbesc(normalise_link($url)),
4078 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4079 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4081 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4082 intval($importer['uid']),
4086 $contact_record = $r[0];
4088 // create notification
4089 $hash = random_string();
4091 if(is_array($contact_record)) {
4092 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4093 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4094 intval($importer['uid']),
4095 intval($contact_record['id']),
4097 dbesc(datetime_convert())
4101 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4102 intval($importer['uid'])
4107 if(intval($r[0]['def_gid'])) {
4108 require_once('include/group.php');
4109 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4112 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4113 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4118 'type' => NOTIFY_INTRO,
4119 'notify_flags' => $r[0]['notify-flags'],
4120 'language' => $r[0]['language'],
4121 'to_name' => $r[0]['username'],
4122 'to_email' => $r[0]['email'],
4123 'uid' => $r[0]['uid'],
4124 'link' => $a->get_baseurl() . '/notifications/intro',
4125 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4126 'source_link' => $contact_record['url'],
4127 'source_photo' => $contact_record['photo'],
4128 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4138 function lose_follower($importer,$contact,$datarray,$item) {
4140 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4141 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4142 intval(CONTACT_IS_SHARING),
4143 intval($contact['id'])
4147 contact_remove($contact['id']);
4151 function lose_sharer($importer,$contact,$datarray,$item) {
4153 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4154 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4155 intval(CONTACT_IS_FOLLOWER),
4156 intval($contact['id'])
4160 contact_remove($contact['id']);
4165 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4169 if(is_array($importer)) {
4170 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4171 intval($importer['uid'])
4175 // Diaspora has different message-ids in feeds than they do
4176 // through the direct Diaspora protocol. If we try and use
4177 // the feed, we'll get duplicates. So don't.
4179 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4182 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4184 // Use a single verify token, even if multiple hubs
4186 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4188 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4190 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4192 if(! strlen($contact['hub-verify'])) {
4193 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4194 dbesc($verify_token),
4195 intval($contact['id'])
4199 post_url($url,$params);
4201 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4208 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4212 $name = xmlify($name);
4213 $uri = xmlify($uri);
4216 $photo = xmlify($photo);
4220 $o .= "<name>$name</name>\r\n";
4221 $o .= "<uri>$uri</uri>\r\n";
4222 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4223 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4225 call_hooks('atom_author', $o);
4227 $o .= "</$tag>\r\n";
4231 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4235 if(! $item['parent'])
4238 if($item['deleted'])
4239 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4242 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4243 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4245 $body = $item['body'];
4248 $o = "\r\n\r\n<entry>\r\n";
4250 if(is_array($author))
4251 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4253 $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']));
4254 if(strlen($item['owner-name']))
4255 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4257 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4258 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4259 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4264 if ($item['title'] != "")
4265 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4267 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4269 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4270 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4271 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4272 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4273 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4274 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4275 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4279 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4281 if($item['location']) {
4282 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4283 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4287 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4289 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4290 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4293 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4294 if($item['bookmark'])
4295 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4298 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4301 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4303 if($item['signed_text']) {
4304 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4305 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4308 $verb = construct_verb($item);
4309 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4310 $actobj = construct_activity_object($item);
4313 $actarg = construct_activity_target($item);
4317 $tags = item_getfeedtags($item);
4319 foreach($tags as $t) {
4320 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4324 $o .= item_getfeedattach($item);
4326 $mentioned = get_mentions($item);
4330 call_hooks('atom_entry', $o);
4332 $o .= '</entry>' . "\r\n";
4337 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4339 if(get_config('system','disable_embedded'))
4344 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4345 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4350 $img_start = strpos($orig_body, '[img');
4351 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4352 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4353 while( ($img_st_close !== false) && ($img_len !== false) ) {
4355 $img_st_close++; // make it point to AFTER the closing bracket
4356 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4358 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4361 if(stristr($image , $site . '/photo/')) {
4362 // Only embed locally hosted photos
4364 $i = basename($image);
4365 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4366 $x = strpos($i,'-');
4369 $res = substr($i,$x+1);
4370 $i = substr($i,0,$x);
4371 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4378 // Check to see if we should replace this photo link with an embedded image
4379 // 1. No need to do so if the photo is public
4380 // 2. If there's a contact-id provided, see if they're in the access list
4381 // for the photo. If so, embed it.
4382 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4383 // permissions, regardless of order but first check to see if they're an exact
4384 // match to save some processing overhead.
4386 if(has_permissions($r[0])) {
4388 $recips = enumerate_permissions($r[0]);
4389 if(in_array($cid, $recips)) {
4394 if(compare_permissions($item,$r[0]))
4399 $data = $r[0]['data'];
4400 $type = $r[0]['type'];
4402 // If a custom width and height were specified, apply before embedding
4403 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4404 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4406 $width = intval($match[1]);
4407 $height = intval($match[2]);
4409 $ph = new Photo($data, $type);
4410 if($ph->is_valid()) {
4411 $ph->scaleImage(max($width, $height));
4412 $data = $ph->imageString();
4413 $type = $ph->getType();
4417 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4418 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4419 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4425 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4426 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4427 if($orig_body === false)
4430 $img_start = strpos($orig_body, '[img');
4431 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4432 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4435 $new_body = $new_body . $orig_body;
4441 function has_permissions($obj) {
4442 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4447 function compare_permissions($obj1,$obj2) {
4448 // first part is easy. Check that these are exactly the same.
4449 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4450 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4451 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4452 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4455 // This is harder. Parse all the permissions and compare the resulting set.
4457 $recipients1 = enumerate_permissions($obj1);
4458 $recipients2 = enumerate_permissions($obj2);
4461 if($recipients1 == $recipients2)
4466 // returns an array of contact-ids that are allowed to see this object
4468 function enumerate_permissions($obj) {
4469 require_once('include/group.php');
4470 $allow_people = expand_acl($obj['allow_cid']);
4471 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4472 $deny_people = expand_acl($obj['deny_cid']);
4473 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4474 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4475 $deny = array_unique(array_merge($deny_people,$deny_groups));
4476 $recipients = array_diff($recipients,$deny);
4480 function item_getfeedtags($item) {
4483 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4485 for($x = 0; $x < $cnt; $x ++) {
4487 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4491 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4493 for($x = 0; $x < $cnt; $x ++) {
4495 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4501 function item_getfeedattach($item) {
4503 $arr = explode('[/attach],',$item['attach']);
4505 foreach($arr as $r) {
4507 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4509 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4510 if(intval($matches[2]))
4511 $ret .= 'length="' . intval($matches[2]) . '" ';
4512 if($matches[4] !== ' ')
4513 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4514 $ret .= ' />' . "\r\n";
4523 function item_expire($uid, $days, $network = "", $force = false) {
4525 if((! $uid) || ($days < 1))
4528 // $expire_network_only = save your own wall posts
4529 // and just expire conversations started by others
4531 $expire_network_only = get_pconfig($uid,'expire','network_only');
4532 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4534 if ($network != "") {
4535 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4536 // There is an index "uid_network_received" but not "uid_network_created"
4537 // This avoids the creation of another index just for one purpose.
4538 // And it doesn't really matter wether to look at "received" or "created"
4539 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4541 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4543 $r = q("SELECT * FROM `item`
4544 WHERE `uid` = %d $range
4555 $expire_items = get_pconfig($uid, 'expire','items');
4556 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4558 // Forcing expiring of items - but not notes and marked items
4560 $expire_items = true;
4562 $expire_notes = get_pconfig($uid, 'expire','notes');
4563 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4565 $expire_starred = get_pconfig($uid, 'expire','starred');
4566 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4568 $expire_photos = get_pconfig($uid, 'expire','photos');
4569 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4571 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4573 foreach($r as $item) {
4575 // don't expire filed items
4577 if(strpos($item['file'],'[') !== false)
4580 // Only expire posts, not photos and photo comments
4582 if($expire_photos==0 && strlen($item['resource-id']))
4584 if($expire_starred==0 && intval($item['starred']))
4586 if($expire_notes==0 && $item['type']=='note')
4588 if($expire_items==0 && $item['type']!='note')
4591 drop_item($item['id'],false);
4594 proc_run('php',"include/notifier.php","expire","$uid");
4599 function drop_items($items) {
4602 if(! local_user() && ! remote_user())
4606 foreach($items as $item) {
4607 $owner = drop_item($item,false);
4608 if($owner && ! $uid)
4613 // multiple threads may have been deleted, send an expire notification
4616 proc_run('php',"include/notifier.php","expire","$uid");
4620 function drop_item($id,$interactive = true) {
4624 // locate item to be deleted
4626 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4633 notice( t('Item not found.') . EOL);
4634 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4639 $owner = $item['uid'];
4643 // check if logged in user is either the author or owner of this item
4645 if(is_array($_SESSION['remote'])) {
4646 foreach($_SESSION['remote'] as $visitor) {
4647 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4648 $cid = $visitor['cid'];
4655 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4657 // Check if we should do HTML-based delete confirmation
4658 if($_REQUEST['confirm']) {
4659 // <form> can't take arguments in its "action" parameter
4660 // so add any arguments as hidden inputs
4661 $query = explode_querystring($a->query_string);
4663 foreach($query['args'] as $arg) {
4664 if(strpos($arg, 'confirm=') === false) {
4665 $arg_parts = explode('=', $arg);
4666 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4670 return replace_macros(get_markup_template('confirm.tpl'), array(
4672 '$message' => t('Do you really want to delete this item?'),
4673 '$extra_inputs' => $inputs,
4674 '$confirm' => t('Yes'),
4675 '$confirm_url' => $query['base'],
4676 '$confirm_name' => 'confirmed',
4677 '$cancel' => t('Cancel'),
4680 // Now check how the user responded to the confirmation query
4681 if($_REQUEST['canceled']) {
4682 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4685 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4688 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4689 dbesc(datetime_convert()),
4690 dbesc(datetime_convert()),
4693 create_tags_from_item($item['id']);
4694 create_files_from_item($item['id']);
4695 delete_thread($item['id'], $item['parent-uri']);
4697 // clean up categories and tags so they don't end up as orphans
4700 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4702 foreach($matches as $mtch) {
4703 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4709 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4711 foreach($matches as $mtch) {
4712 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4716 // If item is a link to a photo resource, nuke all the associated photos
4717 // (visitors will not have photo resources)
4718 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4719 // generate a resource-id and therefore aren't intimately linked to the item.
4721 if(strlen($item['resource-id'])) {
4722 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4723 dbesc($item['resource-id']),
4724 intval($item['uid'])
4726 // ignore the result
4729 // If item is a link to an event, nuke the event record.
4731 if(intval($item['event-id'])) {
4732 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4733 intval($item['event-id']),
4734 intval($item['uid'])
4736 // ignore the result
4739 // clean up item_id and sign meta-data tables
4742 // Old code - caused very long queries and warning entries in the mysql logfiles:
4744 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4745 intval($item['id']),
4746 intval($item['uid'])
4749 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4750 intval($item['id']),
4751 intval($item['uid'])
4755 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4757 // Creating list of parents
4758 $r = q("select id from item where parent = %d and uid = %d",
4759 intval($item['id']),
4760 intval($item['uid'])
4765 foreach ($r AS $row) {
4766 if ($parentid != "")
4769 $parentid .= $row["id"];
4773 if ($parentid != "") {
4774 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4776 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4779 // If it's the parent of a comment thread, kill all the kids
4781 if($item['uri'] == $item['parent-uri']) {
4782 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4783 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4784 dbesc(datetime_convert()),
4785 dbesc(datetime_convert()),
4786 dbesc($item['parent-uri']),
4787 intval($item['uid'])
4789 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4790 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4791 delete_thread_uri($item['parent-uri'], $item['uid']);
4792 // ignore the result
4795 // ensure that last-child is set in case the comment that had it just got wiped.
4796 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4797 dbesc(datetime_convert()),
4798 dbesc($item['parent-uri']),
4799 intval($item['uid'])
4801 // who is the last child now?
4802 $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",
4803 dbesc($item['parent-uri']),
4804 intval($item['uid'])
4807 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4812 // Add a relayable_retraction signature for Diaspora.
4813 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4815 $drop_id = intval($item['id']);
4817 // send the notification upstream/downstream as the case may be
4819 proc_run('php',"include/notifier.php","drop","$drop_id");
4823 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4829 notice( t('Permission denied.') . EOL);
4830 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4837 function first_post_date($uid,$wall = false) {
4838 $r = q("select id, created from item
4839 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4841 order by created asc limit 1",
4843 intval($wall ? 1 : 0)
4846 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4847 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4852 function posted_dates($uid,$wall) {
4853 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4855 $dthen = first_post_date($uid,$wall);
4859 // Set the start and end date to the beginning of the month
4860 $dnow = substr($dnow,0,8).'01';
4861 $dthen = substr($dthen,0,8).'01';
4864 // Starting with the current month, get the first and last days of every
4865 // month down to and including the month of the first post
4866 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4867 $dstart = substr($dnow,0,8) . '01';
4868 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4869 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4870 $end_month = datetime_convert('','',$dend,'Y-m-d');
4871 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4872 $ret[] = array($str,$end_month,$start_month);
4873 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4879 function posted_date_widget($url,$uid,$wall) {
4882 if(! feature_enabled($uid,'archives'))
4885 // For former Facebook folks that left because of "timeline"
4887 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4890 $ret = posted_dates($uid,$wall);
4894 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4895 '$title' => t('Archives'),
4896 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4903 function store_diaspora_retract_sig($item, $user, $baseurl) {
4904 // Note that we can't add a target_author_signature
4905 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4906 // the comment, that means we're the home of the post, and Diaspora will only
4907 // check the parent_author_signature of retractions that it doesn't have to relay further
4909 // I don't think this function gets called for an "unlike," but I'll check anyway
4911 $enabled = intval(get_config('system','diaspora_enabled'));
4913 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4917 logger('drop_item: storing diaspora retraction signature');
4919 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4921 if(local_user() == $item['uid']) {
4923 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4924 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4927 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4928 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4931 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4932 // only handles DFRN deletes
4933 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4934 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4935 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4941 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4942 intval($item['id']),
4943 dbesc($signed_text),