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']);
60 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
64 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
65 $my_id = '1:' . $dfrn_id;
68 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '0:' . $dfrn_id;
76 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
84 require_once('include/security.php');
85 $groups = init_groups_visitor($contact['id']);
88 for($x = 0; $x < count($groups); $x ++)
89 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
90 $gs = implode('|', $groups);
93 $gs = '<<>>' ; // Impossible to match
95 $sql_extra = sprintf("
96 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
97 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
98 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
99 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
101 intval($contact['id']),
102 intval($contact['id']),
113 if(! strlen($last_update))
114 $last_update = 'now -30 days';
116 if(isset($category)) {
117 $sql_extra .= file_tag_file_query('item',$category,'category');
122 $sql_extra .= " AND `contact`.`self` = 1 ";
125 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
127 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
128 // dbesc($check_date),
130 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
131 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
132 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
133 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
134 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
135 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
137 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
138 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
139 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
140 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
141 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
143 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
149 // Will check further below if this actually returned results.
150 // We will provide an empty feed if that is the case.
154 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
158 $hubxml = feed_hublinks();
160 $salmon = feed_salmonlinks($owner_nick);
162 $atom .= replace_macros($feed_template, array(
163 '$version' => xmlify(FRIENDICA_VERSION),
164 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
165 '$feed_title' => xmlify($owner['name']),
166 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
168 '$salmon' => $salmon,
169 '$name' => xmlify($owner['name']),
170 '$profile_page' => xmlify($owner['url']),
171 '$photo' => xmlify($owner['photo']),
172 '$thumb' => xmlify($owner['thumb']),
173 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
174 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
175 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
176 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
177 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
180 call_hooks('atom_feed', $atom);
182 if(! count($items)) {
184 call_hooks('atom_feed_end', $atom);
186 $atom .= '</feed>' . "\r\n";
190 foreach($items as $item) {
192 // prevent private email from leaking.
193 if($item['network'] === NETWORK_MAIL)
196 // public feeds get html, our own nodes use bbcode
200 // catch any email that's in a public conversation and make sure it doesn't leak
208 $atom .= atom_entry($item,$type,null,$owner,true);
211 call_hooks('atom_feed_end', $atom);
213 $atom .= '</feed>' . "\r\n";
219 function construct_verb($item) {
221 return $item['verb'];
222 return ACTIVITY_POST;
225 function construct_activity_object($item) {
227 if($item['object']) {
228 $o = '<as:object>' . "\r\n";
229 $r = parse_xml_string($item['object'],false);
235 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
237 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
239 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
241 if(substr($r->link,0,1) === '<') {
242 // patch up some facebook "like" activity objects that got stored incorrectly
243 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
244 // we can probably remove this hack here and in the following function in a few months time.
245 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
246 $r->link = str_replace('&','&', $r->link);
247 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
251 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
254 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
255 $o .= '</as:object>' . "\r\n";
262 function construct_activity_target($item) {
264 if($item['target']) {
265 $o = '<as:target>' . "\r\n";
266 $r = parse_xml_string($item['target'],false);
270 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
272 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
274 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
276 if(substr($r->link,0,1) === '<') {
277 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
278 $r->link = str_replace('&','&', $r->link);
279 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
283 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
286 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
287 $o .= '</as:target>' . "\r\n";
296 * The purpose of this function is to apply system message length limits to
297 * imported messages without including any embedded photos in the length
299 if(! function_exists('limit_body_size')) {
300 function limit_body_size($body) {
302 // logger('limit_body_size: start', LOGGER_DEBUG);
304 $maxlen = get_max_import_size();
306 // If the length of the body, including the embedded images, is smaller
307 // than the maximum, then don't waste time looking for the images
308 if($maxlen && (strlen($body) > $maxlen)) {
310 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
317 $img_start = strpos($orig_body, '[img');
318 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
319 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
320 while(($img_st_close !== false) && ($img_end !== false)) {
322 $img_st_close++; // make it point to AFTER the closing bracket
323 $img_end += $img_start;
324 $img_end += strlen('[/img]');
326 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
327 // This is an embedded image
329 if( ($textlen + $img_start) > $maxlen ) {
330 if($textlen < $maxlen) {
331 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
332 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
337 $new_body = $new_body . substr($orig_body, 0, $img_start);
338 $textlen += $img_start;
341 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
345 if( ($textlen + $img_end) > $maxlen ) {
346 if($textlen < $maxlen) {
347 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
348 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
353 $new_body = $new_body . substr($orig_body, 0, $img_end);
354 $textlen += $img_end;
357 $orig_body = substr($orig_body, $img_end);
359 if($orig_body === false) // in case the body ends on a closing image tag
362 $img_start = strpos($orig_body, '[img');
363 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
364 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
367 if( ($textlen + strlen($orig_body)) > $maxlen) {
368 if($textlen < $maxlen) {
369 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
370 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
375 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
376 $new_body = $new_body . $orig_body;
377 $textlen += strlen($orig_body);
386 function title_is_body($title, $body) {
388 $title = strip_tags($title);
389 $title = trim($title);
390 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
391 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
393 $body = strip_tags($body);
395 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
396 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
398 if (strlen($title) < strlen($body))
399 $body = substr($body, 0, strlen($title));
401 if (($title != $body) and (substr($title, -3) == "...")) {
402 $pos = strrpos($title, "...");
404 $title = substr($title, 0, $pos);
405 $body = substr($body, 0, $pos);
409 return($title == $body);
414 function get_atom_elements($feed, $item, $contact = array()) {
416 require_once('library/HTMLPurifier.auto.php');
417 require_once('include/html2bbcode.php');
419 $best_photo = array();
423 $author = $item->get_author();
425 $res['author-name'] = unxmlify($author->get_name());
426 $res['author-link'] = unxmlify($author->get_link());
429 $res['author-name'] = unxmlify($feed->get_title());
430 $res['author-link'] = unxmlify($feed->get_permalink());
432 $res['uri'] = unxmlify($item->get_id());
433 $res['title'] = unxmlify($item->get_title());
434 $res['body'] = unxmlify($item->get_content());
435 $res['plink'] = unxmlify($item->get_link(0));
437 // removing the content of the title if its identically to the body
438 // This helps with auto generated titles e.g. from tumblr
439 if (title_is_body($res["title"], $res["body"]))
443 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
447 // look for a photo. We should check media size and find the best one,
448 // but for now let's just find any author photo
450 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
452 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
453 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
454 foreach($base as $link) {
455 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
456 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
457 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
462 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
464 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
465 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
466 if($base && count($base)) {
467 foreach($base as $link) {
468 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
469 $res['author-link'] = unxmlify($link['attribs']['']['href']);
470 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
471 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
472 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
478 // No photo/profile-link on the item - look at the feed level
480 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
481 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
482 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
483 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 foreach($base as $link) {
485 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
486 $res['author-link'] = unxmlify($link['attribs']['']['href']);
487 if(! $res['author-avatar']) {
488 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
489 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
494 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
496 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
497 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
499 if($base && count($base)) {
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
503 if(! (x($res,'author-avatar'))) {
504 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
505 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
513 if($apps && $apps[0]['attribs']['']['source']) {
514 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
515 if($res['app'] === 'web')
516 $res['app'] = 'OStatus';
519 // base64 encoded json structure representing Diaspora signature
521 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
523 $res['dsprsig'] = unxmlify($dsig[0]['data']);
526 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
528 $res['guid'] = unxmlify($dguid[0]['data']);
530 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
532 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
536 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
539 $have_real_body = false;
541 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
543 $have_real_body = true;
544 $res['body'] = $rawenv[0]['data'];
545 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
546 // make sure nobody is trying to sneak some html tags by us
547 $res['body'] = notags(base64url_decode($res['body']));
551 $res['body'] = limit_body_size($res['body']);
553 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
554 // the content type. Our own network only emits text normally, though it might have been converted to
555 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
556 // have to assume it is all html and needs to be purified.
558 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
559 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
560 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
563 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
565 $res['body'] = reltoabs($res['body'],$base_url);
567 $res['body'] = html2bb_video($res['body']);
569 $res['body'] = oembed_html2bbcode($res['body']);
571 $config = HTMLPurifier_Config::createDefault();
572 $config->set('Cache.DefinitionImpl', null);
574 // we shouldn't need a whitelist, because the bbcode converter
575 // will strip out any unsupported tags.
577 $purifier = new HTMLPurifier($config);
578 $res['body'] = $purifier->purify($res['body']);
580 $res['body'] = @html2bbcode($res['body']);
584 elseif(! $have_real_body) {
586 // it's not one of our messages and it has no tags
587 // so it's probably just text. We'll escape it just to be safe.
589 $res['body'] = escape_tags($res['body']);
593 // this tag is obsolete but we keep it for really old sites
595 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
596 if($allow && $allow[0]['data'] == 1)
597 $res['last-child'] = 1;
599 $res['last-child'] = 0;
601 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
602 if($private && intval($private[0]['data']) > 0)
603 $res['private'] = intval($private[0]['data']);
607 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
608 if($extid && $extid[0]['data'])
609 $res['extid'] = $extid[0]['data'];
611 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
613 $res['location'] = unxmlify($rawlocation[0]['data']);
616 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
618 $res['created'] = unxmlify($rawcreated[0]['data']);
621 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
623 $res['edited'] = unxmlify($rawedited[0]['data']);
625 if((x($res,'edited')) && (! (x($res,'created'))))
626 $res['created'] = $res['edited'];
628 if(! $res['created'])
629 $res['created'] = $item->get_date('c');
632 $res['edited'] = $item->get_date('c');
635 // Disallow time travelling posts
637 $d1 = strtotime($res['created']);
638 $d2 = strtotime($res['edited']);
639 $d3 = strtotime('now');
642 $res['created'] = datetime_convert();
644 $res['edited'] = datetime_convert();
646 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
647 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
648 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
649 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
650 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
651 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
652 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
653 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
654 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
656 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
657 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
659 foreach($base as $link) {
660 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
661 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
662 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
667 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
669 $res['coord'] = unxmlify($rawgeo[0]['data']);
672 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
674 // select between supported verbs
677 $res['verb'] = unxmlify($rawverb[0]['data']);
680 // translate OStatus unfollow to activity streams if it happened to get selected
682 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
683 $res['verb'] = ACTIVITY_UNFOLLOW;
685 $cats = $item->get_categories();
688 foreach($cats as $cat) {
689 $term = $cat->get_term();
691 $term = $cat->get_label();
692 $scheme = $cat->get_scheme();
693 if($scheme && $term && stristr($scheme,'X-DFRN:'))
694 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
696 $tag_arr[] = notags(trim($term));
698 $res['tag'] = implode(',', $tag_arr);
701 $attach = $item->get_enclosures();
704 foreach($attach as $att) {
705 $len = intval($att->get_length());
706 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
707 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
708 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
709 if(strpos($type,';'))
710 $type = substr($type,0,strpos($type,';'));
711 if((! $link) || (strpos($link,'http') !== 0))
717 $type = 'application/octet-stream';
719 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
721 $res['attach'] = implode(',', $att_arr);
724 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
727 $res['object'] = '<object>' . "\n";
728 $child = $rawobj[0]['child'];
729 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
730 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
731 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
733 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
734 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
735 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
736 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
737 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
738 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
739 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
740 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
742 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
743 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
744 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
745 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
747 $body = html2bb_video($body);
749 $config = HTMLPurifier_Config::createDefault();
750 $config->set('Cache.DefinitionImpl', null);
752 $purifier = new HTMLPurifier($config);
753 $body = $purifier->purify($body);
754 $body = html2bbcode($body);
757 $res['object'] .= '<content>' . $body . '</content>' . "\n";
760 $res['object'] .= '</object>' . "\n";
763 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
766 $res['target'] = '<target>' . "\n";
767 $child = $rawobj[0]['child'];
768 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
769 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
771 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
772 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
773 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
774 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
775 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
776 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
777 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
778 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
780 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
781 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
782 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
783 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
785 $body = html2bb_video($body);
787 $config = HTMLPurifier_Config::createDefault();
788 $config->set('Cache.DefinitionImpl', null);
790 $purifier = new HTMLPurifier($config);
791 $body = $purifier->purify($body);
792 $body = html2bbcode($body);
795 $res['target'] .= '<content>' . $body . '</content>' . "\n";
798 $res['target'] .= '</target>' . "\n";
801 // This is some experimental stuff. By now retweets are shown with "RT:"
802 // But: There is data so that the message could be shown similar to native retweets
803 // There is some better way to parse this array - but it didn't worked for me.
804 $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"];
805 if (is_array($child)) {
806 logger('get_atom_elements: Looking for status.net repeated message');
808 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
809 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
810 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
811 $uri = $author["uri"][0]["data"];
812 $name = $author["name"][0]["data"];
813 $avatar = @array_shift($author["link"][2]["attribs"]);
814 $avatar = $avatar["href"];
816 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
817 logger('get_atom_elements: fixing sender of repeated message.');
819 if (!intval(get_config('system','wall-to-wall_share'))) {
820 $prefix = "[share author='".str_replace("'", "'",$name).
822 "' avatar='".$avatar.
823 "' link='".$orig_uri."']";
825 $res["body"] = $prefix.html2bbcode($message)."[/share]";
827 $res["owner-name"] = $res["author-name"];
828 $res["owner-link"] = $res["author-link"];
829 $res["owner-avatar"] = $res["author-avatar"];
831 $res["author-name"] = $name;
832 $res["author-link"] = $uri;
833 $res["author-avatar"] = $avatar;
835 $res["body"] = html2bbcode($message);
840 // Search for ostatus conversation url
841 $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"];
843 if (is_array($links)) {
844 foreach ($links as $link) {
845 $conversation = array_shift($link["attribs"]);
847 if ($conversation["rel"] == "ostatus:conversation") {
848 $res["ostatus_conversation"] = $conversation["href"];
849 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
854 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
855 $res["body"] = $res["title"]."\n\n[class=type-link]".fetch_siteinfo($res['plink'])."[/class]";
859 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
861 call_hooks('parse_atom', $arr);
863 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
864 //if (strpos($res["body"], "RT @") !== false) {
865 /*if (strpos($res["body"], "@") !== false) {
866 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
867 file_put_contents($debugfile, serialize($arr));
873 function fetch_siteinfo($url) {
874 require_once("mod/parse_url.php");
876 // Fetch site infos - but only from the meta data
877 $data = parseurl_getsiteinfo($url, true);
881 if (!is_string($data["text"]) AND (sizeof($data["images"]) == 0) AND ($data["title"] == $url))
884 if (is_string($data["title"]))
885 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]\n";
887 if (sizeof($data["images"]) > 0) {
888 $imagedata = $data["images"][0];
889 $text .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]' . "\n";
892 if (is_string($data["text"]))
893 $text .= "[quote]".$data["text"]."[/quote]";
898 function encode_rel_links($links) {
900 if(! ((is_array($links)) && (count($links))))
902 foreach($links as $link) {
904 if($link['attribs']['']['rel'])
905 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
906 if($link['attribs']['']['type'])
907 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
908 if($link['attribs']['']['href'])
909 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
910 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
911 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
912 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
913 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
921 function item_store($arr,$force_parent = false) {
923 // If a Diaspora signature structure was passed in, pull it out of the
924 // item array and set it aside for later storage.
927 if(x($arr,'dsprsig')) {
928 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
929 unset($arr['dsprsig']);
932 // if an OStatus conversation url was passed in, it is stored and then
933 // removed from the array.
934 $ostatus_conversation = null;
936 if (isset($arr["ostatus_conversation"])) {
937 $ostatus_conversation = $arr["ostatus_conversation"];
938 unset($arr["ostatus_conversation"]);
941 if(x($arr, 'gravity'))
942 $arr['gravity'] = intval($arr['gravity']);
943 elseif($arr['parent-uri'] === $arr['uri'])
945 elseif(activity_match($arr['verb'],ACTIVITY_POST))
948 $arr['gravity'] = 6; // extensible catchall
951 $arr['type'] = 'remote';
953 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
955 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
956 $arr['body'] = strip_tags($arr['body']);
959 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
960 require_once('library/langdet/Text/LanguageDetect.php');
961 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
962 $l = new Text_LanguageDetect;
963 //$lng = $l->detectConfidence($naked_body);
964 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
965 $lng = $l->detect($naked_body, 3);
967 if (sizeof($lng) > 0) {
970 foreach ($lng as $language => $score) {
976 $postopts .= $language.";".$score;
978 $arr['postopts'] = $postopts;
982 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
983 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
984 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
985 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
986 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
987 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
988 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
989 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
990 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
991 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
992 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
993 $arr['commented'] = datetime_convert();
994 $arr['received'] = datetime_convert();
995 $arr['changed'] = datetime_convert();
996 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
997 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
998 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
999 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1000 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1001 $arr['deleted'] = 0;
1002 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1003 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1004 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1005 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1006 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1007 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1008 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1009 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1010 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1011 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1012 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1013 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1014 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1015 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1016 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1017 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1018 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1019 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1020 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1021 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1023 if ($arr['network'] == "") {
1024 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1025 intval($arr['contact-id']),
1030 $arr['network'] = $r[0]["network"];
1032 // Fallback to friendica (why is it empty in some cases?)
1033 if ($arr['network'] == "")
1034 $arr['network'] = NETWORK_DFRN;
1036 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1039 $arr['thr-parent'] = $arr['parent-uri'];
1040 if($arr['parent-uri'] === $arr['uri']) {
1042 $parent_deleted = 0;
1043 $allow_cid = $arr['allow_cid'];
1044 $allow_gid = $arr['allow_gid'];
1045 $deny_cid = $arr['deny_cid'];
1046 $deny_gid = $arr['deny_gid'];
1050 // find the parent and snarf the item id and ACLs
1051 // and anything else we need to inherit
1053 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1054 dbesc($arr['parent-uri']),
1060 // is the new message multi-level threaded?
1061 // even though we don't support it now, preserve the info
1062 // and re-attach to the conversation parent.
1064 if($r[0]['uri'] != $r[0]['parent-uri']) {
1065 $arr['parent-uri'] = $r[0]['parent-uri'];
1066 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1067 ORDER BY `id` ASC LIMIT 1",
1068 dbesc($r[0]['parent-uri']),
1069 dbesc($r[0]['parent-uri']),
1076 $parent_id = $r[0]['id'];
1077 $parent_deleted = $r[0]['deleted'];
1078 $allow_cid = $r[0]['allow_cid'];
1079 $allow_gid = $r[0]['allow_gid'];
1080 $deny_cid = $r[0]['deny_cid'];
1081 $deny_gid = $r[0]['deny_gid'];
1082 $arr['wall'] = $r[0]['wall'];
1084 // if the parent is private, force privacy for the entire conversation
1085 // This differs from the above settings as it subtly allows comments from
1086 // email correspondents to be private even if the overall thread is not.
1088 if($r[0]['private'])
1089 $arr['private'] = $r[0]['private'];
1091 // Edge case. We host a public forum that was originally posted to privately.
1092 // The original author commented, but as this is a comment, the permissions
1093 // weren't fixed up so it will still show the comment as private unless we fix it here.
1095 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1096 $arr['private'] = 0;
1099 // If its a post from myself then tag the thread as "mention"
1100 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1101 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1104 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1105 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1106 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1107 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1108 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1114 // Allow one to see reply tweets from status.net even when
1115 // we don't have or can't see the original post.
1118 logger('item_store: $force_parent=true, reply converted to top-level post.');
1120 $arr['parent-uri'] = $arr['uri'];
1121 $arr['gravity'] = 0;
1124 logger('item_store: item parent was not found - ignoring item');
1128 $parent_deleted = 0;
1132 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1136 if($r && count($r)) {
1137 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1141 call_hooks('post_remote',$arr);
1143 if(x($arr,'cancel')) {
1144 logger('item_store: post cancelled by plugin.');
1150 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1152 $r = dbq("INSERT INTO `item` (`"
1153 . implode("`, `", array_keys($arr))
1155 . implode("', '", array_values($arr))
1158 // find the item we just created
1160 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1161 $arr['uri'], // already dbesc'd
1166 $current_post = $r[0]['id'];
1167 logger('item_store: created item ' . $current_post);
1169 // Only check for notifications on start posts
1170 if ($arr['parent-uri'] === $arr['uri']) {
1171 add_thread($r[0]['id']);
1172 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1174 // Send a notification for every new post?
1175 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1176 intval($arr['contact-id']),
1181 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1182 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1183 intval($arr['uid']));
1185 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1186 intval($current_post),
1192 require_once('include/enotify.php');
1194 'type' => NOTIFY_SHARE,
1195 'notify_flags' => $u[0]['notify-flags'],
1196 'language' => $u[0]['language'],
1197 'to_name' => $u[0]['username'],
1198 'to_email' => $u[0]['email'],
1199 'uid' => $u[0]['uid'],
1201 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1202 'source_name' => $item[0]['author-name'],
1203 'source_link' => $item[0]['author-link'],
1204 'source_photo' => $item[0]['author-avatar'],
1205 'verb' => ACTIVITY_TAG,
1208 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1213 logger('item_store: could not locate created item');
1217 logger('item_store: duplicated post occurred. Removing duplicates.');
1218 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1220 intval($arr['uid']),
1221 intval($current_post)
1225 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1226 $parent_id = $current_post;
1228 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1231 $private = $arr['private'];
1233 // Set parent id - and also make sure to inherit the parent's ACLs.
1235 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1236 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1243 intval($parent_deleted),
1244 intval($current_post)
1247 // Complete ostatus threads
1248 if ($ostatus_conversation)
1249 complete_conversation($current_post, $ostatus_conversation);
1251 $arr['id'] = $current_post;
1252 $arr['parent'] = $parent_id;
1253 $arr['allow_cid'] = $allow_cid;
1254 $arr['allow_gid'] = $allow_gid;
1255 $arr['deny_cid'] = $deny_cid;
1256 $arr['deny_gid'] = $deny_gid;
1257 $arr['private'] = $private;
1258 $arr['deleted'] = $parent_deleted;
1260 // update the commented timestamp on the parent
1262 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1263 dbesc(datetime_convert()),
1264 dbesc(datetime_convert()),
1267 update_thread($parent_id);
1270 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1271 intval($current_post),
1272 dbesc($dsprsig->signed_text),
1273 dbesc($dsprsig->signature),
1274 dbesc($dsprsig->signer)
1280 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1283 if($arr['last-child']) {
1284 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1286 intval($arr['uid']),
1287 intval($current_post)
1291 $deleted = tag_deliver($arr['uid'],$current_post);
1293 // current post can be deleted if is for a communuty page and no mention are
1297 // Store the fresh generated item into the cache
1298 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1300 if (($cachefile != '') AND !file_exists($cachefile)) {
1301 $s = prepare_text($arr['body']);
1303 $stamp1 = microtime(true);
1304 file_put_contents($cachefile, $s);
1305 $a->save_timestamp($stamp1, "file");
1306 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1309 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1310 if (count($r) == 1) {
1311 call_hooks('post_remote_end', $r[0]);
1313 logger('item_store: new item not found in DB, id ' . $current_post);
1317 create_tags_from_item($current_post);
1318 create_files_from_item($current_post);
1320 return $current_post;
1323 function get_item_contact($item,$contacts) {
1324 if(! count($contacts) || (! is_array($item)))
1326 foreach($contacts as $contact) {
1327 if($contact['id'] == $item['contact-id']) {
1329 break; // NOTREACHED
1336 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1338 * @param int $item_id
1339 * @return bool true if item was deleted, else false
1341 function tag_deliver($uid,$item_id) {
1349 $u = q("select * from user where uid = %d limit 1",
1355 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1356 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1359 $i = q("select * from item where id = %d and uid = %d limit 1",
1368 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1370 // Diaspora uses their own hardwired link URL in @-tags
1371 // instead of the one we supply with webfinger
1373 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1375 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1377 foreach($matches as $mtch) {
1378 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1380 logger('tag_deliver: mention found: ' . $mtch[2]);
1386 if ( ($community_page || $prvgroup) &&
1387 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1388 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1390 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1391 q("DELETE FROM item WHERE id = %d and uid = %d",
1401 // send a notification
1403 // use a local photo if we have one
1405 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1406 intval($u[0]['uid']),
1407 dbesc(normalise_link($item['author-link']))
1409 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1412 require_once('include/enotify.php');
1414 'type' => NOTIFY_TAGSELF,
1415 'notify_flags' => $u[0]['notify-flags'],
1416 'language' => $u[0]['language'],
1417 'to_name' => $u[0]['username'],
1418 'to_email' => $u[0]['email'],
1419 'uid' => $u[0]['uid'],
1421 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1422 'source_name' => $item['author-name'],
1423 'source_link' => $item['author-link'],
1424 'source_photo' => $photo,
1425 'verb' => ACTIVITY_TAG,
1430 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1432 call_hooks('tagged', $arr);
1434 if((! $community_page) && (! $prvgroup))
1438 // tgroup delivery - setup a second delivery chain
1439 // prevent delivery looping - only proceed
1440 // if the message originated elsewhere and is a top-level post
1442 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1445 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1448 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1449 intval($u[0]['uid'])
1454 // also reset all the privacy bits to the forum default permissions
1456 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1458 $forum_mode = (($prvgroup) ? 2 : 1);
1460 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1461 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1462 intval($forum_mode),
1463 dbesc($c[0]['name']),
1464 dbesc($c[0]['url']),
1465 dbesc($c[0]['thumb']),
1467 dbesc($u[0]['allow_cid']),
1468 dbesc($u[0]['allow_gid']),
1469 dbesc($u[0]['deny_cid']),
1470 dbesc($u[0]['deny_gid']),
1473 update_thread($item_id);
1475 proc_run('php','include/notifier.php','tgroup',$item_id);
1481 function tgroup_check($uid,$item) {
1487 // check that the message originated elsewhere and is a top-level post
1489 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1493 $u = q("select * from user where uid = %d limit 1",
1499 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1500 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1503 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1505 // Diaspora uses their own hardwired link URL in @-tags
1506 // instead of the one we supply with webfinger
1508 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1510 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1512 foreach($matches as $mtch) {
1513 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1515 logger('tgroup_check: mention found: ' . $mtch[2]);
1523 if((! $community_page) && (! $prvgroup))
1537 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1541 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1543 if($contact['duplex'] && $contact['dfrn-id'])
1544 $idtosend = '0:' . $orig_id;
1545 if($contact['duplex'] && $contact['issued-id'])
1546 $idtosend = '1:' . $orig_id;
1548 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1550 $rino_enable = get_config('system','rino_encrypt');
1555 $ssl_val = intval(get_config('system','ssl_policy'));
1559 case SSL_POLICY_FULL:
1560 $ssl_policy = 'full';
1562 case SSL_POLICY_SELFSIGN:
1563 $ssl_policy = 'self';
1565 case SSL_POLICY_NONE:
1567 $ssl_policy = 'none';
1571 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1573 logger('dfrn_deliver: ' . $url);
1575 $xml = fetch_url($url);
1577 $curl_stat = $a->get_curl_code();
1579 return(-1); // timed out
1581 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1586 if(strpos($xml,'<?xml') === false) {
1587 logger('dfrn_deliver: no valid XML returned');
1588 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1592 $res = parse_xml_string($xml);
1594 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1595 return (($res->status) ? $res->status : 3);
1597 $postvars = array();
1598 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1599 $challenge = hex2bin((string) $res->challenge);
1600 $perm = (($res->perm) ? $res->perm : null);
1601 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1602 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1603 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1605 if($owner['page-flags'] == PAGE_PRVGROUP)
1608 $final_dfrn_id = '';
1611 if((($perm == 'rw') && (! intval($contact['writable'])))
1612 || (($perm == 'r') && (intval($contact['writable'])))) {
1613 q("update contact set writable = %d where id = %d",
1614 intval(($perm == 'rw') ? 1 : 0),
1615 intval($contact['id'])
1617 $contact['writable'] = (string) 1 - intval($contact['writable']);
1621 if(($contact['duplex'] && strlen($contact['pubkey']))
1622 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1623 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1624 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1625 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1628 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1629 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1632 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1634 if(strpos($final_dfrn_id,':') == 1)
1635 $final_dfrn_id = substr($final_dfrn_id,2);
1637 if($final_dfrn_id != $orig_id) {
1638 logger('dfrn_deliver: wrong dfrn_id.');
1639 // did not decode properly - cannot trust this site
1643 $postvars['dfrn_id'] = $idtosend;
1644 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1646 $postvars['dissolve'] = '1';
1649 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1650 $postvars['data'] = $atom;
1651 $postvars['perm'] = 'rw';
1654 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1655 $postvars['perm'] = 'r';
1658 $postvars['ssl_policy'] = $ssl_policy;
1661 $postvars['page'] = $page;
1663 if($rino && $rino_allowed && (! $dissolve)) {
1664 $key = substr(random_string(),0,16);
1665 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1666 $postvars['data'] = $data;
1667 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1670 if($dfrn_version >= 2.1) {
1671 if(($contact['duplex'] && strlen($contact['pubkey']))
1672 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1673 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1675 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1678 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1682 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1683 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1686 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1690 logger('md5 rawkey ' . md5($postvars['key']));
1692 $postvars['key'] = bin2hex($postvars['key']);
1695 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1697 $xml = post_url($contact['notify'],$postvars);
1699 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1701 $curl_stat = $a->get_curl_code();
1702 if((! $curl_stat) || (! strlen($xml)))
1703 return(-1); // timed out
1705 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1708 if(strpos($xml,'<?xml') === false) {
1709 logger('dfrn_deliver: phase 2: no valid XML returned');
1710 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1714 if($contact['term-date'] != '0000-00-00 00:00:00') {
1715 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1716 require_once('include/Contact.php');
1717 unmark_for_death($contact);
1720 $res = parse_xml_string($xml);
1722 return $res->status;
1727 This function returns true if $update has an edited timestamp newer
1728 than $existing, i.e. $update contains new data which should override
1729 what's already there. If there is no timestamp yet, the update is
1730 assumed to be newer. If the update has no timestamp, the existing
1731 item is assumed to be up-to-date. If the timestamps are equal it
1732 assumes the update has been seen before and should be ignored.
1734 function edited_timestamp_is_newer($existing, $update) {
1735 if (!x($existing,'edited') || !$existing['edited']) {
1738 if (!x($update,'edited') || !$update['edited']) {
1741 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1742 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1743 return (strcmp($existing_edited, $update_edited) < 0);
1748 * consume_feed - process atom feed and update anything/everything we might need to update
1750 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1752 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1753 * It is this person's stuff that is going to be updated.
1754 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1755 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1756 * have a contact record.
1757 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1758 * might not) try and subscribe to it.
1759 * $datedir sorts in reverse order
1760 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1761 * imported prior to its children being seen in the stream unless we are certain
1762 * of how the feed is arranged/ordered.
1763 * With $pass = 1, we only pull parent items out of the stream.
1764 * With $pass = 2, we only pull children (comments/likes).
1766 * So running this twice, first with pass 1 and then with pass 2 will do the right
1767 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1768 * model where comments can have sub-threads. That would require some massive sorting
1769 * to get all the feed items into a mostly linear ordering, and might still require
1773 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1775 require_once('library/simplepie/simplepie.inc');
1777 if(! strlen($xml)) {
1778 logger('consume_feed: empty input');
1782 $feed = new SimplePie();
1783 $feed->set_raw_data($xml);
1785 $feed->enable_order_by_date(true);
1787 $feed->enable_order_by_date(false);
1791 logger('consume_feed: Error parsing XML: ' . $feed->error());
1793 $permalink = $feed->get_permalink();
1795 // Check at the feed level for updated contact name and/or photo
1799 $photo_timestamp = '';
1803 $hubs = $feed->get_links('hub');
1804 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1807 $hub = implode(',', $hubs);
1809 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1811 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1813 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1814 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1815 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1816 $new_name = $elems['name'][0]['data'];
1818 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1819 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1820 $photo_url = $elems['link'][0]['attribs']['']['href'];
1823 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1824 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1828 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1829 logger('consume_feed: Updating photo for ' . $contact['name']);
1830 require_once("include/Photo.php");
1831 $photo_failure = false;
1832 $have_photo = false;
1834 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1835 intval($contact['id']),
1836 intval($contact['uid'])
1839 $resource_id = $r[0]['resource-id'];
1843 $resource_id = photo_new_resource();
1846 $img_str = fetch_url($photo_url,true);
1847 // guess mimetype from headers or filename
1848 $type = guess_image_type($photo_url,true);
1851 $img = new Photo($img_str, $type);
1852 if($img->is_valid()) {
1854 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1855 dbesc($resource_id),
1856 intval($contact['id']),
1857 intval($contact['uid'])
1861 $img->scaleImageSquare(175);
1863 $hash = $resource_id;
1864 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1866 $img->scaleImage(80);
1867 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1869 $img->scaleImage(48);
1870 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1874 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1875 WHERE `uid` = %d AND `id` = %d",
1876 dbesc(datetime_convert()),
1877 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1878 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1879 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1880 intval($contact['uid']),
1881 intval($contact['id'])
1886 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1887 $r = q("select * from contact where uid = %d and id = %d limit 1",
1888 intval($contact['uid']),
1889 intval($contact['id'])
1892 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1893 dbesc(notags(trim($new_name))),
1894 dbesc(datetime_convert()),
1895 intval($contact['uid']),
1896 intval($contact['id'])
1899 // do our best to update the name on content items
1902 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1903 dbesc(notags(trim($new_name))),
1904 dbesc($r[0]['name']),
1905 dbesc($r[0]['url']),
1906 intval($contact['uid'])
1911 if(strlen($birthday)) {
1912 if(substr($birthday,0,4) != $contact['bdyear']) {
1913 logger('consume_feed: updating birthday: ' . $birthday);
1917 * Add new birthday event for this person
1919 * $bdtext is just a readable placeholder in case the event is shared
1920 * with others. We will replace it during presentation to our $importer
1921 * to contain a sparkle link and perhaps a photo.
1925 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1926 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1929 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1930 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1931 intval($contact['uid']),
1932 intval($contact['id']),
1933 dbesc(datetime_convert()),
1934 dbesc(datetime_convert()),
1935 dbesc(datetime_convert('UTC','UTC', $birthday)),
1936 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1945 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1946 dbesc(substr($birthday,0,4)),
1947 intval($contact['uid']),
1948 intval($contact['id'])
1951 // This function is called twice without reloading the contact
1952 // Make sure we only create one event. This is why &$contact
1953 // is a reference var in this function
1955 $contact['bdyear'] = substr($birthday,0,4);
1960 $community_page = 0;
1961 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1963 $community_page = intval($rawtags[0]['data']);
1965 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1966 q("update contact set forum = %d where id = %d",
1967 intval($community_page),
1968 intval($contact['id'])
1970 $contact['forum'] = (string) $community_page;
1974 // process any deleted entries
1976 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1977 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1978 foreach($del_entries as $dentry) {
1980 if(isset($dentry['attribs']['']['ref'])) {
1981 $uri = $dentry['attribs']['']['ref'];
1983 if(isset($dentry['attribs']['']['when'])) {
1984 $when = $dentry['attribs']['']['when'];
1985 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1988 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1990 if($deleted && is_array($contact)) {
1991 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1992 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1994 intval($importer['uid']),
1995 intval($contact['id'])
2000 if(! $item['deleted'])
2001 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2003 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2004 $xo = parse_xml_string($item['object'],false);
2005 $xt = parse_xml_string($item['target'],false);
2006 if($xt->type === ACTIVITY_OBJ_NOTE) {
2007 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2009 intval($importer['importer_uid'])
2013 // For tags, the owner cannot remove the tag on the author's copy of the post.
2015 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2016 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2017 $author_copy = (($item['origin']) ? true : false);
2019 if($owner_remove && $author_copy)
2021 if($author_remove || $owner_remove) {
2022 $tags = explode(',',$i[0]['tag']);
2025 foreach($tags as $tag)
2026 if(trim($tag) !== trim($xo->body))
2027 $newtags[] = trim($tag);
2029 q("update item set tag = '%s' where id = %d",
2030 dbesc(implode(',',$newtags)),
2033 create_tags_from_item($i[0]['id']);
2039 if($item['uri'] == $item['parent-uri']) {
2040 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2041 `body` = '', `title` = ''
2042 WHERE `parent-uri` = '%s' AND `uid` = %d",
2044 dbesc(datetime_convert()),
2045 dbesc($item['uri']),
2046 intval($importer['uid'])
2048 create_tags_from_itemuri($item['uri'], $importer['uid']);
2049 create_files_from_itemuri($item['uri'], $importer['uid']);
2050 update_thread_uri($item['uri'], $importer['uid']);
2053 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2054 `body` = '', `title` = ''
2055 WHERE `uri` = '%s' AND `uid` = %d",
2057 dbesc(datetime_convert()),
2059 intval($importer['uid'])
2061 create_tags_from_itemuri($uri, $importer['uid']);
2062 create_files_from_itemuri($uri, $importer['uid']);
2063 if($item['last-child']) {
2064 // ensure that last-child is set in case the comment that had it just got wiped.
2065 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2066 dbesc(datetime_convert()),
2067 dbesc($item['parent-uri']),
2068 intval($item['uid'])
2070 // who is the last child now?
2071 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2072 ORDER BY `created` DESC LIMIT 1",
2073 dbesc($item['parent-uri']),
2074 intval($importer['uid'])
2077 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2088 // Now process the feed
2090 if($feed->get_item_quantity()) {
2092 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2094 // in inverse date order
2096 $items = array_reverse($feed->get_items());
2098 $items = $feed->get_items();
2101 foreach($items as $item) {
2104 $item_id = $item->get_id();
2105 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2106 if(isset($rawthread[0]['attribs']['']['ref'])) {
2108 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2111 if(($is_reply) && is_array($contact)) {
2116 // not allowed to post
2118 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2122 // Have we seen it? If not, import it.
2124 $item_id = $item->get_id();
2125 $datarray = get_atom_elements($feed, $item, $contact);
2127 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2128 $datarray['author-name'] = $contact['name'];
2129 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2130 $datarray['author-link'] = $contact['url'];
2131 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2132 $datarray['author-avatar'] = $contact['thumb'];
2134 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2135 logger('consume_feed: no author information! ' . print_r($datarray,true));
2139 $force_parent = false;
2140 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2141 if($contact['network'] === NETWORK_OSTATUS)
2142 $force_parent = true;
2143 if(strlen($datarray['title']))
2144 unset($datarray['title']);
2145 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2146 dbesc(datetime_convert()),
2148 intval($importer['uid'])
2150 $datarray['last-child'] = 1;
2151 update_thread_uri($parent_uri, $importer['uid']);
2155 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2157 intval($importer['uid'])
2160 // Update content if 'updated' changes
2163 if (edited_timestamp_is_newer($r[0], $datarray)) {
2165 // do not accept (ignore) an earlier edit than one we currently have.
2166 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2169 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2170 dbesc($datarray['title']),
2171 dbesc($datarray['body']),
2172 dbesc($datarray['tag']),
2173 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2174 dbesc(datetime_convert()),
2176 intval($importer['uid'])
2178 create_tags_from_itemuri($item_id, $importer['uid']);
2179 update_thread_uri($item_id, $importer['uid']);
2182 // update last-child if it changes
2184 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2185 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2186 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2187 dbesc(datetime_convert()),
2189 intval($importer['uid'])
2191 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2192 intval($allow[0]['data']),
2193 dbesc(datetime_convert()),
2195 intval($importer['uid'])
2197 update_thread_uri($item_id, $importer['uid']);
2203 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2204 // one way feed - no remote comment ability
2205 $datarray['last-child'] = 0;
2207 $datarray['parent-uri'] = $parent_uri;
2208 $datarray['uid'] = $importer['uid'];
2209 $datarray['contact-id'] = $contact['id'];
2210 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2211 $datarray['type'] = 'activity';
2212 $datarray['gravity'] = GRAVITY_LIKE;
2213 // only one like or dislike per person
2214 // splitted into two queries for performance issues
2215 $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",
2216 intval($datarray['uid']),
2217 intval($datarray['contact-id']),
2218 dbesc($datarray['verb']),
2224 $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",
2225 intval($datarray['uid']),
2226 intval($datarray['contact-id']),
2227 dbesc($datarray['verb']),
2234 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2235 $xo = parse_xml_string($datarray['object'],false);
2236 $xt = parse_xml_string($datarray['target'],false);
2238 if($xt->type == ACTIVITY_OBJ_NOTE) {
2239 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2241 intval($importer['importer_uid'])
2246 // extract tag, if not duplicate, add to parent item
2247 if($xo->id && $xo->content) {
2248 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2249 if(! (stristr($r[0]['tag'],$newtag))) {
2250 q("UPDATE item SET tag = '%s' WHERE id = %d",
2251 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2254 create_tags_from_item($r[0]['id']);
2260 $r = item_store($datarray,$force_parent);
2266 // Head post of a conversation. Have we seen it? If not, import it.
2268 $item_id = $item->get_id();
2270 $datarray = get_atom_elements($feed, $item, $contact);
2272 if(is_array($contact)) {
2273 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2274 $datarray['author-name'] = $contact['name'];
2275 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2276 $datarray['author-link'] = $contact['url'];
2277 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2278 $datarray['author-avatar'] = $contact['thumb'];
2281 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2282 logger('consume_feed: no author information! ' . print_r($datarray,true));
2286 // special handling for events
2288 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2289 $ev = bbtoevent($datarray['body']);
2290 if(x($ev,'desc') && x($ev,'start')) {
2291 $ev['uid'] = $importer['uid'];
2292 $ev['uri'] = $item_id;
2293 $ev['edited'] = $datarray['edited'];
2294 $ev['private'] = $datarray['private'];
2296 if(is_array($contact))
2297 $ev['cid'] = $contact['id'];
2298 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2300 intval($importer['uid'])
2303 $ev['id'] = $r[0]['id'];
2304 $xyz = event_store($ev);
2309 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2310 if(strlen($datarray['title']))
2311 unset($datarray['title']);
2312 $datarray['last-child'] = 1;
2316 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2318 intval($importer['uid'])
2321 // Update content if 'updated' changes
2324 if (edited_timestamp_is_newer($r[0], $datarray)) {
2326 // do not accept (ignore) an earlier edit than one we currently have.
2327 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2330 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2331 dbesc($datarray['title']),
2332 dbesc($datarray['body']),
2333 dbesc($datarray['tag']),
2334 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2335 dbesc(datetime_convert()),
2337 intval($importer['uid'])
2339 create_tags_from_itemuri($item_id, $importer['uid']);
2340 update_thread_uri($item_id, $importer['uid']);
2343 // update last-child if it changes
2345 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2346 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2347 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2348 intval($allow[0]['data']),
2349 dbesc(datetime_convert()),
2351 intval($importer['uid'])
2353 update_thread_uri($item_id, $importer['uid']);
2358 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2359 logger('consume-feed: New follower');
2360 new_follower($importer,$contact,$datarray,$item);
2363 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2364 lose_follower($importer,$contact,$datarray,$item);
2368 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2369 logger('consume-feed: New friend request');
2370 new_follower($importer,$contact,$datarray,$item,true);
2373 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2374 lose_sharer($importer,$contact,$datarray,$item);
2379 if(! is_array($contact))
2383 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2384 // one way feed - no remote comment ability
2385 $datarray['last-child'] = 0;
2387 if($contact['network'] === NETWORK_FEED)
2388 $datarray['private'] = 2;
2390 // This is my contact on another system, but it's really me.
2391 // Turn this into a wall post.
2393 if($contact['remote_self']) {
2394 $datarray['wall'] = 1;
2395 if($contact['network'] === NETWORK_FEED) {
2396 $datarray['private'] = 0;
2400 $datarray['parent-uri'] = $item_id;
2401 $datarray['uid'] = $importer['uid'];
2402 $datarray['contact-id'] = $contact['id'];
2404 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2405 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2406 // but otherwise there's a possible data mixup on the sender's system.
2407 // the tgroup delivery code called from item_store will correct it if it's a forum,
2408 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2409 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2410 $datarray['owner-name'] = $contact['name'];
2411 $datarray['owner-link'] = $contact['url'];
2412 $datarray['owner-avatar'] = $contact['thumb'];
2415 // We've allowed "followers" to reach this point so we can decide if they are
2416 // posting an @-tag delivery, which followers are allowed to do for certain
2417 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2419 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2423 $r = item_store($datarray);
2431 function local_delivery($importer,$data) {
2434 logger(__function__, LOGGER_TRACE);
2436 if($importer['readonly']) {
2437 // We aren't receiving stuff from this person. But we will quietly ignore them
2438 // rather than a blatant "go away" message.
2439 logger('local_delivery: ignoring');
2444 // Consume notification feed. This may differ from consuming a public feed in several ways
2445 // - might contain email or friend suggestions
2446 // - might contain remote followup to our message
2447 // - in which case we need to accept it and then notify other conversants
2448 // - we may need to send various email notifications
2450 $feed = new SimplePie();
2451 $feed->set_raw_data($data);
2452 $feed->enable_order_by_date(false);
2457 logger('local_delivery: Error parsing XML: ' . $feed->error());
2460 // Check at the feed level for updated contact name and/or photo
2464 $photo_timestamp = '';
2468 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2470 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2472 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2475 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2476 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2477 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2478 $new_name = $elems['name'][0]['data'];
2480 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2481 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2482 $photo_url = $elems['link'][0]['attribs']['']['href'];
2486 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2487 logger('local_delivery: Updating photo for ' . $importer['name']);
2488 require_once("include/Photo.php");
2489 $photo_failure = false;
2490 $have_photo = false;
2492 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2493 intval($importer['id']),
2494 intval($importer['importer_uid'])
2497 $resource_id = $r[0]['resource-id'];
2501 $resource_id = photo_new_resource();
2504 $img_str = fetch_url($photo_url,true);
2505 // guess mimetype from headers or filename
2506 $type = guess_image_type($photo_url,true);
2509 $img = new Photo($img_str, $type);
2510 if($img->is_valid()) {
2512 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2513 dbesc($resource_id),
2514 intval($importer['id']),
2515 intval($importer['importer_uid'])
2519 $img->scaleImageSquare(175);
2521 $hash = $resource_id;
2522 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2524 $img->scaleImage(80);
2525 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2527 $img->scaleImage(48);
2528 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2532 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2533 WHERE `uid` = %d AND `id` = %d",
2534 dbesc(datetime_convert()),
2535 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2536 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2537 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2538 intval($importer['importer_uid']),
2539 intval($importer['id'])
2544 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2545 $r = q("select * from contact where uid = %d and id = %d limit 1",
2546 intval($importer['importer_uid']),
2547 intval($importer['id'])
2550 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2551 dbesc(notags(trim($new_name))),
2552 dbesc(datetime_convert()),
2553 intval($importer['importer_uid']),
2554 intval($importer['id'])
2557 // do our best to update the name on content items
2560 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2561 dbesc(notags(trim($new_name))),
2562 dbesc($r[0]['name']),
2563 dbesc($r[0]['url']),
2564 intval($importer['importer_uid'])
2571 // Currently unsupported - needs a lot of work
2572 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2573 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2574 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2576 $newloc['uid'] = $importer['importer_uid'];
2577 $newloc['cid'] = $importer['id'];
2578 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2579 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2580 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2581 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2582 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2583 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2584 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2585 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2586 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2587 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2588 /** relocated user must have original key pair */
2589 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2590 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2592 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2595 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2596 intval($importer['id']),
2597 intval($importer['importer_uid']));
2602 $x = q("UPDATE contact SET
2612 `site-pubkey` = '%s'
2613 WHERE id=%d AND uid=%d;",
2614 dbesc($newloc['name']),
2615 dbesc($newloc['photo']),
2616 dbesc($newloc['thumb']),
2617 dbesc($newloc['micro']),
2618 dbesc($newloc['url']),
2619 dbesc($newloc['request']),
2620 dbesc($newloc['confirm']),
2621 dbesc($newloc['notify']),
2622 dbesc($newloc['poll']),
2623 dbesc($newloc['sitepubkey']),
2624 intval($importer['id']),
2625 intval($importer['importer_uid']));
2631 'owner-link' => array($old['url'], $newloc['url']),
2632 'author-link' => array($old['url'], $newloc['url']),
2633 'owner-avatar' => array($old['photo'], $newloc['photo']),
2634 'author-avatar' => array($old['photo'], $newloc['photo']),
2636 foreach ($fields as $n=>$f){
2637 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2640 intval($importer['importer_uid']));
2646 // merge with current record, current contents have priority
2647 // update record, set url-updated
2648 // update profile photos
2654 // handle friend suggestion notification
2656 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2657 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2658 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2660 $fsugg['uid'] = $importer['importer_uid'];
2661 $fsugg['cid'] = $importer['id'];
2662 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2663 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2664 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2665 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2666 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2668 // Does our member already have a friend matching this description?
2670 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2671 dbesc($fsugg['name']),
2672 dbesc(normalise_link($fsugg['url'])),
2673 intval($fsugg['uid'])
2678 // Do we already have an fcontact record for this person?
2681 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2682 dbesc($fsugg['url']),
2683 dbesc($fsugg['name']),
2684 dbesc($fsugg['request'])
2689 // OK, we do. Do we already have an introduction for this person ?
2690 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2691 intval($fsugg['uid']),
2698 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2699 dbesc($fsugg['name']),
2700 dbesc($fsugg['url']),
2701 dbesc($fsugg['photo']),
2702 dbesc($fsugg['request'])
2704 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2705 dbesc($fsugg['url']),
2706 dbesc($fsugg['name']),
2707 dbesc($fsugg['request'])
2712 // database record did not get created. Quietly give up.
2717 $hash = random_string();
2719 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2720 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2721 intval($fsugg['uid']),
2723 intval($fsugg['cid']),
2724 dbesc($fsugg['body']),
2726 dbesc(datetime_convert()),
2731 'type' => NOTIFY_SUGGEST,
2732 'notify_flags' => $importer['notify-flags'],
2733 'language' => $importer['language'],
2734 'to_name' => $importer['username'],
2735 'to_email' => $importer['email'],
2736 'uid' => $importer['importer_uid'],
2738 'link' => $a->get_baseurl() . '/notifications/intros',
2739 'source_name' => $importer['name'],
2740 'source_link' => $importer['url'],
2741 'source_photo' => $importer['photo'],
2742 'verb' => ACTIVITY_REQ_FRIEND,
2751 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2752 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2754 logger('local_delivery: private message received');
2757 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2760 $msg['uid'] = $importer['importer_uid'];
2761 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2762 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2763 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2764 $msg['contact-id'] = $importer['id'];
2765 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2766 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2768 $msg['replied'] = 0;
2769 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2770 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2771 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2775 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2776 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2778 // send notifications.
2780 require_once('include/enotify.php');
2782 $notif_params = array(
2783 'type' => NOTIFY_MAIL,
2784 'notify_flags' => $importer['notify-flags'],
2785 'language' => $importer['language'],
2786 'to_name' => $importer['username'],
2787 'to_email' => $importer['email'],
2788 'uid' => $importer['importer_uid'],
2790 'source_name' => $msg['from-name'],
2791 'source_link' => $importer['url'],
2792 'source_photo' => $importer['thumb'],
2793 'verb' => ACTIVITY_POST,
2797 notification($notif_params);
2803 $community_page = 0;
2804 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2806 $community_page = intval($rawtags[0]['data']);
2808 if(intval($importer['forum']) != $community_page) {
2809 q("update contact set forum = %d where id = %d",
2810 intval($community_page),
2811 intval($importer['id'])
2813 $importer['forum'] = (string) $community_page;
2816 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2818 // process any deleted entries
2820 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2821 if(is_array($del_entries) && count($del_entries)) {
2822 foreach($del_entries as $dentry) {
2824 if(isset($dentry['attribs']['']['ref'])) {
2825 $uri = $dentry['attribs']['']['ref'];
2827 if(isset($dentry['attribs']['']['when'])) {
2828 $when = $dentry['attribs']['']['when'];
2829 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2832 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2836 // check for relayed deletes to our conversation
2839 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2841 intval($importer['importer_uid'])
2844 $parent_uri = $r[0]['parent-uri'];
2845 if($r[0]['id'] != $r[0]['parent'])
2852 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2855 logger('local_delivery: possible community delete');
2858 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2860 // was the top-level post for this reply written by somebody on this site?
2861 // Specifically, the recipient?
2863 $is_a_remote_delete = false;
2865 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2866 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2867 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2868 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2869 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2870 AND `item`.`uid` = %d
2876 intval($importer['importer_uid'])
2879 $is_a_remote_delete = true;
2881 // Does this have the characteristics of a community or private group comment?
2882 // If it's a reply to a wall post on a community/prvgroup page it's a
2883 // valid community comment. Also forum_mode makes it valid for sure.
2884 // If neither, it's not.
2886 if($is_a_remote_delete && $community) {
2887 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2888 $is_a_remote_delete = false;
2889 logger('local_delivery: not a community delete');
2893 if($is_a_remote_delete) {
2894 logger('local_delivery: received remote delete');
2898 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2899 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2901 intval($importer['importer_uid']),
2902 intval($importer['id'])
2908 if($item['deleted'])
2911 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2913 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2914 $xo = parse_xml_string($item['object'],false);
2915 $xt = parse_xml_string($item['target'],false);
2917 if($xt->type === ACTIVITY_OBJ_NOTE) {
2918 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2920 intval($importer['importer_uid'])
2924 // For tags, the owner cannot remove the tag on the author's copy of the post.
2926 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2927 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2928 $author_copy = (($item['origin']) ? true : false);
2930 if($owner_remove && $author_copy)
2932 if($author_remove || $owner_remove) {
2933 $tags = explode(',',$i[0]['tag']);
2936 foreach($tags as $tag)
2937 if(trim($tag) !== trim($xo->body))
2938 $newtags[] = trim($tag);
2940 q("update item set tag = '%s' where id = %d",
2941 dbesc(implode(',',$newtags)),
2944 create_tags_from_item($i[0]['id']);
2950 if($item['uri'] == $item['parent-uri']) {
2951 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2952 `body` = '', `title` = ''
2953 WHERE `parent-uri` = '%s' AND `uid` = %d",
2955 dbesc(datetime_convert()),
2956 dbesc($item['uri']),
2957 intval($importer['importer_uid'])
2959 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2960 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2961 update_thread_uri($item['uri'], $importer['importer_uid']);
2964 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2965 `body` = '', `title` = ''
2966 WHERE `uri` = '%s' AND `uid` = %d",
2968 dbesc(datetime_convert()),
2970 intval($importer['importer_uid'])
2972 create_tags_from_itemuri($uri, $importer['importer_uid']);
2973 create_files_from_itemuri($uri, $importer['importer_uid']);
2974 update_thread_uri($uri, $importer['importer_uid']);
2975 if($item['last-child']) {
2976 // ensure that last-child is set in case the comment that had it just got wiped.
2977 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2978 dbesc(datetime_convert()),
2979 dbesc($item['parent-uri']),
2980 intval($item['uid'])
2982 // who is the last child now?
2983 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2984 ORDER BY `created` DESC LIMIT 1",
2985 dbesc($item['parent-uri']),
2986 intval($importer['importer_uid'])
2989 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2994 // if this is a relayed delete, propagate it to other recipients
2996 if($is_a_remote_delete)
2997 proc_run('php',"include/notifier.php","drop",$item['id']);
3005 foreach($feed->get_items() as $item) {
3008 $item_id = $item->get_id();
3009 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3010 if(isset($rawthread[0]['attribs']['']['ref'])) {
3012 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3018 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3021 logger('local_delivery: possible community reply');
3024 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3026 // was the top-level post for this reply written by somebody on this site?
3027 // Specifically, the recipient?
3029 $is_a_remote_comment = false;
3030 $top_uri = $parent_uri;
3032 $r = q("select `item`.`parent-uri` from `item`
3033 WHERE `item`.`uri` = '%s'
3037 if($r && count($r)) {
3038 $top_uri = $r[0]['parent-uri'];
3040 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3041 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3042 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3043 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3044 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3045 AND `item`.`uid` = %d
3051 intval($importer['importer_uid'])
3054 $is_a_remote_comment = true;
3057 // Does this have the characteristics of a community or private group comment?
3058 // If it's a reply to a wall post on a community/prvgroup page it's a
3059 // valid community comment. Also forum_mode makes it valid for sure.
3060 // If neither, it's not.
3062 if($is_a_remote_comment && $community) {
3063 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3064 $is_a_remote_comment = false;
3065 logger('local_delivery: not a community reply');
3069 if($is_a_remote_comment) {
3070 logger('local_delivery: received remote comment');
3072 // remote reply to our post. Import and then notify everybody else.
3074 $datarray = get_atom_elements($feed, $item);
3076 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3078 intval($importer['importer_uid'])
3081 // Update content if 'updated' changes
3085 if (edited_timestamp_is_newer($r[0], $datarray)) {
3087 // do not accept (ignore) an earlier edit than one we currently have.
3088 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3091 logger('received updated comment' , LOGGER_DEBUG);
3092 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3093 dbesc($datarray['title']),
3094 dbesc($datarray['body']),
3095 dbesc($datarray['tag']),
3096 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3097 dbesc(datetime_convert()),
3099 intval($importer['importer_uid'])
3101 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3103 proc_run('php',"include/notifier.php","comment-import",$iid);
3112 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3113 intval($importer['importer_uid'])
3117 $datarray['type'] = 'remote-comment';
3118 $datarray['wall'] = 1;
3119 $datarray['parent-uri'] = $parent_uri;
3120 $datarray['uid'] = $importer['importer_uid'];
3121 $datarray['owner-name'] = $own[0]['name'];
3122 $datarray['owner-link'] = $own[0]['url'];
3123 $datarray['owner-avatar'] = $own[0]['thumb'];
3124 $datarray['contact-id'] = $importer['id'];
3126 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3128 $datarray['type'] = 'activity';
3129 $datarray['gravity'] = GRAVITY_LIKE;
3130 $datarray['last-child'] = 0;
3131 // only one like or dislike per person
3132 // splitted into two queries for performance issues
3133 $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",
3134 intval($datarray['uid']),
3135 intval($datarray['contact-id']),
3136 dbesc($datarray['verb']),
3137 dbesc($datarray['parent-uri'])
3143 $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",
3144 intval($datarray['uid']),
3145 intval($datarray['contact-id']),
3146 dbesc($datarray['verb']),
3147 dbesc($datarray['parent-uri'])
3154 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3156 $xo = parse_xml_string($datarray['object'],false);
3157 $xt = parse_xml_string($datarray['target'],false);
3159 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3161 // fetch the parent item
3163 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3165 intval($importer['importer_uid'])
3170 // extract tag, if not duplicate, and this user allows tags, add to parent item
3172 if($xo->id && $xo->content) {
3173 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3174 if(! (stristr($tagp[0]['tag'],$newtag))) {
3175 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3176 intval($importer['importer_uid'])
3178 if(count($i) && ! intval($i[0]['blocktags'])) {
3179 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3180 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3181 intval($tagp[0]['id']),
3182 dbesc(datetime_convert()),
3183 dbesc(datetime_convert())
3185 create_tags_from_item($tagp[0]['id']);
3193 $posted_id = item_store($datarray);
3197 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3199 intval($importer['importer_uid'])
3202 $parent = $r[0]['parent'];
3203 $parent_uri = $r[0]['parent-uri'];
3207 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3208 dbesc(datetime_convert()),
3209 intval($importer['importer_uid']),
3210 intval($r[0]['parent'])
3213 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3214 dbesc(datetime_convert()),
3215 intval($importer['importer_uid']),
3220 if($posted_id && $parent) {
3222 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3224 if((! $is_like) && (! $importer['self'])) {
3226 require_once('include/enotify.php');
3229 'type' => NOTIFY_COMMENT,
3230 'notify_flags' => $importer['notify-flags'],
3231 'language' => $importer['language'],
3232 'to_name' => $importer['username'],
3233 'to_email' => $importer['email'],
3234 'uid' => $importer['importer_uid'],
3235 'item' => $datarray,
3236 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3237 'source_name' => stripslashes($datarray['author-name']),
3238 'source_link' => $datarray['author-link'],
3239 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3240 ? $importer['thumb'] : $datarray['author-avatar']),
3241 'verb' => ACTIVITY_POST,
3243 'parent' => $parent,
3244 'parent_uri' => $parent_uri,
3256 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3258 $item_id = $item->get_id();
3259 $datarray = get_atom_elements($feed,$item);
3261 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3264 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3266 intval($importer['importer_uid'])
3269 // Update content if 'updated' changes
3272 if (edited_timestamp_is_newer($r[0], $datarray)) {
3274 // do not accept (ignore) an earlier edit than one we currently have.
3275 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3278 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3279 dbesc($datarray['title']),
3280 dbesc($datarray['body']),
3281 dbesc($datarray['tag']),
3282 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3283 dbesc(datetime_convert()),
3285 intval($importer['importer_uid'])
3287 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3290 // update last-child if it changes
3292 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3293 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3294 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3295 dbesc(datetime_convert()),
3297 intval($importer['importer_uid'])
3299 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3300 intval($allow[0]['data']),
3301 dbesc(datetime_convert()),
3303 intval($importer['importer_uid'])
3309 $datarray['parent-uri'] = $parent_uri;
3310 $datarray['uid'] = $importer['importer_uid'];
3311 $datarray['contact-id'] = $importer['id'];
3312 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3313 $datarray['type'] = 'activity';
3314 $datarray['gravity'] = GRAVITY_LIKE;
3315 // only one like or dislike per person
3316 // splitted into two queries for performance issues
3317 $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",
3318 intval($datarray['uid']),
3319 intval($datarray['contact-id']),
3320 dbesc($datarray['verb']),
3326 $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",
3327 intval($datarray['uid']),
3328 intval($datarray['contact-id']),
3329 dbesc($datarray['verb']),
3337 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3339 $xo = parse_xml_string($datarray['object'],false);
3340 $xt = parse_xml_string($datarray['target'],false);
3342 if($xt->type == ACTIVITY_OBJ_NOTE) {
3343 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3345 intval($importer['importer_uid'])
3350 // extract tag, if not duplicate, add to parent item
3352 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3353 q("UPDATE item SET tag = '%s' WHERE id = %d",
3354 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3357 create_tags_from_item($r[0]['id']);
3363 $posted_id = item_store($datarray);
3365 // find out if our user is involved in this conversation and wants to be notified.
3367 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3369 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3371 intval($importer['importer_uid'])
3374 if(count($myconv)) {
3375 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3377 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3378 if(! link_compare($datarray['author-link'],$importer_url)) {
3381 foreach($myconv as $conv) {
3383 // now if we find a match, it means we're in this conversation
3385 if(! link_compare($conv['author-link'],$importer_url))
3388 require_once('include/enotify.php');
3390 $conv_parent = $conv['parent'];
3393 'type' => NOTIFY_COMMENT,
3394 'notify_flags' => $importer['notify-flags'],
3395 'language' => $importer['language'],
3396 'to_name' => $importer['username'],
3397 'to_email' => $importer['email'],
3398 'uid' => $importer['importer_uid'],
3399 'item' => $datarray,
3400 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3401 'source_name' => stripslashes($datarray['author-name']),
3402 'source_link' => $datarray['author-link'],
3403 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3404 ? $importer['thumb'] : $datarray['author-avatar']),
3405 'verb' => ACTIVITY_POST,
3407 'parent' => $conv_parent,
3408 'parent_uri' => $parent_uri
3412 // only send one notification
3424 // Head post of a conversation. Have we seen it? If not, import it.
3427 $item_id = $item->get_id();
3428 $datarray = get_atom_elements($feed,$item);
3430 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3431 $ev = bbtoevent($datarray['body']);
3432 if(x($ev,'desc') && x($ev,'start')) {
3433 $ev['cid'] = $importer['id'];
3434 $ev['uid'] = $importer['uid'];
3435 $ev['uri'] = $item_id;
3436 $ev['edited'] = $datarray['edited'];
3437 $ev['private'] = $datarray['private'];
3439 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3441 intval($importer['uid'])
3444 $ev['id'] = $r[0]['id'];
3445 $xyz = event_store($ev);
3450 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3452 intval($importer['importer_uid'])
3455 // Update content if 'updated' changes
3458 if (edited_timestamp_is_newer($r[0], $datarray)) {
3460 // do not accept (ignore) an earlier edit than one we currently have.
3461 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3464 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3465 dbesc($datarray['title']),
3466 dbesc($datarray['body']),
3467 dbesc($datarray['tag']),
3468 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3469 dbesc(datetime_convert()),
3471 intval($importer['importer_uid'])
3473 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3474 update_thread_uri($item_id, $importer['importer_uid']);
3477 // update last-child if it changes
3479 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3480 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3481 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3482 intval($allow[0]['data']),
3483 dbesc(datetime_convert()),
3485 intval($importer['importer_uid'])
3491 // This is my contact on another system, but it's really me.
3492 // Turn this into a wall post.
3494 if($importer['remote_self'])
3495 $datarray['wall'] = 1;
3497 $datarray['parent-uri'] = $item_id;
3498 $datarray['uid'] = $importer['importer_uid'];
3499 $datarray['contact-id'] = $importer['id'];
3502 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3503 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3504 // but otherwise there's a possible data mixup on the sender's system.
3505 // the tgroup delivery code called from item_store will correct it if it's a forum,
3506 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3507 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3508 $datarray['owner-name'] = $importer['senderName'];
3509 $datarray['owner-link'] = $importer['url'];
3510 $datarray['owner-avatar'] = $importer['thumb'];
3513 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3516 $posted_id = item_store($datarray);
3518 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3519 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3522 $xo = parse_xml_string($datarray['object'],false);
3524 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3526 // somebody was poked/prodded. Was it me?
3528 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3530 foreach($links->link as $l) {
3531 $atts = $l->attributes();
3532 switch($atts['rel']) {
3534 $Blink = $atts['href'];
3540 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3542 // send a notification
3543 require_once('include/enotify.php');
3546 'type' => NOTIFY_POKE,
3547 'notify_flags' => $importer['notify-flags'],
3548 'language' => $importer['language'],
3549 'to_name' => $importer['username'],
3550 'to_email' => $importer['email'],
3551 'uid' => $importer['importer_uid'],
3552 'item' => $datarray,
3553 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3554 'source_name' => stripslashes($datarray['author-name']),
3555 'source_link' => $datarray['author-link'],
3556 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3557 ? $importer['thumb'] : $datarray['author-avatar']),
3558 'verb' => $datarray['verb'],
3559 'otype' => 'person',
3560 'activity' => $verb,
3577 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3578 $url = notags(trim($datarray['author-link']));
3579 $name = notags(trim($datarray['author-name']));
3580 $photo = notags(trim($datarray['author-avatar']));
3582 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3583 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3584 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3586 if(is_array($contact)) {
3587 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3588 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3589 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3590 intval(CONTACT_IS_FRIEND),
3591 intval($contact['id']),
3592 intval($importer['uid'])
3595 // send email notification to owner?
3599 // create contact record
3601 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3602 `blocked`, `readonly`, `pending`, `writable` )
3603 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3604 intval($importer['uid']),
3605 dbesc(datetime_convert()),
3607 dbesc(normalise_link($url)),
3611 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3612 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3614 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3615 intval($importer['uid']),
3619 $contact_record = $r[0];
3621 // create notification
3622 $hash = random_string();
3624 if(is_array($contact_record)) {
3625 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3626 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3627 intval($importer['uid']),
3628 intval($contact_record['id']),
3630 dbesc(datetime_convert())
3633 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3634 intval($importer['uid'])
3639 if(intval($r[0]['def_gid'])) {
3640 require_once('include/group.php');
3641 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3644 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3645 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3646 $email = replace_macros($email_tpl, array(
3647 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3649 '$myname' => $r[0]['username'],
3650 '$siteurl' => $a->get_baseurl(),
3651 '$sitename' => $a->config['sitename']
3653 $res = mail($r[0]['email'],
3654 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'),
3656 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3657 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3658 . 'Content-transfer-encoding: 8bit' );
3665 function lose_follower($importer,$contact,$datarray,$item) {
3667 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3668 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3669 intval(CONTACT_IS_SHARING),
3670 intval($contact['id'])
3674 contact_remove($contact['id']);
3678 function lose_sharer($importer,$contact,$datarray,$item) {
3680 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3681 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3682 intval(CONTACT_IS_FOLLOWER),
3683 intval($contact['id'])
3687 contact_remove($contact['id']);
3692 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3696 if(is_array($importer)) {
3697 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3698 intval($importer['uid'])
3702 // Diaspora has different message-ids in feeds than they do
3703 // through the direct Diaspora protocol. If we try and use
3704 // the feed, we'll get duplicates. So don't.
3706 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3709 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3711 // Use a single verify token, even if multiple hubs
3713 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3715 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3717 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3719 if(! strlen($contact['hub-verify'])) {
3720 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3721 dbesc($verify_token),
3722 intval($contact['id'])
3726 post_url($url,$params);
3728 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3735 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3739 $name = xmlify($name);
3740 $uri = xmlify($uri);
3743 $photo = xmlify($photo);
3747 $o .= "<name>$name</name>\r\n";
3748 $o .= "<uri>$uri</uri>\r\n";
3749 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3750 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3752 call_hooks('atom_author', $o);
3754 $o .= "</$tag>\r\n";
3758 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3762 if(! $item['parent'])
3765 if($item['deleted'])
3766 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3769 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3770 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3772 $body = $item['body'];
3774 $o = "\r\n\r\n<entry>\r\n";
3776 if(is_array($author))
3777 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3779 $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']));
3780 if(strlen($item['owner-name']))
3781 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3783 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3784 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3785 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3788 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3789 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3790 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3791 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3792 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3793 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3794 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3796 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3798 if($item['location']) {
3799 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3800 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3804 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3806 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3807 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3810 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3811 if($item['bookmark'])
3812 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3815 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3818 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3820 if($item['signed_text']) {
3821 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3822 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3825 $verb = construct_verb($item);
3826 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3827 $actobj = construct_activity_object($item);
3830 $actarg = construct_activity_target($item);
3834 $tags = item_getfeedtags($item);
3836 foreach($tags as $t) {
3837 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3841 $o .= item_getfeedattach($item);
3843 $mentioned = get_mentions($item);
3847 call_hooks('atom_entry', $o);
3849 $o .= '</entry>' . "\r\n";
3854 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3856 if(get_config('system','disable_embedded'))
3861 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3862 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3867 $img_start = strpos($orig_body, '[img');
3868 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3869 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3870 while( ($img_st_close !== false) && ($img_len !== false) ) {
3872 $img_st_close++; // make it point to AFTER the closing bracket
3873 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3875 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3878 if(stristr($image , $site . '/photo/')) {
3879 // Only embed locally hosted photos
3881 $i = basename($image);
3882 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3883 $x = strpos($i,'-');
3886 $res = substr($i,$x+1);
3887 $i = substr($i,0,$x);
3888 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3895 // Check to see if we should replace this photo link with an embedded image
3896 // 1. No need to do so if the photo is public
3897 // 2. If there's a contact-id provided, see if they're in the access list
3898 // for the photo. If so, embed it.
3899 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3900 // permissions, regardless of order but first check to see if they're an exact
3901 // match to save some processing overhead.
3903 if(has_permissions($r[0])) {
3905 $recips = enumerate_permissions($r[0]);
3906 if(in_array($cid, $recips)) {
3911 if(compare_permissions($item,$r[0]))
3916 $data = $r[0]['data'];
3917 $type = $r[0]['type'];
3919 // If a custom width and height were specified, apply before embedding
3920 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3921 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3923 $width = intval($match[1]);
3924 $height = intval($match[2]);
3926 $ph = new Photo($data, $type);
3927 if($ph->is_valid()) {
3928 $ph->scaleImage(max($width, $height));
3929 $data = $ph->imageString();
3930 $type = $ph->getType();
3934 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3935 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3936 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3942 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3943 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3944 if($orig_body === false)
3947 $img_start = strpos($orig_body, '[img');
3948 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3949 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3952 $new_body = $new_body . $orig_body;
3958 function has_permissions($obj) {
3959 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3964 function compare_permissions($obj1,$obj2) {
3965 // first part is easy. Check that these are exactly the same.
3966 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3967 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3968 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3969 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3972 // This is harder. Parse all the permissions and compare the resulting set.
3974 $recipients1 = enumerate_permissions($obj1);
3975 $recipients2 = enumerate_permissions($obj2);
3978 if($recipients1 == $recipients2)
3983 // returns an array of contact-ids that are allowed to see this object
3985 function enumerate_permissions($obj) {
3986 require_once('include/group.php');
3987 $allow_people = expand_acl($obj['allow_cid']);
3988 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3989 $deny_people = expand_acl($obj['deny_cid']);
3990 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3991 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3992 $deny = array_unique(array_merge($deny_people,$deny_groups));
3993 $recipients = array_diff($recipients,$deny);
3997 function item_getfeedtags($item) {
4000 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4002 for($x = 0; $x < $cnt; $x ++) {
4004 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4008 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4010 for($x = 0; $x < $cnt; $x ++) {
4012 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4018 function item_getfeedattach($item) {
4020 $arr = explode('[/attach],',$item['attach']);
4022 foreach($arr as $r) {
4024 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4026 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4027 if(intval($matches[2]))
4028 $ret .= 'length="' . intval($matches[2]) . '" ';
4029 if($matches[4] !== ' ')
4030 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4031 $ret .= ' />' . "\r\n";
4040 function item_expire($uid,$days) {
4042 if((! $uid) || ($days < 1))
4045 // $expire_network_only = save your own wall posts
4046 // and just expire conversations started by others
4048 $expire_network_only = get_pconfig($uid,'expire','network_only');
4049 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4051 $r = q("SELECT * FROM `item`
4053 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4064 $expire_items = get_pconfig($uid, 'expire','items');
4065 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4067 $expire_notes = get_pconfig($uid, 'expire','notes');
4068 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4070 $expire_starred = get_pconfig($uid, 'expire','starred');
4071 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4073 $expire_photos = get_pconfig($uid, 'expire','photos');
4074 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4076 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4078 foreach($r as $item) {
4080 // don't expire filed items
4082 if(strpos($item['file'],'[') !== false)
4085 // Only expire posts, not photos and photo comments
4087 if($expire_photos==0 && strlen($item['resource-id']))
4089 if($expire_starred==0 && intval($item['starred']))
4091 if($expire_notes==0 && $item['type']=='note')
4093 if($expire_items==0 && $item['type']!='note')
4096 drop_item($item['id'],false);
4099 proc_run('php',"include/notifier.php","expire","$uid");
4104 function drop_items($items) {
4107 if(! local_user() && ! remote_user())
4111 foreach($items as $item) {
4112 $owner = drop_item($item,false);
4113 if($owner && ! $uid)
4118 // multiple threads may have been deleted, send an expire notification
4121 proc_run('php',"include/notifier.php","expire","$uid");
4125 function drop_item($id,$interactive = true) {
4129 // locate item to be deleted
4131 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4138 notice( t('Item not found.') . EOL);
4139 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4144 $owner = $item['uid'];
4148 // check if logged in user is either the author or owner of this item
4150 if(is_array($_SESSION['remote'])) {
4151 foreach($_SESSION['remote'] as $visitor) {
4152 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4153 $cid = $visitor['cid'];
4160 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4162 // Check if we should do HTML-based delete confirmation
4163 if($_REQUEST['confirm']) {
4164 // <form> can't take arguments in its "action" parameter
4165 // so add any arguments as hidden inputs
4166 $query = explode_querystring($a->query_string);
4168 foreach($query['args'] as $arg) {
4169 if(strpos($arg, 'confirm=') === false) {
4170 $arg_parts = explode('=', $arg);
4171 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4175 return replace_macros(get_markup_template('confirm.tpl'), array(
4177 '$message' => t('Do you really want to delete this item?'),
4178 '$extra_inputs' => $inputs,
4179 '$confirm' => t('Yes'),
4180 '$confirm_url' => $query['base'],
4181 '$confirm_name' => 'confirmed',
4182 '$cancel' => t('Cancel'),
4185 // Now check how the user responded to the confirmation query
4186 if($_REQUEST['canceled']) {
4187 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4190 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4193 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4194 dbesc(datetime_convert()),
4195 dbesc(datetime_convert()),
4198 create_tags_from_item($item['id']);
4199 create_files_from_item($item['id']);
4200 delete_thread($item['id']);
4202 // clean up categories and tags so they don't end up as orphans
4205 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4207 foreach($matches as $mtch) {
4208 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4214 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4216 foreach($matches as $mtch) {
4217 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4221 // If item is a link to a photo resource, nuke all the associated photos
4222 // (visitors will not have photo resources)
4223 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4224 // generate a resource-id and therefore aren't intimately linked to the item.
4226 if(strlen($item['resource-id'])) {
4227 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4228 dbesc($item['resource-id']),
4229 intval($item['uid'])
4231 // ignore the result
4234 // If item is a link to an event, nuke the event record.
4236 if(intval($item['event-id'])) {
4237 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4238 intval($item['event-id']),
4239 intval($item['uid'])
4241 // ignore the result
4244 // clean up item_id and sign meta-data tables
4247 // Old code - caused very long queries and warning entries in the mysql logfiles:
4249 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4250 intval($item['id']),
4251 intval($item['uid'])
4254 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4255 intval($item['id']),
4256 intval($item['uid'])
4260 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4262 // Creating list of parents
4263 $r = q("select id from item where parent = %d and uid = %d",
4264 intval($item['id']),
4265 intval($item['uid'])
4270 foreach ($r AS $row) {
4271 if ($parentid != "")
4274 $parentid .= $row["id"];
4278 if ($parentid != "") {
4279 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4281 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4284 // If it's the parent of a comment thread, kill all the kids
4286 if($item['uri'] == $item['parent-uri']) {
4287 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4288 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4289 dbesc(datetime_convert()),
4290 dbesc(datetime_convert()),
4291 dbesc($item['parent-uri']),
4292 intval($item['uid'])
4294 create_tags_from_item($item['parent-uri'], $item['uid']);
4295 create_files_from_item($item['parent-uri'], $item['uid']);
4296 delete_thread_uri($item['parent-uri'], $item['uid']);
4297 // ignore the result
4300 // ensure that last-child is set in case the comment that had it just got wiped.
4301 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4302 dbesc(datetime_convert()),
4303 dbesc($item['parent-uri']),
4304 intval($item['uid'])
4306 // who is the last child now?
4307 $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",
4308 dbesc($item['parent-uri']),
4309 intval($item['uid'])
4312 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4317 // Add a relayable_retraction signature for Diaspora.
4318 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4320 $drop_id = intval($item['id']);
4322 // send the notification upstream/downstream as the case may be
4324 proc_run('php',"include/notifier.php","drop","$drop_id");
4328 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4334 notice( t('Permission denied.') . EOL);
4335 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4342 function first_post_date($uid,$wall = false) {
4343 $r = q("select id, created from item
4344 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4346 order by created asc limit 1",
4348 intval($wall ? 1 : 0)
4351 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4352 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4357 function posted_dates($uid,$wall) {
4358 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4360 $dthen = first_post_date($uid,$wall);
4364 // If it's near the end of a long month, backup to the 28th so that in
4365 // consecutive loops we'll always get a whole month difference.
4367 if(intval(substr($dnow,8)) > 28)
4368 $dnow = substr($dnow,0,8) . '28';
4369 if(intval(substr($dthen,8)) > 28)
4370 $dnow = substr($dthen,0,8) . '28';
4373 // Starting with the current month, get the first and last days of every
4374 // month down to and including the month of the first post
4375 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4376 $dstart = substr($dnow,0,8) . '01';
4377 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4378 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4379 $end_month = datetime_convert('','',$dend,'Y-m-d');
4380 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4381 $ret[] = array($str,$end_month,$start_month);
4382 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4388 function posted_date_widget($url,$uid,$wall) {
4391 if(! feature_enabled($uid,'archives'))
4394 // For former Facebook folks that left because of "timeline"
4396 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4399 $ret = posted_dates($uid,$wall);
4403 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4404 '$title' => t('Archives'),
4405 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4412 function store_diaspora_retract_sig($item, $user, $baseurl) {
4413 // Note that we can't add a target_author_signature
4414 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4415 // the comment, that means we're the home of the post, and Diaspora will only
4416 // check the parent_author_signature of retractions that it doesn't have to relay further
4418 // I don't think this function gets called for an "unlike," but I'll check anyway
4420 $enabled = intval(get_config('system','diaspora_enabled'));
4422 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4426 logger('drop_item: storing diaspora retraction signature');
4428 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4430 if(local_user() == $item['uid']) {
4432 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4433 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4436 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4437 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4440 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4441 // only handles DFRN deletes
4442 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4443 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4444 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4450 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4451 intval($item['id']),
4452 dbesc($signed_text),