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,
1572 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1575 update_thread($parent_id);
1576 add_shadow_entry($arr);
1580 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1582 return $current_post;
1585 function item_body_set_hashtags(&$item) {
1587 $tags = get_tags($item["body"]);
1593 // This sorting is important when there are hashtags that are part of other hashtags
1594 // Otherwise there could be problems with hashtags like #test and #test2
1599 $URLSearchString = "^\[\]";
1601 // All hashtags should point to the home server
1602 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1603 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1605 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1606 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1608 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1609 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1611 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1614 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1616 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1619 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1621 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1624 // Repair recursive urls
1625 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1626 "#$2", $item["body"]);
1628 foreach($tags as $tag) {
1629 if(strpos($tag,'#') !== 0)
1632 if(strpos($tag,'[url='))
1635 $basetag = str_replace('_',' ',substr($tag,1));
1637 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1639 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1641 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1642 if(strlen($item["tag"]))
1643 $item["tag"] = ','.$item["tag"];
1644 $item["tag"] = $newtag.$item["tag"];
1648 // Convert back the masked hashtags
1649 $item["body"] = str_replace("#", "#", $item["body"]);
1652 function get_item_guid($id) {
1653 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1655 return($r[0]["guid"]);
1660 function get_item_id($guid, $uid = 0) {
1666 $uid == local_user();
1668 // Does the given user have this item?
1670 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1671 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1672 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1675 $nick = $r[0]["nickname"];
1679 // Or is it anywhere on the server?
1681 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1682 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1683 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1684 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1685 AND `item`.`private` = 0 AND `item`.`wall` = 1
1686 AND `item`.`guid` = '%s'", dbesc($guid));
1689 $nick = $r[0]["nickname"];
1692 return(array("nick" => $nick, "id" => $id));
1696 function get_item_contact($item,$contacts) {
1697 if(! count($contacts) || (! is_array($item)))
1699 foreach($contacts as $contact) {
1700 if($contact['id'] == $item['contact-id']) {
1702 break; // NOTREACHED
1709 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1711 * @param int $item_id
1712 * @return bool true if item was deleted, else false
1714 function tag_deliver($uid,$item_id) {
1722 $u = q("select * from user where uid = %d limit 1",
1728 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1729 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1732 $i = q("select * from item where id = %d and uid = %d limit 1",
1741 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1743 // Diaspora uses their own hardwired link URL in @-tags
1744 // instead of the one we supply with webfinger
1746 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1748 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1750 foreach($matches as $mtch) {
1751 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1753 logger('tag_deliver: mention found: ' . $mtch[2]);
1759 if ( ($community_page || $prvgroup) &&
1760 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1761 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1763 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1764 q("DELETE FROM item WHERE id = %d and uid = %d",
1774 // send a notification
1776 // use a local photo if we have one
1778 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1779 intval($u[0]['uid']),
1780 dbesc(normalise_link($item['author-link']))
1782 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1785 require_once('include/enotify.php');
1787 'type' => NOTIFY_TAGSELF,
1788 'notify_flags' => $u[0]['notify-flags'],
1789 'language' => $u[0]['language'],
1790 'to_name' => $u[0]['username'],
1791 'to_email' => $u[0]['email'],
1792 'uid' => $u[0]['uid'],
1794 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1795 'source_name' => $item['author-name'],
1796 'source_link' => $item['author-link'],
1797 'source_photo' => $photo,
1798 'verb' => ACTIVITY_TAG,
1800 'parent' => $item['parent']
1804 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1806 call_hooks('tagged', $arr);
1808 if((! $community_page) && (! $prvgroup))
1812 // tgroup delivery - setup a second delivery chain
1813 // prevent delivery looping - only proceed
1814 // if the message originated elsewhere and is a top-level post
1816 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1819 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1822 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1823 intval($u[0]['uid'])
1828 // also reset all the privacy bits to the forum default permissions
1830 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1832 $forum_mode = (($prvgroup) ? 2 : 1);
1834 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1835 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1836 intval($forum_mode),
1837 dbesc($c[0]['name']),
1838 dbesc($c[0]['url']),
1839 dbesc($c[0]['thumb']),
1841 dbesc($u[0]['allow_cid']),
1842 dbesc($u[0]['allow_gid']),
1843 dbesc($u[0]['deny_cid']),
1844 dbesc($u[0]['deny_gid']),
1847 update_thread($item_id);
1849 proc_run('php','include/notifier.php','tgroup',$item_id);
1855 function tgroup_check($uid,$item) {
1861 // check that the message originated elsewhere and is a top-level post
1863 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1867 $u = q("select * from user where uid = %d limit 1",
1873 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1874 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1877 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1879 // Diaspora uses their own hardwired link URL in @-tags
1880 // instead of the one we supply with webfinger
1882 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1884 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1886 foreach($matches as $mtch) {
1887 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1889 logger('tgroup_check: mention found: ' . $mtch[2]);
1897 if((! $community_page) && (! $prvgroup))
1911 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1915 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1917 if($contact['duplex'] && $contact['dfrn-id'])
1918 $idtosend = '0:' . $orig_id;
1919 if($contact['duplex'] && $contact['issued-id'])
1920 $idtosend = '1:' . $orig_id;
1922 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1924 $rino_enable = get_config('system','rino_encrypt');
1929 $ssl_val = intval(get_config('system','ssl_policy'));
1933 case SSL_POLICY_FULL:
1934 $ssl_policy = 'full';
1936 case SSL_POLICY_SELFSIGN:
1937 $ssl_policy = 'self';
1939 case SSL_POLICY_NONE:
1941 $ssl_policy = 'none';
1945 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1947 logger('dfrn_deliver: ' . $url);
1949 $xml = fetch_url($url);
1951 $curl_stat = $a->get_curl_code();
1953 return(-1); // timed out
1955 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1960 if(strpos($xml,'<?xml') === false) {
1961 logger('dfrn_deliver: no valid XML returned');
1962 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1966 $res = parse_xml_string($xml);
1968 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1969 return (($res->status) ? $res->status : 3);
1971 $postvars = array();
1972 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1973 $challenge = hex2bin((string) $res->challenge);
1974 $perm = (($res->perm) ? $res->perm : null);
1975 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1976 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1977 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1979 if($owner['page-flags'] == PAGE_PRVGROUP)
1982 $final_dfrn_id = '';
1985 if((($perm == 'rw') && (! intval($contact['writable'])))
1986 || (($perm == 'r') && (intval($contact['writable'])))) {
1987 q("update contact set writable = %d where id = %d",
1988 intval(($perm == 'rw') ? 1 : 0),
1989 intval($contact['id'])
1991 $contact['writable'] = (string) 1 - intval($contact['writable']);
1995 if(($contact['duplex'] && strlen($contact['pubkey']))
1996 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1997 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1998 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1999 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2002 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2003 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2006 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2008 if(strpos($final_dfrn_id,':') == 1)
2009 $final_dfrn_id = substr($final_dfrn_id,2);
2011 if($final_dfrn_id != $orig_id) {
2012 logger('dfrn_deliver: wrong dfrn_id.');
2013 // did not decode properly - cannot trust this site
2017 $postvars['dfrn_id'] = $idtosend;
2018 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2020 $postvars['dissolve'] = '1';
2023 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2024 $postvars['data'] = $atom;
2025 $postvars['perm'] = 'rw';
2028 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2029 $postvars['perm'] = 'r';
2032 $postvars['ssl_policy'] = $ssl_policy;
2035 $postvars['page'] = $page;
2037 if($rino && $rino_allowed && (! $dissolve)) {
2038 $key = substr(random_string(),0,16);
2039 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2040 $postvars['data'] = $data;
2041 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2044 if($dfrn_version >= 2.1) {
2045 if(($contact['duplex'] && strlen($contact['pubkey']))
2046 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2047 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2049 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2052 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2056 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2057 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2060 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2064 logger('md5 rawkey ' . md5($postvars['key']));
2066 $postvars['key'] = bin2hex($postvars['key']);
2069 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2071 $xml = post_url($contact['notify'],$postvars);
2073 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2075 $curl_stat = $a->get_curl_code();
2076 if((! $curl_stat) || (! strlen($xml)))
2077 return(-1); // timed out
2079 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2082 if(strpos($xml,'<?xml') === false) {
2083 logger('dfrn_deliver: phase 2: no valid XML returned');
2084 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2088 if($contact['term-date'] != '0000-00-00 00:00:00') {
2089 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2090 require_once('include/Contact.php');
2091 unmark_for_death($contact);
2094 $res = parse_xml_string($xml);
2096 return $res->status;
2101 This function returns true if $update has an edited timestamp newer
2102 than $existing, i.e. $update contains new data which should override
2103 what's already there. If there is no timestamp yet, the update is
2104 assumed to be newer. If the update has no timestamp, the existing
2105 item is assumed to be up-to-date. If the timestamps are equal it
2106 assumes the update has been seen before and should be ignored.
2108 function edited_timestamp_is_newer($existing, $update) {
2109 if (!x($existing,'edited') || !$existing['edited']) {
2112 if (!x($update,'edited') || !$update['edited']) {
2115 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2116 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2117 return (strcmp($existing_edited, $update_edited) < 0);
2122 * consume_feed - process atom feed and update anything/everything we might need to update
2124 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2126 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2127 * It is this person's stuff that is going to be updated.
2128 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2129 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2130 * have a contact record.
2131 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2132 * might not) try and subscribe to it.
2133 * $datedir sorts in reverse order
2134 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2135 * imported prior to its children being seen in the stream unless we are certain
2136 * of how the feed is arranged/ordered.
2137 * With $pass = 1, we only pull parent items out of the stream.
2138 * With $pass = 2, we only pull children (comments/likes).
2140 * So running this twice, first with pass 1 and then with pass 2 will do the right
2141 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2142 * model where comments can have sub-threads. That would require some massive sorting
2143 * to get all the feed items into a mostly linear ordering, and might still require
2147 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2149 require_once('library/simplepie/simplepie.inc');
2150 require_once('include/contact_selectors.php');
2152 if(! strlen($xml)) {
2153 logger('consume_feed: empty input');
2157 $feed = new SimplePie();
2158 $feed->set_raw_data($xml);
2160 $feed->enable_order_by_date(true);
2162 $feed->enable_order_by_date(false);
2166 logger('consume_feed: Error parsing XML: ' . $feed->error());
2168 $permalink = $feed->get_permalink();
2170 // Check at the feed level for updated contact name and/or photo
2174 $photo_timestamp = '';
2177 $contact_updated = '';
2179 $hubs = $feed->get_links('hub');
2180 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2183 $hub = implode(',', $hubs);
2185 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2187 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2189 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2190 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2191 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2192 $new_name = $elems['name'][0]['data'];
2194 // Manually checking for changed contact names
2195 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2196 $name_updated = date("c");
2197 $photo_timestamp = date("c");
2200 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2201 if ($photo_timestamp == "")
2202 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2203 $photo_url = $elems['link'][0]['attribs']['']['href'];
2206 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2207 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2211 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2212 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2214 $contact_updated = $photo_timestamp;
2216 require_once("include/Photo.php");
2217 $photo_failure = false;
2218 $have_photo = false;
2220 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2221 intval($contact['id']),
2222 intval($contact['uid'])
2225 $resource_id = $r[0]['resource-id'];
2229 $resource_id = photo_new_resource();
2232 $img_str = fetch_url($photo_url,true);
2233 // guess mimetype from headers or filename
2234 $type = guess_image_type($photo_url,true);
2237 $img = new Photo($img_str, $type);
2238 if($img->is_valid()) {
2240 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2241 dbesc($resource_id),
2242 intval($contact['id']),
2243 intval($contact['uid'])
2247 $img->scaleImageSquare(175);
2249 $hash = $resource_id;
2250 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2252 $img->scaleImage(80);
2253 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2255 $img->scaleImage(48);
2256 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2260 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2261 WHERE `uid` = %d AND `id` = %d",
2262 dbesc(datetime_convert()),
2263 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2264 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2265 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2266 intval($contact['uid']),
2267 intval($contact['id'])
2272 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2273 if ($name_updated > $contact_updated)
2274 $contact_updated = $name_updated;
2276 $r = q("select * from contact where uid = %d and id = %d limit 1",
2277 intval($contact['uid']),
2278 intval($contact['id'])
2281 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2282 dbesc(notags(trim($new_name))),
2283 dbesc(datetime_convert()),
2284 intval($contact['uid']),
2285 intval($contact['id'])
2288 // do our best to update the name on content items
2291 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2292 dbesc(notags(trim($new_name))),
2293 dbesc($r[0]['name']),
2294 dbesc($r[0]['url']),
2295 intval($contact['uid'])
2300 if ($contact_updated AND $new_name AND $photo_url)
2301 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2303 if(strlen($birthday)) {
2304 if(substr($birthday,0,4) != $contact['bdyear']) {
2305 logger('consume_feed: updating birthday: ' . $birthday);
2309 * Add new birthday event for this person
2311 * $bdtext is just a readable placeholder in case the event is shared
2312 * with others. We will replace it during presentation to our $importer
2313 * to contain a sparkle link and perhaps a photo.
2317 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2318 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2321 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2322 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2323 intval($contact['uid']),
2324 intval($contact['id']),
2325 dbesc(datetime_convert()),
2326 dbesc(datetime_convert()),
2327 dbesc(datetime_convert('UTC','UTC', $birthday)),
2328 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2337 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2338 dbesc(substr($birthday,0,4)),
2339 intval($contact['uid']),
2340 intval($contact['id'])
2343 // This function is called twice without reloading the contact
2344 // Make sure we only create one event. This is why &$contact
2345 // is a reference var in this function
2347 $contact['bdyear'] = substr($birthday,0,4);
2351 $community_page = 0;
2352 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2354 $community_page = intval($rawtags[0]['data']);
2356 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2357 q("update contact set forum = %d where id = %d",
2358 intval($community_page),
2359 intval($contact['id'])
2361 $contact['forum'] = (string) $community_page;
2365 // process any deleted entries
2367 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2368 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2369 foreach($del_entries as $dentry) {
2371 if(isset($dentry['attribs']['']['ref'])) {
2372 $uri = $dentry['attribs']['']['ref'];
2374 if(isset($dentry['attribs']['']['when'])) {
2375 $when = $dentry['attribs']['']['when'];
2376 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2379 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2381 if($deleted && is_array($contact)) {
2382 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2383 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2385 intval($importer['uid']),
2386 intval($contact['id'])
2391 if(! $item['deleted'])
2392 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2394 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2395 $xo = parse_xml_string($item['object'],false);
2396 $xt = parse_xml_string($item['target'],false);
2397 if($xt->type === ACTIVITY_OBJ_NOTE) {
2398 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2400 intval($importer['importer_uid'])
2404 // For tags, the owner cannot remove the tag on the author's copy of the post.
2406 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2407 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2408 $author_copy = (($item['origin']) ? true : false);
2410 if($owner_remove && $author_copy)
2412 if($author_remove || $owner_remove) {
2413 $tags = explode(',',$i[0]['tag']);
2416 foreach($tags as $tag)
2417 if(trim($tag) !== trim($xo->body))
2418 $newtags[] = trim($tag);
2420 q("update item set tag = '%s' where id = %d",
2421 dbesc(implode(',',$newtags)),
2424 create_tags_from_item($i[0]['id']);
2430 if($item['uri'] == $item['parent-uri']) {
2431 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2432 `body` = '', `title` = ''
2433 WHERE `parent-uri` = '%s' AND `uid` = %d",
2435 dbesc(datetime_convert()),
2436 dbesc($item['uri']),
2437 intval($importer['uid'])
2439 create_tags_from_itemuri($item['uri'], $importer['uid']);
2440 create_files_from_itemuri($item['uri'], $importer['uid']);
2441 update_thread_uri($item['uri'], $importer['uid']);
2444 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2445 `body` = '', `title` = ''
2446 WHERE `uri` = '%s' AND `uid` = %d",
2448 dbesc(datetime_convert()),
2450 intval($importer['uid'])
2452 create_tags_from_itemuri($uri, $importer['uid']);
2453 create_files_from_itemuri($uri, $importer['uid']);
2454 if($item['last-child']) {
2455 // ensure that last-child is set in case the comment that had it just got wiped.
2456 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2457 dbesc(datetime_convert()),
2458 dbesc($item['parent-uri']),
2459 intval($item['uid'])
2461 // who is the last child now?
2462 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2463 ORDER BY `created` DESC LIMIT 1",
2464 dbesc($item['parent-uri']),
2465 intval($importer['uid'])
2468 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2479 // Now process the feed
2481 if($feed->get_item_quantity()) {
2483 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2485 // in inverse date order
2487 $items = array_reverse($feed->get_items());
2489 $items = $feed->get_items();
2492 foreach($items as $item) {
2495 $item_id = $item->get_id();
2496 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2497 if(isset($rawthread[0]['attribs']['']['ref'])) {
2499 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2502 if(($is_reply) && is_array($contact)) {
2507 // not allowed to post
2509 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2513 // Have we seen it? If not, import it.
2515 $item_id = $item->get_id();
2516 $datarray = get_atom_elements($feed, $item, $contact);
2518 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2519 $datarray['author-name'] = $contact['name'];
2520 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2521 $datarray['author-link'] = $contact['url'];
2522 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2523 $datarray['author-avatar'] = $contact['thumb'];
2525 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2526 logger('consume_feed: no author information! ' . print_r($datarray,true));
2530 $force_parent = false;
2531 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2532 if($contact['network'] === NETWORK_OSTATUS)
2533 $force_parent = true;
2534 if(strlen($datarray['title']))
2535 unset($datarray['title']);
2536 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2537 dbesc(datetime_convert()),
2539 intval($importer['uid'])
2541 $datarray['last-child'] = 1;
2542 update_thread_uri($parent_uri, $importer['uid']);
2546 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2548 intval($importer['uid'])
2551 // Update content if 'updated' changes
2554 if (edited_timestamp_is_newer($r[0], $datarray)) {
2556 // do not accept (ignore) an earlier edit than one we currently have.
2557 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2560 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2561 dbesc($datarray['title']),
2562 dbesc($datarray['body']),
2563 dbesc($datarray['tag']),
2564 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2565 dbesc(datetime_convert()),
2567 intval($importer['uid'])
2569 create_tags_from_itemuri($item_id, $importer['uid']);
2570 update_thread_uri($item_id, $importer['uid']);
2573 // update last-child if it changes
2575 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2576 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2577 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2578 dbesc(datetime_convert()),
2580 intval($importer['uid'])
2582 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2583 intval($allow[0]['data']),
2584 dbesc(datetime_convert()),
2586 intval($importer['uid'])
2588 update_thread_uri($item_id, $importer['uid']);
2594 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2595 // one way feed - no remote comment ability
2596 $datarray['last-child'] = 0;
2598 $datarray['parent-uri'] = $parent_uri;
2599 $datarray['uid'] = $importer['uid'];
2600 $datarray['contact-id'] = $contact['id'];
2601 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2602 $datarray['type'] = 'activity';
2603 $datarray['gravity'] = GRAVITY_LIKE;
2604 // only one like or dislike per person
2605 // splitted into two queries for performance issues
2606 $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",
2607 intval($datarray['uid']),
2608 intval($datarray['contact-id']),
2609 dbesc($datarray['verb']),
2615 $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",
2616 intval($datarray['uid']),
2617 intval($datarray['contact-id']),
2618 dbesc($datarray['verb']),
2625 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2626 $xo = parse_xml_string($datarray['object'],false);
2627 $xt = parse_xml_string($datarray['target'],false);
2629 if($xt->type == ACTIVITY_OBJ_NOTE) {
2630 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2632 intval($importer['importer_uid'])
2637 // extract tag, if not duplicate, add to parent item
2638 if($xo->id && $xo->content) {
2639 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2640 if(! (stristr($r[0]['tag'],$newtag))) {
2641 q("UPDATE item SET tag = '%s' WHERE id = %d",
2642 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2645 create_tags_from_item($r[0]['id']);
2651 $r = item_store($datarray,$force_parent);
2657 // Head post of a conversation. Have we seen it? If not, import it.
2659 $item_id = $item->get_id();
2661 $datarray = get_atom_elements($feed, $item, $contact);
2663 if(is_array($contact)) {
2664 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2665 $datarray['author-name'] = $contact['name'];
2666 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2667 $datarray['author-link'] = $contact['url'];
2668 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2669 $datarray['author-avatar'] = $contact['thumb'];
2672 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2673 logger('consume_feed: no author information! ' . print_r($datarray,true));
2677 // special handling for events
2679 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2680 $ev = bbtoevent($datarray['body']);
2681 if(x($ev,'desc') && x($ev,'start')) {
2682 $ev['uid'] = $importer['uid'];
2683 $ev['uri'] = $item_id;
2684 $ev['edited'] = $datarray['edited'];
2685 $ev['private'] = $datarray['private'];
2687 if(is_array($contact))
2688 $ev['cid'] = $contact['id'];
2689 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2691 intval($importer['uid'])
2694 $ev['id'] = $r[0]['id'];
2695 $xyz = event_store($ev);
2700 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2701 if(strlen($datarray['title']))
2702 unset($datarray['title']);
2703 $datarray['last-child'] = 1;
2707 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2709 intval($importer['uid'])
2712 // Update content if 'updated' changes
2715 if (edited_timestamp_is_newer($r[0], $datarray)) {
2717 // do not accept (ignore) an earlier edit than one we currently have.
2718 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2721 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2722 dbesc($datarray['title']),
2723 dbesc($datarray['body']),
2724 dbesc($datarray['tag']),
2725 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2726 dbesc(datetime_convert()),
2728 intval($importer['uid'])
2730 create_tags_from_itemuri($item_id, $importer['uid']);
2731 update_thread_uri($item_id, $importer['uid']);
2734 // update last-child if it changes
2736 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2737 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2738 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2739 intval($allow[0]['data']),
2740 dbesc(datetime_convert()),
2742 intval($importer['uid'])
2744 update_thread_uri($item_id, $importer['uid']);
2749 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2750 logger('consume-feed: New follower');
2751 new_follower($importer,$contact,$datarray,$item);
2754 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2755 lose_follower($importer,$contact,$datarray,$item);
2759 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2760 logger('consume-feed: New friend request');
2761 new_follower($importer,$contact,$datarray,$item,true);
2764 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2765 lose_sharer($importer,$contact,$datarray,$item);
2770 if(! is_array($contact))
2774 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2775 // one way feed - no remote comment ability
2776 $datarray['last-child'] = 0;
2778 if($contact['network'] === NETWORK_FEED)
2779 $datarray['private'] = 2;
2781 $datarray['parent-uri'] = $item_id;
2782 $datarray['uid'] = $importer['uid'];
2783 $datarray['contact-id'] = $contact['id'];
2785 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2786 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2787 // but otherwise there's a possible data mixup on the sender's system.
2788 // the tgroup delivery code called from item_store will correct it if it's a forum,
2789 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2790 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2791 $datarray['owner-name'] = $contact['name'];
2792 $datarray['owner-link'] = $contact['url'];
2793 $datarray['owner-avatar'] = $contact['thumb'];
2796 // We've allowed "followers" to reach this point so we can decide if they are
2797 // posting an @-tag delivery, which followers are allowed to do for certain
2798 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2800 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2803 // This is my contact on another system, but it's really me.
2804 // Turn this into a wall post.
2805 $notify = item_is_remote_self($contact, $datarray);
2807 $r = item_store($datarray, false, $notify);
2808 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2816 function item_is_remote_self($contact, &$datarray) {
2819 if (!$contact['remote_self'])
2822 // Prevent the forwarding of posts that are forwarded
2823 if ($datarray["extid"] == NETWORK_DFRN)
2826 // Prevent to forward already forwarded posts
2827 if ($datarray["app"] == $a->get_hostname())
2830 // Only forward posts
2831 if ($datarray["verb"] != ACTIVITY_POST)
2834 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2837 $datarray2 = $datarray;
2838 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2839 if ($contact['remote_self'] == 2) {
2840 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2841 intval($contact['uid']));
2843 $datarray['contact-id'] = $r[0]["id"];
2845 $datarray['owner-name'] = $r[0]["name"];
2846 $datarray['owner-link'] = $r[0]["url"];
2847 $datarray['owner-avatar'] = $r[0]["thumb"];
2849 $datarray['author-name'] = $datarray['owner-name'];
2850 $datarray['author-link'] = $datarray['owner-link'];
2851 $datarray['author-avatar'] = $datarray['owner-avatar'];
2854 if ($contact['network'] != NETWORK_FEED) {
2855 $datarray["guid"] = get_guid(32);
2856 unset($datarray["plink"]);
2857 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2858 $datarray["parent-uri"] = $datarray["uri"];
2859 $datarray["extid"] = $contact['network'];
2860 $urlpart = parse_url($datarray2['author-link']);
2861 $datarray["app"] = $urlpart["host"];
2863 $datarray['private'] = 0;
2866 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2867 // $datarray["app"] = network_to_name($contact['network']);
2869 if ($contact['network'] != NETWORK_FEED) {
2870 // Store the original post
2871 $r = item_store($datarray2, false, false);
2872 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2874 $datarray["app"] = "Feed";
2879 function local_delivery($importer,$data) {
2882 logger(__function__, LOGGER_TRACE);
2884 if($importer['readonly']) {
2885 // We aren't receiving stuff from this person. But we will quietly ignore them
2886 // rather than a blatant "go away" message.
2887 logger('local_delivery: ignoring');
2892 // Consume notification feed. This may differ from consuming a public feed in several ways
2893 // - might contain email or friend suggestions
2894 // - might contain remote followup to our message
2895 // - in which case we need to accept it and then notify other conversants
2896 // - we may need to send various email notifications
2898 $feed = new SimplePie();
2899 $feed->set_raw_data($data);
2900 $feed->enable_order_by_date(false);
2905 logger('local_delivery: Error parsing XML: ' . $feed->error());
2908 // Check at the feed level for updated contact name and/or photo
2912 $photo_timestamp = '';
2914 $contact_updated = '';
2917 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2919 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2921 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2924 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2925 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2926 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2927 $new_name = $elems['name'][0]['data'];
2929 // Manually checking for changed contact names
2930 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2931 $name_updated = date("c");
2932 $photo_timestamp = date("c");
2935 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2936 if ($photo_timestamp == "")
2937 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2938 $photo_url = $elems['link'][0]['attribs']['']['href'];
2942 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2944 $contact_updated = $photo_timestamp;
2946 logger('local_delivery: Updating photo for ' . $importer['name']);
2947 require_once("include/Photo.php");
2948 $photo_failure = false;
2949 $have_photo = false;
2951 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2952 intval($importer['id']),
2953 intval($importer['importer_uid'])
2956 $resource_id = $r[0]['resource-id'];
2960 $resource_id = photo_new_resource();
2963 $img_str = fetch_url($photo_url,true);
2964 // guess mimetype from headers or filename
2965 $type = guess_image_type($photo_url,true);
2968 $img = new Photo($img_str, $type);
2969 if($img->is_valid()) {
2971 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2972 dbesc($resource_id),
2973 intval($importer['id']),
2974 intval($importer['importer_uid'])
2978 $img->scaleImageSquare(175);
2980 $hash = $resource_id;
2981 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2983 $img->scaleImage(80);
2984 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2986 $img->scaleImage(48);
2987 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2991 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2992 WHERE `uid` = %d AND `id` = %d",
2993 dbesc(datetime_convert()),
2994 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2995 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2996 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2997 intval($importer['importer_uid']),
2998 intval($importer['id'])
3003 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3004 if ($name_updated > $contact_updated)
3005 $contact_updated = $name_updated;
3007 $r = q("select * from contact where uid = %d and id = %d limit 1",
3008 intval($importer['importer_uid']),
3009 intval($importer['id'])
3012 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3013 dbesc(notags(trim($new_name))),
3014 dbesc(datetime_convert()),
3015 intval($importer['importer_uid']),
3016 intval($importer['id'])
3019 // do our best to update the name on content items
3022 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3023 dbesc(notags(trim($new_name))),
3024 dbesc($r[0]['name']),
3025 dbesc($r[0]['url']),
3026 intval($importer['importer_uid'])
3031 if ($contact_updated AND $new_name AND $photo_url)
3032 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3034 // Currently unsupported - needs a lot of work
3035 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3036 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3037 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3039 $newloc['uid'] = $importer['importer_uid'];
3040 $newloc['cid'] = $importer['id'];
3041 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3042 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3043 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3044 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3045 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3046 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3047 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3048 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3049 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3050 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3051 /** relocated user must have original key pair */
3052 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3053 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3055 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3058 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3059 intval($importer['id']),
3060 intval($importer['importer_uid']));
3065 $x = q("UPDATE contact SET
3076 `site-pubkey` = '%s'
3077 WHERE id=%d AND uid=%d;",
3078 dbesc($newloc['name']),
3079 dbesc($newloc['photo']),
3080 dbesc($newloc['thumb']),
3081 dbesc($newloc['micro']),
3082 dbesc($newloc['url']),
3083 dbesc(normalise_link($newloc['url'])),
3084 dbesc($newloc['request']),
3085 dbesc($newloc['confirm']),
3086 dbesc($newloc['notify']),
3087 dbesc($newloc['poll']),
3088 dbesc($newloc['sitepubkey']),
3089 intval($importer['id']),
3090 intval($importer['importer_uid']));
3096 'owner-link' => array($old['url'], $newloc['url']),
3097 'author-link' => array($old['url'], $newloc['url']),
3098 'owner-avatar' => array($old['photo'], $newloc['photo']),
3099 'author-avatar' => array($old['photo'], $newloc['photo']),
3101 foreach ($fields as $n=>$f){
3102 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3105 intval($importer['importer_uid']));
3111 // merge with current record, current contents have priority
3112 // update record, set url-updated
3113 // update profile photos
3119 // handle friend suggestion notification
3121 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3122 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3123 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3125 $fsugg['uid'] = $importer['importer_uid'];
3126 $fsugg['cid'] = $importer['id'];
3127 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3128 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3129 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3130 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3131 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3133 // Does our member already have a friend matching this description?
3135 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3136 dbesc($fsugg['name']),
3137 dbesc(normalise_link($fsugg['url'])),
3138 intval($fsugg['uid'])
3143 // Do we already have an fcontact record for this person?
3146 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3147 dbesc($fsugg['url']),
3148 dbesc($fsugg['name']),
3149 dbesc($fsugg['request'])
3154 // OK, we do. Do we already have an introduction for this person ?
3155 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3156 intval($fsugg['uid']),
3163 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3164 dbesc($fsugg['name']),
3165 dbesc($fsugg['url']),
3166 dbesc($fsugg['photo']),
3167 dbesc($fsugg['request'])
3169 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3170 dbesc($fsugg['url']),
3171 dbesc($fsugg['name']),
3172 dbesc($fsugg['request'])
3177 // database record did not get created. Quietly give up.
3182 $hash = random_string();
3184 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3185 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3186 intval($fsugg['uid']),
3188 intval($fsugg['cid']),
3189 dbesc($fsugg['body']),
3191 dbesc(datetime_convert()),
3196 'type' => NOTIFY_SUGGEST,
3197 'notify_flags' => $importer['notify-flags'],
3198 'language' => $importer['language'],
3199 'to_name' => $importer['username'],
3200 'to_email' => $importer['email'],
3201 'uid' => $importer['importer_uid'],
3203 'link' => $a->get_baseurl() . '/notifications/intros',
3204 'source_name' => $importer['name'],
3205 'source_link' => $importer['url'],
3206 'source_photo' => $importer['photo'],
3207 'verb' => ACTIVITY_REQ_FRIEND,
3216 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3217 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3219 logger('local_delivery: private message received');
3222 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3225 $msg['uid'] = $importer['importer_uid'];
3226 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3227 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3228 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3229 $msg['contact-id'] = $importer['id'];
3230 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3231 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3233 $msg['replied'] = 0;
3234 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3235 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3236 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3240 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3241 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3243 // send notifications.
3245 require_once('include/enotify.php');
3247 $notif_params = array(
3248 'type' => NOTIFY_MAIL,
3249 'notify_flags' => $importer['notify-flags'],
3250 'language' => $importer['language'],
3251 'to_name' => $importer['username'],
3252 'to_email' => $importer['email'],
3253 'uid' => $importer['importer_uid'],
3255 'source_name' => $msg['from-name'],
3256 'source_link' => $importer['url'],
3257 'source_photo' => $importer['thumb'],
3258 'verb' => ACTIVITY_POST,
3262 notification($notif_params);
3268 $community_page = 0;
3269 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3271 $community_page = intval($rawtags[0]['data']);
3273 if(intval($importer['forum']) != $community_page) {
3274 q("update contact set forum = %d where id = %d",
3275 intval($community_page),
3276 intval($importer['id'])
3278 $importer['forum'] = (string) $community_page;
3281 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3283 // process any deleted entries
3285 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3286 if(is_array($del_entries) && count($del_entries)) {
3287 foreach($del_entries as $dentry) {
3289 if(isset($dentry['attribs']['']['ref'])) {
3290 $uri = $dentry['attribs']['']['ref'];
3292 if(isset($dentry['attribs']['']['when'])) {
3293 $when = $dentry['attribs']['']['when'];
3294 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3297 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3301 // check for relayed deletes to our conversation
3304 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3306 intval($importer['importer_uid'])
3309 $parent_uri = $r[0]['parent-uri'];
3310 if($r[0]['id'] != $r[0]['parent'])
3317 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3320 logger('local_delivery: possible community delete');
3323 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3325 // was the top-level post for this reply written by somebody on this site?
3326 // Specifically, the recipient?
3328 $is_a_remote_delete = false;
3330 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3331 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3332 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3333 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3334 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3335 AND `item`.`uid` = %d
3341 intval($importer['importer_uid'])
3344 $is_a_remote_delete = true;
3346 // Does this have the characteristics of a community or private group comment?
3347 // If it's a reply to a wall post on a community/prvgroup page it's a
3348 // valid community comment. Also forum_mode makes it valid for sure.
3349 // If neither, it's not.
3351 if($is_a_remote_delete && $community) {
3352 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3353 $is_a_remote_delete = false;
3354 logger('local_delivery: not a community delete');
3358 if($is_a_remote_delete) {
3359 logger('local_delivery: received remote delete');
3363 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3364 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3366 intval($importer['importer_uid']),
3367 intval($importer['id'])
3373 if($item['deleted'])
3376 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3378 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3379 $xo = parse_xml_string($item['object'],false);
3380 $xt = parse_xml_string($item['target'],false);
3382 if($xt->type === ACTIVITY_OBJ_NOTE) {
3383 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3385 intval($importer['importer_uid'])
3389 // For tags, the owner cannot remove the tag on the author's copy of the post.
3391 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3392 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3393 $author_copy = (($item['origin']) ? true : false);
3395 if($owner_remove && $author_copy)
3397 if($author_remove || $owner_remove) {
3398 $tags = explode(',',$i[0]['tag']);
3401 foreach($tags as $tag)
3402 if(trim($tag) !== trim($xo->body))
3403 $newtags[] = trim($tag);
3405 q("update item set tag = '%s' where id = %d",
3406 dbesc(implode(',',$newtags)),
3409 create_tags_from_item($i[0]['id']);
3415 if($item['uri'] == $item['parent-uri']) {
3416 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3417 `body` = '', `title` = ''
3418 WHERE `parent-uri` = '%s' AND `uid` = %d",
3420 dbesc(datetime_convert()),
3421 dbesc($item['uri']),
3422 intval($importer['importer_uid'])
3424 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3425 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3426 update_thread_uri($item['uri'], $importer['importer_uid']);
3429 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3430 `body` = '', `title` = ''
3431 WHERE `uri` = '%s' AND `uid` = %d",
3433 dbesc(datetime_convert()),
3435 intval($importer['importer_uid'])
3437 create_tags_from_itemuri($uri, $importer['importer_uid']);
3438 create_files_from_itemuri($uri, $importer['importer_uid']);
3439 update_thread_uri($uri, $importer['importer_uid']);
3440 if($item['last-child']) {
3441 // ensure that last-child is set in case the comment that had it just got wiped.
3442 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3443 dbesc(datetime_convert()),
3444 dbesc($item['parent-uri']),
3445 intval($item['uid'])
3447 // who is the last child now?
3448 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3449 ORDER BY `created` DESC LIMIT 1",
3450 dbesc($item['parent-uri']),
3451 intval($importer['importer_uid'])
3454 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3459 // if this is a relayed delete, propagate it to other recipients
3461 if($is_a_remote_delete)
3462 proc_run('php',"include/notifier.php","drop",$item['id']);
3470 foreach($feed->get_items() as $item) {
3473 $item_id = $item->get_id();
3474 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3475 if(isset($rawthread[0]['attribs']['']['ref'])) {
3477 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3483 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3486 logger('local_delivery: possible community reply');
3489 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3491 // was the top-level post for this reply written by somebody on this site?
3492 // Specifically, the recipient?
3494 $is_a_remote_comment = false;
3495 $top_uri = $parent_uri;
3497 $r = q("select `item`.`parent-uri` from `item`
3498 WHERE `item`.`uri` = '%s'
3502 if($r && count($r)) {
3503 $top_uri = $r[0]['parent-uri'];
3505 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3506 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3507 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3508 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3509 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3510 AND `item`.`uid` = %d
3516 intval($importer['importer_uid'])
3519 $is_a_remote_comment = true;
3522 // Does this have the characteristics of a community or private group comment?
3523 // If it's a reply to a wall post on a community/prvgroup page it's a
3524 // valid community comment. Also forum_mode makes it valid for sure.
3525 // If neither, it's not.
3527 if($is_a_remote_comment && $community) {
3528 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3529 $is_a_remote_comment = false;
3530 logger('local_delivery: not a community reply');
3534 if($is_a_remote_comment) {
3535 logger('local_delivery: received remote comment');
3537 // remote reply to our post. Import and then notify everybody else.
3539 $datarray = get_atom_elements($feed, $item);
3541 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3543 intval($importer['importer_uid'])
3546 // Update content if 'updated' changes
3550 if (edited_timestamp_is_newer($r[0], $datarray)) {
3552 // do not accept (ignore) an earlier edit than one we currently have.
3553 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3556 logger('received updated comment' , LOGGER_DEBUG);
3557 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3558 dbesc($datarray['title']),
3559 dbesc($datarray['body']),
3560 dbesc($datarray['tag']),
3561 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3562 dbesc(datetime_convert()),
3564 intval($importer['importer_uid'])
3566 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3568 proc_run('php',"include/notifier.php","comment-import",$iid);
3577 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3578 intval($importer['importer_uid'])
3582 $datarray['type'] = 'remote-comment';
3583 $datarray['wall'] = 1;
3584 $datarray['parent-uri'] = $parent_uri;
3585 $datarray['uid'] = $importer['importer_uid'];
3586 $datarray['owner-name'] = $own[0]['name'];
3587 $datarray['owner-link'] = $own[0]['url'];
3588 $datarray['owner-avatar'] = $own[0]['thumb'];
3589 $datarray['contact-id'] = $importer['id'];
3591 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3593 $datarray['type'] = 'activity';
3594 $datarray['gravity'] = GRAVITY_LIKE;
3595 $datarray['last-child'] = 0;
3596 // only one like or dislike per person
3597 // splitted into two queries for performance issues
3598 $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",
3599 intval($datarray['uid']),
3600 intval($datarray['contact-id']),
3601 dbesc($datarray['verb']),
3602 dbesc($datarray['parent-uri'])
3608 $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",
3609 intval($datarray['uid']),
3610 intval($datarray['contact-id']),
3611 dbesc($datarray['verb']),
3612 dbesc($datarray['parent-uri'])
3619 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3621 $xo = parse_xml_string($datarray['object'],false);
3622 $xt = parse_xml_string($datarray['target'],false);
3624 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3626 // fetch the parent item
3628 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3630 intval($importer['importer_uid'])
3635 // extract tag, if not duplicate, and this user allows tags, add to parent item
3637 if($xo->id && $xo->content) {
3638 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3639 if(! (stristr($tagp[0]['tag'],$newtag))) {
3640 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3641 intval($importer['importer_uid'])
3643 if(count($i) && ! intval($i[0]['blocktags'])) {
3644 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3645 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3646 intval($tagp[0]['id']),
3647 dbesc(datetime_convert()),
3648 dbesc(datetime_convert())
3650 create_tags_from_item($tagp[0]['id']);
3658 $posted_id = item_store($datarray);
3662 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3664 intval($importer['importer_uid'])
3667 $parent = $r[0]['parent'];
3668 $parent_uri = $r[0]['parent-uri'];
3672 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3673 dbesc(datetime_convert()),
3674 intval($importer['importer_uid']),
3675 intval($r[0]['parent'])
3678 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3679 dbesc(datetime_convert()),
3680 intval($importer['importer_uid']),
3685 if($posted_id && $parent) {
3687 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3689 if((! $is_like) && (! $importer['self'])) {
3691 require_once('include/enotify.php');
3694 'type' => NOTIFY_COMMENT,
3695 'notify_flags' => $importer['notify-flags'],
3696 'language' => $importer['language'],
3697 'to_name' => $importer['username'],
3698 'to_email' => $importer['email'],
3699 'uid' => $importer['importer_uid'],
3700 'item' => $datarray,
3701 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3702 'source_name' => stripslashes($datarray['author-name']),
3703 'source_link' => $datarray['author-link'],
3704 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3705 ? $importer['thumb'] : $datarray['author-avatar']),
3706 'verb' => ACTIVITY_POST,
3708 'parent' => $parent,
3709 'parent_uri' => $parent_uri,
3721 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3723 $item_id = $item->get_id();
3724 $datarray = get_atom_elements($feed,$item);
3726 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3729 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3731 intval($importer['importer_uid'])
3734 // Update content if 'updated' changes
3737 if (edited_timestamp_is_newer($r[0], $datarray)) {
3739 // do not accept (ignore) an earlier edit than one we currently have.
3740 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3743 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3744 dbesc($datarray['title']),
3745 dbesc($datarray['body']),
3746 dbesc($datarray['tag']),
3747 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3748 dbesc(datetime_convert()),
3750 intval($importer['importer_uid'])
3752 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3755 // update last-child if it changes
3757 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3758 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3759 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3760 dbesc(datetime_convert()),
3762 intval($importer['importer_uid'])
3764 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3765 intval($allow[0]['data']),
3766 dbesc(datetime_convert()),
3768 intval($importer['importer_uid'])
3774 $datarray['parent-uri'] = $parent_uri;
3775 $datarray['uid'] = $importer['importer_uid'];
3776 $datarray['contact-id'] = $importer['id'];
3777 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3778 $datarray['type'] = 'activity';
3779 $datarray['gravity'] = GRAVITY_LIKE;
3780 // only one like or dislike per person
3781 // splitted into two queries for performance issues
3782 $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",
3783 intval($datarray['uid']),
3784 intval($datarray['contact-id']),
3785 dbesc($datarray['verb']),
3791 $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",
3792 intval($datarray['uid']),
3793 intval($datarray['contact-id']),
3794 dbesc($datarray['verb']),
3802 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3804 $xo = parse_xml_string($datarray['object'],false);
3805 $xt = parse_xml_string($datarray['target'],false);
3807 if($xt->type == ACTIVITY_OBJ_NOTE) {
3808 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3810 intval($importer['importer_uid'])
3815 // extract tag, if not duplicate, add to parent item
3817 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3818 q("UPDATE item SET tag = '%s' WHERE id = %d",
3819 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3822 create_tags_from_item($r[0]['id']);
3828 $posted_id = item_store($datarray);
3830 // find out if our user is involved in this conversation and wants to be notified.
3832 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3834 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3836 intval($importer['importer_uid'])
3839 if(count($myconv)) {
3840 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3842 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3843 if(! link_compare($datarray['author-link'],$importer_url)) {
3846 foreach($myconv as $conv) {
3848 // now if we find a match, it means we're in this conversation
3850 if(! link_compare($conv['author-link'],$importer_url))
3853 require_once('include/enotify.php');
3855 $conv_parent = $conv['parent'];
3858 'type' => NOTIFY_COMMENT,
3859 'notify_flags' => $importer['notify-flags'],
3860 'language' => $importer['language'],
3861 'to_name' => $importer['username'],
3862 'to_email' => $importer['email'],
3863 'uid' => $importer['importer_uid'],
3864 'item' => $datarray,
3865 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3866 'source_name' => stripslashes($datarray['author-name']),
3867 'source_link' => $datarray['author-link'],
3868 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3869 ? $importer['thumb'] : $datarray['author-avatar']),
3870 'verb' => ACTIVITY_POST,
3872 'parent' => $conv_parent,
3873 'parent_uri' => $parent_uri
3877 // only send one notification
3889 // Head post of a conversation. Have we seen it? If not, import it.
3892 $item_id = $item->get_id();
3893 $datarray = get_atom_elements($feed,$item);
3895 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3896 $ev = bbtoevent($datarray['body']);
3897 if(x($ev,'desc') && x($ev,'start')) {
3898 $ev['cid'] = $importer['id'];
3899 $ev['uid'] = $importer['uid'];
3900 $ev['uri'] = $item_id;
3901 $ev['edited'] = $datarray['edited'];
3902 $ev['private'] = $datarray['private'];
3904 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3906 intval($importer['uid'])
3909 $ev['id'] = $r[0]['id'];
3910 $xyz = event_store($ev);
3915 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3917 intval($importer['importer_uid'])
3920 // Update content if 'updated' changes
3923 if (edited_timestamp_is_newer($r[0], $datarray)) {
3925 // do not accept (ignore) an earlier edit than one we currently have.
3926 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3929 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3930 dbesc($datarray['title']),
3931 dbesc($datarray['body']),
3932 dbesc($datarray['tag']),
3933 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3934 dbesc(datetime_convert()),
3936 intval($importer['importer_uid'])
3938 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3939 update_thread_uri($item_id, $importer['importer_uid']);
3942 // update last-child if it changes
3944 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3945 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3946 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3947 intval($allow[0]['data']),
3948 dbesc(datetime_convert()),
3950 intval($importer['importer_uid'])
3956 $datarray['parent-uri'] = $item_id;
3957 $datarray['uid'] = $importer['importer_uid'];
3958 $datarray['contact-id'] = $importer['id'];
3961 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3962 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3963 // but otherwise there's a possible data mixup on the sender's system.
3964 // the tgroup delivery code called from item_store will correct it if it's a forum,
3965 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3966 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3967 $datarray['owner-name'] = $importer['senderName'];
3968 $datarray['owner-link'] = $importer['url'];
3969 $datarray['owner-avatar'] = $importer['thumb'];
3972 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3975 // This is my contact on another system, but it's really me.
3976 // Turn this into a wall post.
3977 $notify = item_is_remote_self($importer, $datarray);
3979 $posted_id = item_store($datarray, false, $notify);
3981 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3982 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3985 $xo = parse_xml_string($datarray['object'],false);
3987 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3989 // somebody was poked/prodded. Was it me?
3991 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3993 foreach($links->link as $l) {
3994 $atts = $l->attributes();
3995 switch($atts['rel']) {
3997 $Blink = $atts['href'];
4003 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4005 // send a notification
4006 require_once('include/enotify.php');
4009 'type' => NOTIFY_POKE,
4010 'notify_flags' => $importer['notify-flags'],
4011 'language' => $importer['language'],
4012 'to_name' => $importer['username'],
4013 'to_email' => $importer['email'],
4014 'uid' => $importer['importer_uid'],
4015 'item' => $datarray,
4016 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4017 'source_name' => stripslashes($datarray['author-name']),
4018 'source_link' => $datarray['author-link'],
4019 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4020 ? $importer['thumb'] : $datarray['author-avatar']),
4021 'verb' => $datarray['verb'],
4022 'otype' => 'person',
4023 'activity' => $verb,
4040 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4041 $url = notags(trim($datarray['author-link']));
4042 $name = notags(trim($datarray['author-name']));
4043 $photo = notags(trim($datarray['author-avatar']));
4045 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4046 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4047 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4049 if(is_array($contact)) {
4050 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4051 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4052 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4053 intval(CONTACT_IS_FRIEND),
4054 intval($contact['id']),
4055 intval($importer['uid'])
4058 // send email notification to owner?
4062 // create contact record
4064 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4065 `blocked`, `readonly`, `pending`, `writable` )
4066 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4067 intval($importer['uid']),
4068 dbesc(datetime_convert()),
4070 dbesc(normalise_link($url)),
4074 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4075 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4077 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4078 intval($importer['uid']),
4082 $contact_record = $r[0];
4084 // create notification
4085 $hash = random_string();
4087 if(is_array($contact_record)) {
4088 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4089 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4090 intval($importer['uid']),
4091 intval($contact_record['id']),
4093 dbesc(datetime_convert())
4097 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4098 intval($importer['uid'])
4103 if(intval($r[0]['def_gid'])) {
4104 require_once('include/group.php');
4105 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4108 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4109 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4114 'type' => NOTIFY_INTRO,
4115 'notify_flags' => $r[0]['notify-flags'],
4116 'language' => $r[0]['language'],
4117 'to_name' => $r[0]['username'],
4118 'to_email' => $r[0]['email'],
4119 'uid' => $r[0]['uid'],
4120 'link' => $a->get_baseurl() . '/notifications/intro',
4121 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4122 'source_link' => $contact_record['url'],
4123 'source_photo' => $contact_record['photo'],
4124 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4134 function lose_follower($importer,$contact,$datarray,$item) {
4136 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4137 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4138 intval(CONTACT_IS_SHARING),
4139 intval($contact['id'])
4143 contact_remove($contact['id']);
4147 function lose_sharer($importer,$contact,$datarray,$item) {
4149 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4150 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4151 intval(CONTACT_IS_FOLLOWER),
4152 intval($contact['id'])
4156 contact_remove($contact['id']);
4161 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4165 if(is_array($importer)) {
4166 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4167 intval($importer['uid'])
4171 // Diaspora has different message-ids in feeds than they do
4172 // through the direct Diaspora protocol. If we try and use
4173 // the feed, we'll get duplicates. So don't.
4175 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4178 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4180 // Use a single verify token, even if multiple hubs
4182 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4184 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4186 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4188 if(! strlen($contact['hub-verify'])) {
4189 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4190 dbesc($verify_token),
4191 intval($contact['id'])
4195 post_url($url,$params);
4197 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4204 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4208 $name = xmlify($name);
4209 $uri = xmlify($uri);
4212 $photo = xmlify($photo);
4216 $o .= "<name>$name</name>\r\n";
4217 $o .= "<uri>$uri</uri>\r\n";
4218 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4219 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4221 call_hooks('atom_author', $o);
4223 $o .= "</$tag>\r\n";
4227 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4231 if(! $item['parent'])
4234 if($item['deleted'])
4235 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4238 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4239 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4241 $body = $item['body'];
4244 $o = "\r\n\r\n<entry>\r\n";
4246 if(is_array($author))
4247 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4249 $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']));
4250 if(strlen($item['owner-name']))
4251 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4253 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4254 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4255 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4260 if ($item['title'] != "")
4261 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4263 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4265 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4266 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4267 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4268 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4269 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4270 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4271 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4275 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4277 if($item['location']) {
4278 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4279 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4283 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4285 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4286 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4289 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4290 if($item['bookmark'])
4291 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4294 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4297 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4299 if($item['signed_text']) {
4300 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4301 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4304 $verb = construct_verb($item);
4305 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4306 $actobj = construct_activity_object($item);
4309 $actarg = construct_activity_target($item);
4313 $tags = item_getfeedtags($item);
4315 foreach($tags as $t) {
4316 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4320 $o .= item_getfeedattach($item);
4322 $mentioned = get_mentions($item);
4326 call_hooks('atom_entry', $o);
4328 $o .= '</entry>' . "\r\n";
4333 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4335 if(get_config('system','disable_embedded'))
4340 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4341 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4346 $img_start = strpos($orig_body, '[img');
4347 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4348 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4349 while( ($img_st_close !== false) && ($img_len !== false) ) {
4351 $img_st_close++; // make it point to AFTER the closing bracket
4352 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4354 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4357 if(stristr($image , $site . '/photo/')) {
4358 // Only embed locally hosted photos
4360 $i = basename($image);
4361 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4362 $x = strpos($i,'-');
4365 $res = substr($i,$x+1);
4366 $i = substr($i,0,$x);
4367 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4374 // Check to see if we should replace this photo link with an embedded image
4375 // 1. No need to do so if the photo is public
4376 // 2. If there's a contact-id provided, see if they're in the access list
4377 // for the photo. If so, embed it.
4378 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4379 // permissions, regardless of order but first check to see if they're an exact
4380 // match to save some processing overhead.
4382 if(has_permissions($r[0])) {
4384 $recips = enumerate_permissions($r[0]);
4385 if(in_array($cid, $recips)) {
4390 if(compare_permissions($item,$r[0]))
4395 $data = $r[0]['data'];
4396 $type = $r[0]['type'];
4398 // If a custom width and height were specified, apply before embedding
4399 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4400 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4402 $width = intval($match[1]);
4403 $height = intval($match[2]);
4405 $ph = new Photo($data, $type);
4406 if($ph->is_valid()) {
4407 $ph->scaleImage(max($width, $height));
4408 $data = $ph->imageString();
4409 $type = $ph->getType();
4413 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4414 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4415 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4421 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4422 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4423 if($orig_body === false)
4426 $img_start = strpos($orig_body, '[img');
4427 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4428 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4431 $new_body = $new_body . $orig_body;
4437 function has_permissions($obj) {
4438 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4443 function compare_permissions($obj1,$obj2) {
4444 // first part is easy. Check that these are exactly the same.
4445 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4446 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4447 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4448 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4451 // This is harder. Parse all the permissions and compare the resulting set.
4453 $recipients1 = enumerate_permissions($obj1);
4454 $recipients2 = enumerate_permissions($obj2);
4457 if($recipients1 == $recipients2)
4462 // returns an array of contact-ids that are allowed to see this object
4464 function enumerate_permissions($obj) {
4465 require_once('include/group.php');
4466 $allow_people = expand_acl($obj['allow_cid']);
4467 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4468 $deny_people = expand_acl($obj['deny_cid']);
4469 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4470 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4471 $deny = array_unique(array_merge($deny_people,$deny_groups));
4472 $recipients = array_diff($recipients,$deny);
4476 function item_getfeedtags($item) {
4479 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4481 for($x = 0; $x < $cnt; $x ++) {
4483 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4487 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4489 for($x = 0; $x < $cnt; $x ++) {
4491 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4497 function item_getfeedattach($item) {
4499 $arr = explode('[/attach],',$item['attach']);
4501 foreach($arr as $r) {
4503 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4505 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4506 if(intval($matches[2]))
4507 $ret .= 'length="' . intval($matches[2]) . '" ';
4508 if($matches[4] !== ' ')
4509 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4510 $ret .= ' />' . "\r\n";
4519 function item_expire($uid, $days, $network = "", $force = false) {
4521 if((! $uid) || ($days < 1))
4524 // $expire_network_only = save your own wall posts
4525 // and just expire conversations started by others
4527 $expire_network_only = get_pconfig($uid,'expire','network_only');
4528 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4530 if ($network != "") {
4531 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4532 // There is an index "uid_network_received" but not "uid_network_created"
4533 // This avoids the creation of another index just for one purpose.
4534 // And it doesn't really matter wether to look at "received" or "created"
4535 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4537 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4539 $r = q("SELECT * FROM `item`
4540 WHERE `uid` = %d $range
4551 $expire_items = get_pconfig($uid, 'expire','items');
4552 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4554 // Forcing expiring of items - but not notes and marked items
4556 $expire_items = true;
4558 $expire_notes = get_pconfig($uid, 'expire','notes');
4559 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4561 $expire_starred = get_pconfig($uid, 'expire','starred');
4562 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4564 $expire_photos = get_pconfig($uid, 'expire','photos');
4565 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4567 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4569 foreach($r as $item) {
4571 // don't expire filed items
4573 if(strpos($item['file'],'[') !== false)
4576 // Only expire posts, not photos and photo comments
4578 if($expire_photos==0 && strlen($item['resource-id']))
4580 if($expire_starred==0 && intval($item['starred']))
4582 if($expire_notes==0 && $item['type']=='note')
4584 if($expire_items==0 && $item['type']!='note')
4587 drop_item($item['id'],false);
4590 proc_run('php',"include/notifier.php","expire","$uid");
4595 function drop_items($items) {
4598 if(! local_user() && ! remote_user())
4602 foreach($items as $item) {
4603 $owner = drop_item($item,false);
4604 if($owner && ! $uid)
4609 // multiple threads may have been deleted, send an expire notification
4612 proc_run('php',"include/notifier.php","expire","$uid");
4616 function drop_item($id,$interactive = true) {
4620 // locate item to be deleted
4622 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4629 notice( t('Item not found.') . EOL);
4630 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4635 $owner = $item['uid'];
4639 // check if logged in user is either the author or owner of this item
4641 if(is_array($_SESSION['remote'])) {
4642 foreach($_SESSION['remote'] as $visitor) {
4643 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4644 $cid = $visitor['cid'];
4651 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4653 // Check if we should do HTML-based delete confirmation
4654 if($_REQUEST['confirm']) {
4655 // <form> can't take arguments in its "action" parameter
4656 // so add any arguments as hidden inputs
4657 $query = explode_querystring($a->query_string);
4659 foreach($query['args'] as $arg) {
4660 if(strpos($arg, 'confirm=') === false) {
4661 $arg_parts = explode('=', $arg);
4662 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4666 return replace_macros(get_markup_template('confirm.tpl'), array(
4668 '$message' => t('Do you really want to delete this item?'),
4669 '$extra_inputs' => $inputs,
4670 '$confirm' => t('Yes'),
4671 '$confirm_url' => $query['base'],
4672 '$confirm_name' => 'confirmed',
4673 '$cancel' => t('Cancel'),
4676 // Now check how the user responded to the confirmation query
4677 if($_REQUEST['canceled']) {
4678 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4681 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4684 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4685 dbesc(datetime_convert()),
4686 dbesc(datetime_convert()),
4689 create_tags_from_item($item['id']);
4690 create_files_from_item($item['id']);
4691 delete_thread($item['id'], $item['parent-uri']);
4693 // clean up categories and tags so they don't end up as orphans
4696 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4698 foreach($matches as $mtch) {
4699 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4705 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4707 foreach($matches as $mtch) {
4708 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4712 // If item is a link to a photo resource, nuke all the associated photos
4713 // (visitors will not have photo resources)
4714 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4715 // generate a resource-id and therefore aren't intimately linked to the item.
4717 if(strlen($item['resource-id'])) {
4718 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4719 dbesc($item['resource-id']),
4720 intval($item['uid'])
4722 // ignore the result
4725 // If item is a link to an event, nuke the event record.
4727 if(intval($item['event-id'])) {
4728 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4729 intval($item['event-id']),
4730 intval($item['uid'])
4732 // ignore the result
4735 // clean up item_id and sign meta-data tables
4738 // Old code - caused very long queries and warning entries in the mysql logfiles:
4740 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4741 intval($item['id']),
4742 intval($item['uid'])
4745 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4746 intval($item['id']),
4747 intval($item['uid'])
4751 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4753 // Creating list of parents
4754 $r = q("select id from item where parent = %d and uid = %d",
4755 intval($item['id']),
4756 intval($item['uid'])
4761 foreach ($r AS $row) {
4762 if ($parentid != "")
4765 $parentid .= $row["id"];
4769 if ($parentid != "") {
4770 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4772 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4775 // If it's the parent of a comment thread, kill all the kids
4777 if($item['uri'] == $item['parent-uri']) {
4778 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4779 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4780 dbesc(datetime_convert()),
4781 dbesc(datetime_convert()),
4782 dbesc($item['parent-uri']),
4783 intval($item['uid'])
4785 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4786 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4787 delete_thread_uri($item['parent-uri'], $item['uid']);
4788 // ignore the result
4791 // ensure that last-child is set in case the comment that had it just got wiped.
4792 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4793 dbesc(datetime_convert()),
4794 dbesc($item['parent-uri']),
4795 intval($item['uid'])
4797 // who is the last child now?
4798 $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",
4799 dbesc($item['parent-uri']),
4800 intval($item['uid'])
4803 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4808 // Add a relayable_retraction signature for Diaspora.
4809 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4811 $drop_id = intval($item['id']);
4813 // send the notification upstream/downstream as the case may be
4815 proc_run('php',"include/notifier.php","drop","$drop_id");
4819 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4825 notice( t('Permission denied.') . EOL);
4826 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4833 function first_post_date($uid,$wall = false) {
4834 $r = q("select id, created from item
4835 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4837 order by created asc limit 1",
4839 intval($wall ? 1 : 0)
4842 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4843 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4848 function posted_dates($uid,$wall) {
4849 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4851 $dthen = first_post_date($uid,$wall);
4855 // Set the start and end date to the beginning of the month
4856 $dnow = substr($dnow,0,8).'01';
4857 $dthen = substr($dthen,0,8).'01';
4860 // Starting with the current month, get the first and last days of every
4861 // month down to and including the month of the first post
4862 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4863 $dstart = substr($dnow,0,8) . '01';
4864 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4865 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4866 $end_month = datetime_convert('','',$dend,'Y-m-d');
4867 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4868 $ret[] = array($str,$end_month,$start_month);
4869 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4875 function posted_date_widget($url,$uid,$wall) {
4878 if(! feature_enabled($uid,'archives'))
4881 // For former Facebook folks that left because of "timeline"
4883 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4886 $ret = posted_dates($uid,$wall);
4890 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4891 '$title' => t('Archives'),
4892 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4899 function store_diaspora_retract_sig($item, $user, $baseurl) {
4900 // Note that we can't add a target_author_signature
4901 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4902 // the comment, that means we're the home of the post, and Diaspora will only
4903 // check the parent_author_signature of retractions that it doesn't have to relay further
4905 // I don't think this function gets called for an "unlike," but I'll check anyway
4907 $enabled = intval(get_config('system','diaspora_enabled'));
4909 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4913 logger('drop_item: storing diaspora retraction signature');
4915 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4917 if(local_user() == $item['uid']) {
4919 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4920 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4923 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4924 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4927 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4928 // only handles DFRN deletes
4929 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4930 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4931 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4937 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4938 intval($item['id']),
4939 dbesc($signed_text),