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');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $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` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 // removing the content of the title if its identically to the body
442 // This helps with auto generated titles e.g. from tumblr
443 if (title_is_body($res["title"], $res["body"]))
447 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
451 // look for a photo. We should check media size and find the best one,
452 // but for now let's just find any author photo
454 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
456 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
457 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
458 foreach($base as $link) {
459 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
460 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
461 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
466 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
468 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
469 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
470 if($base && count($base)) {
471 foreach($base as $link) {
472 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
473 $res['author-link'] = unxmlify($link['attribs']['']['href']);
474 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
475 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
476 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
482 // No photo/profile-link on the item - look at the feed level
484 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
485 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
486 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
487 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
488 foreach($base as $link) {
489 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
490 $res['author-link'] = unxmlify($link['attribs']['']['href']);
491 if(! $res['author-avatar']) {
492 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
493 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
498 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
500 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
501 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
503 if($base && count($base)) {
504 foreach($base as $link) {
505 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
506 $res['author-link'] = unxmlify($link['attribs']['']['href']);
507 if(! (x($res,'author-avatar'))) {
508 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
509 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
516 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
517 if($apps && $apps[0]['attribs']['']['source']) {
518 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
519 if($res['app'] === 'web')
520 $res['app'] = 'OStatus';
523 // base64 encoded json structure representing Diaspora signature
525 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
527 $res['dsprsig'] = unxmlify($dsig[0]['data']);
530 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
532 $res['guid'] = unxmlify($dguid[0]['data']);
534 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
536 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
540 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
543 $have_real_body = false;
545 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
547 $have_real_body = true;
548 $res['body'] = $rawenv[0]['data'];
549 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
550 // make sure nobody is trying to sneak some html tags by us
551 $res['body'] = notags(base64url_decode($res['body']));
555 $res['body'] = limit_body_size($res['body']);
557 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
558 // the content type. Our own network only emits text normally, though it might have been converted to
559 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
560 // have to assume it is all html and needs to be purified.
562 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
563 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
564 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
567 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
569 $res['body'] = reltoabs($res['body'],$base_url);
571 $res['body'] = html2bb_video($res['body']);
573 $res['body'] = oembed_html2bbcode($res['body']);
575 $config = HTMLPurifier_Config::createDefault();
576 $config->set('Cache.DefinitionImpl', null);
578 // we shouldn't need a whitelist, because the bbcode converter
579 // will strip out any unsupported tags.
581 $purifier = new HTMLPurifier($config);
582 $res['body'] = $purifier->purify($res['body']);
584 $res['body'] = @html2bbcode($res['body']);
588 elseif(! $have_real_body) {
590 // it's not one of our messages and it has no tags
591 // so it's probably just text. We'll escape it just to be safe.
593 $res['body'] = escape_tags($res['body']);
597 // this tag is obsolete but we keep it for really old sites
599 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
600 if($allow && $allow[0]['data'] == 1)
601 $res['last-child'] = 1;
603 $res['last-child'] = 0;
605 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
606 if($private && intval($private[0]['data']) > 0)
607 $res['private'] = intval($private[0]['data']);
611 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
612 if($extid && $extid[0]['data'])
613 $res['extid'] = $extid[0]['data'];
615 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
617 $res['location'] = unxmlify($rawlocation[0]['data']);
620 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
622 $res['created'] = unxmlify($rawcreated[0]['data']);
625 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
627 $res['edited'] = unxmlify($rawedited[0]['data']);
629 if((x($res,'edited')) && (! (x($res,'created'))))
630 $res['created'] = $res['edited'];
632 if(! $res['created'])
633 $res['created'] = $item->get_date('c');
636 $res['edited'] = $item->get_date('c');
639 // Disallow time travelling posts
641 $d1 = strtotime($res['created']);
642 $d2 = strtotime($res['edited']);
643 $d3 = strtotime('now');
646 $res['created'] = datetime_convert();
648 $res['edited'] = datetime_convert();
650 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
651 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
652 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
653 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
654 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
655 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
656 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
657 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
658 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
660 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
661 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
663 foreach($base as $link) {
664 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
665 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
666 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
671 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
673 $res['coord'] = unxmlify($rawgeo[0]['data']);
676 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
678 // select between supported verbs
681 $res['verb'] = unxmlify($rawverb[0]['data']);
684 // translate OStatus unfollow to activity streams if it happened to get selected
686 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
687 $res['verb'] = ACTIVITY_UNFOLLOW;
689 $cats = $item->get_categories();
692 foreach($cats as $cat) {
693 $term = $cat->get_term();
695 $term = $cat->get_label();
696 $scheme = $cat->get_scheme();
697 if($scheme && $term && stristr($scheme,'X-DFRN:'))
698 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
700 $tag_arr[] = notags(trim($term));
702 $res['tag'] = implode(',', $tag_arr);
705 $attach = $item->get_enclosures();
708 foreach($attach as $att) {
709 $len = intval($att->get_length());
710 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
711 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
712 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
713 if(strpos($type,';'))
714 $type = substr($type,0,strpos($type,';'));
715 if((! $link) || (strpos($link,'http') !== 0))
721 $type = 'application/octet-stream';
723 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
725 $res['attach'] = implode(',', $att_arr);
728 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
731 $res['object'] = '<object>' . "\n";
732 $child = $rawobj[0]['child'];
733 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
734 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
735 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
737 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
738 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
739 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
740 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
741 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
742 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
743 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
744 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
746 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
747 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
748 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
749 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
751 $body = html2bb_video($body);
753 $config = HTMLPurifier_Config::createDefault();
754 $config->set('Cache.DefinitionImpl', null);
756 $purifier = new HTMLPurifier($config);
757 $body = $purifier->purify($body);
758 $body = html2bbcode($body);
761 $res['object'] .= '<content>' . $body . '</content>' . "\n";
764 $res['object'] .= '</object>' . "\n";
767 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
770 $res['target'] = '<target>' . "\n";
771 $child = $rawobj[0]['child'];
772 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
773 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
775 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
776 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
777 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
778 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
779 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
780 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
781 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
782 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
784 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
785 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
786 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
787 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
789 $body = html2bb_video($body);
791 $config = HTMLPurifier_Config::createDefault();
792 $config->set('Cache.DefinitionImpl', null);
794 $purifier = new HTMLPurifier($config);
795 $body = $purifier->purify($body);
796 $body = html2bbcode($body);
799 $res['target'] .= '<content>' . $body . '</content>' . "\n";
802 $res['target'] .= '</target>' . "\n";
805 // This is some experimental stuff. By now retweets are shown with "RT:"
806 // But: There is data so that the message could be shown similar to native retweets
807 // There is some better way to parse this array - but it didn't worked for me.
808 $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"];
809 if (is_array($child)) {
810 logger('get_atom_elements: Looking for status.net repeated message');
812 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
813 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
814 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
815 $uri = $author["uri"][0]["data"];
816 $name = $author["name"][0]["data"];
817 $avatar = @array_shift($author["link"][2]["attribs"]);
818 $avatar = $avatar["href"];
820 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
821 logger('get_atom_elements: fixing sender of repeated message.');
823 if (!intval(get_config('system','wall-to-wall_share'))) {
824 $prefix = "[share author='".str_replace("'", "'",$name).
826 "' avatar='".$avatar.
827 "' link='".$orig_uri."']";
829 $res["body"] = $prefix.html2bbcode($message)."[/share]";
831 $res["owner-name"] = $res["author-name"];
832 $res["owner-link"] = $res["author-link"];
833 $res["owner-avatar"] = $res["author-avatar"];
835 $res["author-name"] = $name;
836 $res["author-link"] = $uri;
837 $res["author-avatar"] = $avatar;
839 $res["body"] = html2bbcode($message);
844 // Search for ostatus conversation url
845 $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"];
847 if (is_array($links)) {
848 foreach ($links as $link) {
849 $conversation = array_shift($link["attribs"]);
851 if ($conversation["rel"] == "ostatus:conversation") {
852 $res["ostatus_conversation"] = $conversation["href"];
853 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
858 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
859 $res["body"] = $res["title"]."\n\n[class=type-link]".fetch_siteinfo($res['plink'])."[/class]";
863 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
865 call_hooks('parse_atom', $arr);
867 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
868 //if (strpos($res["body"], "RT @") !== false) {
869 /*if (strpos($res["body"], "@") !== false) {
870 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
871 file_put_contents($debugfile, serialize($arr));
877 function fetch_siteinfo($url) {
878 require_once("mod/parse_url.php");
880 // Fetch site infos - but only from the meta data
881 $data = parseurl_getsiteinfo($url, true);
885 if (!is_string($data["text"]) AND (sizeof($data["images"]) == 0) AND ($data["title"] == $url))
888 if (is_string($data["title"]))
889 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]\n";
891 if (sizeof($data["images"]) > 0) {
892 $imagedata = $data["images"][0];
893 $text .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]' . "\n";
896 if (is_string($data["text"]))
897 $text .= "[quote]".$data["text"]."[/quote]";
902 function encode_rel_links($links) {
904 if(! ((is_array($links)) && (count($links))))
906 foreach($links as $link) {
908 if($link['attribs']['']['rel'])
909 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
910 if($link['attribs']['']['type'])
911 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
912 if($link['attribs']['']['href'])
913 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
914 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
915 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
916 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
917 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
925 function item_store($arr,$force_parent = false) {
927 // If a Diaspora signature structure was passed in, pull it out of the
928 // item array and set it aside for later storage.
931 if(x($arr,'dsprsig')) {
932 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
933 unset($arr['dsprsig']);
936 // if an OStatus conversation url was passed in, it is stored and then
937 // removed from the array.
938 $ostatus_conversation = null;
940 if (isset($arr["ostatus_conversation"])) {
941 $ostatus_conversation = $arr["ostatus_conversation"];
942 unset($arr["ostatus_conversation"]);
945 if(x($arr, 'gravity'))
946 $arr['gravity'] = intval($arr['gravity']);
947 elseif($arr['parent-uri'] === $arr['uri'])
949 elseif(activity_match($arr['verb'],ACTIVITY_POST))
952 $arr['gravity'] = 6; // extensible catchall
955 $arr['type'] = 'remote';
957 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
959 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
960 $arr['body'] = strip_tags($arr['body']);
963 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
964 require_once('library/langdet/Text/LanguageDetect.php');
965 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
966 $l = new Text_LanguageDetect;
967 //$lng = $l->detectConfidence($naked_body);
968 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
969 $lng = $l->detect($naked_body, 3);
971 if (sizeof($lng) > 0) {
974 foreach ($lng as $language => $score) {
980 $postopts .= $language.";".$score;
982 $arr['postopts'] = $postopts;
986 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
987 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
988 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
989 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
990 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
991 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
992 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
993 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
994 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
995 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
996 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
997 $arr['commented'] = datetime_convert();
998 $arr['received'] = datetime_convert();
999 $arr['changed'] = datetime_convert();
1000 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1001 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1002 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1003 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1004 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1005 $arr['deleted'] = 0;
1006 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1007 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1008 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1009 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1010 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1011 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1012 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1013 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1014 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1015 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1016 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1017 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1018 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1019 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1020 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1021 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1022 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1023 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1024 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1025 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1027 if ($arr['network'] == "") {
1028 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1029 intval($arr['contact-id']),
1034 $arr['network'] = $r[0]["network"];
1036 // Fallback to friendica (why is it empty in some cases?)
1037 if ($arr['network'] == "")
1038 $arr['network'] = NETWORK_DFRN;
1040 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1043 $arr['thr-parent'] = $arr['parent-uri'];
1044 if($arr['parent-uri'] === $arr['uri']) {
1046 $parent_deleted = 0;
1047 $allow_cid = $arr['allow_cid'];
1048 $allow_gid = $arr['allow_gid'];
1049 $deny_cid = $arr['deny_cid'];
1050 $deny_gid = $arr['deny_gid'];
1054 // find the parent and snarf the item id and ACLs
1055 // and anything else we need to inherit
1057 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1058 dbesc($arr['parent-uri']),
1064 // is the new message multi-level threaded?
1065 // even though we don't support it now, preserve the info
1066 // and re-attach to the conversation parent.
1068 if($r[0]['uri'] != $r[0]['parent-uri']) {
1069 $arr['parent-uri'] = $r[0]['parent-uri'];
1070 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1071 ORDER BY `id` ASC LIMIT 1",
1072 dbesc($r[0]['parent-uri']),
1073 dbesc($r[0]['parent-uri']),
1080 $parent_id = $r[0]['id'];
1081 $parent_deleted = $r[0]['deleted'];
1082 $allow_cid = $r[0]['allow_cid'];
1083 $allow_gid = $r[0]['allow_gid'];
1084 $deny_cid = $r[0]['deny_cid'];
1085 $deny_gid = $r[0]['deny_gid'];
1086 $arr['wall'] = $r[0]['wall'];
1088 // if the parent is private, force privacy for the entire conversation
1089 // This differs from the above settings as it subtly allows comments from
1090 // email correspondents to be private even if the overall thread is not.
1092 if($r[0]['private'])
1093 $arr['private'] = $r[0]['private'];
1095 // Edge case. We host a public forum that was originally posted to privately.
1096 // The original author commented, but as this is a comment, the permissions
1097 // weren't fixed up so it will still show the comment as private unless we fix it here.
1099 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1100 $arr['private'] = 0;
1103 // If its a post from myself then tag the thread as "mention"
1104 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1105 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1108 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1109 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1110 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1111 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1112 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1118 // Allow one to see reply tweets from status.net even when
1119 // we don't have or can't see the original post.
1122 logger('item_store: $force_parent=true, reply converted to top-level post.');
1124 $arr['parent-uri'] = $arr['uri'];
1125 $arr['gravity'] = 0;
1128 logger('item_store: item parent was not found - ignoring item');
1132 $parent_deleted = 0;
1136 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1140 if($r && count($r)) {
1141 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1145 call_hooks('post_remote',$arr);
1147 if(x($arr,'cancel')) {
1148 logger('item_store: post cancelled by plugin.');
1154 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1156 $r = dbq("INSERT INTO `item` (`"
1157 . implode("`, `", array_keys($arr))
1159 . implode("', '", array_values($arr))
1162 // find the item we just created
1164 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1165 $arr['uri'], // already dbesc'd
1170 $current_post = $r[0]['id'];
1171 logger('item_store: created item ' . $current_post);
1173 // Only check for notifications on start posts
1174 if ($arr['parent-uri'] === $arr['uri']) {
1175 add_thread($r[0]['id']);
1176 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1178 // Send a notification for every new post?
1179 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1180 intval($arr['contact-id']),
1185 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1186 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1187 intval($arr['uid']));
1189 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1190 intval($current_post),
1196 require_once('include/enotify.php');
1198 'type' => NOTIFY_SHARE,
1199 'notify_flags' => $u[0]['notify-flags'],
1200 'language' => $u[0]['language'],
1201 'to_name' => $u[0]['username'],
1202 'to_email' => $u[0]['email'],
1203 'uid' => $u[0]['uid'],
1205 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1206 'source_name' => $item[0]['author-name'],
1207 'source_link' => $item[0]['author-link'],
1208 'source_photo' => $item[0]['author-avatar'],
1209 'verb' => ACTIVITY_TAG,
1212 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1217 logger('item_store: could not locate created item');
1221 logger('item_store: duplicated post occurred. Removing duplicates.');
1222 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1224 intval($arr['uid']),
1225 intval($current_post)
1229 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1230 $parent_id = $current_post;
1232 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1235 $private = $arr['private'];
1237 // Set parent id - and also make sure to inherit the parent's ACLs.
1239 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1240 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1247 intval($parent_deleted),
1248 intval($current_post)
1251 // Complete ostatus threads
1252 if ($ostatus_conversation)
1253 complete_conversation($current_post, $ostatus_conversation);
1255 $arr['id'] = $current_post;
1256 $arr['parent'] = $parent_id;
1257 $arr['allow_cid'] = $allow_cid;
1258 $arr['allow_gid'] = $allow_gid;
1259 $arr['deny_cid'] = $deny_cid;
1260 $arr['deny_gid'] = $deny_gid;
1261 $arr['private'] = $private;
1262 $arr['deleted'] = $parent_deleted;
1264 // update the commented timestamp on the parent
1266 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1267 dbesc(datetime_convert()),
1268 dbesc(datetime_convert()),
1271 update_thread($parent_id);
1274 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1275 intval($current_post),
1276 dbesc($dsprsig->signed_text),
1277 dbesc($dsprsig->signature),
1278 dbesc($dsprsig->signer)
1284 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1287 if($arr['last-child']) {
1288 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1290 intval($arr['uid']),
1291 intval($current_post)
1295 $deleted = tag_deliver($arr['uid'],$current_post);
1297 // current post can be deleted if is for a communuty page and no mention are
1301 // Store the fresh generated item into the cache
1302 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1304 if (($cachefile != '') AND !file_exists($cachefile)) {
1305 $s = prepare_text($arr['body']);
1307 $stamp1 = microtime(true);
1308 file_put_contents($cachefile, $s);
1309 $a->save_timestamp($stamp1, "file");
1310 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1313 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1314 if (count($r) == 1) {
1315 call_hooks('post_remote_end', $r[0]);
1317 logger('item_store: new item not found in DB, id ' . $current_post);
1321 create_tags_from_item($current_post);
1322 create_files_from_item($current_post);
1324 return $current_post;
1327 function get_item_contact($item,$contacts) {
1328 if(! count($contacts) || (! is_array($item)))
1330 foreach($contacts as $contact) {
1331 if($contact['id'] == $item['contact-id']) {
1333 break; // NOTREACHED
1340 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1342 * @param int $item_id
1343 * @return bool true if item was deleted, else false
1345 function tag_deliver($uid,$item_id) {
1353 $u = q("select * from user where uid = %d limit 1",
1359 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1360 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1363 $i = q("select * from item where id = %d and uid = %d limit 1",
1372 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1374 // Diaspora uses their own hardwired link URL in @-tags
1375 // instead of the one we supply with webfinger
1377 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1379 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1381 foreach($matches as $mtch) {
1382 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1384 logger('tag_deliver: mention found: ' . $mtch[2]);
1390 if ( ($community_page || $prvgroup) &&
1391 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1392 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1394 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1395 q("DELETE FROM item WHERE id = %d and uid = %d",
1405 // send a notification
1407 // use a local photo if we have one
1409 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1410 intval($u[0]['uid']),
1411 dbesc(normalise_link($item['author-link']))
1413 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1416 require_once('include/enotify.php');
1418 'type' => NOTIFY_TAGSELF,
1419 'notify_flags' => $u[0]['notify-flags'],
1420 'language' => $u[0]['language'],
1421 'to_name' => $u[0]['username'],
1422 'to_email' => $u[0]['email'],
1423 'uid' => $u[0]['uid'],
1425 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1426 'source_name' => $item['author-name'],
1427 'source_link' => $item['author-link'],
1428 'source_photo' => $photo,
1429 'verb' => ACTIVITY_TAG,
1434 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1436 call_hooks('tagged', $arr);
1438 if((! $community_page) && (! $prvgroup))
1442 // tgroup delivery - setup a second delivery chain
1443 // prevent delivery looping - only proceed
1444 // if the message originated elsewhere and is a top-level post
1446 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1449 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1452 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1453 intval($u[0]['uid'])
1458 // also reset all the privacy bits to the forum default permissions
1460 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1462 $forum_mode = (($prvgroup) ? 2 : 1);
1464 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1465 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1466 intval($forum_mode),
1467 dbesc($c[0]['name']),
1468 dbesc($c[0]['url']),
1469 dbesc($c[0]['thumb']),
1471 dbesc($u[0]['allow_cid']),
1472 dbesc($u[0]['allow_gid']),
1473 dbesc($u[0]['deny_cid']),
1474 dbesc($u[0]['deny_gid']),
1477 update_thread($item_id);
1479 proc_run('php','include/notifier.php','tgroup',$item_id);
1485 function tgroup_check($uid,$item) {
1491 // check that the message originated elsewhere and is a top-level post
1493 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1497 $u = q("select * from user where uid = %d limit 1",
1503 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1504 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1507 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1509 // Diaspora uses their own hardwired link URL in @-tags
1510 // instead of the one we supply with webfinger
1512 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1514 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1516 foreach($matches as $mtch) {
1517 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1519 logger('tgroup_check: mention found: ' . $mtch[2]);
1527 if((! $community_page) && (! $prvgroup))
1541 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1545 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1547 if($contact['duplex'] && $contact['dfrn-id'])
1548 $idtosend = '0:' . $orig_id;
1549 if($contact['duplex'] && $contact['issued-id'])
1550 $idtosend = '1:' . $orig_id;
1552 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1554 $rino_enable = get_config('system','rino_encrypt');
1559 $ssl_val = intval(get_config('system','ssl_policy'));
1563 case SSL_POLICY_FULL:
1564 $ssl_policy = 'full';
1566 case SSL_POLICY_SELFSIGN:
1567 $ssl_policy = 'self';
1569 case SSL_POLICY_NONE:
1571 $ssl_policy = 'none';
1575 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1577 logger('dfrn_deliver: ' . $url);
1579 $xml = fetch_url($url);
1581 $curl_stat = $a->get_curl_code();
1583 return(-1); // timed out
1585 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1590 if(strpos($xml,'<?xml') === false) {
1591 logger('dfrn_deliver: no valid XML returned');
1592 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1596 $res = parse_xml_string($xml);
1598 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1599 return (($res->status) ? $res->status : 3);
1601 $postvars = array();
1602 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1603 $challenge = hex2bin((string) $res->challenge);
1604 $perm = (($res->perm) ? $res->perm : null);
1605 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1606 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1607 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1609 if($owner['page-flags'] == PAGE_PRVGROUP)
1612 $final_dfrn_id = '';
1615 if((($perm == 'rw') && (! intval($contact['writable'])))
1616 || (($perm == 'r') && (intval($contact['writable'])))) {
1617 q("update contact set writable = %d where id = %d",
1618 intval(($perm == 'rw') ? 1 : 0),
1619 intval($contact['id'])
1621 $contact['writable'] = (string) 1 - intval($contact['writable']);
1625 if(($contact['duplex'] && strlen($contact['pubkey']))
1626 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1627 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1628 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1629 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1632 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1633 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1636 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1638 if(strpos($final_dfrn_id,':') == 1)
1639 $final_dfrn_id = substr($final_dfrn_id,2);
1641 if($final_dfrn_id != $orig_id) {
1642 logger('dfrn_deliver: wrong dfrn_id.');
1643 // did not decode properly - cannot trust this site
1647 $postvars['dfrn_id'] = $idtosend;
1648 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1650 $postvars['dissolve'] = '1';
1653 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1654 $postvars['data'] = $atom;
1655 $postvars['perm'] = 'rw';
1658 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1659 $postvars['perm'] = 'r';
1662 $postvars['ssl_policy'] = $ssl_policy;
1665 $postvars['page'] = $page;
1667 if($rino && $rino_allowed && (! $dissolve)) {
1668 $key = substr(random_string(),0,16);
1669 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1670 $postvars['data'] = $data;
1671 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1674 if($dfrn_version >= 2.1) {
1675 if(($contact['duplex'] && strlen($contact['pubkey']))
1676 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1677 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1679 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1682 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1686 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1687 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1690 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1694 logger('md5 rawkey ' . md5($postvars['key']));
1696 $postvars['key'] = bin2hex($postvars['key']);
1699 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1701 $xml = post_url($contact['notify'],$postvars);
1703 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1705 $curl_stat = $a->get_curl_code();
1706 if((! $curl_stat) || (! strlen($xml)))
1707 return(-1); // timed out
1709 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1712 if(strpos($xml,'<?xml') === false) {
1713 logger('dfrn_deliver: phase 2: no valid XML returned');
1714 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1718 if($contact['term-date'] != '0000-00-00 00:00:00') {
1719 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1720 require_once('include/Contact.php');
1721 unmark_for_death($contact);
1724 $res = parse_xml_string($xml);
1726 return $res->status;
1731 This function returns true if $update has an edited timestamp newer
1732 than $existing, i.e. $update contains new data which should override
1733 what's already there. If there is no timestamp yet, the update is
1734 assumed to be newer. If the update has no timestamp, the existing
1735 item is assumed to be up-to-date. If the timestamps are equal it
1736 assumes the update has been seen before and should be ignored.
1738 function edited_timestamp_is_newer($existing, $update) {
1739 if (!x($existing,'edited') || !$existing['edited']) {
1742 if (!x($update,'edited') || !$update['edited']) {
1745 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1746 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1747 return (strcmp($existing_edited, $update_edited) < 0);
1752 * consume_feed - process atom feed and update anything/everything we might need to update
1754 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1756 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1757 * It is this person's stuff that is going to be updated.
1758 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1759 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1760 * have a contact record.
1761 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1762 * might not) try and subscribe to it.
1763 * $datedir sorts in reverse order
1764 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1765 * imported prior to its children being seen in the stream unless we are certain
1766 * of how the feed is arranged/ordered.
1767 * With $pass = 1, we only pull parent items out of the stream.
1768 * With $pass = 2, we only pull children (comments/likes).
1770 * So running this twice, first with pass 1 and then with pass 2 will do the right
1771 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1772 * model where comments can have sub-threads. That would require some massive sorting
1773 * to get all the feed items into a mostly linear ordering, and might still require
1777 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1779 require_once('library/simplepie/simplepie.inc');
1781 if(! strlen($xml)) {
1782 logger('consume_feed: empty input');
1786 $feed = new SimplePie();
1787 $feed->set_raw_data($xml);
1789 $feed->enable_order_by_date(true);
1791 $feed->enable_order_by_date(false);
1795 logger('consume_feed: Error parsing XML: ' . $feed->error());
1797 $permalink = $feed->get_permalink();
1799 // Check at the feed level for updated contact name and/or photo
1803 $photo_timestamp = '';
1807 $hubs = $feed->get_links('hub');
1808 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1811 $hub = implode(',', $hubs);
1813 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1815 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1817 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1818 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1819 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1820 $new_name = $elems['name'][0]['data'];
1822 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1823 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1824 $photo_url = $elems['link'][0]['attribs']['']['href'];
1827 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1828 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1832 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1833 logger('consume_feed: Updating photo for ' . $contact['name']);
1834 require_once("include/Photo.php");
1835 $photo_failure = false;
1836 $have_photo = false;
1838 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1839 intval($contact['id']),
1840 intval($contact['uid'])
1843 $resource_id = $r[0]['resource-id'];
1847 $resource_id = photo_new_resource();
1850 $img_str = fetch_url($photo_url,true);
1851 // guess mimetype from headers or filename
1852 $type = guess_image_type($photo_url,true);
1855 $img = new Photo($img_str, $type);
1856 if($img->is_valid()) {
1858 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1859 dbesc($resource_id),
1860 intval($contact['id']),
1861 intval($contact['uid'])
1865 $img->scaleImageSquare(175);
1867 $hash = $resource_id;
1868 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1870 $img->scaleImage(80);
1871 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1873 $img->scaleImage(48);
1874 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1878 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1879 WHERE `uid` = %d AND `id` = %d",
1880 dbesc(datetime_convert()),
1881 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1882 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1883 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1884 intval($contact['uid']),
1885 intval($contact['id'])
1890 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1891 $r = q("select * from contact where uid = %d and id = %d limit 1",
1892 intval($contact['uid']),
1893 intval($contact['id'])
1896 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1897 dbesc(notags(trim($new_name))),
1898 dbesc(datetime_convert()),
1899 intval($contact['uid']),
1900 intval($contact['id'])
1903 // do our best to update the name on content items
1906 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1907 dbesc(notags(trim($new_name))),
1908 dbesc($r[0]['name']),
1909 dbesc($r[0]['url']),
1910 intval($contact['uid'])
1915 if(strlen($birthday)) {
1916 if(substr($birthday,0,4) != $contact['bdyear']) {
1917 logger('consume_feed: updating birthday: ' . $birthday);
1921 * Add new birthday event for this person
1923 * $bdtext is just a readable placeholder in case the event is shared
1924 * with others. We will replace it during presentation to our $importer
1925 * to contain a sparkle link and perhaps a photo.
1929 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1930 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1933 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1934 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1935 intval($contact['uid']),
1936 intval($contact['id']),
1937 dbesc(datetime_convert()),
1938 dbesc(datetime_convert()),
1939 dbesc(datetime_convert('UTC','UTC', $birthday)),
1940 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1949 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1950 dbesc(substr($birthday,0,4)),
1951 intval($contact['uid']),
1952 intval($contact['id'])
1955 // This function is called twice without reloading the contact
1956 // Make sure we only create one event. This is why &$contact
1957 // is a reference var in this function
1959 $contact['bdyear'] = substr($birthday,0,4);
1964 $community_page = 0;
1965 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1967 $community_page = intval($rawtags[0]['data']);
1969 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1970 q("update contact set forum = %d where id = %d",
1971 intval($community_page),
1972 intval($contact['id'])
1974 $contact['forum'] = (string) $community_page;
1978 // process any deleted entries
1980 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1981 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1982 foreach($del_entries as $dentry) {
1984 if(isset($dentry['attribs']['']['ref'])) {
1985 $uri = $dentry['attribs']['']['ref'];
1987 if(isset($dentry['attribs']['']['when'])) {
1988 $when = $dentry['attribs']['']['when'];
1989 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1992 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1994 if($deleted && is_array($contact)) {
1995 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1996 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1998 intval($importer['uid']),
1999 intval($contact['id'])
2004 if(! $item['deleted'])
2005 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2007 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2008 $xo = parse_xml_string($item['object'],false);
2009 $xt = parse_xml_string($item['target'],false);
2010 if($xt->type === ACTIVITY_OBJ_NOTE) {
2011 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2013 intval($importer['importer_uid'])
2017 // For tags, the owner cannot remove the tag on the author's copy of the post.
2019 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2020 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2021 $author_copy = (($item['origin']) ? true : false);
2023 if($owner_remove && $author_copy)
2025 if($author_remove || $owner_remove) {
2026 $tags = explode(',',$i[0]['tag']);
2029 foreach($tags as $tag)
2030 if(trim($tag) !== trim($xo->body))
2031 $newtags[] = trim($tag);
2033 q("update item set tag = '%s' where id = %d",
2034 dbesc(implode(',',$newtags)),
2037 create_tags_from_item($i[0]['id']);
2043 if($item['uri'] == $item['parent-uri']) {
2044 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2045 `body` = '', `title` = ''
2046 WHERE `parent-uri` = '%s' AND `uid` = %d",
2048 dbesc(datetime_convert()),
2049 dbesc($item['uri']),
2050 intval($importer['uid'])
2052 create_tags_from_itemuri($item['uri'], $importer['uid']);
2053 create_files_from_itemuri($item['uri'], $importer['uid']);
2054 update_thread_uri($item['uri'], $importer['uid']);
2057 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2058 `body` = '', `title` = ''
2059 WHERE `uri` = '%s' AND `uid` = %d",
2061 dbesc(datetime_convert()),
2063 intval($importer['uid'])
2065 create_tags_from_itemuri($uri, $importer['uid']);
2066 create_files_from_itemuri($uri, $importer['uid']);
2067 if($item['last-child']) {
2068 // ensure that last-child is set in case the comment that had it just got wiped.
2069 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2070 dbesc(datetime_convert()),
2071 dbesc($item['parent-uri']),
2072 intval($item['uid'])
2074 // who is the last child now?
2075 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2076 ORDER BY `created` DESC LIMIT 1",
2077 dbesc($item['parent-uri']),
2078 intval($importer['uid'])
2081 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2092 // Now process the feed
2094 if($feed->get_item_quantity()) {
2096 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2098 // in inverse date order
2100 $items = array_reverse($feed->get_items());
2102 $items = $feed->get_items();
2105 foreach($items as $item) {
2108 $item_id = $item->get_id();
2109 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2110 if(isset($rawthread[0]['attribs']['']['ref'])) {
2112 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2115 if(($is_reply) && is_array($contact)) {
2120 // not allowed to post
2122 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2126 // Have we seen it? If not, import it.
2128 $item_id = $item->get_id();
2129 $datarray = get_atom_elements($feed, $item, $contact);
2131 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2132 $datarray['author-name'] = $contact['name'];
2133 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2134 $datarray['author-link'] = $contact['url'];
2135 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2136 $datarray['author-avatar'] = $contact['thumb'];
2138 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2139 logger('consume_feed: no author information! ' . print_r($datarray,true));
2143 $force_parent = false;
2144 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2145 if($contact['network'] === NETWORK_OSTATUS)
2146 $force_parent = true;
2147 if(strlen($datarray['title']))
2148 unset($datarray['title']);
2149 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2150 dbesc(datetime_convert()),
2152 intval($importer['uid'])
2154 $datarray['last-child'] = 1;
2155 update_thread_uri($parent_uri, $importer['uid']);
2159 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2161 intval($importer['uid'])
2164 // Update content if 'updated' changes
2167 if (edited_timestamp_is_newer($r[0], $datarray)) {
2169 // do not accept (ignore) an earlier edit than one we currently have.
2170 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2173 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2174 dbesc($datarray['title']),
2175 dbesc($datarray['body']),
2176 dbesc($datarray['tag']),
2177 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2178 dbesc(datetime_convert()),
2180 intval($importer['uid'])
2182 create_tags_from_itemuri($item_id, $importer['uid']);
2183 update_thread_uri($item_id, $importer['uid']);
2186 // update last-child if it changes
2188 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2189 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2190 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2191 dbesc(datetime_convert()),
2193 intval($importer['uid'])
2195 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2196 intval($allow[0]['data']),
2197 dbesc(datetime_convert()),
2199 intval($importer['uid'])
2201 update_thread_uri($item_id, $importer['uid']);
2207 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2208 // one way feed - no remote comment ability
2209 $datarray['last-child'] = 0;
2211 $datarray['parent-uri'] = $parent_uri;
2212 $datarray['uid'] = $importer['uid'];
2213 $datarray['contact-id'] = $contact['id'];
2214 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2215 $datarray['type'] = 'activity';
2216 $datarray['gravity'] = GRAVITY_LIKE;
2217 // only one like or dislike per person
2218 // splitted into two queries for performance issues
2219 $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",
2220 intval($datarray['uid']),
2221 intval($datarray['contact-id']),
2222 dbesc($datarray['verb']),
2228 $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",
2229 intval($datarray['uid']),
2230 intval($datarray['contact-id']),
2231 dbesc($datarray['verb']),
2238 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2239 $xo = parse_xml_string($datarray['object'],false);
2240 $xt = parse_xml_string($datarray['target'],false);
2242 if($xt->type == ACTIVITY_OBJ_NOTE) {
2243 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2245 intval($importer['importer_uid'])
2250 // extract tag, if not duplicate, add to parent item
2251 if($xo->id && $xo->content) {
2252 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2253 if(! (stristr($r[0]['tag'],$newtag))) {
2254 q("UPDATE item SET tag = '%s' WHERE id = %d",
2255 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2258 create_tags_from_item($r[0]['id']);
2264 $r = item_store($datarray,$force_parent);
2270 // Head post of a conversation. Have we seen it? If not, import it.
2272 $item_id = $item->get_id();
2274 $datarray = get_atom_elements($feed, $item, $contact);
2276 if(is_array($contact)) {
2277 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2278 $datarray['author-name'] = $contact['name'];
2279 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2280 $datarray['author-link'] = $contact['url'];
2281 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2282 $datarray['author-avatar'] = $contact['thumb'];
2285 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2286 logger('consume_feed: no author information! ' . print_r($datarray,true));
2290 // special handling for events
2292 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2293 $ev = bbtoevent($datarray['body']);
2294 if(x($ev,'desc') && x($ev,'start')) {
2295 $ev['uid'] = $importer['uid'];
2296 $ev['uri'] = $item_id;
2297 $ev['edited'] = $datarray['edited'];
2298 $ev['private'] = $datarray['private'];
2300 if(is_array($contact))
2301 $ev['cid'] = $contact['id'];
2302 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2304 intval($importer['uid'])
2307 $ev['id'] = $r[0]['id'];
2308 $xyz = event_store($ev);
2313 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2314 if(strlen($datarray['title']))
2315 unset($datarray['title']);
2316 $datarray['last-child'] = 1;
2320 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2322 intval($importer['uid'])
2325 // Update content if 'updated' changes
2328 if (edited_timestamp_is_newer($r[0], $datarray)) {
2330 // do not accept (ignore) an earlier edit than one we currently have.
2331 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2334 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2335 dbesc($datarray['title']),
2336 dbesc($datarray['body']),
2337 dbesc($datarray['tag']),
2338 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2339 dbesc(datetime_convert()),
2341 intval($importer['uid'])
2343 create_tags_from_itemuri($item_id, $importer['uid']);
2344 update_thread_uri($item_id, $importer['uid']);
2347 // update last-child if it changes
2349 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2350 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2351 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2352 intval($allow[0]['data']),
2353 dbesc(datetime_convert()),
2355 intval($importer['uid'])
2357 update_thread_uri($item_id, $importer['uid']);
2362 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2363 logger('consume-feed: New follower');
2364 new_follower($importer,$contact,$datarray,$item);
2367 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2368 lose_follower($importer,$contact,$datarray,$item);
2372 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2373 logger('consume-feed: New friend request');
2374 new_follower($importer,$contact,$datarray,$item,true);
2377 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2378 lose_sharer($importer,$contact,$datarray,$item);
2383 if(! is_array($contact))
2387 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2388 // one way feed - no remote comment ability
2389 $datarray['last-child'] = 0;
2391 if($contact['network'] === NETWORK_FEED)
2392 $datarray['private'] = 2;
2394 // This is my contact on another system, but it's really me.
2395 // Turn this into a wall post.
2397 if($contact['remote_self']) {
2398 $datarray['wall'] = 1;
2399 if($contact['network'] === NETWORK_FEED) {
2400 $datarray['private'] = 0;
2404 $datarray['parent-uri'] = $item_id;
2405 $datarray['uid'] = $importer['uid'];
2406 $datarray['contact-id'] = $contact['id'];
2408 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2409 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2410 // but otherwise there's a possible data mixup on the sender's system.
2411 // the tgroup delivery code called from item_store will correct it if it's a forum,
2412 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2413 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2414 $datarray['owner-name'] = $contact['name'];
2415 $datarray['owner-link'] = $contact['url'];
2416 $datarray['owner-avatar'] = $contact['thumb'];
2419 // We've allowed "followers" to reach this point so we can decide if they are
2420 // posting an @-tag delivery, which followers are allowed to do for certain
2421 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2423 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2427 $r = item_store($datarray);
2435 function local_delivery($importer,$data) {
2438 logger(__function__, LOGGER_TRACE);
2440 if($importer['readonly']) {
2441 // We aren't receiving stuff from this person. But we will quietly ignore them
2442 // rather than a blatant "go away" message.
2443 logger('local_delivery: ignoring');
2448 // Consume notification feed. This may differ from consuming a public feed in several ways
2449 // - might contain email or friend suggestions
2450 // - might contain remote followup to our message
2451 // - in which case we need to accept it and then notify other conversants
2452 // - we may need to send various email notifications
2454 $feed = new SimplePie();
2455 $feed->set_raw_data($data);
2456 $feed->enable_order_by_date(false);
2461 logger('local_delivery: Error parsing XML: ' . $feed->error());
2464 // Check at the feed level for updated contact name and/or photo
2468 $photo_timestamp = '';
2472 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2474 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2476 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2479 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2480 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2481 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2482 $new_name = $elems['name'][0]['data'];
2484 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2485 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2486 $photo_url = $elems['link'][0]['attribs']['']['href'];
2490 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2491 logger('local_delivery: Updating photo for ' . $importer['name']);
2492 require_once("include/Photo.php");
2493 $photo_failure = false;
2494 $have_photo = false;
2496 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2497 intval($importer['id']),
2498 intval($importer['importer_uid'])
2501 $resource_id = $r[0]['resource-id'];
2505 $resource_id = photo_new_resource();
2508 $img_str = fetch_url($photo_url,true);
2509 // guess mimetype from headers or filename
2510 $type = guess_image_type($photo_url,true);
2513 $img = new Photo($img_str, $type);
2514 if($img->is_valid()) {
2516 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2517 dbesc($resource_id),
2518 intval($importer['id']),
2519 intval($importer['importer_uid'])
2523 $img->scaleImageSquare(175);
2525 $hash = $resource_id;
2526 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2528 $img->scaleImage(80);
2529 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2531 $img->scaleImage(48);
2532 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2536 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2537 WHERE `uid` = %d AND `id` = %d",
2538 dbesc(datetime_convert()),
2539 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2540 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2541 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2542 intval($importer['importer_uid']),
2543 intval($importer['id'])
2548 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2549 $r = q("select * from contact where uid = %d and id = %d limit 1",
2550 intval($importer['importer_uid']),
2551 intval($importer['id'])
2554 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2555 dbesc(notags(trim($new_name))),
2556 dbesc(datetime_convert()),
2557 intval($importer['importer_uid']),
2558 intval($importer['id'])
2561 // do our best to update the name on content items
2564 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2565 dbesc(notags(trim($new_name))),
2566 dbesc($r[0]['name']),
2567 dbesc($r[0]['url']),
2568 intval($importer['importer_uid'])
2575 // Currently unsupported - needs a lot of work
2576 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2577 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2578 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2580 $newloc['uid'] = $importer['importer_uid'];
2581 $newloc['cid'] = $importer['id'];
2582 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2583 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2584 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2585 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2586 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2587 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2588 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2589 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2590 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2591 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2592 /** relocated user must have original key pair */
2593 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2594 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2596 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2599 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2600 intval($importer['id']),
2601 intval($importer['importer_uid']));
2606 $x = q("UPDATE contact SET
2616 `site-pubkey` = '%s'
2617 WHERE id=%d AND uid=%d;",
2618 dbesc($newloc['name']),
2619 dbesc($newloc['photo']),
2620 dbesc($newloc['thumb']),
2621 dbesc($newloc['micro']),
2622 dbesc($newloc['url']),
2623 dbesc($newloc['request']),
2624 dbesc($newloc['confirm']),
2625 dbesc($newloc['notify']),
2626 dbesc($newloc['poll']),
2627 dbesc($newloc['sitepubkey']),
2628 intval($importer['id']),
2629 intval($importer['importer_uid']));
2635 'owner-link' => array($old['url'], $newloc['url']),
2636 'author-link' => array($old['url'], $newloc['url']),
2637 'owner-avatar' => array($old['photo'], $newloc['photo']),
2638 'author-avatar' => array($old['photo'], $newloc['photo']),
2640 foreach ($fields as $n=>$f){
2641 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2644 intval($importer['importer_uid']));
2650 // merge with current record, current contents have priority
2651 // update record, set url-updated
2652 // update profile photos
2658 // handle friend suggestion notification
2660 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2661 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2662 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2664 $fsugg['uid'] = $importer['importer_uid'];
2665 $fsugg['cid'] = $importer['id'];
2666 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2667 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2668 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2669 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2670 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2672 // Does our member already have a friend matching this description?
2674 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2675 dbesc($fsugg['name']),
2676 dbesc(normalise_link($fsugg['url'])),
2677 intval($fsugg['uid'])
2682 // Do we already have an fcontact record for this person?
2685 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2686 dbesc($fsugg['url']),
2687 dbesc($fsugg['name']),
2688 dbesc($fsugg['request'])
2693 // OK, we do. Do we already have an introduction for this person ?
2694 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2695 intval($fsugg['uid']),
2702 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2703 dbesc($fsugg['name']),
2704 dbesc($fsugg['url']),
2705 dbesc($fsugg['photo']),
2706 dbesc($fsugg['request'])
2708 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2709 dbesc($fsugg['url']),
2710 dbesc($fsugg['name']),
2711 dbesc($fsugg['request'])
2716 // database record did not get created. Quietly give up.
2721 $hash = random_string();
2723 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2724 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2725 intval($fsugg['uid']),
2727 intval($fsugg['cid']),
2728 dbesc($fsugg['body']),
2730 dbesc(datetime_convert()),
2735 'type' => NOTIFY_SUGGEST,
2736 'notify_flags' => $importer['notify-flags'],
2737 'language' => $importer['language'],
2738 'to_name' => $importer['username'],
2739 'to_email' => $importer['email'],
2740 'uid' => $importer['importer_uid'],
2742 'link' => $a->get_baseurl() . '/notifications/intros',
2743 'source_name' => $importer['name'],
2744 'source_link' => $importer['url'],
2745 'source_photo' => $importer['photo'],
2746 'verb' => ACTIVITY_REQ_FRIEND,
2755 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2756 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2758 logger('local_delivery: private message received');
2761 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2764 $msg['uid'] = $importer['importer_uid'];
2765 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2766 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2767 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2768 $msg['contact-id'] = $importer['id'];
2769 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2770 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2772 $msg['replied'] = 0;
2773 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2774 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2775 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2779 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2780 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2782 // send notifications.
2784 require_once('include/enotify.php');
2786 $notif_params = array(
2787 'type' => NOTIFY_MAIL,
2788 'notify_flags' => $importer['notify-flags'],
2789 'language' => $importer['language'],
2790 'to_name' => $importer['username'],
2791 'to_email' => $importer['email'],
2792 'uid' => $importer['importer_uid'],
2794 'source_name' => $msg['from-name'],
2795 'source_link' => $importer['url'],
2796 'source_photo' => $importer['thumb'],
2797 'verb' => ACTIVITY_POST,
2801 notification($notif_params);
2807 $community_page = 0;
2808 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2810 $community_page = intval($rawtags[0]['data']);
2812 if(intval($importer['forum']) != $community_page) {
2813 q("update contact set forum = %d where id = %d",
2814 intval($community_page),
2815 intval($importer['id'])
2817 $importer['forum'] = (string) $community_page;
2820 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2822 // process any deleted entries
2824 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2825 if(is_array($del_entries) && count($del_entries)) {
2826 foreach($del_entries as $dentry) {
2828 if(isset($dentry['attribs']['']['ref'])) {
2829 $uri = $dentry['attribs']['']['ref'];
2831 if(isset($dentry['attribs']['']['when'])) {
2832 $when = $dentry['attribs']['']['when'];
2833 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2836 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2840 // check for relayed deletes to our conversation
2843 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2845 intval($importer['importer_uid'])
2848 $parent_uri = $r[0]['parent-uri'];
2849 if($r[0]['id'] != $r[0]['parent'])
2856 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2859 logger('local_delivery: possible community delete');
2862 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2864 // was the top-level post for this reply written by somebody on this site?
2865 // Specifically, the recipient?
2867 $is_a_remote_delete = false;
2869 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2870 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2871 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2872 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2873 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2874 AND `item`.`uid` = %d
2880 intval($importer['importer_uid'])
2883 $is_a_remote_delete = true;
2885 // Does this have the characteristics of a community or private group comment?
2886 // If it's a reply to a wall post on a community/prvgroup page it's a
2887 // valid community comment. Also forum_mode makes it valid for sure.
2888 // If neither, it's not.
2890 if($is_a_remote_delete && $community) {
2891 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2892 $is_a_remote_delete = false;
2893 logger('local_delivery: not a community delete');
2897 if($is_a_remote_delete) {
2898 logger('local_delivery: received remote delete');
2902 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2903 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2905 intval($importer['importer_uid']),
2906 intval($importer['id'])
2912 if($item['deleted'])
2915 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2917 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2918 $xo = parse_xml_string($item['object'],false);
2919 $xt = parse_xml_string($item['target'],false);
2921 if($xt->type === ACTIVITY_OBJ_NOTE) {
2922 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2924 intval($importer['importer_uid'])
2928 // For tags, the owner cannot remove the tag on the author's copy of the post.
2930 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2931 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2932 $author_copy = (($item['origin']) ? true : false);
2934 if($owner_remove && $author_copy)
2936 if($author_remove || $owner_remove) {
2937 $tags = explode(',',$i[0]['tag']);
2940 foreach($tags as $tag)
2941 if(trim($tag) !== trim($xo->body))
2942 $newtags[] = trim($tag);
2944 q("update item set tag = '%s' where id = %d",
2945 dbesc(implode(',',$newtags)),
2948 create_tags_from_item($i[0]['id']);
2954 if($item['uri'] == $item['parent-uri']) {
2955 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2956 `body` = '', `title` = ''
2957 WHERE `parent-uri` = '%s' AND `uid` = %d",
2959 dbesc(datetime_convert()),
2960 dbesc($item['uri']),
2961 intval($importer['importer_uid'])
2963 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2964 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2965 update_thread_uri($item['uri'], $importer['importer_uid']);
2968 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2969 `body` = '', `title` = ''
2970 WHERE `uri` = '%s' AND `uid` = %d",
2972 dbesc(datetime_convert()),
2974 intval($importer['importer_uid'])
2976 create_tags_from_itemuri($uri, $importer['importer_uid']);
2977 create_files_from_itemuri($uri, $importer['importer_uid']);
2978 update_thread_uri($uri, $importer['importer_uid']);
2979 if($item['last-child']) {
2980 // ensure that last-child is set in case the comment that had it just got wiped.
2981 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2982 dbesc(datetime_convert()),
2983 dbesc($item['parent-uri']),
2984 intval($item['uid'])
2986 // who is the last child now?
2987 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2988 ORDER BY `created` DESC LIMIT 1",
2989 dbesc($item['parent-uri']),
2990 intval($importer['importer_uid'])
2993 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2998 // if this is a relayed delete, propagate it to other recipients
3000 if($is_a_remote_delete)
3001 proc_run('php',"include/notifier.php","drop",$item['id']);
3009 foreach($feed->get_items() as $item) {
3012 $item_id = $item->get_id();
3013 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3014 if(isset($rawthread[0]['attribs']['']['ref'])) {
3016 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3022 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3025 logger('local_delivery: possible community reply');
3028 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3030 // was the top-level post for this reply written by somebody on this site?
3031 // Specifically, the recipient?
3033 $is_a_remote_comment = false;
3034 $top_uri = $parent_uri;
3036 $r = q("select `item`.`parent-uri` from `item`
3037 WHERE `item`.`uri` = '%s'
3041 if($r && count($r)) {
3042 $top_uri = $r[0]['parent-uri'];
3044 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3045 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3046 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3047 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3048 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3049 AND `item`.`uid` = %d
3055 intval($importer['importer_uid'])
3058 $is_a_remote_comment = true;
3061 // Does this have the characteristics of a community or private group comment?
3062 // If it's a reply to a wall post on a community/prvgroup page it's a
3063 // valid community comment. Also forum_mode makes it valid for sure.
3064 // If neither, it's not.
3066 if($is_a_remote_comment && $community) {
3067 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3068 $is_a_remote_comment = false;
3069 logger('local_delivery: not a community reply');
3073 if($is_a_remote_comment) {
3074 logger('local_delivery: received remote comment');
3076 // remote reply to our post. Import and then notify everybody else.
3078 $datarray = get_atom_elements($feed, $item);
3080 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3082 intval($importer['importer_uid'])
3085 // Update content if 'updated' changes
3089 if (edited_timestamp_is_newer($r[0], $datarray)) {
3091 // do not accept (ignore) an earlier edit than one we currently have.
3092 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3095 logger('received updated comment' , LOGGER_DEBUG);
3096 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3097 dbesc($datarray['title']),
3098 dbesc($datarray['body']),
3099 dbesc($datarray['tag']),
3100 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3101 dbesc(datetime_convert()),
3103 intval($importer['importer_uid'])
3105 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3107 proc_run('php',"include/notifier.php","comment-import",$iid);
3116 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3117 intval($importer['importer_uid'])
3121 $datarray['type'] = 'remote-comment';
3122 $datarray['wall'] = 1;
3123 $datarray['parent-uri'] = $parent_uri;
3124 $datarray['uid'] = $importer['importer_uid'];
3125 $datarray['owner-name'] = $own[0]['name'];
3126 $datarray['owner-link'] = $own[0]['url'];
3127 $datarray['owner-avatar'] = $own[0]['thumb'];
3128 $datarray['contact-id'] = $importer['id'];
3130 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3132 $datarray['type'] = 'activity';
3133 $datarray['gravity'] = GRAVITY_LIKE;
3134 $datarray['last-child'] = 0;
3135 // only one like or dislike per person
3136 // splitted into two queries for performance issues
3137 $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",
3138 intval($datarray['uid']),
3139 intval($datarray['contact-id']),
3140 dbesc($datarray['verb']),
3141 dbesc($datarray['parent-uri'])
3147 $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",
3148 intval($datarray['uid']),
3149 intval($datarray['contact-id']),
3150 dbesc($datarray['verb']),
3151 dbesc($datarray['parent-uri'])
3158 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3160 $xo = parse_xml_string($datarray['object'],false);
3161 $xt = parse_xml_string($datarray['target'],false);
3163 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3165 // fetch the parent item
3167 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3169 intval($importer['importer_uid'])
3174 // extract tag, if not duplicate, and this user allows tags, add to parent item
3176 if($xo->id && $xo->content) {
3177 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3178 if(! (stristr($tagp[0]['tag'],$newtag))) {
3179 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3180 intval($importer['importer_uid'])
3182 if(count($i) && ! intval($i[0]['blocktags'])) {
3183 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3184 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3185 intval($tagp[0]['id']),
3186 dbesc(datetime_convert()),
3187 dbesc(datetime_convert())
3189 create_tags_from_item($tagp[0]['id']);
3197 $posted_id = item_store($datarray);
3201 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3203 intval($importer['importer_uid'])
3206 $parent = $r[0]['parent'];
3207 $parent_uri = $r[0]['parent-uri'];
3211 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3212 dbesc(datetime_convert()),
3213 intval($importer['importer_uid']),
3214 intval($r[0]['parent'])
3217 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3218 dbesc(datetime_convert()),
3219 intval($importer['importer_uid']),
3224 if($posted_id && $parent) {
3226 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3228 if((! $is_like) && (! $importer['self'])) {
3230 require_once('include/enotify.php');
3233 'type' => NOTIFY_COMMENT,
3234 'notify_flags' => $importer['notify-flags'],
3235 'language' => $importer['language'],
3236 'to_name' => $importer['username'],
3237 'to_email' => $importer['email'],
3238 'uid' => $importer['importer_uid'],
3239 'item' => $datarray,
3240 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3241 'source_name' => stripslashes($datarray['author-name']),
3242 'source_link' => $datarray['author-link'],
3243 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3244 ? $importer['thumb'] : $datarray['author-avatar']),
3245 'verb' => ACTIVITY_POST,
3247 'parent' => $parent,
3248 'parent_uri' => $parent_uri,
3260 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3262 $item_id = $item->get_id();
3263 $datarray = get_atom_elements($feed,$item);
3265 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3268 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3270 intval($importer['importer_uid'])
3273 // Update content if 'updated' changes
3276 if (edited_timestamp_is_newer($r[0], $datarray)) {
3278 // do not accept (ignore) an earlier edit than one we currently have.
3279 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3282 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3283 dbesc($datarray['title']),
3284 dbesc($datarray['body']),
3285 dbesc($datarray['tag']),
3286 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3287 dbesc(datetime_convert()),
3289 intval($importer['importer_uid'])
3291 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3294 // update last-child if it changes
3296 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3297 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3298 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3299 dbesc(datetime_convert()),
3301 intval($importer['importer_uid'])
3303 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3304 intval($allow[0]['data']),
3305 dbesc(datetime_convert()),
3307 intval($importer['importer_uid'])
3313 $datarray['parent-uri'] = $parent_uri;
3314 $datarray['uid'] = $importer['importer_uid'];
3315 $datarray['contact-id'] = $importer['id'];
3316 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3317 $datarray['type'] = 'activity';
3318 $datarray['gravity'] = GRAVITY_LIKE;
3319 // only one like or dislike per person
3320 // splitted into two queries for performance issues
3321 $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",
3322 intval($datarray['uid']),
3323 intval($datarray['contact-id']),
3324 dbesc($datarray['verb']),
3330 $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",
3331 intval($datarray['uid']),
3332 intval($datarray['contact-id']),
3333 dbesc($datarray['verb']),
3341 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3343 $xo = parse_xml_string($datarray['object'],false);
3344 $xt = parse_xml_string($datarray['target'],false);
3346 if($xt->type == ACTIVITY_OBJ_NOTE) {
3347 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3349 intval($importer['importer_uid'])
3354 // extract tag, if not duplicate, add to parent item
3356 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3357 q("UPDATE item SET tag = '%s' WHERE id = %d",
3358 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3361 create_tags_from_item($r[0]['id']);
3367 $posted_id = item_store($datarray);
3369 // find out if our user is involved in this conversation and wants to be notified.
3371 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3373 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3375 intval($importer['importer_uid'])
3378 if(count($myconv)) {
3379 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3381 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3382 if(! link_compare($datarray['author-link'],$importer_url)) {
3385 foreach($myconv as $conv) {
3387 // now if we find a match, it means we're in this conversation
3389 if(! link_compare($conv['author-link'],$importer_url))
3392 require_once('include/enotify.php');
3394 $conv_parent = $conv['parent'];
3397 'type' => NOTIFY_COMMENT,
3398 'notify_flags' => $importer['notify-flags'],
3399 'language' => $importer['language'],
3400 'to_name' => $importer['username'],
3401 'to_email' => $importer['email'],
3402 'uid' => $importer['importer_uid'],
3403 'item' => $datarray,
3404 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3405 'source_name' => stripslashes($datarray['author-name']),
3406 'source_link' => $datarray['author-link'],
3407 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3408 ? $importer['thumb'] : $datarray['author-avatar']),
3409 'verb' => ACTIVITY_POST,
3411 'parent' => $conv_parent,
3412 'parent_uri' => $parent_uri
3416 // only send one notification
3428 // Head post of a conversation. Have we seen it? If not, import it.
3431 $item_id = $item->get_id();
3432 $datarray = get_atom_elements($feed,$item);
3434 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3435 $ev = bbtoevent($datarray['body']);
3436 if(x($ev,'desc') && x($ev,'start')) {
3437 $ev['cid'] = $importer['id'];
3438 $ev['uid'] = $importer['uid'];
3439 $ev['uri'] = $item_id;
3440 $ev['edited'] = $datarray['edited'];
3441 $ev['private'] = $datarray['private'];
3443 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3445 intval($importer['uid'])
3448 $ev['id'] = $r[0]['id'];
3449 $xyz = event_store($ev);
3454 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3456 intval($importer['importer_uid'])
3459 // Update content if 'updated' changes
3462 if (edited_timestamp_is_newer($r[0], $datarray)) {
3464 // do not accept (ignore) an earlier edit than one we currently have.
3465 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3468 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3469 dbesc($datarray['title']),
3470 dbesc($datarray['body']),
3471 dbesc($datarray['tag']),
3472 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3473 dbesc(datetime_convert()),
3475 intval($importer['importer_uid'])
3477 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3478 update_thread_uri($item_id, $importer['importer_uid']);
3481 // update last-child if it changes
3483 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3484 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3485 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3486 intval($allow[0]['data']),
3487 dbesc(datetime_convert()),
3489 intval($importer['importer_uid'])
3495 // This is my contact on another system, but it's really me.
3496 // Turn this into a wall post.
3498 if($importer['remote_self'])
3499 $datarray['wall'] = 1;
3501 $datarray['parent-uri'] = $item_id;
3502 $datarray['uid'] = $importer['importer_uid'];
3503 $datarray['contact-id'] = $importer['id'];
3506 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3507 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3508 // but otherwise there's a possible data mixup on the sender's system.
3509 // the tgroup delivery code called from item_store will correct it if it's a forum,
3510 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3511 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3512 $datarray['owner-name'] = $importer['senderName'];
3513 $datarray['owner-link'] = $importer['url'];
3514 $datarray['owner-avatar'] = $importer['thumb'];
3517 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3520 $posted_id = item_store($datarray);
3522 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3523 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3526 $xo = parse_xml_string($datarray['object'],false);
3528 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3530 // somebody was poked/prodded. Was it me?
3532 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3534 foreach($links->link as $l) {
3535 $atts = $l->attributes();
3536 switch($atts['rel']) {
3538 $Blink = $atts['href'];
3544 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3546 // send a notification
3547 require_once('include/enotify.php');
3550 'type' => NOTIFY_POKE,
3551 'notify_flags' => $importer['notify-flags'],
3552 'language' => $importer['language'],
3553 'to_name' => $importer['username'],
3554 'to_email' => $importer['email'],
3555 'uid' => $importer['importer_uid'],
3556 'item' => $datarray,
3557 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3558 'source_name' => stripslashes($datarray['author-name']),
3559 'source_link' => $datarray['author-link'],
3560 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3561 ? $importer['thumb'] : $datarray['author-avatar']),
3562 'verb' => $datarray['verb'],
3563 'otype' => 'person',
3564 'activity' => $verb,
3581 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3582 $url = notags(trim($datarray['author-link']));
3583 $name = notags(trim($datarray['author-name']));
3584 $photo = notags(trim($datarray['author-avatar']));
3586 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3587 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3588 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3590 if(is_array($contact)) {
3591 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3592 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3593 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3594 intval(CONTACT_IS_FRIEND),
3595 intval($contact['id']),
3596 intval($importer['uid'])
3599 // send email notification to owner?
3603 // create contact record
3605 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3606 `blocked`, `readonly`, `pending`, `writable` )
3607 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3608 intval($importer['uid']),
3609 dbesc(datetime_convert()),
3611 dbesc(normalise_link($url)),
3615 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3616 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3618 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3619 intval($importer['uid']),
3623 $contact_record = $r[0];
3625 // create notification
3626 $hash = random_string();
3628 if(is_array($contact_record)) {
3629 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3630 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3631 intval($importer['uid']),
3632 intval($contact_record['id']),
3634 dbesc(datetime_convert())
3637 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3638 intval($importer['uid'])
3643 if(intval($r[0]['def_gid'])) {
3644 require_once('include/group.php');
3645 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3648 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3649 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3650 $email = replace_macros($email_tpl, array(
3651 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3653 '$myname' => $r[0]['username'],
3654 '$siteurl' => $a->get_baseurl(),
3655 '$sitename' => $a->config['sitename']
3657 $res = mail($r[0]['email'],
3658 email_header_encode((($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'],'UTF-8'),
3660 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3661 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3662 . 'Content-transfer-encoding: 8bit' );
3669 function lose_follower($importer,$contact,$datarray,$item) {
3671 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3672 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3673 intval(CONTACT_IS_SHARING),
3674 intval($contact['id'])
3678 contact_remove($contact['id']);
3682 function lose_sharer($importer,$contact,$datarray,$item) {
3684 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3685 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3686 intval(CONTACT_IS_FOLLOWER),
3687 intval($contact['id'])
3691 contact_remove($contact['id']);
3696 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3700 if(is_array($importer)) {
3701 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3702 intval($importer['uid'])
3706 // Diaspora has different message-ids in feeds than they do
3707 // through the direct Diaspora protocol. If we try and use
3708 // the feed, we'll get duplicates. So don't.
3710 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3713 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3715 // Use a single verify token, even if multiple hubs
3717 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3719 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3721 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3723 if(! strlen($contact['hub-verify'])) {
3724 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3725 dbesc($verify_token),
3726 intval($contact['id'])
3730 post_url($url,$params);
3732 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3739 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3743 $name = xmlify($name);
3744 $uri = xmlify($uri);
3747 $photo = xmlify($photo);
3751 $o .= "<name>$name</name>\r\n";
3752 $o .= "<uri>$uri</uri>\r\n";
3753 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3754 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3756 call_hooks('atom_author', $o);
3758 $o .= "</$tag>\r\n";
3762 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3766 if(! $item['parent'])
3769 if($item['deleted'])
3770 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3773 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3774 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3776 $body = $item['body'];
3778 $o = "\r\n\r\n<entry>\r\n";
3780 if(is_array($author))
3781 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3783 $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']));
3784 if(strlen($item['owner-name']))
3785 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3787 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3788 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3789 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3792 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3793 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3794 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3795 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3796 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3797 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3798 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3800 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3802 if($item['location']) {
3803 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3804 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3808 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3810 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3811 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3814 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3815 if($item['bookmark'])
3816 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3819 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3822 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3824 if($item['signed_text']) {
3825 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3826 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3829 $verb = construct_verb($item);
3830 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3831 $actobj = construct_activity_object($item);
3834 $actarg = construct_activity_target($item);
3838 $tags = item_getfeedtags($item);
3840 foreach($tags as $t) {
3841 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3845 $o .= item_getfeedattach($item);
3847 $mentioned = get_mentions($item);
3851 call_hooks('atom_entry', $o);
3853 $o .= '</entry>' . "\r\n";
3858 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3860 if(get_config('system','disable_embedded'))
3865 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3866 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3871 $img_start = strpos($orig_body, '[img');
3872 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3873 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3874 while( ($img_st_close !== false) && ($img_len !== false) ) {
3876 $img_st_close++; // make it point to AFTER the closing bracket
3877 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3879 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3882 if(stristr($image , $site . '/photo/')) {
3883 // Only embed locally hosted photos
3885 $i = basename($image);
3886 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3887 $x = strpos($i,'-');
3890 $res = substr($i,$x+1);
3891 $i = substr($i,0,$x);
3892 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3899 // Check to see if we should replace this photo link with an embedded image
3900 // 1. No need to do so if the photo is public
3901 // 2. If there's a contact-id provided, see if they're in the access list
3902 // for the photo. If so, embed it.
3903 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3904 // permissions, regardless of order but first check to see if they're an exact
3905 // match to save some processing overhead.
3907 if(has_permissions($r[0])) {
3909 $recips = enumerate_permissions($r[0]);
3910 if(in_array($cid, $recips)) {
3915 if(compare_permissions($item,$r[0]))
3920 $data = $r[0]['data'];
3921 $type = $r[0]['type'];
3923 // If a custom width and height were specified, apply before embedding
3924 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3925 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3927 $width = intval($match[1]);
3928 $height = intval($match[2]);
3930 $ph = new Photo($data, $type);
3931 if($ph->is_valid()) {
3932 $ph->scaleImage(max($width, $height));
3933 $data = $ph->imageString();
3934 $type = $ph->getType();
3938 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3939 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3940 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3946 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3947 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3948 if($orig_body === false)
3951 $img_start = strpos($orig_body, '[img');
3952 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3953 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3956 $new_body = $new_body . $orig_body;
3962 function has_permissions($obj) {
3963 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3968 function compare_permissions($obj1,$obj2) {
3969 // first part is easy. Check that these are exactly the same.
3970 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3971 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3972 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3973 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3976 // This is harder. Parse all the permissions and compare the resulting set.
3978 $recipients1 = enumerate_permissions($obj1);
3979 $recipients2 = enumerate_permissions($obj2);
3982 if($recipients1 == $recipients2)
3987 // returns an array of contact-ids that are allowed to see this object
3989 function enumerate_permissions($obj) {
3990 require_once('include/group.php');
3991 $allow_people = expand_acl($obj['allow_cid']);
3992 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3993 $deny_people = expand_acl($obj['deny_cid']);
3994 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3995 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3996 $deny = array_unique(array_merge($deny_people,$deny_groups));
3997 $recipients = array_diff($recipients,$deny);
4001 function item_getfeedtags($item) {
4004 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4006 for($x = 0; $x < $cnt; $x ++) {
4008 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4012 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4014 for($x = 0; $x < $cnt; $x ++) {
4016 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4022 function item_getfeedattach($item) {
4024 $arr = explode('[/attach],',$item['attach']);
4026 foreach($arr as $r) {
4028 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4030 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4031 if(intval($matches[2]))
4032 $ret .= 'length="' . intval($matches[2]) . '" ';
4033 if($matches[4] !== ' ')
4034 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4035 $ret .= ' />' . "\r\n";
4044 function item_expire($uid,$days) {
4046 if((! $uid) || ($days < 1))
4049 // $expire_network_only = save your own wall posts
4050 // and just expire conversations started by others
4052 $expire_network_only = get_pconfig($uid,'expire','network_only');
4053 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4055 $r = q("SELECT * FROM `item`
4057 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4068 $expire_items = get_pconfig($uid, 'expire','items');
4069 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4071 $expire_notes = get_pconfig($uid, 'expire','notes');
4072 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4074 $expire_starred = get_pconfig($uid, 'expire','starred');
4075 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4077 $expire_photos = get_pconfig($uid, 'expire','photos');
4078 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4080 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4082 foreach($r as $item) {
4084 // don't expire filed items
4086 if(strpos($item['file'],'[') !== false)
4089 // Only expire posts, not photos and photo comments
4091 if($expire_photos==0 && strlen($item['resource-id']))
4093 if($expire_starred==0 && intval($item['starred']))
4095 if($expire_notes==0 && $item['type']=='note')
4097 if($expire_items==0 && $item['type']!='note')
4100 drop_item($item['id'],false);
4103 proc_run('php',"include/notifier.php","expire","$uid");
4108 function drop_items($items) {
4111 if(! local_user() && ! remote_user())
4115 foreach($items as $item) {
4116 $owner = drop_item($item,false);
4117 if($owner && ! $uid)
4122 // multiple threads may have been deleted, send an expire notification
4125 proc_run('php',"include/notifier.php","expire","$uid");
4129 function drop_item($id,$interactive = true) {
4133 // locate item to be deleted
4135 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4142 notice( t('Item not found.') . EOL);
4143 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4148 $owner = $item['uid'];
4152 // check if logged in user is either the author or owner of this item
4154 if(is_array($_SESSION['remote'])) {
4155 foreach($_SESSION['remote'] as $visitor) {
4156 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4157 $cid = $visitor['cid'];
4164 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4166 // Check if we should do HTML-based delete confirmation
4167 if($_REQUEST['confirm']) {
4168 // <form> can't take arguments in its "action" parameter
4169 // so add any arguments as hidden inputs
4170 $query = explode_querystring($a->query_string);
4172 foreach($query['args'] as $arg) {
4173 if(strpos($arg, 'confirm=') === false) {
4174 $arg_parts = explode('=', $arg);
4175 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4179 return replace_macros(get_markup_template('confirm.tpl'), array(
4181 '$message' => t('Do you really want to delete this item?'),
4182 '$extra_inputs' => $inputs,
4183 '$confirm' => t('Yes'),
4184 '$confirm_url' => $query['base'],
4185 '$confirm_name' => 'confirmed',
4186 '$cancel' => t('Cancel'),
4189 // Now check how the user responded to the confirmation query
4190 if($_REQUEST['canceled']) {
4191 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4194 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4197 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4198 dbesc(datetime_convert()),
4199 dbesc(datetime_convert()),
4202 create_tags_from_item($item['id']);
4203 create_files_from_item($item['id']);
4204 delete_thread($item['id']);
4206 // clean up categories and tags so they don't end up as orphans
4209 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4211 foreach($matches as $mtch) {
4212 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4218 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4220 foreach($matches as $mtch) {
4221 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4225 // If item is a link to a photo resource, nuke all the associated photos
4226 // (visitors will not have photo resources)
4227 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4228 // generate a resource-id and therefore aren't intimately linked to the item.
4230 if(strlen($item['resource-id'])) {
4231 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4232 dbesc($item['resource-id']),
4233 intval($item['uid'])
4235 // ignore the result
4238 // If item is a link to an event, nuke the event record.
4240 if(intval($item['event-id'])) {
4241 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4242 intval($item['event-id']),
4243 intval($item['uid'])
4245 // ignore the result
4248 // clean up item_id and sign meta-data tables
4251 // Old code - caused very long queries and warning entries in the mysql logfiles:
4253 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4254 intval($item['id']),
4255 intval($item['uid'])
4258 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4259 intval($item['id']),
4260 intval($item['uid'])
4264 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4266 // Creating list of parents
4267 $r = q("select id from item where parent = %d and uid = %d",
4268 intval($item['id']),
4269 intval($item['uid'])
4274 foreach ($r AS $row) {
4275 if ($parentid != "")
4278 $parentid .= $row["id"];
4282 if ($parentid != "") {
4283 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4285 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4288 // If it's the parent of a comment thread, kill all the kids
4290 if($item['uri'] == $item['parent-uri']) {
4291 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4292 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4293 dbesc(datetime_convert()),
4294 dbesc(datetime_convert()),
4295 dbesc($item['parent-uri']),
4296 intval($item['uid'])
4298 create_tags_from_item($item['parent-uri'], $item['uid']);
4299 create_files_from_item($item['parent-uri'], $item['uid']);
4300 delete_thread_uri($item['parent-uri'], $item['uid']);
4301 // ignore the result
4304 // ensure that last-child is set in case the comment that had it just got wiped.
4305 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4306 dbesc(datetime_convert()),
4307 dbesc($item['parent-uri']),
4308 intval($item['uid'])
4310 // who is the last child now?
4311 $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",
4312 dbesc($item['parent-uri']),
4313 intval($item['uid'])
4316 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4321 // Add a relayable_retraction signature for Diaspora.
4322 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4324 $drop_id = intval($item['id']);
4326 // send the notification upstream/downstream as the case may be
4328 proc_run('php',"include/notifier.php","drop","$drop_id");
4332 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4338 notice( t('Permission denied.') . EOL);
4339 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4346 function first_post_date($uid,$wall = false) {
4347 $r = q("select id, created from item
4348 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4350 order by created asc limit 1",
4352 intval($wall ? 1 : 0)
4355 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4356 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4361 function posted_dates($uid,$wall) {
4362 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4364 $dthen = first_post_date($uid,$wall);
4368 // If it's near the end of a long month, backup to the 28th so that in
4369 // consecutive loops we'll always get a whole month difference.
4371 if(intval(substr($dnow,8)) > 28)
4372 $dnow = substr($dnow,0,8) . '28';
4373 if(intval(substr($dthen,8)) > 28)
4374 $dnow = substr($dthen,0,8) . '28';
4377 // Starting with the current month, get the first and last days of every
4378 // month down to and including the month of the first post
4379 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4380 $dstart = substr($dnow,0,8) . '01';
4381 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4382 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4383 $end_month = datetime_convert('','',$dend,'Y-m-d');
4384 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4385 $ret[] = array($str,$end_month,$start_month);
4386 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4392 function posted_date_widget($url,$uid,$wall) {
4395 if(! feature_enabled($uid,'archives'))
4398 // For former Facebook folks that left because of "timeline"
4400 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4403 $ret = posted_dates($uid,$wall);
4407 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4408 '$title' => t('Archives'),
4409 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4416 function store_diaspora_retract_sig($item, $user, $baseurl) {
4417 // Note that we can't add a target_author_signature
4418 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4419 // the comment, that means we're the home of the post, and Diaspora will only
4420 // check the parent_author_signature of retractions that it doesn't have to relay further
4422 // I don't think this function gets called for an "unlike," but I'll check anyway
4424 $enabled = intval(get_config('system','diaspora_enabled'));
4426 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4430 logger('drop_item: storing diaspora retraction signature');
4432 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4434 if(local_user() == $item['uid']) {
4436 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4437 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4440 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4441 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4444 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4445 // only handles DFRN deletes
4446 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4447 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4448 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4454 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4455 intval($item['id']),
4456 dbesc($signed_text),