3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15 require_once('mod/share.php');
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
20 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21 $public_feed = (($dfrn_id) ? false : true);
22 $starred = false; // not yet implemented, possible security issues
25 if($public_feed && $a->argc > 2) {
26 for($x = 2; $x < $a->argc; $x++) {
27 if($a->argv[$x] == 'converse')
29 if($a->argv[$x] == 'starred')
31 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32 $category = $a->argv[$x+1];
38 // default permissions - anonymous user
40 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
42 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
52 $owner_id = $owner['user_uid'];
53 $owner_nick = $owner['nickname'];
55 $birthday = feed_birthday($owner_id,$owner['timezone']);
64 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
68 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '1:' . $dfrn_id;
72 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
73 $my_id = '0:' . $dfrn_id;
80 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
88 require_once('include/security.php');
89 $groups = init_groups_visitor($contact['id']);
92 for($x = 0; $x < count($groups); $x ++)
93 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
94 $gs = implode('|', $groups);
97 $gs = '<<>>' ; // Impossible to match
99 $sql_extra = sprintf("
100 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
101 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
102 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
103 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
105 intval($contact['id']),
106 intval($contact['id']),
117 if(! strlen($last_update))
118 $last_update = 'now -30 days';
120 if(isset($category)) {
121 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
122 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
123 //$sql_extra .= file_tag_file_query('item',$category,'category');
128 $sql_extra .= " AND `contact`.`self` = 1 ";
131 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
133 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
134 // dbesc($check_date),
136 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
137 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
138 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
139 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
140 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
141 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
142 FROM `item` $sql_post_table
143 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
144 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
145 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
146 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
147 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
149 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
155 // Will check further below if this actually returned results.
156 // We will provide an empty feed if that is the case.
160 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
164 $hubxml = feed_hublinks();
166 $salmon = feed_salmonlinks($owner_nick);
168 $alternatelink = $owner['url'];
171 $alternatelink .= "/category/".$category;
173 $atom .= replace_macros($feed_template, array(
174 '$version' => xmlify(FRIENDICA_VERSION),
175 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
176 '$feed_title' => xmlify($owner['name']),
177 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
179 '$salmon' => $salmon,
180 '$alternatelink' => xmlify($alternatelink),
181 '$name' => xmlify($owner['name']),
182 '$profile_page' => xmlify($owner['url']),
183 '$photo' => xmlify($owner['photo']),
184 '$thumb' => xmlify($owner['thumb']),
185 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
186 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
187 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
188 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
189 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
192 call_hooks('atom_feed', $atom);
194 if(! count($items)) {
196 call_hooks('atom_feed_end', $atom);
198 $atom .= '</feed>' . "\r\n";
202 foreach($items as $item) {
204 // prevent private email from leaking.
205 if($item['network'] === NETWORK_MAIL)
208 // public feeds get html, our own nodes use bbcode
212 // catch any email that's in a public conversation and make sure it doesn't leak
220 $atom .= atom_entry($item,$type,null,$owner,true);
223 call_hooks('atom_feed_end', $atom);
225 $atom .= '</feed>' . "\r\n";
231 function construct_verb($item) {
233 return $item['verb'];
234 return ACTIVITY_POST;
237 function construct_activity_object($item) {
239 if($item['object']) {
240 $o = '<as:object>' . "\r\n";
241 $r = parse_xml_string($item['object'],false);
247 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
249 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
251 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
253 if(substr($r->link,0,1) === '<') {
254 // patch up some facebook "like" activity objects that got stored incorrectly
255 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
256 // we can probably remove this hack here and in the following function in a few months time.
257 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
258 $r->link = str_replace('&','&', $r->link);
259 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
263 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
266 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
267 $o .= '</as:object>' . "\r\n";
274 function construct_activity_target($item) {
276 if($item['target']) {
277 $o = '<as:target>' . "\r\n";
278 $r = parse_xml_string($item['target'],false);
282 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
284 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
286 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
288 if(substr($r->link,0,1) === '<') {
289 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
290 $r->link = str_replace('&','&', $r->link);
291 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
295 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
298 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
299 $o .= '</as:target>' . "\r\n";
308 * The purpose of this function is to apply system message length limits to
309 * imported messages without including any embedded photos in the length
311 if(! function_exists('limit_body_size')) {
312 function limit_body_size($body) {
314 // logger('limit_body_size: start', LOGGER_DEBUG);
316 $maxlen = get_max_import_size();
318 // If the length of the body, including the embedded images, is smaller
319 // than the maximum, then don't waste time looking for the images
320 if($maxlen && (strlen($body) > $maxlen)) {
322 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
329 $img_start = strpos($orig_body, '[img');
330 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
331 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
332 while(($img_st_close !== false) && ($img_end !== false)) {
334 $img_st_close++; // make it point to AFTER the closing bracket
335 $img_end += $img_start;
336 $img_end += strlen('[/img]');
338 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
339 // This is an embedded image
341 if( ($textlen + $img_start) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_start);
350 $textlen += $img_start;
353 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
357 if( ($textlen + $img_end) > $maxlen ) {
358 if($textlen < $maxlen) {
359 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
360 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
365 $new_body = $new_body . substr($orig_body, 0, $img_end);
366 $textlen += $img_end;
369 $orig_body = substr($orig_body, $img_end);
371 if($orig_body === false) // in case the body ends on a closing image tag
374 $img_start = strpos($orig_body, '[img');
375 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
376 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
379 if( ($textlen + strlen($orig_body)) > $maxlen) {
380 if($textlen < $maxlen) {
381 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
382 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
387 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
388 $new_body = $new_body . $orig_body;
389 $textlen += strlen($orig_body);
398 function title_is_body($title, $body) {
400 $title = strip_tags($title);
401 $title = trim($title);
402 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
403 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
405 $body = strip_tags($body);
407 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
408 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
410 if (strlen($title) < strlen($body))
411 $body = substr($body, 0, strlen($title));
413 if (($title != $body) and (substr($title, -3) == "...")) {
414 $pos = strrpos($title, "...");
416 $title = substr($title, 0, $pos);
417 $body = substr($body, 0, $pos);
421 return($title == $body);
426 function get_atom_elements($feed, $item, $contact = array()) {
428 require_once('library/HTMLPurifier.auto.php');
429 require_once('include/html2bbcode.php');
431 $best_photo = array();
435 $author = $item->get_author();
437 $res['author-name'] = unxmlify($author->get_name());
438 $res['author-link'] = unxmlify($author->get_link());
441 $res['author-name'] = unxmlify($feed->get_title());
442 $res['author-link'] = unxmlify($feed->get_permalink());
444 $res['uri'] = unxmlify($item->get_id());
445 $res['title'] = unxmlify($item->get_title());
446 $res['body'] = unxmlify($item->get_content());
447 $res['plink'] = unxmlify($item->get_link(0));
449 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
450 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
452 $res['body'] = nl2br($res['body']);
455 // removing the content of the title if its identically to the body
456 // This helps with auto generated titles e.g. from tumblr
457 if (title_is_body($res["title"], $res["body"]))
461 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
465 // look for a photo. We should check media size and find the best one,
466 // but for now let's just find any author photo
468 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
470 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
471 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
472 foreach($base as $link) {
473 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
474 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
475 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
480 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
482 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
483 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 if($base && count($base)) {
485 foreach($base as $link) {
486 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
487 $res['author-link'] = unxmlify($link['attribs']['']['href']);
488 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
489 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
490 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
496 // No photo/profile-link on the item - look at the feed level
498 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
499 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(! $res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(! (x($res,'author-avatar'))) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
530 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
531 if($apps && $apps[0]['attribs']['']['source']) {
532 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
533 if($res['app'] === 'web')
534 $res['app'] = 'OStatus';
537 // base64 encoded json structure representing Diaspora signature
539 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
541 $res['dsprsig'] = unxmlify($dsig[0]['data']);
544 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
546 $res['guid'] = unxmlify($dguid[0]['data']);
548 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
550 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
554 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
557 $have_real_body = false;
559 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
561 $have_real_body = true;
562 $res['body'] = $rawenv[0]['data'];
563 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
564 // make sure nobody is trying to sneak some html tags by us
565 $res['body'] = notags(base64url_decode($res['body']));
569 $res['body'] = limit_body_size($res['body']);
571 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
572 // the content type. Our own network only emits text normally, though it might have been converted to
573 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
574 // have to assume it is all html and needs to be purified.
576 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
577 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
578 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
581 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
583 $res['body'] = reltoabs($res['body'],$base_url);
585 $res['body'] = html2bb_video($res['body']);
587 $res['body'] = oembed_html2bbcode($res['body']);
589 $config = HTMLPurifier_Config::createDefault();
590 $config->set('Cache.DefinitionImpl', null);
592 // we shouldn't need a whitelist, because the bbcode converter
593 // will strip out any unsupported tags.
595 $purifier = new HTMLPurifier($config);
596 $res['body'] = $purifier->purify($res['body']);
598 $res['body'] = @html2bbcode($res['body']);
602 elseif(! $have_real_body) {
604 // it's not one of our messages and it has no tags
605 // so it's probably just text. We'll escape it just to be safe.
607 $res['body'] = escape_tags($res['body']);
611 // this tag is obsolete but we keep it for really old sites
613 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
614 if($allow && $allow[0]['data'] == 1)
615 $res['last-child'] = 1;
617 $res['last-child'] = 0;
619 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
620 if($private && intval($private[0]['data']) > 0)
621 $res['private'] = intval($private[0]['data']);
625 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
626 if($extid && $extid[0]['data'])
627 $res['extid'] = $extid[0]['data'];
629 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
631 $res['location'] = unxmlify($rawlocation[0]['data']);
634 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
636 $res['created'] = unxmlify($rawcreated[0]['data']);
639 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
641 $res['edited'] = unxmlify($rawedited[0]['data']);
643 if((x($res,'edited')) && (! (x($res,'created'))))
644 $res['created'] = $res['edited'];
646 if(! $res['created'])
647 $res['created'] = $item->get_date('c');
650 $res['edited'] = $item->get_date('c');
653 // Disallow time travelling posts
655 $d1 = strtotime($res['created']);
656 $d2 = strtotime($res['edited']);
657 $d3 = strtotime('now');
660 $res['created'] = datetime_convert();
662 $res['edited'] = datetime_convert();
664 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
665 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
667 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
668 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
669 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
671 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
672 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
674 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
675 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
677 foreach($base as $link) {
678 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
679 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
680 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
685 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
687 $res['coord'] = unxmlify($rawgeo[0]['data']);
689 if ($contact["network"] == NETWORK_FEED) {
690 $res['verb'] = ACTIVITY_POST;
691 $res['object-type'] = ACTIVITY_OBJ_NOTE;
694 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
696 // select between supported verbs
699 $res['verb'] = unxmlify($rawverb[0]['data']);
702 // translate OStatus unfollow to activity streams if it happened to get selected
704 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
705 $res['verb'] = ACTIVITY_UNFOLLOW;
707 $cats = $item->get_categories();
710 foreach($cats as $cat) {
711 $term = $cat->get_term();
713 $term = $cat->get_label();
714 $scheme = $cat->get_scheme();
715 if($scheme && $term && stristr($scheme,'X-DFRN:'))
716 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
718 $tag_arr[] = notags(trim($term));
720 $res['tag'] = implode(',', $tag_arr);
723 $attach = $item->get_enclosures();
726 foreach($attach as $att) {
727 $len = intval($att->get_length());
728 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
729 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
730 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
731 if(strpos($type,';'))
732 $type = substr($type,0,strpos($type,';'));
733 if((! $link) || (strpos($link,'http') !== 0))
739 $type = 'application/octet-stream';
741 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
743 $res['attach'] = implode(',', $att_arr);
746 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
749 $res['object'] = '<object>' . "\n";
750 $child = $rawobj[0]['child'];
751 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
752 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
753 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
756 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
758 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
760 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
761 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
764 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
765 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
766 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
767 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
769 $body = html2bb_video($body);
771 $config = HTMLPurifier_Config::createDefault();
772 $config->set('Cache.DefinitionImpl', null);
774 $purifier = new HTMLPurifier($config);
775 $body = $purifier->purify($body);
776 $body = html2bbcode($body);
779 $res['object'] .= '<content>' . $body . '</content>' . "\n";
782 $res['object'] .= '</object>' . "\n";
785 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
788 $res['target'] = '<target>' . "\n";
789 $child = $rawobj[0]['child'];
790 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
791 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
794 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
796 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
798 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
799 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
802 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
803 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
804 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
805 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
807 $body = html2bb_video($body);
809 $config = HTMLPurifier_Config::createDefault();
810 $config->set('Cache.DefinitionImpl', null);
812 $purifier = new HTMLPurifier($config);
813 $body = $purifier->purify($body);
814 $body = html2bbcode($body);
817 $res['target'] .= '<content>' . $body . '</content>' . "\n";
820 $res['target'] .= '</target>' . "\n";
823 // This is some experimental stuff. By now retweets are shown with "RT:"
824 // But: There is data so that the message could be shown similar to native retweets
825 // There is some better way to parse this array - but it didn't worked for me.
826 $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
827 if (is_array($child)) {
828 logger('get_atom_elements: Looking for status.net repeated message');
830 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
831 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
832 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
833 $uri = $author["uri"][0]["data"];
834 $name = $author["name"][0]["data"];
835 $avatar = @array_shift($author["link"][2]["attribs"]);
836 $avatar = $avatar["href"];
838 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
839 logger('get_atom_elements: fixing sender of repeated message.');
841 if (!intval(get_config('system','wall-to-wall_share'))) {
842 $prefix = share_header($name, $uri, $avatar, "", "", $orig_uri);
844 $res["body"] = $prefix.html2bbcode($message)."[/share]";
846 $res["owner-name"] = $res["author-name"];
847 $res["owner-link"] = $res["author-link"];
848 $res["owner-avatar"] = $res["author-avatar"];
850 $res["author-name"] = $name;
851 $res["author-link"] = $uri;
852 $res["author-avatar"] = $avatar;
854 $res["body"] = html2bbcode($message);
859 // Search for ostatus conversation url
860 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
862 if (is_array($links)) {
863 foreach ($links as $link) {
864 $conversation = array_shift($link["attribs"]);
866 if ($conversation["rel"] == "ostatus:conversation") {
867 $res["ostatus_conversation"] = ostatus_convert_href($conversation["href"]);
868 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
873 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
876 // Handle enclosures and treat them as preview picture
878 foreach ($attach AS $attachment)
879 if ($attachment->type == "image/jpeg")
880 $preview = $attachment->link;
882 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
883 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
885 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
886 unset($res["attach"]);
887 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
888 $res["body"] = add_page_info_to_body($res["body"]);
889 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
890 $res["body"] = add_page_info_to_body($res["body"]);
893 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
895 call_hooks('parse_atom', $arr);
900 function add_page_info_data($data) {
901 call_hooks('page_info_data', $data);
903 // It maybe is a rich content, but if it does have everything that a link has,
904 // then treat it that way
905 if (($data["type"] == "rich") AND is_string($data["title"]) AND
906 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
907 $data["type"] = "link";
909 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
912 if ($no_photos AND ($data["type"] == "photo"))
915 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
916 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
917 require_once("include/network.php");
918 $data["url"] = short_link($data["url"]);
921 if (($data["type"] != "photo") AND is_string($data["title"]))
922 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
924 if (($data["type"] != "video") AND ($photo != ""))
925 $text .= '[img]'.$photo.'[/img]';
926 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
927 $imagedata = $data["images"][0];
928 $text .= '[img]'.$imagedata["src"].'[/img]';
931 if (($data["type"] != "photo") AND is_string($data["text"]))
932 $text .= "[quote]".$data["text"]."[/quote]";
935 if (isset($data["keywords"]) AND count($data["keywords"])) {
938 foreach ($data["keywords"] AS $keyword) {
939 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
940 array("","", "", "", "", ""), $keyword);
941 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
945 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
948 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
949 require_once("mod/parse_url.php");
951 $data = Cache::get("parse_url:".$url);
953 $data = parseurl_getsiteinfo($url, true);
954 Cache::set("parse_url:".$url,serialize($data));
956 $data = unserialize($data);
959 $data["images"][0]["src"] = $photo;
961 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
963 if (!$keywords AND isset($data["keywords"]))
964 unset($data["keywords"]);
966 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
967 $list = explode(",", $keyword_blacklist);
968 foreach ($list AS $keyword) {
969 $keyword = trim($keyword);
970 $index = array_search($keyword, $data["keywords"]);
971 if ($index !== false)
972 unset($data["keywords"][$index]);
979 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
980 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
983 if (isset($data["keywords"]) AND count($data["keywords"])) {
985 foreach ($data["keywords"] AS $keyword) {
986 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
987 array("","", "", "", "", ""), $keyword);
992 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
999 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1000 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 $text = add_page_info_data($data);
1007 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1009 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1011 $URLSearchString = "^\[\]";
1013 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1014 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1017 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1019 // Convert urls without bbcode elements
1020 if (!$matches AND $texturl) {
1021 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1023 // Yeah, a hack. I really hate regular expressions :)
1025 $matches[1] = $matches[2];
1029 $footer = add_page_info($matches[1], $no_photos);
1031 // Remove the link from the body if the link is attached at the end of the post
1032 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1033 $removedlink = trim(str_replace($matches[1], "", $body));
1034 if (($removedlink == "") OR strstr($body, $removedlink))
1035 $body = $removedlink;
1037 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1038 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1039 if (($removedlink == "") OR strstr($body, $removedlink))
1040 $body = $removedlink;
1043 // Add the page information to the bottom
1044 if (isset($footer) AND (trim($footer) != ""))
1050 function encode_rel_links($links) {
1052 if(! ((is_array($links)) && (count($links))))
1054 foreach($links as $link) {
1056 if($link['attribs']['']['rel'])
1057 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1058 if($link['attribs']['']['type'])
1059 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1060 if($link['attribs']['']['href'])
1061 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1062 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1063 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1064 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1065 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1066 $o .= ' />' . "\n" ;
1073 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1075 // If it is a posting where users should get notifications, then define it as wall posting
1078 $arr['type'] = 'wall';
1080 $arr['last-child'] = 1;
1081 $arr['network'] = NETWORK_DFRN;
1084 // If a Diaspora signature structure was passed in, pull it out of the
1085 // item array and set it aside for later storage.
1088 if(x($arr,'dsprsig')) {
1089 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1090 unset($arr['dsprsig']);
1093 // Converting the plink
1094 if ($arr['network'] == NETWORK_OSTATUS) {
1095 if (isset($arr['plink']))
1096 $arr['plink'] = ostatus_convert_href($arr['plink']);
1097 elseif (isset($arr['uri']))
1098 $arr['plink'] = ostatus_convert_href($arr['uri']);
1101 // if an OStatus conversation url was passed in, it is stored and then
1102 // removed from the array.
1103 $ostatus_conversation = null;
1105 if (isset($arr["ostatus_conversation"])) {
1106 $ostatus_conversation = $arr["ostatus_conversation"];
1107 unset($arr["ostatus_conversation"]);
1110 if(x($arr, 'gravity'))
1111 $arr['gravity'] = intval($arr['gravity']);
1112 elseif($arr['parent-uri'] === $arr['uri'])
1113 $arr['gravity'] = 0;
1114 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1115 $arr['gravity'] = 6;
1117 $arr['gravity'] = 6; // extensible catchall
1119 if(! x($arr,'type'))
1120 $arr['type'] = 'remote';
1124 /* check for create date and expire time */
1125 $uid = intval($arr['uid']);
1126 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1128 $expire_interval = $r[0]['expire'];
1129 if ($expire_interval>0) {
1130 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1131 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1132 if ($created_date < $expire_date) {
1133 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1139 // If there is no guid then take the same guid that was taken before for the same uri
1140 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1141 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1142 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1143 dbesc(trim($arr['uri']))
1147 $arr['guid'] = $r[0]["guid"];
1148 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1152 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1153 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1154 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1155 // $arr['body'] = strip_tags($arr['body']);
1158 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1159 require_once('library/langdet/Text/LanguageDetect.php');
1160 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1161 $l = new Text_LanguageDetect;
1162 //$lng = $l->detectConfidence($naked_body);
1163 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1164 $lng = $l->detect($naked_body, 3);
1166 if (sizeof($lng) > 0) {
1169 foreach ($lng as $language => $score) {
1170 if ($postopts == "")
1171 $postopts = "lang=";
1175 $postopts .= $language.";".$score;
1177 $arr['postopts'] = $postopts;
1181 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1182 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1183 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1184 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1185 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1186 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1187 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1188 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1189 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1190 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1191 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1192 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1193 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1194 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1195 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1196 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1197 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1198 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1199 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1200 $arr['deleted'] = 0;
1201 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1202 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1203 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1204 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1205 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1206 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1207 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1208 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1209 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1210 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1211 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1212 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1213 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1214 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1215 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1216 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1217 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1218 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1219 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1220 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1221 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1222 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1223 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1224 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1225 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1227 if ($arr['plink'] == "") {
1229 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1232 if ($arr['network'] == "") {
1233 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1234 intval($arr['contact-id']),
1239 $arr['network'] = $r[0]["network"];
1241 // Fallback to friendica (why is it empty in some cases?)
1242 if ($arr['network'] == "")
1243 $arr['network'] = NETWORK_DFRN;
1245 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1248 if ($arr['guid'] != "") {
1249 // Checking if there is already an item with the same guid
1250 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1251 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1252 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1255 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1260 // Check for hashtags in the body and repair or add hashtag links
1261 item_body_set_hashtags($arr);
1263 $arr['thr-parent'] = $arr['parent-uri'];
1264 if($arr['parent-uri'] === $arr['uri']) {
1266 $parent_deleted = 0;
1267 $allow_cid = $arr['allow_cid'];
1268 $allow_gid = $arr['allow_gid'];
1269 $deny_cid = $arr['deny_cid'];
1270 $deny_gid = $arr['deny_gid'];
1271 $notify_type = 'wall-new';
1275 // find the parent and snarf the item id and ACLs
1276 // and anything else we need to inherit
1278 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1279 dbesc($arr['parent-uri']),
1285 // is the new message multi-level threaded?
1286 // even though we don't support it now, preserve the info
1287 // and re-attach to the conversation parent.
1289 if($r[0]['uri'] != $r[0]['parent-uri']) {
1290 $arr['parent-uri'] = $r[0]['parent-uri'];
1291 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1292 ORDER BY `id` ASC LIMIT 1",
1293 dbesc($r[0]['parent-uri']),
1294 dbesc($r[0]['parent-uri']),
1301 $parent_id = $r[0]['id'];
1302 $parent_deleted = $r[0]['deleted'];
1303 $allow_cid = $r[0]['allow_cid'];
1304 $allow_gid = $r[0]['allow_gid'];
1305 $deny_cid = $r[0]['deny_cid'];
1306 $deny_gid = $r[0]['deny_gid'];
1307 $arr['wall'] = $r[0]['wall'];
1308 $notify_type = 'comment-new';
1310 // if the parent is private, force privacy for the entire conversation
1311 // This differs from the above settings as it subtly allows comments from
1312 // email correspondents to be private even if the overall thread is not.
1314 if($r[0]['private'])
1315 $arr['private'] = $r[0]['private'];
1317 // Edge case. We host a public forum that was originally posted to privately.
1318 // The original author commented, but as this is a comment, the permissions
1319 // weren't fixed up so it will still show the comment as private unless we fix it here.
1321 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1322 $arr['private'] = 0;
1325 // If its a post from myself then tag the thread as "mention"
1326 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1327 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1330 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1331 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1332 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1333 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1334 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1340 // Allow one to see reply tweets from status.net even when
1341 // we don't have or can't see the original post.
1344 logger('item_store: $force_parent=true, reply converted to top-level post.');
1346 $arr['parent-uri'] = $arr['uri'];
1347 $arr['gravity'] = 0;
1350 logger('item_store: item parent was not found - ignoring item');
1354 $parent_deleted = 0;
1358 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1363 if($r && count($r)) {
1364 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1367 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1368 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1369 dbesc($arr['body']),
1370 dbesc($arr['created']),
1371 intval($arr['contact-id']),
1374 if($r && count($r)) {
1375 logger('duplicated item with the same body found. ' . print_r($arr,true));
1380 // Is this item available in the global items (with uid=0)?
1381 if ($arr["uid"] == 0) {
1382 $arr["global"] = true;
1384 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1386 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1388 $arr["global"] = (count($isglobal) > 0);
1391 // Fill the cache field
1392 put_item_in_cache($arr);
1394 call_hooks('post_remote',$arr);
1396 if(x($arr,'cancel')) {
1397 logger('item_store: post cancelled by plugin.');
1401 // Store the unescaped version
1406 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1408 $r = dbq("INSERT INTO `item` (`"
1409 . implode("`, `", array_keys($arr))
1411 . implode("', '", array_values($arr))
1417 // find the item we just created
1418 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1424 $current_post = $r[0]['id'];
1425 logger('item_store: created item ' . $current_post);
1427 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1428 // This can be used to filter for inactive contacts.
1429 // Only do this for public postings to avoid privacy problems, since poco data is public.
1430 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1432 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1434 // Is it a forum? Then we don't care about the rules from above
1435 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1436 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1437 intval($arr['contact-id']));
1443 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1444 dbesc($arr['received']),
1445 dbesc($arr['received']),
1446 intval($arr['contact-id'])
1449 logger('item_store: could not locate created item');
1453 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1454 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1456 intval($arr['uid']),
1457 intval($current_post)
1461 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1462 $parent_id = $current_post;
1464 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1467 $private = $arr['private'];
1469 // Set parent id - and also make sure to inherit the parent's ACLs.
1471 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1472 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1479 intval($parent_deleted),
1480 intval($current_post)
1483 // Complete ostatus threads
1484 if ($ostatus_conversation)
1485 complete_conversation($current_post, $ostatus_conversation);
1487 $arr['id'] = $current_post;
1488 $arr['parent'] = $parent_id;
1489 $arr['allow_cid'] = $allow_cid;
1490 $arr['allow_gid'] = $allow_gid;
1491 $arr['deny_cid'] = $deny_cid;
1492 $arr['deny_gid'] = $deny_gid;
1493 $arr['private'] = $private;
1494 $arr['deleted'] = $parent_deleted;
1496 // update the commented timestamp on the parent
1497 // Only update "commented" if it is really a comment
1498 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1499 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1500 dbesc(datetime_convert()),
1501 dbesc(datetime_convert()),
1505 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1506 dbesc(datetime_convert()),
1511 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1512 intval($current_post),
1513 dbesc($dsprsig->signed_text),
1514 dbesc($dsprsig->signature),
1515 dbesc($dsprsig->signer)
1521 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1524 if($arr['last-child']) {
1525 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1527 intval($arr['uid']),
1528 intval($current_post)
1532 $deleted = tag_deliver($arr['uid'],$current_post);
1534 // current post can be deleted if is for a community page and no mention are
1536 if (!$deleted AND !$dontcache) {
1538 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1539 if (count($r) == 1) {
1540 call_hooks('post_remote_end', $r[0]);
1542 logger('item_store: new item not found in DB, id ' . $current_post);
1545 // Add every contact of the post to the global contact table
1548 create_tags_from_item($current_post);
1549 create_files_from_item($current_post);
1551 // Only check for notifications on start posts
1552 if ($arr['parent-uri'] === $arr['uri']) {
1553 add_thread($current_post);
1554 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1556 // Send a notification for every new post?
1557 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1558 intval($arr['contact-id']),
1561 $send_notification = count($r);
1563 if (!$send_notification) {
1564 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1565 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1568 foreach ($tags AS $tag) {
1569 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1570 normalise_link($tag["url"]), intval($arr['uid']));
1572 $send_notification = true;
1577 if ($send_notification) {
1578 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1579 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1580 intval($arr['uid']));
1582 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1583 intval($current_post),
1589 require_once('include/enotify.php');
1591 'type' => NOTIFY_SHARE,
1592 'notify_flags' => $u[0]['notify-flags'],
1593 'language' => $u[0]['language'],
1594 'to_name' => $u[0]['username'],
1595 'to_email' => $u[0]['email'],
1596 'uid' => $u[0]['uid'],
1598 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1599 'source_name' => $item[0]['author-name'],
1600 'source_link' => $item[0]['author-link'],
1601 'source_photo' => $item[0]['author-avatar'],
1602 'verb' => ACTIVITY_TAG,
1604 'parent' => $arr['parent']
1606 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1609 update_thread($parent_id);
1610 add_shadow_entry($arr);
1614 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1616 return $current_post;
1619 function item_body_set_hashtags(&$item) {
1621 $tags = get_tags($item["body"]);
1627 // This sorting is important when there are hashtags that are part of other hashtags
1628 // Otherwise there could be problems with hashtags like #test and #test2
1633 $URLSearchString = "^\[\]";
1635 // All hashtags should point to the home server
1636 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1637 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1639 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1640 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1642 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1643 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1645 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1648 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1650 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1653 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1655 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1658 // Repair recursive urls
1659 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1660 "#$2", $item["body"]);
1662 foreach($tags as $tag) {
1663 if(strpos($tag,'#') !== 0)
1666 if(strpos($tag,'[url='))
1669 $basetag = str_replace('_',' ',substr($tag,1));
1671 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1673 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1675 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1676 if(strlen($item["tag"]))
1677 $item["tag"] = ','.$item["tag"];
1678 $item["tag"] = $newtag.$item["tag"];
1682 // Convert back the masked hashtags
1683 $item["body"] = str_replace("#", "#", $item["body"]);
1686 function get_item_guid($id) {
1687 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1689 return($r[0]["guid"]);
1694 function get_item_id($guid, $uid = 0) {
1700 $uid == local_user();
1702 // Does the given user have this item?
1704 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1705 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1706 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1709 $nick = $r[0]["nickname"];
1713 // Or is it anywhere on the server?
1715 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1716 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1717 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1718 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1719 AND `item`.`private` = 0 AND `item`.`wall` = 1
1720 AND `item`.`guid` = '%s'", dbesc($guid));
1723 $nick = $r[0]["nickname"];
1726 return(array("nick" => $nick, "id" => $id));
1730 function get_item_contact($item,$contacts) {
1731 if(! count($contacts) || (! is_array($item)))
1733 foreach($contacts as $contact) {
1734 if($contact['id'] == $item['contact-id']) {
1736 break; // NOTREACHED
1743 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1745 * @param int $item_id
1746 * @return bool true if item was deleted, else false
1748 function tag_deliver($uid,$item_id) {
1756 $u = q("select * from user where uid = %d limit 1",
1762 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1763 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1766 $i = q("select * from item where id = %d and uid = %d limit 1",
1775 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1777 // Diaspora uses their own hardwired link URL in @-tags
1778 // instead of the one we supply with webfinger
1780 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1782 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1784 foreach($matches as $mtch) {
1785 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1787 logger('tag_deliver: mention found: ' . $mtch[2]);
1793 if ( ($community_page || $prvgroup) &&
1794 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1795 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1797 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1798 q("DELETE FROM item WHERE id = %d and uid = %d",
1808 // send a notification
1810 // use a local photo if we have one
1812 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1813 intval($u[0]['uid']),
1814 dbesc(normalise_link($item['author-link']))
1816 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1819 require_once('include/enotify.php');
1821 'type' => NOTIFY_TAGSELF,
1822 'notify_flags' => $u[0]['notify-flags'],
1823 'language' => $u[0]['language'],
1824 'to_name' => $u[0]['username'],
1825 'to_email' => $u[0]['email'],
1826 'uid' => $u[0]['uid'],
1828 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1829 'source_name' => $item['author-name'],
1830 'source_link' => $item['author-link'],
1831 'source_photo' => $photo,
1832 'verb' => ACTIVITY_TAG,
1834 'parent' => $item['parent']
1838 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1840 call_hooks('tagged', $arr);
1842 if((! $community_page) && (! $prvgroup))
1846 // tgroup delivery - setup a second delivery chain
1847 // prevent delivery looping - only proceed
1848 // if the message originated elsewhere and is a top-level post
1850 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1853 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1856 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1857 intval($u[0]['uid'])
1862 // also reset all the privacy bits to the forum default permissions
1864 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1866 $forum_mode = (($prvgroup) ? 2 : 1);
1868 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1869 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1870 intval($forum_mode),
1871 dbesc($c[0]['name']),
1872 dbesc($c[0]['url']),
1873 dbesc($c[0]['thumb']),
1875 dbesc($u[0]['allow_cid']),
1876 dbesc($u[0]['allow_gid']),
1877 dbesc($u[0]['deny_cid']),
1878 dbesc($u[0]['deny_gid']),
1881 update_thread($item_id);
1883 proc_run('php','include/notifier.php','tgroup',$item_id);
1889 function tgroup_check($uid,$item) {
1895 // check that the message originated elsewhere and is a top-level post
1897 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1901 $u = q("select * from user where uid = %d limit 1",
1907 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1908 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1911 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1913 // Diaspora uses their own hardwired link URL in @-tags
1914 // instead of the one we supply with webfinger
1916 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1918 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1920 foreach($matches as $mtch) {
1921 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1923 logger('tgroup_check: mention found: ' . $mtch[2]);
1931 if((! $community_page) && (! $prvgroup))
1945 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1949 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1951 if($contact['duplex'] && $contact['dfrn-id'])
1952 $idtosend = '0:' . $orig_id;
1953 if($contact['duplex'] && $contact['issued-id'])
1954 $idtosend = '1:' . $orig_id;
1956 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1958 $rino_enable = get_config('system','rino_encrypt');
1963 $ssl_val = intval(get_config('system','ssl_policy'));
1967 case SSL_POLICY_FULL:
1968 $ssl_policy = 'full';
1970 case SSL_POLICY_SELFSIGN:
1971 $ssl_policy = 'self';
1973 case SSL_POLICY_NONE:
1975 $ssl_policy = 'none';
1979 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1981 logger('dfrn_deliver: ' . $url);
1983 $xml = fetch_url($url);
1985 $curl_stat = $a->get_curl_code();
1987 return(-1); // timed out
1989 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1994 if(strpos($xml,'<?xml') === false) {
1995 logger('dfrn_deliver: no valid XML returned');
1996 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2000 $res = parse_xml_string($xml);
2002 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2003 return (($res->status) ? $res->status : 3);
2005 $postvars = array();
2006 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2007 $challenge = hex2bin((string) $res->challenge);
2008 $perm = (($res->perm) ? $res->perm : null);
2009 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2010 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2011 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2013 if($owner['page-flags'] == PAGE_PRVGROUP)
2016 $final_dfrn_id = '';
2019 if((($perm == 'rw') && (! intval($contact['writable'])))
2020 || (($perm == 'r') && (intval($contact['writable'])))) {
2021 q("update contact set writable = %d where id = %d",
2022 intval(($perm == 'rw') ? 1 : 0),
2023 intval($contact['id'])
2025 $contact['writable'] = (string) 1 - intval($contact['writable']);
2029 if(($contact['duplex'] && strlen($contact['pubkey']))
2030 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2031 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2032 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2033 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2036 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2037 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2040 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2042 if(strpos($final_dfrn_id,':') == 1)
2043 $final_dfrn_id = substr($final_dfrn_id,2);
2045 if($final_dfrn_id != $orig_id) {
2046 logger('dfrn_deliver: wrong dfrn_id.');
2047 // did not decode properly - cannot trust this site
2051 $postvars['dfrn_id'] = $idtosend;
2052 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2054 $postvars['dissolve'] = '1';
2057 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2058 $postvars['data'] = $atom;
2059 $postvars['perm'] = 'rw';
2062 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2063 $postvars['perm'] = 'r';
2066 $postvars['ssl_policy'] = $ssl_policy;
2069 $postvars['page'] = $page;
2071 if($rino && $rino_allowed && (! $dissolve)) {
2072 $key = substr(random_string(),0,16);
2073 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2074 $postvars['data'] = $data;
2075 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2078 if($dfrn_version >= 2.1) {
2079 if(($contact['duplex'] && strlen($contact['pubkey']))
2080 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2081 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2083 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2086 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2090 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2091 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2094 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2098 logger('md5 rawkey ' . md5($postvars['key']));
2100 $postvars['key'] = bin2hex($postvars['key']);
2103 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2105 $xml = post_url($contact['notify'],$postvars);
2107 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2109 $curl_stat = $a->get_curl_code();
2110 if((! $curl_stat) || (! strlen($xml)))
2111 return(-1); // timed out
2113 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2116 if(strpos($xml,'<?xml') === false) {
2117 logger('dfrn_deliver: phase 2: no valid XML returned');
2118 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2122 if($contact['term-date'] != '0000-00-00 00:00:00') {
2123 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2124 require_once('include/Contact.php');
2125 unmark_for_death($contact);
2128 $res = parse_xml_string($xml);
2130 return $res->status;
2135 This function returns true if $update has an edited timestamp newer
2136 than $existing, i.e. $update contains new data which should override
2137 what's already there. If there is no timestamp yet, the update is
2138 assumed to be newer. If the update has no timestamp, the existing
2139 item is assumed to be up-to-date. If the timestamps are equal it
2140 assumes the update has been seen before and should be ignored.
2142 function edited_timestamp_is_newer($existing, $update) {
2143 if (!x($existing,'edited') || !$existing['edited']) {
2146 if (!x($update,'edited') || !$update['edited']) {
2149 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2150 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2151 return (strcmp($existing_edited, $update_edited) < 0);
2156 * consume_feed - process atom feed and update anything/everything we might need to update
2158 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2160 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2161 * It is this person's stuff that is going to be updated.
2162 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2163 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2164 * have a contact record.
2165 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2166 * might not) try and subscribe to it.
2167 * $datedir sorts in reverse order
2168 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2169 * imported prior to its children being seen in the stream unless we are certain
2170 * of how the feed is arranged/ordered.
2171 * With $pass = 1, we only pull parent items out of the stream.
2172 * With $pass = 2, we only pull children (comments/likes).
2174 * So running this twice, first with pass 1 and then with pass 2 will do the right
2175 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2176 * model where comments can have sub-threads. That would require some massive sorting
2177 * to get all the feed items into a mostly linear ordering, and might still require
2181 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2183 require_once('library/simplepie/simplepie.inc');
2184 require_once('include/contact_selectors.php');
2186 if(! strlen($xml)) {
2187 logger('consume_feed: empty input');
2191 $feed = new SimplePie();
2192 $feed->set_raw_data($xml);
2194 $feed->enable_order_by_date(true);
2196 $feed->enable_order_by_date(false);
2200 logger('consume_feed: Error parsing XML: ' . $feed->error());
2202 $permalink = $feed->get_permalink();
2204 // Check at the feed level for updated contact name and/or photo
2208 $photo_timestamp = '';
2211 $contact_updated = '';
2213 $hubs = $feed->get_links('hub');
2214 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2217 $hub = implode(',', $hubs);
2219 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2221 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2223 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2224 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2225 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2226 $new_name = $elems['name'][0]['data'];
2228 // Manually checking for changed contact names
2229 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2230 $name_updated = date("c");
2231 $photo_timestamp = date("c");
2234 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2235 if ($photo_timestamp == "")
2236 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2237 $photo_url = $elems['link'][0]['attribs']['']['href'];
2240 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2241 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2245 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2246 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2248 $contact_updated = $photo_timestamp;
2250 require_once("include/Photo.php");
2251 $photo_failure = false;
2252 $have_photo = false;
2254 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2255 intval($contact['id']),
2256 intval($contact['uid'])
2259 $resource_id = $r[0]['resource-id'];
2263 $resource_id = photo_new_resource();
2266 $img_str = fetch_url($photo_url,true);
2267 // guess mimetype from headers or filename
2268 $type = guess_image_type($photo_url,true);
2271 $img = new Photo($img_str, $type);
2272 if($img->is_valid()) {
2274 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2275 dbesc($resource_id),
2276 intval($contact['id']),
2277 intval($contact['uid'])
2281 $img->scaleImageSquare(175);
2283 $hash = $resource_id;
2284 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2286 $img->scaleImage(80);
2287 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2289 $img->scaleImage(48);
2290 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2294 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2295 WHERE `uid` = %d AND `id` = %d",
2296 dbesc(datetime_convert()),
2297 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2298 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2299 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2300 intval($contact['uid']),
2301 intval($contact['id'])
2306 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2307 if ($name_updated > $contact_updated)
2308 $contact_updated = $name_updated;
2310 $r = q("select * from contact where uid = %d and id = %d limit 1",
2311 intval($contact['uid']),
2312 intval($contact['id'])
2315 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2316 dbesc(notags(trim($new_name))),
2317 dbesc(datetime_convert()),
2318 intval($contact['uid']),
2319 intval($contact['id'])
2322 // do our best to update the name on content items
2325 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2326 dbesc(notags(trim($new_name))),
2327 dbesc($r[0]['name']),
2328 dbesc($r[0]['url']),
2329 intval($contact['uid'])
2334 if ($contact_updated AND $new_name AND $photo_url)
2335 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2337 if(strlen($birthday)) {
2338 if(substr($birthday,0,4) != $contact['bdyear']) {
2339 logger('consume_feed: updating birthday: ' . $birthday);
2343 * Add new birthday event for this person
2345 * $bdtext is just a readable placeholder in case the event is shared
2346 * with others. We will replace it during presentation to our $importer
2347 * to contain a sparkle link and perhaps a photo.
2351 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2352 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2355 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2356 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2357 intval($contact['uid']),
2358 intval($contact['id']),
2359 dbesc(datetime_convert()),
2360 dbesc(datetime_convert()),
2361 dbesc(datetime_convert('UTC','UTC', $birthday)),
2362 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2371 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2372 dbesc(substr($birthday,0,4)),
2373 intval($contact['uid']),
2374 intval($contact['id'])
2377 // This function is called twice without reloading the contact
2378 // Make sure we only create one event. This is why &$contact
2379 // is a reference var in this function
2381 $contact['bdyear'] = substr($birthday,0,4);
2385 $community_page = 0;
2386 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2388 $community_page = intval($rawtags[0]['data']);
2390 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2391 q("update contact set forum = %d where id = %d",
2392 intval($community_page),
2393 intval($contact['id'])
2395 $contact['forum'] = (string) $community_page;
2399 // process any deleted entries
2401 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2402 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2403 foreach($del_entries as $dentry) {
2405 if(isset($dentry['attribs']['']['ref'])) {
2406 $uri = $dentry['attribs']['']['ref'];
2408 if(isset($dentry['attribs']['']['when'])) {
2409 $when = $dentry['attribs']['']['when'];
2410 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2413 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2415 if($deleted && is_array($contact)) {
2416 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2417 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2419 intval($importer['uid']),
2420 intval($contact['id'])
2425 if(! $item['deleted'])
2426 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2428 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2429 $xo = parse_xml_string($item['object'],false);
2430 $xt = parse_xml_string($item['target'],false);
2431 if($xt->type === ACTIVITY_OBJ_NOTE) {
2432 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2434 intval($importer['importer_uid'])
2438 // For tags, the owner cannot remove the tag on the author's copy of the post.
2440 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2441 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2442 $author_copy = (($item['origin']) ? true : false);
2444 if($owner_remove && $author_copy)
2446 if($author_remove || $owner_remove) {
2447 $tags = explode(',',$i[0]['tag']);
2450 foreach($tags as $tag)
2451 if(trim($tag) !== trim($xo->body))
2452 $newtags[] = trim($tag);
2454 q("update item set tag = '%s' where id = %d",
2455 dbesc(implode(',',$newtags)),
2458 create_tags_from_item($i[0]['id']);
2464 if($item['uri'] == $item['parent-uri']) {
2465 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2466 `body` = '', `title` = ''
2467 WHERE `parent-uri` = '%s' AND `uid` = %d",
2469 dbesc(datetime_convert()),
2470 dbesc($item['uri']),
2471 intval($importer['uid'])
2473 create_tags_from_itemuri($item['uri'], $importer['uid']);
2474 create_files_from_itemuri($item['uri'], $importer['uid']);
2475 update_thread_uri($item['uri'], $importer['uid']);
2478 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2479 `body` = '', `title` = ''
2480 WHERE `uri` = '%s' AND `uid` = %d",
2482 dbesc(datetime_convert()),
2484 intval($importer['uid'])
2486 create_tags_from_itemuri($uri, $importer['uid']);
2487 create_files_from_itemuri($uri, $importer['uid']);
2488 if($item['last-child']) {
2489 // ensure that last-child is set in case the comment that had it just got wiped.
2490 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2491 dbesc(datetime_convert()),
2492 dbesc($item['parent-uri']),
2493 intval($item['uid'])
2495 // who is the last child now?
2496 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2497 ORDER BY `created` DESC LIMIT 1",
2498 dbesc($item['parent-uri']),
2499 intval($importer['uid'])
2502 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2513 // Now process the feed
2515 if($feed->get_item_quantity()) {
2517 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2519 // in inverse date order
2521 $items = array_reverse($feed->get_items());
2523 $items = $feed->get_items();
2526 foreach($items as $item) {
2529 $item_id = $item->get_id();
2530 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2531 if(isset($rawthread[0]['attribs']['']['ref'])) {
2533 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2536 if(($is_reply) && is_array($contact)) {
2541 // not allowed to post
2543 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2547 // Have we seen it? If not, import it.
2549 $item_id = $item->get_id();
2550 $datarray = get_atom_elements($feed, $item, $contact);
2552 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2553 $datarray['author-name'] = $contact['name'];
2554 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2555 $datarray['author-link'] = $contact['url'];
2556 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2557 $datarray['author-avatar'] = $contact['thumb'];
2559 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2560 logger('consume_feed: no author information! ' . print_r($datarray,true));
2564 $force_parent = false;
2565 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2566 if($contact['network'] === NETWORK_OSTATUS)
2567 $force_parent = true;
2568 if(strlen($datarray['title']))
2569 unset($datarray['title']);
2570 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2571 dbesc(datetime_convert()),
2573 intval($importer['uid'])
2575 $datarray['last-child'] = 1;
2576 update_thread_uri($parent_uri, $importer['uid']);
2580 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2582 intval($importer['uid'])
2585 // Update content if 'updated' changes
2588 if (edited_timestamp_is_newer($r[0], $datarray)) {
2590 // do not accept (ignore) an earlier edit than one we currently have.
2591 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2594 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2595 dbesc($datarray['title']),
2596 dbesc($datarray['body']),
2597 dbesc($datarray['tag']),
2598 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2599 dbesc(datetime_convert()),
2601 intval($importer['uid'])
2603 create_tags_from_itemuri($item_id, $importer['uid']);
2604 update_thread_uri($item_id, $importer['uid']);
2607 // update last-child if it changes
2609 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2610 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2611 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2612 dbesc(datetime_convert()),
2614 intval($importer['uid'])
2616 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2617 intval($allow[0]['data']),
2618 dbesc(datetime_convert()),
2620 intval($importer['uid'])
2622 update_thread_uri($item_id, $importer['uid']);
2628 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2629 // one way feed - no remote comment ability
2630 $datarray['last-child'] = 0;
2632 $datarray['parent-uri'] = $parent_uri;
2633 $datarray['uid'] = $importer['uid'];
2634 $datarray['contact-id'] = $contact['id'];
2635 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2636 $datarray['type'] = 'activity';
2637 $datarray['gravity'] = GRAVITY_LIKE;
2638 // only one like or dislike per person
2639 // splitted into two queries for performance issues
2640 $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",
2641 intval($datarray['uid']),
2642 intval($datarray['contact-id']),
2643 dbesc($datarray['verb']),
2649 $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",
2650 intval($datarray['uid']),
2651 intval($datarray['contact-id']),
2652 dbesc($datarray['verb']),
2659 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2660 $xo = parse_xml_string($datarray['object'],false);
2661 $xt = parse_xml_string($datarray['target'],false);
2663 if($xt->type == ACTIVITY_OBJ_NOTE) {
2664 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2666 intval($importer['importer_uid'])
2671 // extract tag, if not duplicate, add to parent item
2672 if($xo->id && $xo->content) {
2673 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2674 if(! (stristr($r[0]['tag'],$newtag))) {
2675 q("UPDATE item SET tag = '%s' WHERE id = %d",
2676 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2679 create_tags_from_item($r[0]['id']);
2685 $r = item_store($datarray,$force_parent);
2691 // Head post of a conversation. Have we seen it? If not, import it.
2693 $item_id = $item->get_id();
2695 $datarray = get_atom_elements($feed, $item, $contact);
2697 if(is_array($contact)) {
2698 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2699 $datarray['author-name'] = $contact['name'];
2700 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2701 $datarray['author-link'] = $contact['url'];
2702 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2703 $datarray['author-avatar'] = $contact['thumb'];
2706 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2707 logger('consume_feed: no author information! ' . print_r($datarray,true));
2711 // special handling for events
2713 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2714 $ev = bbtoevent($datarray['body']);
2715 if(x($ev,'desc') && x($ev,'start')) {
2716 $ev['uid'] = $importer['uid'];
2717 $ev['uri'] = $item_id;
2718 $ev['edited'] = $datarray['edited'];
2719 $ev['private'] = $datarray['private'];
2721 if(is_array($contact))
2722 $ev['cid'] = $contact['id'];
2723 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2725 intval($importer['uid'])
2728 $ev['id'] = $r[0]['id'];
2729 $xyz = event_store($ev);
2734 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2735 if(strlen($datarray['title']))
2736 unset($datarray['title']);
2737 $datarray['last-child'] = 1;
2741 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2743 intval($importer['uid'])
2746 // Update content if 'updated' changes
2749 if (edited_timestamp_is_newer($r[0], $datarray)) {
2751 // do not accept (ignore) an earlier edit than one we currently have.
2752 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2755 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2756 dbesc($datarray['title']),
2757 dbesc($datarray['body']),
2758 dbesc($datarray['tag']),
2759 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2760 dbesc(datetime_convert()),
2762 intval($importer['uid'])
2764 create_tags_from_itemuri($item_id, $importer['uid']);
2765 update_thread_uri($item_id, $importer['uid']);
2768 // update last-child if it changes
2770 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2771 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2772 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2773 intval($allow[0]['data']),
2774 dbesc(datetime_convert()),
2776 intval($importer['uid'])
2778 update_thread_uri($item_id, $importer['uid']);
2783 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2784 logger('consume-feed: New follower');
2785 new_follower($importer,$contact,$datarray,$item);
2788 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2789 lose_follower($importer,$contact,$datarray,$item);
2793 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2794 logger('consume-feed: New friend request');
2795 new_follower($importer,$contact,$datarray,$item,true);
2798 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2799 lose_sharer($importer,$contact,$datarray,$item);
2804 if(! is_array($contact))
2808 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2809 // one way feed - no remote comment ability
2810 $datarray['last-child'] = 0;
2812 if($contact['network'] === NETWORK_FEED)
2813 $datarray['private'] = 2;
2815 $datarray['parent-uri'] = $item_id;
2816 $datarray['uid'] = $importer['uid'];
2817 $datarray['contact-id'] = $contact['id'];
2819 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2820 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2821 // but otherwise there's a possible data mixup on the sender's system.
2822 // the tgroup delivery code called from item_store will correct it if it's a forum,
2823 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2824 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2825 $datarray['owner-name'] = $contact['name'];
2826 $datarray['owner-link'] = $contact['url'];
2827 $datarray['owner-avatar'] = $contact['thumb'];
2830 // We've allowed "followers" to reach this point so we can decide if they are
2831 // posting an @-tag delivery, which followers are allowed to do for certain
2832 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2834 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2837 // This is my contact on another system, but it's really me.
2838 // Turn this into a wall post.
2839 $notify = item_is_remote_self($contact, $datarray);
2841 $r = item_store($datarray, false, $notify);
2842 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2850 function item_is_remote_self($contact, &$datarray) {
2853 if (!$contact['remote_self'])
2856 // Prevent the forwarding of posts that are forwarded
2857 if ($datarray["extid"] == NETWORK_DFRN)
2860 // Prevent to forward already forwarded posts
2861 if ($datarray["app"] == $a->get_hostname())
2864 // Only forward posts
2865 if ($datarray["verb"] != ACTIVITY_POST)
2868 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2871 $datarray2 = $datarray;
2872 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2873 if ($contact['remote_self'] == 2) {
2874 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2875 intval($contact['uid']));
2877 $datarray['contact-id'] = $r[0]["id"];
2879 $datarray['owner-name'] = $r[0]["name"];
2880 $datarray['owner-link'] = $r[0]["url"];
2881 $datarray['owner-avatar'] = $r[0]["thumb"];
2883 $datarray['author-name'] = $datarray['owner-name'];
2884 $datarray['author-link'] = $datarray['owner-link'];
2885 $datarray['author-avatar'] = $datarray['owner-avatar'];
2888 if ($contact['network'] != NETWORK_FEED) {
2889 $datarray["guid"] = get_guid(32);
2890 unset($datarray["plink"]);
2891 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2892 $datarray["parent-uri"] = $datarray["uri"];
2893 $datarray["extid"] = $contact['network'];
2894 $urlpart = parse_url($datarray2['author-link']);
2895 $datarray["app"] = $urlpart["host"];
2897 $datarray['private'] = 0;
2900 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2901 // $datarray["app"] = network_to_name($contact['network']);
2903 if ($contact['network'] != NETWORK_FEED) {
2904 // Store the original post
2905 $r = item_store($datarray2, false, false);
2906 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2908 $datarray["app"] = "Feed";
2913 function local_delivery($importer,$data) {
2916 logger(__function__, LOGGER_TRACE);
2918 if($importer['readonly']) {
2919 // We aren't receiving stuff from this person. But we will quietly ignore them
2920 // rather than a blatant "go away" message.
2921 logger('local_delivery: ignoring');
2926 // Consume notification feed. This may differ from consuming a public feed in several ways
2927 // - might contain email or friend suggestions
2928 // - might contain remote followup to our message
2929 // - in which case we need to accept it and then notify other conversants
2930 // - we may need to send various email notifications
2932 $feed = new SimplePie();
2933 $feed->set_raw_data($data);
2934 $feed->enable_order_by_date(false);
2939 logger('local_delivery: Error parsing XML: ' . $feed->error());
2942 // Check at the feed level for updated contact name and/or photo
2946 $photo_timestamp = '';
2948 $contact_updated = '';
2951 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2953 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2955 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2958 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2959 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2960 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2961 $new_name = $elems['name'][0]['data'];
2963 // Manually checking for changed contact names
2964 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2965 $name_updated = date("c");
2966 $photo_timestamp = date("c");
2969 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2970 if ($photo_timestamp == "")
2971 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2972 $photo_url = $elems['link'][0]['attribs']['']['href'];
2976 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2978 $contact_updated = $photo_timestamp;
2980 logger('local_delivery: Updating photo for ' . $importer['name']);
2981 require_once("include/Photo.php");
2982 $photo_failure = false;
2983 $have_photo = false;
2985 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2986 intval($importer['id']),
2987 intval($importer['importer_uid'])
2990 $resource_id = $r[0]['resource-id'];
2994 $resource_id = photo_new_resource();
2997 $img_str = fetch_url($photo_url,true);
2998 // guess mimetype from headers or filename
2999 $type = guess_image_type($photo_url,true);
3002 $img = new Photo($img_str, $type);
3003 if($img->is_valid()) {
3005 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3006 dbesc($resource_id),
3007 intval($importer['id']),
3008 intval($importer['importer_uid'])
3012 $img->scaleImageSquare(175);
3014 $hash = $resource_id;
3015 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3017 $img->scaleImage(80);
3018 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3020 $img->scaleImage(48);
3021 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3025 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3026 WHERE `uid` = %d AND `id` = %d",
3027 dbesc(datetime_convert()),
3028 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3029 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3030 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3031 intval($importer['importer_uid']),
3032 intval($importer['id'])
3037 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3038 if ($name_updated > $contact_updated)
3039 $contact_updated = $name_updated;
3041 $r = q("select * from contact where uid = %d and id = %d limit 1",
3042 intval($importer['importer_uid']),
3043 intval($importer['id'])
3046 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3047 dbesc(notags(trim($new_name))),
3048 dbesc(datetime_convert()),
3049 intval($importer['importer_uid']),
3050 intval($importer['id'])
3053 // do our best to update the name on content items
3056 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3057 dbesc(notags(trim($new_name))),
3058 dbesc($r[0]['name']),
3059 dbesc($r[0]['url']),
3060 intval($importer['importer_uid'])
3065 if ($contact_updated AND $new_name AND $photo_url)
3066 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3068 // Currently unsupported - needs a lot of work
3069 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3070 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3071 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3073 $newloc['uid'] = $importer['importer_uid'];
3074 $newloc['cid'] = $importer['id'];
3075 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3076 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3077 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3078 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3079 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3080 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3081 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3082 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3083 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3084 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3085 /** relocated user must have original key pair */
3086 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3087 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3089 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3092 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3093 intval($importer['id']),
3094 intval($importer['importer_uid']));
3099 $x = q("UPDATE contact SET
3110 `site-pubkey` = '%s'
3111 WHERE id=%d AND uid=%d;",
3112 dbesc($newloc['name']),
3113 dbesc($newloc['photo']),
3114 dbesc($newloc['thumb']),
3115 dbesc($newloc['micro']),
3116 dbesc($newloc['url']),
3117 dbesc(normalise_link($newloc['url'])),
3118 dbesc($newloc['request']),
3119 dbesc($newloc['confirm']),
3120 dbesc($newloc['notify']),
3121 dbesc($newloc['poll']),
3122 dbesc($newloc['sitepubkey']),
3123 intval($importer['id']),
3124 intval($importer['importer_uid']));
3130 'owner-link' => array($old['url'], $newloc['url']),
3131 'author-link' => array($old['url'], $newloc['url']),
3132 'owner-avatar' => array($old['photo'], $newloc['photo']),
3133 'author-avatar' => array($old['photo'], $newloc['photo']),
3135 foreach ($fields as $n=>$f){
3136 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3139 intval($importer['importer_uid']));
3145 // merge with current record, current contents have priority
3146 // update record, set url-updated
3147 // update profile photos
3153 // handle friend suggestion notification
3155 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3156 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3157 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3159 $fsugg['uid'] = $importer['importer_uid'];
3160 $fsugg['cid'] = $importer['id'];
3161 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3162 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3163 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3164 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3165 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3167 // Does our member already have a friend matching this description?
3169 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3170 dbesc($fsugg['name']),
3171 dbesc(normalise_link($fsugg['url'])),
3172 intval($fsugg['uid'])
3177 // Do we already have an fcontact record for this person?
3180 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3181 dbesc($fsugg['url']),
3182 dbesc($fsugg['name']),
3183 dbesc($fsugg['request'])
3188 // OK, we do. Do we already have an introduction for this person ?
3189 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3190 intval($fsugg['uid']),
3197 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3198 dbesc($fsugg['name']),
3199 dbesc($fsugg['url']),
3200 dbesc($fsugg['photo']),
3201 dbesc($fsugg['request'])
3203 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3204 dbesc($fsugg['url']),
3205 dbesc($fsugg['name']),
3206 dbesc($fsugg['request'])
3211 // database record did not get created. Quietly give up.
3216 $hash = random_string();
3218 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3219 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3220 intval($fsugg['uid']),
3222 intval($fsugg['cid']),
3223 dbesc($fsugg['body']),
3225 dbesc(datetime_convert()),
3230 'type' => NOTIFY_SUGGEST,
3231 'notify_flags' => $importer['notify-flags'],
3232 'language' => $importer['language'],
3233 'to_name' => $importer['username'],
3234 'to_email' => $importer['email'],
3235 'uid' => $importer['importer_uid'],
3237 'link' => $a->get_baseurl() . '/notifications/intros',
3238 'source_name' => $importer['name'],
3239 'source_link' => $importer['url'],
3240 'source_photo' => $importer['photo'],
3241 'verb' => ACTIVITY_REQ_FRIEND,
3250 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3251 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3253 logger('local_delivery: private message received');
3256 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3259 $msg['uid'] = $importer['importer_uid'];
3260 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3261 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3262 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3263 $msg['contact-id'] = $importer['id'];
3264 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3265 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3267 $msg['replied'] = 0;
3268 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3269 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3270 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3274 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3275 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3277 // send notifications.
3279 require_once('include/enotify.php');
3281 $notif_params = array(
3282 'type' => NOTIFY_MAIL,
3283 'notify_flags' => $importer['notify-flags'],
3284 'language' => $importer['language'],
3285 'to_name' => $importer['username'],
3286 'to_email' => $importer['email'],
3287 'uid' => $importer['importer_uid'],
3289 'source_name' => $msg['from-name'],
3290 'source_link' => $importer['url'],
3291 'source_photo' => $importer['thumb'],
3292 'verb' => ACTIVITY_POST,
3296 notification($notif_params);
3302 $community_page = 0;
3303 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3305 $community_page = intval($rawtags[0]['data']);
3307 if(intval($importer['forum']) != $community_page) {
3308 q("update contact set forum = %d where id = %d",
3309 intval($community_page),
3310 intval($importer['id'])
3312 $importer['forum'] = (string) $community_page;
3315 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3317 // process any deleted entries
3319 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3320 if(is_array($del_entries) && count($del_entries)) {
3321 foreach($del_entries as $dentry) {
3323 if(isset($dentry['attribs']['']['ref'])) {
3324 $uri = $dentry['attribs']['']['ref'];
3326 if(isset($dentry['attribs']['']['when'])) {
3327 $when = $dentry['attribs']['']['when'];
3328 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3331 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3335 // check for relayed deletes to our conversation
3338 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3340 intval($importer['importer_uid'])
3343 $parent_uri = $r[0]['parent-uri'];
3344 if($r[0]['id'] != $r[0]['parent'])
3351 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3354 logger('local_delivery: possible community delete');
3357 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3359 // was the top-level post for this reply written by somebody on this site?
3360 // Specifically, the recipient?
3362 $is_a_remote_delete = false;
3364 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3365 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3366 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3367 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3368 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3369 AND `item`.`uid` = %d
3375 intval($importer['importer_uid'])
3378 $is_a_remote_delete = true;
3380 // Does this have the characteristics of a community or private group comment?
3381 // If it's a reply to a wall post on a community/prvgroup page it's a
3382 // valid community comment. Also forum_mode makes it valid for sure.
3383 // If neither, it's not.
3385 if($is_a_remote_delete && $community) {
3386 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3387 $is_a_remote_delete = false;
3388 logger('local_delivery: not a community delete');
3392 if($is_a_remote_delete) {
3393 logger('local_delivery: received remote delete');
3397 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3398 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3400 intval($importer['importer_uid']),
3401 intval($importer['id'])
3407 if($item['deleted'])
3410 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3412 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3413 $xo = parse_xml_string($item['object'],false);
3414 $xt = parse_xml_string($item['target'],false);
3416 if($xt->type === ACTIVITY_OBJ_NOTE) {
3417 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3419 intval($importer['importer_uid'])
3423 // For tags, the owner cannot remove the tag on the author's copy of the post.
3425 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3426 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3427 $author_copy = (($item['origin']) ? true : false);
3429 if($owner_remove && $author_copy)
3431 if($author_remove || $owner_remove) {
3432 $tags = explode(',',$i[0]['tag']);
3435 foreach($tags as $tag)
3436 if(trim($tag) !== trim($xo->body))
3437 $newtags[] = trim($tag);
3439 q("update item set tag = '%s' where id = %d",
3440 dbesc(implode(',',$newtags)),
3443 create_tags_from_item($i[0]['id']);
3449 if($item['uri'] == $item['parent-uri']) {
3450 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3451 `body` = '', `title` = ''
3452 WHERE `parent-uri` = '%s' AND `uid` = %d",
3454 dbesc(datetime_convert()),
3455 dbesc($item['uri']),
3456 intval($importer['importer_uid'])
3458 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3459 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3460 update_thread_uri($item['uri'], $importer['importer_uid']);
3463 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3464 `body` = '', `title` = ''
3465 WHERE `uri` = '%s' AND `uid` = %d",
3467 dbesc(datetime_convert()),
3469 intval($importer['importer_uid'])
3471 create_tags_from_itemuri($uri, $importer['importer_uid']);
3472 create_files_from_itemuri($uri, $importer['importer_uid']);
3473 update_thread_uri($uri, $importer['importer_uid']);
3474 if($item['last-child']) {
3475 // ensure that last-child is set in case the comment that had it just got wiped.
3476 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3477 dbesc(datetime_convert()),
3478 dbesc($item['parent-uri']),
3479 intval($item['uid'])
3481 // who is the last child now?
3482 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3483 ORDER BY `created` DESC LIMIT 1",
3484 dbesc($item['parent-uri']),
3485 intval($importer['importer_uid'])
3488 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3493 // if this is a relayed delete, propagate it to other recipients
3495 if($is_a_remote_delete)
3496 proc_run('php',"include/notifier.php","drop",$item['id']);
3504 foreach($feed->get_items() as $item) {
3507 $item_id = $item->get_id();
3508 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3509 if(isset($rawthread[0]['attribs']['']['ref'])) {
3511 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3517 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3520 logger('local_delivery: possible community reply');
3523 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3525 // was the top-level post for this reply written by somebody on this site?
3526 // Specifically, the recipient?
3528 $is_a_remote_comment = false;
3529 $top_uri = $parent_uri;
3531 $r = q("select `item`.`parent-uri` from `item`
3532 WHERE `item`.`uri` = '%s'
3536 if($r && count($r)) {
3537 $top_uri = $r[0]['parent-uri'];
3539 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3540 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3541 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3542 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3543 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3544 AND `item`.`uid` = %d
3550 intval($importer['importer_uid'])
3553 $is_a_remote_comment = true;
3556 // Does this have the characteristics of a community or private group comment?
3557 // If it's a reply to a wall post on a community/prvgroup page it's a
3558 // valid community comment. Also forum_mode makes it valid for sure.
3559 // If neither, it's not.
3561 if($is_a_remote_comment && $community) {
3562 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3563 $is_a_remote_comment = false;
3564 logger('local_delivery: not a community reply');
3568 if($is_a_remote_comment) {
3569 logger('local_delivery: received remote comment');
3571 // remote reply to our post. Import and then notify everybody else.
3573 $datarray = get_atom_elements($feed, $item);
3575 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3577 intval($importer['importer_uid'])
3580 // Update content if 'updated' changes
3584 if (edited_timestamp_is_newer($r[0], $datarray)) {
3586 // do not accept (ignore) an earlier edit than one we currently have.
3587 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3590 logger('received updated comment' , LOGGER_DEBUG);
3591 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3592 dbesc($datarray['title']),
3593 dbesc($datarray['body']),
3594 dbesc($datarray['tag']),
3595 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3596 dbesc(datetime_convert()),
3598 intval($importer['importer_uid'])
3600 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3602 proc_run('php',"include/notifier.php","comment-import",$iid);
3611 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3612 intval($importer['importer_uid'])
3616 $datarray['type'] = 'remote-comment';
3617 $datarray['wall'] = 1;
3618 $datarray['parent-uri'] = $parent_uri;
3619 $datarray['uid'] = $importer['importer_uid'];
3620 $datarray['owner-name'] = $own[0]['name'];
3621 $datarray['owner-link'] = $own[0]['url'];
3622 $datarray['owner-avatar'] = $own[0]['thumb'];
3623 $datarray['contact-id'] = $importer['id'];
3625 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3627 $datarray['type'] = 'activity';
3628 $datarray['gravity'] = GRAVITY_LIKE;
3629 $datarray['last-child'] = 0;
3630 // only one like or dislike per person
3631 // splitted into two queries for performance issues
3632 $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",
3633 intval($datarray['uid']),
3634 intval($datarray['contact-id']),
3635 dbesc($datarray['verb']),
3636 dbesc($datarray['parent-uri'])
3642 $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",
3643 intval($datarray['uid']),
3644 intval($datarray['contact-id']),
3645 dbesc($datarray['verb']),
3646 dbesc($datarray['parent-uri'])
3653 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3655 $xo = parse_xml_string($datarray['object'],false);
3656 $xt = parse_xml_string($datarray['target'],false);
3658 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3660 // fetch the parent item
3662 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3664 intval($importer['importer_uid'])
3669 // extract tag, if not duplicate, and this user allows tags, add to parent item
3671 if($xo->id && $xo->content) {
3672 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3673 if(! (stristr($tagp[0]['tag'],$newtag))) {
3674 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3675 intval($importer['importer_uid'])
3677 if(count($i) && ! intval($i[0]['blocktags'])) {
3678 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3679 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3680 intval($tagp[0]['id']),
3681 dbesc(datetime_convert()),
3682 dbesc(datetime_convert())
3684 create_tags_from_item($tagp[0]['id']);
3692 $posted_id = item_store($datarray);
3697 $datarray["id"] = $posted_id;
3699 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3701 intval($importer['importer_uid'])
3704 $parent = $r[0]['parent'];
3705 $parent_uri = $r[0]['parent-uri'];
3709 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3710 dbesc(datetime_convert()),
3711 intval($importer['importer_uid']),
3712 intval($r[0]['parent'])
3715 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3716 dbesc(datetime_convert()),
3717 intval($importer['importer_uid']),
3722 if($posted_id && $parent) {
3724 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3726 if((! $is_like) && (! $importer['self'])) {
3728 require_once('include/enotify.php');
3731 'type' => NOTIFY_COMMENT,
3732 'notify_flags' => $importer['notify-flags'],
3733 'language' => $importer['language'],
3734 'to_name' => $importer['username'],
3735 'to_email' => $importer['email'],
3736 'uid' => $importer['importer_uid'],
3737 'item' => $datarray,
3738 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3739 'source_name' => stripslashes($datarray['author-name']),
3740 'source_link' => $datarray['author-link'],
3741 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3742 ? $importer['thumb'] : $datarray['author-avatar']),
3743 'verb' => ACTIVITY_POST,
3745 'parent' => $parent,
3746 'parent_uri' => $parent_uri,
3758 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3760 $item_id = $item->get_id();
3761 $datarray = get_atom_elements($feed,$item);
3763 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3766 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3768 intval($importer['importer_uid'])
3771 // Update content if 'updated' changes
3774 if (edited_timestamp_is_newer($r[0], $datarray)) {
3776 // do not accept (ignore) an earlier edit than one we currently have.
3777 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3780 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3781 dbesc($datarray['title']),
3782 dbesc($datarray['body']),
3783 dbesc($datarray['tag']),
3784 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3785 dbesc(datetime_convert()),
3787 intval($importer['importer_uid'])
3789 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3792 // update last-child if it changes
3794 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3795 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3796 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3797 dbesc(datetime_convert()),
3799 intval($importer['importer_uid'])
3801 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3802 intval($allow[0]['data']),
3803 dbesc(datetime_convert()),
3805 intval($importer['importer_uid'])
3811 $datarray['parent-uri'] = $parent_uri;
3812 $datarray['uid'] = $importer['importer_uid'];
3813 $datarray['contact-id'] = $importer['id'];
3814 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3815 $datarray['type'] = 'activity';
3816 $datarray['gravity'] = GRAVITY_LIKE;
3817 // only one like or dislike per person
3818 // splitted into two queries for performance issues
3819 $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",
3820 intval($datarray['uid']),
3821 intval($datarray['contact-id']),
3822 dbesc($datarray['verb']),
3828 $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",
3829 intval($datarray['uid']),
3830 intval($datarray['contact-id']),
3831 dbesc($datarray['verb']),
3839 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3841 $xo = parse_xml_string($datarray['object'],false);
3842 $xt = parse_xml_string($datarray['target'],false);
3844 if($xt->type == ACTIVITY_OBJ_NOTE) {
3845 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3847 intval($importer['importer_uid'])
3852 // extract tag, if not duplicate, add to parent item
3854 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3855 q("UPDATE item SET tag = '%s' WHERE id = %d",
3856 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3859 create_tags_from_item($r[0]['id']);
3865 $posted_id = item_store($datarray);
3867 // find out if our user is involved in this conversation and wants to be notified.
3869 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3871 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3873 intval($importer['importer_uid'])
3876 if(count($myconv)) {
3877 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3879 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3880 if(! link_compare($datarray['author-link'],$importer_url)) {
3883 foreach($myconv as $conv) {
3885 // now if we find a match, it means we're in this conversation
3887 if(! link_compare($conv['author-link'],$importer_url))
3890 require_once('include/enotify.php');
3892 $conv_parent = $conv['parent'];
3895 'type' => NOTIFY_COMMENT,
3896 'notify_flags' => $importer['notify-flags'],
3897 'language' => $importer['language'],
3898 'to_name' => $importer['username'],
3899 'to_email' => $importer['email'],
3900 'uid' => $importer['importer_uid'],
3901 'item' => $datarray,
3902 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3903 'source_name' => stripslashes($datarray['author-name']),
3904 'source_link' => $datarray['author-link'],
3905 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3906 ? $importer['thumb'] : $datarray['author-avatar']),
3907 'verb' => ACTIVITY_POST,
3909 'parent' => $conv_parent,
3910 'parent_uri' => $parent_uri
3914 // only send one notification
3926 // Head post of a conversation. Have we seen it? If not, import it.
3929 $item_id = $item->get_id();
3930 $datarray = get_atom_elements($feed,$item);
3932 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3933 $ev = bbtoevent($datarray['body']);
3934 if(x($ev,'desc') && x($ev,'start')) {
3935 $ev['cid'] = $importer['id'];
3936 $ev['uid'] = $importer['uid'];
3937 $ev['uri'] = $item_id;
3938 $ev['edited'] = $datarray['edited'];
3939 $ev['private'] = $datarray['private'];
3941 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3943 intval($importer['uid'])
3946 $ev['id'] = $r[0]['id'];
3947 $xyz = event_store($ev);
3952 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3954 intval($importer['importer_uid'])
3957 // Update content if 'updated' changes
3960 if (edited_timestamp_is_newer($r[0], $datarray)) {
3962 // do not accept (ignore) an earlier edit than one we currently have.
3963 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3966 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3967 dbesc($datarray['title']),
3968 dbesc($datarray['body']),
3969 dbesc($datarray['tag']),
3970 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3971 dbesc(datetime_convert()),
3973 intval($importer['importer_uid'])
3975 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3976 update_thread_uri($item_id, $importer['importer_uid']);
3979 // update last-child if it changes
3981 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3982 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3983 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3984 intval($allow[0]['data']),
3985 dbesc(datetime_convert()),
3987 intval($importer['importer_uid'])
3993 $datarray['parent-uri'] = $item_id;
3994 $datarray['uid'] = $importer['importer_uid'];
3995 $datarray['contact-id'] = $importer['id'];
3998 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3999 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4000 // but otherwise there's a possible data mixup on the sender's system.
4001 // the tgroup delivery code called from item_store will correct it if it's a forum,
4002 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4003 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4004 $datarray['owner-name'] = $importer['senderName'];
4005 $datarray['owner-link'] = $importer['url'];
4006 $datarray['owner-avatar'] = $importer['thumb'];
4009 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4012 // This is my contact on another system, but it's really me.
4013 // Turn this into a wall post.
4014 $notify = item_is_remote_self($importer, $datarray);
4016 $posted_id = item_store($datarray, false, $notify);
4018 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4019 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4022 $xo = parse_xml_string($datarray['object'],false);
4024 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4026 // somebody was poked/prodded. Was it me?
4028 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4030 foreach($links->link as $l) {
4031 $atts = $l->attributes();
4032 switch($atts['rel']) {
4034 $Blink = $atts['href'];
4040 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4042 // send a notification
4043 require_once('include/enotify.php');
4046 'type' => NOTIFY_POKE,
4047 'notify_flags' => $importer['notify-flags'],
4048 'language' => $importer['language'],
4049 'to_name' => $importer['username'],
4050 'to_email' => $importer['email'],
4051 'uid' => $importer['importer_uid'],
4052 'item' => $datarray,
4053 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4054 'source_name' => stripslashes($datarray['author-name']),
4055 'source_link' => $datarray['author-link'],
4056 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4057 ? $importer['thumb'] : $datarray['author-avatar']),
4058 'verb' => $datarray['verb'],
4059 'otype' => 'person',
4060 'activity' => $verb,
4061 'parent' => $datarray['parent']
4077 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4078 $url = notags(trim($datarray['author-link']));
4079 $name = notags(trim($datarray['author-name']));
4080 $photo = notags(trim($datarray['author-avatar']));
4082 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4083 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4084 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4086 if(is_array($contact)) {
4087 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4088 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4089 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4090 intval(CONTACT_IS_FRIEND),
4091 intval($contact['id']),
4092 intval($importer['uid'])
4095 // send email notification to owner?
4099 // create contact record
4101 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4102 `blocked`, `readonly`, `pending`, `writable` )
4103 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4104 intval($importer['uid']),
4105 dbesc(datetime_convert()),
4107 dbesc(normalise_link($url)),
4111 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4112 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4114 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4115 intval($importer['uid']),
4119 $contact_record = $r[0];
4121 // create notification
4122 $hash = random_string();
4124 if(is_array($contact_record)) {
4125 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4126 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4127 intval($importer['uid']),
4128 intval($contact_record['id']),
4130 dbesc(datetime_convert())
4134 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4135 intval($importer['uid'])
4140 if(intval($r[0]['def_gid'])) {
4141 require_once('include/group.php');
4142 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4145 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4146 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4149 'type' => NOTIFY_INTRO,
4150 'notify_flags' => $r[0]['notify-flags'],
4151 'language' => $r[0]['language'],
4152 'to_name' => $r[0]['username'],
4153 'to_email' => $r[0]['email'],
4154 'uid' => $r[0]['uid'],
4155 'link' => $a->get_baseurl() . '/notifications/intro',
4156 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4157 'source_link' => $contact_record['url'],
4158 'source_photo' => $contact_record['photo'],
4159 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4168 function lose_follower($importer,$contact,$datarray,$item) {
4170 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4171 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4172 intval(CONTACT_IS_SHARING),
4173 intval($contact['id'])
4177 contact_remove($contact['id']);
4181 function lose_sharer($importer,$contact,$datarray,$item) {
4183 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4184 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4185 intval(CONTACT_IS_FOLLOWER),
4186 intval($contact['id'])
4190 contact_remove($contact['id']);
4195 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4199 if(is_array($importer)) {
4200 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4201 intval($importer['uid'])
4205 // Diaspora has different message-ids in feeds than they do
4206 // through the direct Diaspora protocol. If we try and use
4207 // the feed, we'll get duplicates. So don't.
4209 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4212 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4214 // Use a single verify token, even if multiple hubs
4216 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4218 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4220 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4222 if(! strlen($contact['hub-verify'])) {
4223 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4224 dbesc($verify_token),
4225 intval($contact['id'])
4229 post_url($url,$params);
4231 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4238 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4242 $name = xmlify($name);
4243 $uri = xmlify($uri);
4246 $photo = xmlify($photo);
4250 $o .= "<name>$name</name>\r\n";
4251 $o .= "<uri>$uri</uri>\r\n";
4252 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4253 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4255 call_hooks('atom_author', $o);
4257 $o .= "</$tag>\r\n";
4261 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4265 if(! $item['parent'])
4268 if($item['deleted'])
4269 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4272 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4273 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4275 $body = $item['body'];
4278 $o = "\r\n\r\n<entry>\r\n";
4280 if(is_array($author))
4281 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4283 $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']));
4284 if(strlen($item['owner-name']))
4285 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4287 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4288 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4289 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4294 if ($item['title'] != "")
4295 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4297 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4299 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4300 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4301 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4302 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4303 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4304 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4305 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4309 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4311 if($item['location']) {
4312 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4313 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4317 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4319 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4320 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4323 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4324 if($item['bookmark'])
4325 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4328 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4331 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4333 if($item['signed_text']) {
4334 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4335 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4338 $verb = construct_verb($item);
4339 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4340 $actobj = construct_activity_object($item);
4343 $actarg = construct_activity_target($item);
4347 $tags = item_getfeedtags($item);
4349 foreach($tags as $t) {
4350 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4354 $o .= item_getfeedattach($item);
4356 $mentioned = get_mentions($item);
4360 call_hooks('atom_entry', $o);
4362 $o .= '</entry>' . "\r\n";
4367 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4369 if(get_config('system','disable_embedded'))
4374 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4375 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4380 $img_start = strpos($orig_body, '[img');
4381 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4382 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4383 while( ($img_st_close !== false) && ($img_len !== false) ) {
4385 $img_st_close++; // make it point to AFTER the closing bracket
4386 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4388 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4391 if(stristr($image , $site . '/photo/')) {
4392 // Only embed locally hosted photos
4394 $i = basename($image);
4395 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4396 $x = strpos($i,'-');
4399 $res = substr($i,$x+1);
4400 $i = substr($i,0,$x);
4401 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4408 // Check to see if we should replace this photo link with an embedded image
4409 // 1. No need to do so if the photo is public
4410 // 2. If there's a contact-id provided, see if they're in the access list
4411 // for the photo. If so, embed it.
4412 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4413 // permissions, regardless of order but first check to see if they're an exact
4414 // match to save some processing overhead.
4416 if(has_permissions($r[0])) {
4418 $recips = enumerate_permissions($r[0]);
4419 if(in_array($cid, $recips)) {
4424 if(compare_permissions($item,$r[0]))
4429 $data = $r[0]['data'];
4430 $type = $r[0]['type'];
4432 // If a custom width and height were specified, apply before embedding
4433 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4434 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4436 $width = intval($match[1]);
4437 $height = intval($match[2]);
4439 $ph = new Photo($data, $type);
4440 if($ph->is_valid()) {
4441 $ph->scaleImage(max($width, $height));
4442 $data = $ph->imageString();
4443 $type = $ph->getType();
4447 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4448 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4449 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4455 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4456 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4457 if($orig_body === false)
4460 $img_start = strpos($orig_body, '[img');
4461 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4462 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4465 $new_body = $new_body . $orig_body;
4471 function has_permissions($obj) {
4472 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4477 function compare_permissions($obj1,$obj2) {
4478 // first part is easy. Check that these are exactly the same.
4479 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4480 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4481 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4482 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4485 // This is harder. Parse all the permissions and compare the resulting set.
4487 $recipients1 = enumerate_permissions($obj1);
4488 $recipients2 = enumerate_permissions($obj2);
4491 if($recipients1 == $recipients2)
4496 // returns an array of contact-ids that are allowed to see this object
4498 function enumerate_permissions($obj) {
4499 require_once('include/group.php');
4500 $allow_people = expand_acl($obj['allow_cid']);
4501 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4502 $deny_people = expand_acl($obj['deny_cid']);
4503 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4504 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4505 $deny = array_unique(array_merge($deny_people,$deny_groups));
4506 $recipients = array_diff($recipients,$deny);
4510 function item_getfeedtags($item) {
4513 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4515 for($x = 0; $x < $cnt; $x ++) {
4517 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4521 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4523 for($x = 0; $x < $cnt; $x ++) {
4525 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4531 function item_getfeedattach($item) {
4533 $arr = explode('[/attach],',$item['attach']);
4535 foreach($arr as $r) {
4537 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4539 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4540 if(intval($matches[2]))
4541 $ret .= 'length="' . intval($matches[2]) . '" ';
4542 if($matches[4] !== ' ')
4543 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4544 $ret .= ' />' . "\r\n";
4553 function item_expire($uid, $days, $network = "", $force = false) {
4555 if((! $uid) || ($days < 1))
4558 // $expire_network_only = save your own wall posts
4559 // and just expire conversations started by others
4561 $expire_network_only = get_pconfig($uid,'expire','network_only');
4562 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4564 if ($network != "") {
4565 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4566 // There is an index "uid_network_received" but not "uid_network_created"
4567 // This avoids the creation of another index just for one purpose.
4568 // And it doesn't really matter wether to look at "received" or "created"
4569 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4571 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4573 $r = q("SELECT * FROM `item`
4574 WHERE `uid` = %d $range
4585 $expire_items = get_pconfig($uid, 'expire','items');
4586 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4588 // Forcing expiring of items - but not notes and marked items
4590 $expire_items = true;
4592 $expire_notes = get_pconfig($uid, 'expire','notes');
4593 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4595 $expire_starred = get_pconfig($uid, 'expire','starred');
4596 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4598 $expire_photos = get_pconfig($uid, 'expire','photos');
4599 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4601 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4603 foreach($r as $item) {
4605 // don't expire filed items
4607 if(strpos($item['file'],'[') !== false)
4610 // Only expire posts, not photos and photo comments
4612 if($expire_photos==0 && strlen($item['resource-id']))
4614 if($expire_starred==0 && intval($item['starred']))
4616 if($expire_notes==0 && $item['type']=='note')
4618 if($expire_items==0 && $item['type']!='note')
4621 drop_item($item['id'],false);
4624 proc_run('php',"include/notifier.php","expire","$uid");
4629 function drop_items($items) {
4632 if(! local_user() && ! remote_user())
4636 foreach($items as $item) {
4637 $owner = drop_item($item,false);
4638 if($owner && ! $uid)
4643 // multiple threads may have been deleted, send an expire notification
4646 proc_run('php',"include/notifier.php","expire","$uid");
4650 function drop_item($id,$interactive = true) {
4654 // locate item to be deleted
4656 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4663 notice( t('Item not found.') . EOL);
4664 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4669 $owner = $item['uid'];
4673 // check if logged in user is either the author or owner of this item
4675 if(is_array($_SESSION['remote'])) {
4676 foreach($_SESSION['remote'] as $visitor) {
4677 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4678 $cid = $visitor['cid'];
4685 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4687 // Check if we should do HTML-based delete confirmation
4688 if($_REQUEST['confirm']) {
4689 // <form> can't take arguments in its "action" parameter
4690 // so add any arguments as hidden inputs
4691 $query = explode_querystring($a->query_string);
4693 foreach($query['args'] as $arg) {
4694 if(strpos($arg, 'confirm=') === false) {
4695 $arg_parts = explode('=', $arg);
4696 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4700 return replace_macros(get_markup_template('confirm.tpl'), array(
4702 '$message' => t('Do you really want to delete this item?'),
4703 '$extra_inputs' => $inputs,
4704 '$confirm' => t('Yes'),
4705 '$confirm_url' => $query['base'],
4706 '$confirm_name' => 'confirmed',
4707 '$cancel' => t('Cancel'),
4710 // Now check how the user responded to the confirmation query
4711 if($_REQUEST['canceled']) {
4712 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4715 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4718 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4719 dbesc(datetime_convert()),
4720 dbesc(datetime_convert()),
4723 create_tags_from_item($item['id']);
4724 create_files_from_item($item['id']);
4725 delete_thread($item['id'], $item['parent-uri']);
4727 // clean up categories and tags so they don't end up as orphans
4730 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4732 foreach($matches as $mtch) {
4733 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4739 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4741 foreach($matches as $mtch) {
4742 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4746 // If item is a link to a photo resource, nuke all the associated photos
4747 // (visitors will not have photo resources)
4748 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4749 // generate a resource-id and therefore aren't intimately linked to the item.
4751 if(strlen($item['resource-id'])) {
4752 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4753 dbesc($item['resource-id']),
4754 intval($item['uid'])
4756 // ignore the result
4759 // If item is a link to an event, nuke the event record.
4761 if(intval($item['event-id'])) {
4762 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4763 intval($item['event-id']),
4764 intval($item['uid'])
4766 // ignore the result
4769 // clean up item_id and sign meta-data tables
4772 // Old code - caused very long queries and warning entries in the mysql logfiles:
4774 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4775 intval($item['id']),
4776 intval($item['uid'])
4779 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4780 intval($item['id']),
4781 intval($item['uid'])
4785 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4787 // Creating list of parents
4788 $r = q("select id from item where parent = %d and uid = %d",
4789 intval($item['id']),
4790 intval($item['uid'])
4795 foreach ($r AS $row) {
4796 if ($parentid != "")
4799 $parentid .= $row["id"];
4803 if ($parentid != "") {
4804 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4806 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4809 // If it's the parent of a comment thread, kill all the kids
4811 if($item['uri'] == $item['parent-uri']) {
4812 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4813 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4814 dbesc(datetime_convert()),
4815 dbesc(datetime_convert()),
4816 dbesc($item['parent-uri']),
4817 intval($item['uid'])
4819 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4820 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4821 delete_thread_uri($item['parent-uri'], $item['uid']);
4822 // ignore the result
4825 // ensure that last-child is set in case the comment that had it just got wiped.
4826 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4827 dbesc(datetime_convert()),
4828 dbesc($item['parent-uri']),
4829 intval($item['uid'])
4831 // who is the last child now?
4832 $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",
4833 dbesc($item['parent-uri']),
4834 intval($item['uid'])
4837 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4842 // Add a relayable_retraction signature for Diaspora.
4843 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4845 $drop_id = intval($item['id']);
4847 // send the notification upstream/downstream as the case may be
4849 proc_run('php',"include/notifier.php","drop","$drop_id");
4853 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4859 notice( t('Permission denied.') . EOL);
4860 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4867 function first_post_date($uid,$wall = false) {
4868 $r = q("select id, created from item
4869 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4871 order by created asc limit 1",
4873 intval($wall ? 1 : 0)
4876 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4877 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4882 /* modified posted_dates() {below} to arrange the list in years */
4883 function list_post_dates($uid, $wall) {
4884 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4886 $dthen = first_post_date($uid, $wall);
4890 // Set the start and end date to the beginning of the month
4891 $dnow = substr($dnow,0,8).'01';
4892 $dthen = substr($dthen,0,8).'01';
4896 // Starting with the current month, get the first and last days of every
4897 // month down to and including the month of the first post
4898 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4899 $dyear = intval(substr($dnow,0,4));
4900 $dstart = substr($dnow,0,8) . '01';
4901 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4902 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4903 $end_month = datetime_convert('','',$dend,'Y-m-d');
4904 $str = day_translate(datetime_convert('','',$dnow,'F'));
4906 $ret[$dyear] = array();
4907 $ret[$dyear][] = array($str,$end_month,$start_month);
4908 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4913 function posted_dates($uid,$wall) {
4914 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4916 $dthen = first_post_date($uid,$wall);
4920 // Set the start and end date to the beginning of the month
4921 $dnow = substr($dnow,0,8).'01';
4922 $dthen = substr($dthen,0,8).'01';
4925 // Starting with the current month, get the first and last days of every
4926 // month down to and including the month of the first post
4927 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4928 $dstart = substr($dnow,0,8) . '01';
4929 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4930 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4931 $end_month = datetime_convert('','',$dend,'Y-m-d');
4932 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4933 $ret[] = array($str,$end_month,$start_month);
4934 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4940 function posted_date_widget($url,$uid,$wall) {
4943 if(! feature_enabled($uid,'archives'))
4946 // For former Facebook folks that left because of "timeline"
4948 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4951 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4952 if(! $visible_years)
4955 $ret = list_post_dates($uid,$wall);
4960 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4961 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4963 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4964 '$title' => t('Archives'),
4965 '$size' => $visible_years,
4966 '$cutoff_year' => $cutoff_year,
4967 '$cutoff' => $cutoff,
4970 '$showmore' => t('show more')
4976 function store_diaspora_retract_sig($item, $user, $baseurl) {
4977 // Note that we can't add a target_author_signature
4978 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4979 // the comment, that means we're the home of the post, and Diaspora will only
4980 // check the parent_author_signature of retractions that it doesn't have to relay further
4982 // I don't think this function gets called for an "unlike," but I'll check anyway
4984 $enabled = intval(get_config('system','diaspora_enabled'));
4986 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4990 logger('drop_item: storing diaspora retraction signature');
4992 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4994 if(local_user() == $item['uid']) {
4996 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4997 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5000 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5001 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5004 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5005 // only handles DFRN deletes
5006 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5007 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5008 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5014 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5015 intval($item['id']),
5016 dbesc($signed_text),