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/text.php');
10 require_once('include/email.php');
11 require_once('include/ostatus_conversation.php');
13 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
16 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
17 $public_feed = (($dfrn_id) ? false : true);
18 $starred = false; // not yet implemented, possible security issues
21 if($public_feed && $a->argc > 2) {
22 for($x = 2; $x < $a->argc; $x++) {
23 if($a->argv[$x] == 'converse')
25 if($a->argv[$x] == 'starred')
27 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
28 $category = $a->argv[$x+1];
34 // default permissions - anonymous user
36 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
38 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
39 FROM `contact` LEFT JOIN `user` ON `user`.`uid` = `contact`.`uid`
40 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
48 $owner_id = $owner['user_uid'];
49 $owner_nick = $owner['nickname'];
51 $birthday = feed_birthday($owner_id,$owner['timezone']);
58 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
62 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
63 $my_id = '1:' . $dfrn_id;
66 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '0:' . $dfrn_id;
74 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
82 require_once('include/security.php');
83 $groups = init_groups_visitor($contact['id']);
86 for($x = 0; $x < count($groups); $x ++)
87 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
88 $gs = implode('|', $groups);
91 $gs = '<<>>' ; // Impossible to match
93 $sql_extra = sprintf("
94 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
95 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
96 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
97 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
99 intval($contact['id']),
100 intval($contact['id']),
111 if(! strlen($last_update))
112 $last_update = 'now -30 days';
114 if(isset($category)) {
115 $sql_extra .= file_tag_file_query('item',$category,'category');
120 $sql_extra .= " AND `contact`.`self` = 1 ";
123 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
125 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
126 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
127 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
128 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
129 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
130 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
131 FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
132 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
133 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
134 AND `item`.`wall` = 1 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
135 AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
137 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
144 // Will check further below if this actually returned results.
145 // We will provide an empty feed if that is the case.
149 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
153 $hubxml = feed_hublinks();
155 $salmon = feed_salmonlinks($owner_nick);
157 $atom .= replace_macros($feed_template, array(
158 '$version' => xmlify(FRIENDICA_VERSION),
159 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
160 '$feed_title' => xmlify($owner['name']),
161 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
163 '$salmon' => $salmon,
164 '$name' => xmlify($owner['name']),
165 '$profile_page' => xmlify($owner['url']),
166 '$photo' => xmlify($owner['photo']),
167 '$thumb' => xmlify($owner['thumb']),
168 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
169 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
170 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
171 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
172 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
175 call_hooks('atom_feed', $atom);
177 if(! count($items)) {
179 call_hooks('atom_feed_end', $atom);
181 $atom .= '</feed>' . "\r\n";
185 foreach($items as $item) {
187 // prevent private email from leaking.
188 if($item['network'] === NETWORK_MAIL)
191 // public feeds get html, our own nodes use bbcode
195 // catch any email that's in a public conversation and make sure it doesn't leak
203 $atom .= atom_entry($item,$type,null,$owner,true);
206 call_hooks('atom_feed_end', $atom);
208 $atom .= '</feed>' . "\r\n";
214 function construct_verb($item) {
216 return $item['verb'];
217 return ACTIVITY_POST;
220 function construct_activity_object($item) {
222 if($item['object']) {
223 $o = '<as:object>' . "\r\n";
224 $r = parse_xml_string($item['object'],false);
230 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
232 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
234 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
236 if(substr($r->link,0,1) === '<') {
237 // patch up some facebook "like" activity objects that got stored incorrectly
238 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
239 // we can probably remove this hack here and in the following function in a few months time.
240 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
241 $r->link = str_replace('&','&', $r->link);
242 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
246 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
249 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
250 $o .= '</as:object>' . "\r\n";
257 function construct_activity_target($item) {
259 if($item['target']) {
260 $o = '<as:target>' . "\r\n";
261 $r = parse_xml_string($item['target'],false);
265 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
267 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
269 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
271 if(substr($r->link,0,1) === '<') {
272 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
273 $r->link = str_replace('&','&', $r->link);
274 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
278 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
281 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
282 $o .= '</as:target>' . "\r\n";
291 * The purpose of this function is to apply system message length limits to
292 * imported messages without including any embedded photos in the length
294 if(! function_exists('limit_body_size')) {
295 function limit_body_size($body) {
297 logger('limit_body_size: start', LOGGER_DEBUG);
299 $maxlen = get_max_import_size();
301 // If the length of the body, including the embedded images, is smaller
302 // than the maximum, then don't waste time looking for the images
303 if($maxlen && (strlen($body) > $maxlen)) {
305 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
312 $img_start = strpos($orig_body, '[img');
313 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
314 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
315 while(($img_st_close !== false) && ($img_end !== false)) {
317 $img_st_close++; // make it point to AFTER the closing bracket
318 $img_end += $img_start;
319 $img_end += strlen('[/img]');
321 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
322 // This is an embedded image
324 if( ($textlen + $img_start) > $maxlen ) {
325 if($textlen < $maxlen) {
326 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
327 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
332 $new_body = $new_body . substr($orig_body, 0, $img_start);
333 $textlen += $img_start;
336 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
340 if( ($textlen + $img_end) > $maxlen ) {
341 if($textlen < $maxlen) {
342 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
343 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
348 $new_body = $new_body . substr($orig_body, 0, $img_end);
349 $textlen += $img_end;
352 $orig_body = substr($orig_body, $img_end);
354 if($orig_body === false) // in case the body ends on a closing image tag
357 $img_start = strpos($orig_body, '[img');
358 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
359 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
362 if( ($textlen + strlen($orig_body)) > $maxlen) {
363 if($textlen < $maxlen) {
364 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
365 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
370 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
371 $new_body = $new_body . $orig_body;
372 $textlen += strlen($orig_body);
381 function title_is_body($title, $body) {
383 $title = strip_tags($title);
384 $title = trim($title);
385 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
386 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
388 $body = strip_tags($body);
390 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
391 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
393 if (strlen($title) < strlen($body))
394 $body = substr($body, 0, strlen($title));
396 if (($title != $body) and (substr($title, -3) == "...")) {
397 $pos = strrpos($title, "...");
399 $title = substr($title, 0, $pos);
400 $body = substr($body, 0, $pos);
404 return($title == $body);
409 function get_atom_elements($feed,$item) {
411 require_once('library/HTMLPurifier.auto.php');
412 require_once('include/html2bbcode.php');
414 $best_photo = array();
418 $author = $item->get_author();
420 $res['author-name'] = unxmlify($author->get_name());
421 $res['author-link'] = unxmlify($author->get_link());
424 $res['author-name'] = unxmlify($feed->get_title());
425 $res['author-link'] = unxmlify($feed->get_permalink());
427 $res['uri'] = unxmlify($item->get_id());
428 $res['title'] = unxmlify($item->get_title());
429 $res['body'] = unxmlify($item->get_content());
430 $res['plink'] = unxmlify($item->get_link(0));
432 // removing the content of the title if its identically to the body
433 // This helps with auto generated titles e.g. from tumblr
434 if (title_is_body($res["title"], $res["body"]))
438 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
442 // look for a photo. We should check media size and find the best one,
443 // but for now let's just find any author photo
445 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
447 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
448 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
449 foreach($base as $link) {
450 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
451 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
452 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
457 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
459 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
460 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
461 if($base && count($base)) {
462 foreach($base as $link) {
463 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
464 $res['author-link'] = unxmlify($link['attribs']['']['href']);
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
473 // No photo/profile-link on the item - look at the feed level
475 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
476 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
477 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
478 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
479 foreach($base as $link) {
480 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
481 $res['author-link'] = unxmlify($link['attribs']['']['href']);
482 if(! $res['author-avatar']) {
483 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
484 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
489 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
491 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
492 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 if($base && count($base)) {
495 foreach($base as $link) {
496 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
497 $res['author-link'] = unxmlify($link['attribs']['']['href']);
498 if(! (x($res,'author-avatar'))) {
499 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
500 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
507 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
508 if($apps && $apps[0]['attribs']['']['source']) {
509 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
510 if($res['app'] === 'web')
511 $res['app'] = 'OStatus';
514 // base64 encoded json structure representing Diaspora signature
516 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
518 $res['dsprsig'] = unxmlify($dsig[0]['data']);
521 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
523 $res['guid'] = unxmlify($dguid[0]['data']);
525 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
527 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
531 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
534 $have_real_body = false;
536 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
538 $have_real_body = true;
539 $res['body'] = $rawenv[0]['data'];
540 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
541 // make sure nobody is trying to sneak some html tags by us
542 $res['body'] = notags(base64url_decode($res['body']));
546 $res['body'] = limit_body_size($res['body']);
548 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
549 // the content type. Our own network only emits text normally, though it might have been converted to
550 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
551 // have to assume it is all html and needs to be purified.
553 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
554 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
555 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
558 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
560 $res['body'] = reltoabs($res['body'],$base_url);
562 $res['body'] = html2bb_video($res['body']);
564 $res['body'] = oembed_html2bbcode($res['body']);
566 $config = HTMLPurifier_Config::createDefault();
567 $config->set('Cache.DefinitionImpl', null);
569 // we shouldn't need a whitelist, because the bbcode converter
570 // will strip out any unsupported tags.
572 $purifier = new HTMLPurifier($config);
573 $res['body'] = $purifier->purify($res['body']);
575 $res['body'] = @html2bbcode($res['body']);
579 elseif(! $have_real_body) {
581 // it's not one of our messages and it has no tags
582 // so it's probably just text. We'll escape it just to be safe.
584 $res['body'] = escape_tags($res['body']);
588 // this tag is obsolete but we keep it for really old sites
590 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
591 if($allow && $allow[0]['data'] == 1)
592 $res['last-child'] = 1;
594 $res['last-child'] = 0;
596 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
597 if($private && intval($private[0]['data']) > 0)
598 $res['private'] = intval($private[0]['data']);
602 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
603 if($extid && $extid[0]['data'])
604 $res['extid'] = $extid[0]['data'];
606 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
608 $res['location'] = unxmlify($rawlocation[0]['data']);
611 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
613 $res['created'] = unxmlify($rawcreated[0]['data']);
616 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
618 $res['edited'] = unxmlify($rawedited[0]['data']);
620 if((x($res,'edited')) && (! (x($res,'created'))))
621 $res['created'] = $res['edited'];
623 if(! $res['created'])
624 $res['created'] = $item->get_date('c');
627 $res['edited'] = $item->get_date('c');
630 // Disallow time travelling posts
632 $d1 = strtotime($res['created']);
633 $d2 = strtotime($res['edited']);
634 $d3 = strtotime('now');
637 $res['created'] = datetime_convert();
639 $res['edited'] = datetime_convert();
641 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
642 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
643 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
644 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
645 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
646 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
647 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
648 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
649 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
651 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
652 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
654 foreach($base as $link) {
655 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
656 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
657 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
662 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
664 $res['coord'] = unxmlify($rawgeo[0]['data']);
667 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
669 // select between supported verbs
672 $res['verb'] = unxmlify($rawverb[0]['data']);
675 // translate OStatus unfollow to activity streams if it happened to get selected
677 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
678 $res['verb'] = ACTIVITY_UNFOLLOW;
680 $cats = $item->get_categories();
683 foreach($cats as $cat) {
684 $term = $cat->get_term();
686 $term = $cat->get_label();
687 $scheme = $cat->get_scheme();
688 if($scheme && $term && stristr($scheme,'X-DFRN:'))
689 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
691 $tag_arr[] = notags(trim($term));
693 $res['tag'] = implode(',', $tag_arr);
696 $attach = $item->get_enclosures();
699 foreach($attach as $att) {
700 $len = intval($att->get_length());
701 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
702 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
703 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
704 if(strpos($type,';'))
705 $type = substr($type,0,strpos($type,';'));
706 if((! $link) || (strpos($link,'http') !== 0))
712 $type = 'application/octet-stream';
714 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
716 $res['attach'] = implode(',', $att_arr);
719 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
722 $res['object'] = '<object>' . "\n";
723 $child = $rawobj[0]['child'];
724 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
725 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
726 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
728 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
729 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
730 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
731 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
732 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
733 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
734 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
735 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
737 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
738 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
739 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
740 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
742 $body = html2bb_video($body);
744 $config = HTMLPurifier_Config::createDefault();
745 $config->set('Cache.DefinitionImpl', null);
747 $purifier = new HTMLPurifier($config);
748 $body = $purifier->purify($body);
749 $body = html2bbcode($body);
752 $res['object'] .= '<content>' . $body . '</content>' . "\n";
755 $res['object'] .= '</object>' . "\n";
758 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
761 $res['target'] = '<target>' . "\n";
762 $child = $rawobj[0]['child'];
763 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
764 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
766 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
767 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
768 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
769 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
770 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
771 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
772 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
773 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
775 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
776 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
777 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
778 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
780 $body = html2bb_video($body);
782 $config = HTMLPurifier_Config::createDefault();
783 $config->set('Cache.DefinitionImpl', null);
785 $purifier = new HTMLPurifier($config);
786 $body = $purifier->purify($body);
787 $body = html2bbcode($body);
790 $res['target'] .= '<content>' . $body . '</content>' . "\n";
793 $res['target'] .= '</target>' . "\n";
796 // This is some experimental stuff. By now retweets are shown with "RT:"
797 // But: There is data so that the message could be shown similar to native retweets
798 // There is some better way to parse this array - but it didn't worked for me.
799 $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"];
800 if (is_array($child)) {
801 logger('get_atom_elements: Looking for status.net repeated message');
803 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
804 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
805 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
806 $uri = $author["uri"][0]["data"];
807 $name = $author["name"][0]["data"];
808 $avatar = @array_shift($author["link"][2]["attribs"]);
809 $avatar = $avatar["href"];
811 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
812 logger('get_atom_elements: fixing sender of repeated message.');
814 if (intval(get_config('system','new_share'))) {
815 $prefix = "[share author='".str_replace("'", "'",$name).
817 "' avatar='".$avatar.
818 "' link='".$orig_uri."']";
820 $res["body"] = $prefix.html2bbcode($message)."[/share]";
822 $res["owner-name"] = $res["author-name"];
823 $res["owner-link"] = $res["author-link"];
824 $res["owner-avatar"] = $res["author-avatar"];
826 $res["author-name"] = $name;
827 $res["author-link"] = $uri;
828 $res["author-avatar"] = $avatar;
830 $res["body"] = html2bbcode($message);
835 // Search for ostatus conversation url
836 $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"];
838 if (is_array($links)) {
839 foreach ($links as $link) {
840 $conversation = array_shift($link["attribs"]);
842 if ($conversation["rel"] == "ostatus:conversation") {
843 $res["ostatus_conversation"] = $conversation["href"];
844 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
849 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
851 call_hooks('parse_atom', $arr);
853 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
854 //if (strpos($res["body"], "RT @") !== false) {
855 /*if (strpos($res["body"], "@") !== false) {
856 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
857 file_put_contents($debugfile, serialize($arr));
863 function encode_rel_links($links) {
865 if(! ((is_array($links)) && (count($links))))
867 foreach($links as $link) {
869 if($link['attribs']['']['rel'])
870 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
871 if($link['attribs']['']['type'])
872 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
873 if($link['attribs']['']['href'])
874 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
875 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
876 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
877 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
878 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
886 function item_store($arr,$force_parent = false) {
888 // If a Diaspora signature structure was passed in, pull it out of the
889 // item array and set it aside for later storage.
892 if(x($arr,'dsprsig')) {
893 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
894 unset($arr['dsprsig']);
897 // if an OStatus conversation url was passed in, it is stored and then
898 // removed from the array.
899 $ostatus_conversation = null;
901 if (isset($arr["ostatus_conversation"])) {
902 $ostatus_conversation = $arr["ostatus_conversation"];
903 unset($arr["ostatus_conversation"]);
906 if(x($arr, 'gravity'))
907 $arr['gravity'] = intval($arr['gravity']);
908 elseif($arr['parent-uri'] === $arr['uri'])
910 elseif(activity_match($arr['verb'],ACTIVITY_POST))
913 $arr['gravity'] = 6; // extensible catchall
916 $arr['type'] = 'remote';
918 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
920 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
921 $arr['body'] = strip_tags($arr['body']);
924 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
925 require_once('library/langdet/Text/LanguageDetect.php');
926 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
927 $l = new Text_LanguageDetect;
928 //$lng = $l->detectConfidence($naked_body);
929 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
930 $lng = $l->detect($naked_body, 3);
932 if (sizeof($lng) > 0) {
935 foreach ($lng as $language => $score) {
941 $postopts .= $language.";".$score;
943 $arr['postopts'] = $postopts;
947 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
948 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
949 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
950 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
951 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
952 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
953 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
954 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
955 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
956 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
957 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
958 $arr['commented'] = datetime_convert();
959 $arr['received'] = datetime_convert();
960 $arr['changed'] = datetime_convert();
961 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
962 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
963 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
964 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
965 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
967 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
968 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
969 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
970 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
971 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
972 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
973 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
974 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
975 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
976 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
977 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
978 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
979 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
980 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
981 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
982 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
983 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
984 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
985 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
988 $arr['thr-parent'] = $arr['parent-uri'];
989 if($arr['parent-uri'] === $arr['uri']) {
992 $allow_cid = $arr['allow_cid'];
993 $allow_gid = $arr['allow_gid'];
994 $deny_cid = $arr['deny_cid'];
995 $deny_gid = $arr['deny_gid'];
999 // find the parent and snarf the item id and ACLs
1000 // and anything else we need to inherit
1002 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1003 dbesc($arr['parent-uri']),
1009 // is the new message multi-level threaded?
1010 // even though we don't support it now, preserve the info
1011 // and re-attach to the conversation parent.
1013 if($r[0]['uri'] != $r[0]['parent-uri']) {
1014 $arr['parent-uri'] = $r[0]['parent-uri'];
1015 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1016 ORDER BY `id` ASC LIMIT 1",
1017 dbesc($r[0]['parent-uri']),
1018 dbesc($r[0]['parent-uri']),
1025 $parent_id = $r[0]['id'];
1026 $parent_deleted = $r[0]['deleted'];
1027 $allow_cid = $r[0]['allow_cid'];
1028 $allow_gid = $r[0]['allow_gid'];
1029 $deny_cid = $r[0]['deny_cid'];
1030 $deny_gid = $r[0]['deny_gid'];
1031 $arr['wall'] = $r[0]['wall'];
1033 // if the parent is private, force privacy for the entire conversation
1034 // This differs from the above settings as it subtly allows comments from
1035 // email correspondents to be private even if the overall thread is not.
1037 if($r[0]['private'])
1038 $arr['private'] = $r[0]['private'];
1040 // Edge case. We host a public forum that was originally posted to privately.
1041 // The original author commented, but as this is a comment, the permissions
1042 // weren't fixed up so it will still show the comment as private unless we fix it here.
1044 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1045 $arr['private'] = 0;
1049 // Allow one to see reply tweets from status.net even when
1050 // we don't have or can't see the original post.
1053 logger('item_store: $force_parent=true, reply converted to top-level post.');
1055 $arr['parent-uri'] = $arr['uri'];
1056 $arr['gravity'] = 0;
1059 logger('item_store: item parent was not found - ignoring item');
1063 $parent_deleted = 0;
1067 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1071 if($r && count($r)) {
1072 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1076 call_hooks('post_remote',$arr);
1078 if(x($arr,'cancel')) {
1079 logger('item_store: post cancelled by plugin.');
1085 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1087 $r = dbq("INSERT INTO `item` (`"
1088 . implode("`, `", array_keys($arr))
1090 . implode("', '", array_values($arr))
1093 // find the item we just created
1095 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1096 $arr['uri'], // already dbesc'd
1101 $current_post = $r[0]['id'];
1102 logger('item_store: created item ' . $current_post);
1103 create_tags_from_item($r[0]['id']);
1105 logger('item_store: could not locate created item');
1109 logger('item_store: duplicated post occurred. Removing duplicates.');
1110 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1112 intval($arr['uid']),
1113 intval($current_post)
1117 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1118 $parent_id = $current_post;
1120 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1123 $private = $arr['private'];
1125 // Set parent id - and also make sure to inherit the parent's ACL's.
1127 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1128 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d LIMIT 1",
1135 intval($parent_deleted),
1136 intval($current_post)
1138 create_tags_from_item($current_post);
1140 // Complete ostatus threads
1141 if ($ostatus_conversation)
1142 complete_conversation($current_post, $ostatus_conversation);
1144 $arr['id'] = $current_post;
1145 $arr['parent'] = $parent_id;
1146 $arr['allow_cid'] = $allow_cid;
1147 $arr['allow_gid'] = $allow_gid;
1148 $arr['deny_cid'] = $deny_cid;
1149 $arr['deny_gid'] = $deny_gid;
1150 $arr['private'] = $private;
1151 $arr['deleted'] = $parent_deleted;
1153 // update the commented timestamp on the parent
1155 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
1156 dbesc(datetime_convert()),
1157 dbesc(datetime_convert()),
1162 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1163 intval($current_post),
1164 dbesc($dsprsig->signed_text),
1165 dbesc($dsprsig->signature),
1166 dbesc($dsprsig->signer)
1172 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1175 if($arr['last-child']) {
1176 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1178 intval($arr['uid']),
1179 intval($current_post)
1183 tag_deliver($arr['uid'],$current_post);
1185 // Store the fresh generated item into the cache
1186 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1188 if (($cachefile != '') AND !file_exists($cachefile)) {
1189 $s = prepare_text($arr['body']);
1191 $stamp1 = microtime(true);
1192 file_put_contents($cachefile, $s);
1193 $a->save_timestamp($stamp1, "file");
1194 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1197 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1198 if (count($r) == 1) {
1199 call_hooks('post_remote_end', $r[0]);
1202 logger('item_store: new item not found in DB, id ' . $current_post);
1205 return $current_post;
1208 function get_item_contact($item,$contacts) {
1209 if(! count($contacts) || (! is_array($item)))
1211 foreach($contacts as $contact) {
1212 if($contact['id'] == $item['contact-id']) {
1214 break; // NOTREACHED
1221 function tag_deliver($uid,$item_id) {
1223 // look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1229 $u = q("select * from user where uid = %d limit 1",
1235 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1236 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1239 $i = q("select * from item where id = %d and uid = %d limit 1",
1248 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1250 // Diaspora uses their own hardwired link URL in @-tags
1251 // instead of the one we supply with webfinger
1253 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1255 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1257 foreach($matches as $mtch) {
1258 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1260 logger('tag_deliver: mention found: ' . $mtch[2]);
1268 // send a notification
1270 // use a local photo if we have one
1272 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1273 intval($u[0]['uid']),
1274 dbesc(normalise_link($item['author-link']))
1276 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1279 require_once('include/enotify.php');
1281 'type' => NOTIFY_TAGSELF,
1282 'notify_flags' => $u[0]['notify-flags'],
1283 'language' => $u[0]['language'],
1284 'to_name' => $u[0]['username'],
1285 'to_email' => $u[0]['email'],
1286 'uid' => $u[0]['uid'],
1288 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1289 'source_name' => $item['author-name'],
1290 'source_link' => $item['author-link'],
1291 'source_photo' => $photo,
1292 'verb' => ACTIVITY_TAG,
1297 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1299 call_hooks('tagged', $arr);
1301 if((! $community_page) && (! $prvgroup))
1305 // tgroup delivery - setup a second delivery chain
1306 // prevent delivery looping - only proceed
1307 // if the message originated elsewhere and is a top-level post
1309 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1312 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1315 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1316 intval($u[0]['uid'])
1321 // also reset all the privacy bits to the forum default permissions
1323 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1325 $forum_mode = (($prvgroup) ? 2 : 1);
1327 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1328 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d limit 1",
1329 intval($forum_mode),
1330 dbesc($c[0]['name']),
1331 dbesc($c[0]['url']),
1332 dbesc($c[0]['thumb']),
1334 dbesc($u[0]['allow_cid']),
1335 dbesc($u[0]['allow_gid']),
1336 dbesc($u[0]['deny_cid']),
1337 dbesc($u[0]['deny_gid']),
1341 proc_run('php','include/notifier.php','tgroup',$item_id);
1347 function tgroup_check($uid,$item) {
1353 // check that the message originated elsewhere and is a top-level post
1355 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1359 $u = q("select * from user where uid = %d limit 1",
1365 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1366 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1369 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1371 // Diaspora uses their own hardwired link URL in @-tags
1372 // instead of the one we supply with webfinger
1374 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1376 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1378 foreach($matches as $mtch) {
1379 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1381 logger('tgroup_check: mention found: ' . $mtch[2]);
1389 if((! $community_page) && (! $prvgroup))
1403 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1407 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1409 if($contact['duplex'] && $contact['dfrn-id'])
1410 $idtosend = '0:' . $orig_id;
1411 if($contact['duplex'] && $contact['issued-id'])
1412 $idtosend = '1:' . $orig_id;
1414 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1416 $rino_enable = get_config('system','rino_encrypt');
1421 $ssl_val = intval(get_config('system','ssl_policy'));
1425 case SSL_POLICY_FULL:
1426 $ssl_policy = 'full';
1428 case SSL_POLICY_SELFSIGN:
1429 $ssl_policy = 'self';
1431 case SSL_POLICY_NONE:
1433 $ssl_policy = 'none';
1437 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1439 logger('dfrn_deliver: ' . $url);
1441 $xml = fetch_url($url);
1443 $curl_stat = $a->get_curl_code();
1445 return(-1); // timed out
1447 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1452 if(strpos($xml,'<?xml') === false) {
1453 logger('dfrn_deliver: no valid XML returned');
1454 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1458 $res = parse_xml_string($xml);
1460 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1461 return (($res->status) ? $res->status : 3);
1463 $postvars = array();
1464 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1465 $challenge = hex2bin((string) $res->challenge);
1466 $perm = (($res->perm) ? $res->perm : null);
1467 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1468 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1469 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1471 if($owner['page-flags'] == PAGE_PRVGROUP)
1474 $final_dfrn_id = '';
1477 if((($perm == 'rw') && (! intval($contact['writable'])))
1478 || (($perm == 'r') && (intval($contact['writable'])))) {
1479 q("update contact set writable = %d where id = %d limit 1",
1480 intval(($perm == 'rw') ? 1 : 0),
1481 intval($contact['id'])
1483 $contact['writable'] = (string) 1 - intval($contact['writable']);
1487 if(($contact['duplex'] && strlen($contact['pubkey']))
1488 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1489 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1490 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1491 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1494 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1495 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1498 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1500 if(strpos($final_dfrn_id,':') == 1)
1501 $final_dfrn_id = substr($final_dfrn_id,2);
1503 if($final_dfrn_id != $orig_id) {
1504 logger('dfrn_deliver: wrong dfrn_id.');
1505 // did not decode properly - cannot trust this site
1509 $postvars['dfrn_id'] = $idtosend;
1510 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1512 $postvars['dissolve'] = '1';
1515 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1516 $postvars['data'] = $atom;
1517 $postvars['perm'] = 'rw';
1520 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1521 $postvars['perm'] = 'r';
1524 $postvars['ssl_policy'] = $ssl_policy;
1527 $postvars['page'] = $page;
1529 if($rino && $rino_allowed && (! $dissolve)) {
1530 $key = substr(random_string(),0,16);
1531 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1532 $postvars['data'] = $data;
1533 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1536 if($dfrn_version >= 2.1) {
1537 if(($contact['duplex'] && strlen($contact['pubkey']))
1538 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1539 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1541 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1544 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1548 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1549 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1552 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1556 logger('md5 rawkey ' . md5($postvars['key']));
1558 $postvars['key'] = bin2hex($postvars['key']);
1561 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1563 $xml = post_url($contact['notify'],$postvars);
1565 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1567 $curl_stat = $a->get_curl_code();
1568 if((! $curl_stat) || (! strlen($xml)))
1569 return(-1); // timed out
1571 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1574 if(strpos($xml,'<?xml') === false) {
1575 logger('dfrn_deliver: phase 2: no valid XML returned');
1576 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1580 if($contact['term-date'] != '0000-00-00 00:00:00') {
1581 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1582 require_once('include/Contact.php');
1583 unmark_for_death($contact);
1586 $res = parse_xml_string($xml);
1588 return $res->status;
1593 This function returns true if $update has an edited timestamp newer
1594 than $existing, i.e. $update contains new data which should override
1595 what's already there. If there is no timestamp yet, the update is
1596 assumed to be newer. If the update has no timestamp, the existing
1597 item is assumed to be up-to-date. If the timestamps are equal it
1598 assumes the update has been seen before and should be ignored.
1600 function edited_timestamp_is_newer($existing, $update) {
1601 if (!x($existing,'edited') || !$existing['edited']) {
1604 if (!x($update,'edited') || !$update['edited']) {
1607 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1608 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1609 return (strcmp($existing_edited, $update_edited) < 0);
1614 * consume_feed - process atom feed and update anything/everything we might need to update
1616 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1618 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1619 * It is this person's stuff that is going to be updated.
1620 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1621 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1622 * have a contact record.
1623 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1624 * might not) try and subscribe to it.
1625 * $datedir sorts in reverse order
1626 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1627 * imported prior to its children being seen in the stream unless we are certain
1628 * of how the feed is arranged/ordered.
1629 * With $pass = 1, we only pull parent items out of the stream.
1630 * With $pass = 2, we only pull children (comments/likes).
1632 * So running this twice, first with pass 1 and then with pass 2 will do the right
1633 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1634 * model where comments can have sub-threads. That would require some massive sorting
1635 * to get all the feed items into a mostly linear ordering, and might still require
1639 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1641 require_once('library/simplepie/simplepie.inc');
1643 if(! strlen($xml)) {
1644 logger('consume_feed: empty input');
1648 $feed = new SimplePie();
1649 $feed->set_raw_data($xml);
1651 $feed->enable_order_by_date(true);
1653 $feed->enable_order_by_date(false);
1657 logger('consume_feed: Error parsing XML: ' . $feed->error());
1659 $permalink = $feed->get_permalink();
1661 // Check at the feed level for updated contact name and/or photo
1665 $photo_timestamp = '';
1669 $hubs = $feed->get_links('hub');
1670 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1673 $hub = implode(',', $hubs);
1675 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1677 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1679 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1680 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1681 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1682 $new_name = $elems['name'][0]['data'];
1684 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1685 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1686 $photo_url = $elems['link'][0]['attribs']['']['href'];
1689 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1690 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1694 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1695 logger('consume_feed: Updating photo for ' . $contact['name']);
1696 require_once("include/Photo.php");
1697 $photo_failure = false;
1698 $have_photo = false;
1700 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1701 intval($contact['id']),
1702 intval($contact['uid'])
1705 $resource_id = $r[0]['resource-id'];
1709 $resource_id = photo_new_resource();
1712 $img_str = fetch_url($photo_url,true);
1713 // guess mimetype from headers or filename
1714 $type = guess_image_type($photo_url,true);
1717 $img = new Photo($img_str, $type);
1718 if($img->is_valid()) {
1720 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1721 dbesc($resource_id),
1722 intval($contact['id']),
1723 intval($contact['uid'])
1727 $img->scaleImageSquare(175);
1729 $hash = $resource_id;
1730 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1732 $img->scaleImage(80);
1733 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1735 $img->scaleImage(48);
1736 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1740 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1741 WHERE `uid` = %d AND `id` = %d LIMIT 1",
1742 dbesc(datetime_convert()),
1743 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1744 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1745 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1746 intval($contact['uid']),
1747 intval($contact['id'])
1752 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1753 $r = q("select * from contact where uid = %d and id = %d limit 1",
1754 intval($contact['uid']),
1755 intval($contact['id'])
1758 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1759 dbesc(notags(trim($new_name))),
1760 dbesc(datetime_convert()),
1761 intval($contact['uid']),
1762 intval($contact['id'])
1765 // do our best to update the name on content items
1768 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1769 dbesc(notags(trim($new_name))),
1770 dbesc($r[0]['name']),
1771 dbesc($r[0]['url']),
1772 intval($contact['uid'])
1777 if(strlen($birthday)) {
1778 if(substr($birthday,0,4) != $contact['bdyear']) {
1779 logger('consume_feed: updating birthday: ' . $birthday);
1783 * Add new birthday event for this person
1785 * $bdtext is just a readable placeholder in case the event is shared
1786 * with others. We will replace it during presentation to our $importer
1787 * to contain a sparkle link and perhaps a photo.
1791 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1792 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1795 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1796 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1797 intval($contact['uid']),
1798 intval($contact['id']),
1799 dbesc(datetime_convert()),
1800 dbesc(datetime_convert()),
1801 dbesc(datetime_convert('UTC','UTC', $birthday)),
1802 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1811 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1812 dbesc(substr($birthday,0,4)),
1813 intval($contact['uid']),
1814 intval($contact['id'])
1817 // This function is called twice without reloading the contact
1818 // Make sure we only create one event. This is why &$contact
1819 // is a reference var in this function
1821 $contact['bdyear'] = substr($birthday,0,4);
1826 $community_page = 0;
1827 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1829 $community_page = intval($rawtags[0]['data']);
1831 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1832 q("update contact set forum = %d where id = %d limit 1",
1833 intval($community_page),
1834 intval($contact['id'])
1836 $contact['forum'] = (string) $community_page;
1840 // process any deleted entries
1842 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1843 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1844 foreach($del_entries as $dentry) {
1846 if(isset($dentry['attribs']['']['ref'])) {
1847 $uri = $dentry['attribs']['']['ref'];
1849 if(isset($dentry['attribs']['']['when'])) {
1850 $when = $dentry['attribs']['']['when'];
1851 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1854 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1856 if($deleted && is_array($contact)) {
1857 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id`
1858 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1860 intval($importer['uid']),
1861 intval($contact['id'])
1866 if(! $item['deleted'])
1867 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1869 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1870 $xo = parse_xml_string($item['object'],false);
1871 $xt = parse_xml_string($item['target'],false);
1872 if($xt->type === ACTIVITY_OBJ_NOTE) {
1873 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1875 intval($importer['importer_uid'])
1879 // For tags, the owner cannot remove the tag on the author's copy of the post.
1881 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1882 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1883 $author_copy = (($item['origin']) ? true : false);
1885 if($owner_remove && $author_copy)
1887 if($author_remove || $owner_remove) {
1888 $tags = explode(',',$i[0]['tag']);
1891 foreach($tags as $tag)
1892 if(trim($tag) !== trim($xo->body))
1893 $newtags[] = trim($tag);
1895 q("update item set tag = '%s' where id = %d limit 1",
1896 dbesc(implode(',',$newtags)),
1899 create_tags_from_item($i[0]['id']);
1905 if($item['uri'] == $item['parent-uri']) {
1906 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1907 `body` = '', `title` = ''
1908 WHERE `parent-uri` = '%s' AND `uid` = %d",
1910 dbesc(datetime_convert()),
1911 dbesc($item['uri']),
1912 intval($importer['uid'])
1914 create_tags_from_itemuri($item['uri'], $importer['uid']);
1917 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1918 `body` = '', `title` = ''
1919 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1921 dbesc(datetime_convert()),
1923 intval($importer['uid'])
1925 create_tags_from_itemuri($uri, $importer['uid']);
1926 if($item['last-child']) {
1927 // ensure that last-child is set in case the comment that had it just got wiped.
1928 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1929 dbesc(datetime_convert()),
1930 dbesc($item['parent-uri']),
1931 intval($item['uid'])
1933 // who is the last child now?
1934 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1935 ORDER BY `created` DESC LIMIT 1",
1936 dbesc($item['parent-uri']),
1937 intval($importer['uid'])
1940 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
1951 // Now process the feed
1953 if($feed->get_item_quantity()) {
1955 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1957 // in inverse date order
1959 $items = array_reverse($feed->get_items());
1961 $items = $feed->get_items();
1964 foreach($items as $item) {
1967 $item_id = $item->get_id();
1968 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
1969 if(isset($rawthread[0]['attribs']['']['ref'])) {
1971 $parent_uri = $rawthread[0]['attribs']['']['ref'];
1974 if(($is_reply) && is_array($contact)) {
1979 // not allowed to post
1981 if($contact['rel'] == CONTACT_IS_FOLLOWER)
1985 // Have we seen it? If not, import it.
1987 $item_id = $item->get_id();
1988 $datarray = get_atom_elements($feed,$item);
1990 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
1991 $datarray['author-name'] = $contact['name'];
1992 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
1993 $datarray['author-link'] = $contact['url'];
1994 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
1995 $datarray['author-avatar'] = $contact['thumb'];
1997 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
1998 logger('consume_feed: no author information! ' . print_r($datarray,true));
2002 $force_parent = false;
2003 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2004 if($contact['network'] === NETWORK_OSTATUS)
2005 $force_parent = true;
2006 if(strlen($datarray['title']))
2007 unset($datarray['title']);
2008 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2009 dbesc(datetime_convert()),
2011 intval($importer['uid'])
2013 $datarray['last-child'] = 1;
2017 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2019 intval($importer['uid'])
2022 // Update content if 'updated' changes
2025 if (edited_timestamp_is_newer($r[0], $datarray)) {
2027 // do not accept (ignore) an earlier edit than one we currently have.
2028 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2031 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2032 dbesc($datarray['title']),
2033 dbesc($datarray['body']),
2034 dbesc($datarray['tag']),
2035 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2037 intval($importer['uid'])
2039 create_tags_from_itemuri($item_id, $importer['uid']);
2042 // update last-child if it changes
2044 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2045 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2046 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2047 dbesc(datetime_convert()),
2049 intval($importer['uid'])
2051 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2052 intval($allow[0]['data']),
2053 dbesc(datetime_convert()),
2055 intval($importer['uid'])
2062 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2063 // one way feed - no remote comment ability
2064 $datarray['last-child'] = 0;
2066 $datarray['parent-uri'] = $parent_uri;
2067 $datarray['uid'] = $importer['uid'];
2068 $datarray['contact-id'] = $contact['id'];
2069 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2070 $datarray['type'] = 'activity';
2071 $datarray['gravity'] = GRAVITY_LIKE;
2072 // only one like or dislike per person
2073 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s' OR `thr-parent` = '%s') limit 1",
2074 intval($datarray['uid']),
2075 intval($datarray['contact-id']),
2076 dbesc($datarray['verb']),
2084 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2085 $xo = parse_xml_string($datarray['object'],false);
2086 $xt = parse_xml_string($datarray['target'],false);
2088 if($xt->type == ACTIVITY_OBJ_NOTE) {
2089 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2091 intval($importer['importer_uid'])
2096 // extract tag, if not duplicate, add to parent item
2097 if($xo->id && $xo->content) {
2098 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2099 if(! (stristr($r[0]['tag'],$newtag))) {
2100 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
2101 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2104 create_tags_from_item($r[0]['id']);
2110 $r = item_store($datarray,$force_parent);
2116 // Head post of a conversation. Have we seen it? If not, import it.
2118 $item_id = $item->get_id();
2120 $datarray = get_atom_elements($feed,$item);
2122 if(is_array($contact)) {
2123 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2124 $datarray['author-name'] = $contact['name'];
2125 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2126 $datarray['author-link'] = $contact['url'];
2127 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2128 $datarray['author-avatar'] = $contact['thumb'];
2131 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2132 logger('consume_feed: no author information! ' . print_r($datarray,true));
2136 // special handling for events
2138 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2139 $ev = bbtoevent($datarray['body']);
2140 if(x($ev,'desc') && x($ev,'start')) {
2141 $ev['uid'] = $importer['uid'];
2142 $ev['uri'] = $item_id;
2143 $ev['edited'] = $datarray['edited'];
2144 $ev['private'] = $datarray['private'];
2146 if(is_array($contact))
2147 $ev['cid'] = $contact['id'];
2148 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2150 intval($importer['uid'])
2153 $ev['id'] = $r[0]['id'];
2154 $xyz = event_store($ev);
2159 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2160 if(strlen($datarray['title']))
2161 unset($datarray['title']);
2162 $datarray['last-child'] = 1;
2166 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2168 intval($importer['uid'])
2171 // Update content if 'updated' changes
2174 if (edited_timestamp_is_newer($r[0], $datarray)) {
2176 // do not accept (ignore) an earlier edit than one we currently have.
2177 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2180 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2181 dbesc($datarray['title']),
2182 dbesc($datarray['body']),
2183 dbesc($datarray['tag']),
2184 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2186 intval($importer['uid'])
2188 create_tags_from_itemuri($item_id, $importer['uid']);
2191 // update last-child if it changes
2193 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2194 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2195 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2196 intval($allow[0]['data']),
2197 dbesc(datetime_convert()),
2199 intval($importer['uid'])
2205 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2206 logger('consume-feed: New follower');
2207 new_follower($importer,$contact,$datarray,$item);
2210 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2211 lose_follower($importer,$contact,$datarray,$item);
2215 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2216 logger('consume-feed: New friend request');
2217 new_follower($importer,$contact,$datarray,$item,true);
2220 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2221 lose_sharer($importer,$contact,$datarray,$item);
2226 if(! is_array($contact))
2230 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2231 // one way feed - no remote comment ability
2232 $datarray['last-child'] = 0;
2234 if($contact['network'] === NETWORK_FEED)
2235 $datarray['private'] = 2;
2237 // This is my contact on another system, but it's really me.
2238 // Turn this into a wall post.
2240 if($contact['remote_self']) {
2241 $datarray['wall'] = 1;
2242 if($contact['network'] === NETWORK_FEED) {
2243 $datarray['private'] = 0;
2247 $datarray['parent-uri'] = $item_id;
2248 $datarray['uid'] = $importer['uid'];
2249 $datarray['contact-id'] = $contact['id'];
2251 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2252 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2253 // but otherwise there's a possible data mixup on the sender's system.
2254 // the tgroup delivery code called from item_store will correct it if it's a forum,
2255 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2256 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2257 $datarray['owner-name'] = $contact['name'];
2258 $datarray['owner-link'] = $contact['url'];
2259 $datarray['owner-avatar'] = $contact['thumb'];
2262 // We've allowed "followers" to reach this point so we can decide if they are
2263 // posting an @-tag delivery, which followers are allowed to do for certain
2264 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2266 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2270 $r = item_store($datarray);
2278 function local_delivery($importer,$data) {
2281 logger(__function__, LOGGER_TRACE);
2283 if($importer['readonly']) {
2284 // We aren't receiving stuff from this person. But we will quietly ignore them
2285 // rather than a blatant "go away" message.
2286 logger('local_delivery: ignoring');
2291 // Consume notification feed. This may differ from consuming a public feed in several ways
2292 // - might contain email or friend suggestions
2293 // - might contain remote followup to our message
2294 // - in which case we need to accept it and then notify other conversants
2295 // - we may need to send various email notifications
2297 $feed = new SimplePie();
2298 $feed->set_raw_data($data);
2299 $feed->enable_order_by_date(false);
2304 logger('local_delivery: Error parsing XML: ' . $feed->error());
2307 // Check at the feed level for updated contact name and/or photo
2311 $photo_timestamp = '';
2315 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2317 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2319 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2322 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2323 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2324 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2325 $new_name = $elems['name'][0]['data'];
2327 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2328 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2329 $photo_url = $elems['link'][0]['attribs']['']['href'];
2333 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2334 logger('local_delivery: Updating photo for ' . $importer['name']);
2335 require_once("include/Photo.php");
2336 $photo_failure = false;
2337 $have_photo = false;
2339 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2340 intval($importer['id']),
2341 intval($importer['importer_uid'])
2344 $resource_id = $r[0]['resource-id'];
2348 $resource_id = photo_new_resource();
2351 $img_str = fetch_url($photo_url,true);
2352 // guess mimetype from headers or filename
2353 $type = guess_image_type($photo_url,true);
2356 $img = new Photo($img_str, $type);
2357 if($img->is_valid()) {
2359 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2360 dbesc($resource_id),
2361 intval($importer['id']),
2362 intval($importer['importer_uid'])
2366 $img->scaleImageSquare(175);
2368 $hash = $resource_id;
2369 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2371 $img->scaleImage(80);
2372 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2374 $img->scaleImage(48);
2375 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2379 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2380 WHERE `uid` = %d AND `id` = %d LIMIT 1",
2381 dbesc(datetime_convert()),
2382 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2383 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2384 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2385 intval($importer['importer_uid']),
2386 intval($importer['id'])
2391 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2392 $r = q("select * from contact where uid = %d and id = %d limit 1",
2393 intval($importer['importer_uid']),
2394 intval($importer['id'])
2397 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
2398 dbesc(notags(trim($new_name))),
2399 dbesc(datetime_convert()),
2400 intval($importer['importer_uid']),
2401 intval($importer['id'])
2404 // do our best to update the name on content items
2407 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2408 dbesc(notags(trim($new_name))),
2409 dbesc($r[0]['name']),
2410 dbesc($r[0]['url']),
2411 intval($importer['importer_uid'])
2418 // Currently unsupported - needs a lot of work
2419 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2420 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2421 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2423 $newloc['uid'] = $importer['importer_uid'];
2424 $newloc['cid'] = $importer['id'];
2425 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2426 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2427 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2428 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2429 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2430 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2431 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2432 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2433 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2434 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2435 /** relocated user must have original key pair */
2436 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2437 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2439 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2442 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2443 intval($importer['id']),
2444 intval($importer['importer_uid']));
2449 $x = q("UPDATE contact SET
2459 `site-pubkey` = '%s'
2460 WHERE id=%d AND uid=%d;",
2461 dbesc($newloc['name']),
2462 dbesc($newloc['photo']),
2463 dbesc($newloc['thumb']),
2464 dbesc($newloc['micro']),
2465 dbesc($newloc['url']),
2466 dbesc($newloc['request']),
2467 dbesc($newloc['confirm']),
2468 dbesc($newloc['notify']),
2469 dbesc($newloc['poll']),
2470 dbesc($newloc['sitepubkey']),
2471 intval($importer['id']),
2472 intval($importer['importer_uid']));
2478 'owner-link' => array($old['url'], $newloc['url']),
2479 'author-link' => array($old['url'], $newloc['url']),
2480 'owner-avatar' => array($old['photo'], $newloc['photo']),
2481 'author-avatar' => array($old['photo'], $newloc['photo']),
2483 foreach ($fields as $n=>$f){
2484 $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2487 intval($importer['importer_uid']));
2493 // merge with current record, current contents have priority
2494 // update record, set url-updated
2495 // update profile photos
2501 // handle friend suggestion notification
2503 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2504 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2505 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2507 $fsugg['uid'] = $importer['importer_uid'];
2508 $fsugg['cid'] = $importer['id'];
2509 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2510 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2511 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2512 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2513 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2515 // Does our member already have a friend matching this description?
2517 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2518 dbesc($fsugg['name']),
2519 dbesc(normalise_link($fsugg['url'])),
2520 intval($fsugg['uid'])
2525 // Do we already have an fcontact record for this person?
2528 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2529 dbesc($fsugg['url']),
2530 dbesc($fsugg['name']),
2531 dbesc($fsugg['request'])
2536 // OK, we do. Do we already have an introduction for this person ?
2537 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2538 intval($fsugg['uid']),
2545 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2546 dbesc($fsugg['name']),
2547 dbesc($fsugg['url']),
2548 dbesc($fsugg['photo']),
2549 dbesc($fsugg['request'])
2551 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2552 dbesc($fsugg['url']),
2553 dbesc($fsugg['name']),
2554 dbesc($fsugg['request'])
2559 // database record did not get created. Quietly give up.
2564 $hash = random_string();
2566 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2567 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2568 intval($fsugg['uid']),
2570 intval($fsugg['cid']),
2571 dbesc($fsugg['body']),
2573 dbesc(datetime_convert()),
2578 'type' => NOTIFY_SUGGEST,
2579 'notify_flags' => $importer['notify-flags'],
2580 'language' => $importer['language'],
2581 'to_name' => $importer['username'],
2582 'to_email' => $importer['email'],
2583 'uid' => $importer['importer_uid'],
2585 'link' => $a->get_baseurl() . '/notifications/intros',
2586 'source_name' => $importer['name'],
2587 'source_link' => $importer['url'],
2588 'source_photo' => $importer['photo'],
2589 'verb' => ACTIVITY_REQ_FRIEND,
2598 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2599 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2601 logger('local_delivery: private message received');
2604 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2607 $msg['uid'] = $importer['importer_uid'];
2608 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2609 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2610 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2611 $msg['contact-id'] = $importer['id'];
2612 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2613 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2615 $msg['replied'] = 0;
2616 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2617 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2618 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2622 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2623 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2625 // send notifications.
2627 require_once('include/enotify.php');
2629 $notif_params = array(
2630 'type' => NOTIFY_MAIL,
2631 'notify_flags' => $importer['notify-flags'],
2632 'language' => $importer['language'],
2633 'to_name' => $importer['username'],
2634 'to_email' => $importer['email'],
2635 'uid' => $importer['importer_uid'],
2637 'source_name' => $msg['from-name'],
2638 'source_link' => $importer['url'],
2639 'source_photo' => $importer['thumb'],
2640 'verb' => ACTIVITY_POST,
2644 notification($notif_params);
2650 $community_page = 0;
2651 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2653 $community_page = intval($rawtags[0]['data']);
2655 if(intval($importer['forum']) != $community_page) {
2656 q("update contact set forum = %d where id = %d limit 1",
2657 intval($community_page),
2658 intval($importer['id'])
2660 $importer['forum'] = (string) $community_page;
2663 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2665 // process any deleted entries
2667 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2668 if(is_array($del_entries) && count($del_entries)) {
2669 foreach($del_entries as $dentry) {
2671 if(isset($dentry['attribs']['']['ref'])) {
2672 $uri = $dentry['attribs']['']['ref'];
2674 if(isset($dentry['attribs']['']['when'])) {
2675 $when = $dentry['attribs']['']['when'];
2676 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2679 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2683 // check for relayed deletes to our conversation
2686 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2688 intval($importer['importer_uid'])
2691 $parent_uri = $r[0]['parent-uri'];
2692 if($r[0]['id'] != $r[0]['parent'])
2699 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2702 logger('local_delivery: possible community delete');
2705 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2707 // was the top-level post for this reply written by somebody on this site?
2708 // Specifically, the recipient?
2710 $is_a_remote_delete = false;
2712 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2713 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2714 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2715 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2716 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2717 AND `item`.`uid` = %d
2723 intval($importer['importer_uid'])
2726 $is_a_remote_delete = true;
2728 // Does this have the characteristics of a community or private group comment?
2729 // If it's a reply to a wall post on a community/prvgroup page it's a
2730 // valid community comment. Also forum_mode makes it valid for sure.
2731 // If neither, it's not.
2733 if($is_a_remote_delete && $community) {
2734 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2735 $is_a_remote_delete = false;
2736 logger('local_delivery: not a community delete');
2740 if($is_a_remote_delete) {
2741 logger('local_delivery: received remote delete');
2745 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2746 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2748 intval($importer['importer_uid']),
2749 intval($importer['id'])
2755 if($item['deleted'])
2758 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2760 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2761 $xo = parse_xml_string($item['object'],false);
2762 $xt = parse_xml_string($item['target'],false);
2764 if($xt->type === ACTIVITY_OBJ_NOTE) {
2765 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2767 intval($importer['importer_uid'])
2771 // For tags, the owner cannot remove the tag on the author's copy of the post.
2773 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2774 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2775 $author_copy = (($item['origin']) ? true : false);
2777 if($owner_remove && $author_copy)
2779 if($author_remove || $owner_remove) {
2780 $tags = explode(',',$i[0]['tag']);
2783 foreach($tags as $tag)
2784 if(trim($tag) !== trim($xo->body))
2785 $newtags[] = trim($tag);
2787 q("update item set tag = '%s' where id = %d limit 1",
2788 dbesc(implode(',',$newtags)),
2791 create_tags_from_item($i[0]['id']);
2797 if($item['uri'] == $item['parent-uri']) {
2798 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2799 `body` = '', `title` = ''
2800 WHERE `parent-uri` = '%s' AND `uid` = %d",
2802 dbesc(datetime_convert()),
2803 dbesc($item['uri']),
2804 intval($importer['importer_uid'])
2806 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2809 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2810 `body` = '', `title` = ''
2811 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2813 dbesc(datetime_convert()),
2815 intval($importer['importer_uid'])
2817 create_tags_from_itemuri($uri, $importer['importer_uid']);
2818 if($item['last-child']) {
2819 // ensure that last-child is set in case the comment that had it just got wiped.
2820 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2821 dbesc(datetime_convert()),
2822 dbesc($item['parent-uri']),
2823 intval($item['uid'])
2825 // who is the last child now?
2826 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2827 ORDER BY `created` DESC LIMIT 1",
2828 dbesc($item['parent-uri']),
2829 intval($importer['importer_uid'])
2832 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
2837 // if this is a relayed delete, propagate it to other recipients
2839 if($is_a_remote_delete)
2840 proc_run('php',"include/notifier.php","drop",$item['id']);
2848 foreach($feed->get_items() as $item) {
2851 $item_id = $item->get_id();
2852 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2853 if(isset($rawthread[0]['attribs']['']['ref'])) {
2855 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2861 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2864 logger('local_delivery: possible community reply');
2867 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2869 // was the top-level post for this reply written by somebody on this site?
2870 // Specifically, the recipient?
2872 $is_a_remote_comment = false;
2873 $top_uri = $parent_uri;
2875 $r = q("select `item`.`parent-uri` from `item`
2876 WHERE `item`.`uri` = '%s'
2880 if($r && count($r)) {
2881 $top_uri = $r[0]['parent-uri'];
2883 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2884 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2885 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2886 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2887 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2888 AND `item`.`uid` = %d
2894 intval($importer['importer_uid'])
2897 $is_a_remote_comment = true;
2900 // Does this have the characteristics of a community or private group comment?
2901 // If it's a reply to a wall post on a community/prvgroup page it's a
2902 // valid community comment. Also forum_mode makes it valid for sure.
2903 // If neither, it's not.
2905 if($is_a_remote_comment && $community) {
2906 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2907 $is_a_remote_comment = false;
2908 logger('local_delivery: not a community reply');
2912 if($is_a_remote_comment) {
2913 logger('local_delivery: received remote comment');
2915 // remote reply to our post. Import and then notify everybody else.
2917 $datarray = get_atom_elements($feed,$item);
2919 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2921 intval($importer['importer_uid'])
2924 // Update content if 'updated' changes
2928 if (edited_timestamp_is_newer($r[0], $datarray)) {
2930 // do not accept (ignore) an earlier edit than one we currently have.
2931 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2934 logger('received updated comment' , LOGGER_DEBUG);
2935 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2936 dbesc($datarray['title']),
2937 dbesc($datarray['body']),
2938 dbesc($datarray['tag']),
2939 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2941 intval($importer['importer_uid'])
2943 create_tags_from_itemuri($item_id, $importer['importer_uid']);
2945 proc_run('php',"include/notifier.php","comment-import",$iid);
2954 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
2955 intval($importer['importer_uid'])
2959 $datarray['type'] = 'remote-comment';
2960 $datarray['wall'] = 1;
2961 $datarray['parent-uri'] = $parent_uri;
2962 $datarray['uid'] = $importer['importer_uid'];
2963 $datarray['owner-name'] = $own[0]['name'];
2964 $datarray['owner-link'] = $own[0]['url'];
2965 $datarray['owner-avatar'] = $own[0]['thumb'];
2966 $datarray['contact-id'] = $importer['id'];
2968 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
2970 $datarray['type'] = 'activity';
2971 $datarray['gravity'] = GRAVITY_LIKE;
2972 $datarray['last-child'] = 0;
2973 // only one like or dislike per person
2974 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s' or `parent-uri` = '%s') and deleted = 0 limit 1",
2975 intval($datarray['uid']),
2976 intval($datarray['contact-id']),
2977 dbesc($datarray['verb']),
2978 dbesc($datarray['parent-uri']),
2979 dbesc($datarray['parent-uri'])
2986 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2988 $xo = parse_xml_string($datarray['object'],false);
2989 $xt = parse_xml_string($datarray['target'],false);
2991 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
2993 // fetch the parent item
2995 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
2997 intval($importer['importer_uid'])
3002 // extract tag, if not duplicate, and this user allows tags, add to parent item
3004 if($xo->id && $xo->content) {
3005 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3006 if(! (stristr($tagp[0]['tag'],$newtag))) {
3007 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3008 intval($importer['importer_uid'])
3010 if(count($i) && ! intval($i[0]['blocktags'])) {
3011 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d LIMIT 1",
3012 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3013 intval($tagp[0]['id']),
3014 dbesc(datetime_convert())
3016 create_tags_from_item($tagp[0]['id']);
3024 $posted_id = item_store($datarray);
3028 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3030 intval($importer['importer_uid'])
3033 $parent = $r[0]['parent'];
3034 $parent_uri = $r[0]['parent-uri'];
3038 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3039 dbesc(datetime_convert()),
3040 intval($importer['importer_uid']),
3041 intval($r[0]['parent'])
3044 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
3045 dbesc(datetime_convert()),
3046 intval($importer['importer_uid']),
3051 if($posted_id && $parent) {
3053 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3055 if((! $is_like) && (! $importer['self'])) {
3057 require_once('include/enotify.php');
3060 'type' => NOTIFY_COMMENT,
3061 'notify_flags' => $importer['notify-flags'],
3062 'language' => $importer['language'],
3063 'to_name' => $importer['username'],
3064 'to_email' => $importer['email'],
3065 'uid' => $importer['importer_uid'],
3066 'item' => $datarray,
3067 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3068 'source_name' => stripslashes($datarray['author-name']),
3069 'source_link' => $datarray['author-link'],
3070 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3071 ? $importer['thumb'] : $datarray['author-avatar']),
3072 'verb' => ACTIVITY_POST,
3074 'parent' => $parent,
3075 'parent_uri' => $parent_uri,
3087 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3089 $item_id = $item->get_id();
3090 $datarray = get_atom_elements($feed,$item);
3092 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3095 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3097 intval($importer['importer_uid'])
3100 // Update content if 'updated' changes
3103 if (edited_timestamp_is_newer($r[0], $datarray)) {
3105 // do not accept (ignore) an earlier edit than one we currently have.
3106 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3109 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3110 dbesc($datarray['title']),
3111 dbesc($datarray['body']),
3112 dbesc($datarray['tag']),
3113 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3115 intval($importer['importer_uid'])
3117 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3120 // update last-child if it changes
3122 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3123 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3124 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3125 dbesc(datetime_convert()),
3127 intval($importer['importer_uid'])
3129 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3130 intval($allow[0]['data']),
3131 dbesc(datetime_convert()),
3133 intval($importer['importer_uid'])
3139 $datarray['parent-uri'] = $parent_uri;
3140 $datarray['uid'] = $importer['importer_uid'];
3141 $datarray['contact-id'] = $importer['id'];
3142 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3143 $datarray['type'] = 'activity';
3144 $datarray['gravity'] = GRAVITY_LIKE;
3145 // only one like or dislike per person
3146 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s' OR `thr-parent` = '%s') limit 1",
3147 intval($datarray['uid']),
3148 intval($datarray['contact-id']),
3149 dbesc($datarray['verb']),
3158 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3160 $xo = parse_xml_string($datarray['object'],false);
3161 $xt = parse_xml_string($datarray['target'],false);
3163 if($xt->type == ACTIVITY_OBJ_NOTE) {
3164 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3166 intval($importer['importer_uid'])
3171 // extract tag, if not duplicate, add to parent item
3173 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3174 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
3175 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3178 create_tags_from_item($r[0]['id']);
3184 $posted_id = item_store($datarray);
3186 // find out if our user is involved in this conversation and wants to be notified.
3188 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3190 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3192 intval($importer['importer_uid'])
3195 if(count($myconv)) {
3196 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3198 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3199 if(! link_compare($datarray['author-link'],$importer_url)) {
3202 foreach($myconv as $conv) {
3204 // now if we find a match, it means we're in this conversation
3206 if(! link_compare($conv['author-link'],$importer_url))
3209 require_once('include/enotify.php');
3211 $conv_parent = $conv['parent'];
3214 'type' => NOTIFY_COMMENT,
3215 'notify_flags' => $importer['notify-flags'],
3216 'language' => $importer['language'],
3217 'to_name' => $importer['username'],
3218 'to_email' => $importer['email'],
3219 'uid' => $importer['importer_uid'],
3220 'item' => $datarray,
3221 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3222 'source_name' => stripslashes($datarray['author-name']),
3223 'source_link' => $datarray['author-link'],
3224 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3225 ? $importer['thumb'] : $datarray['author-avatar']),
3226 'verb' => ACTIVITY_POST,
3228 'parent' => $conv_parent,
3229 'parent_uri' => $parent_uri
3233 // only send one notification
3245 // Head post of a conversation. Have we seen it? If not, import it.
3248 $item_id = $item->get_id();
3249 $datarray = get_atom_elements($feed,$item);
3251 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3252 $ev = bbtoevent($datarray['body']);
3253 if(x($ev,'desc') && x($ev,'start')) {
3254 $ev['cid'] = $importer['id'];
3255 $ev['uid'] = $importer['uid'];
3256 $ev['uri'] = $item_id;
3257 $ev['edited'] = $datarray['edited'];
3258 $ev['private'] = $datarray['private'];
3260 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3262 intval($importer['uid'])
3265 $ev['id'] = $r[0]['id'];
3266 $xyz = event_store($ev);
3271 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3273 intval($importer['importer_uid'])
3276 // Update content if 'updated' changes
3279 if (edited_timestamp_is_newer($r[0], $datarray)) {
3281 // do not accept (ignore) an earlier edit than one we currently have.
3282 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3285 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3286 dbesc($datarray['title']),
3287 dbesc($datarray['body']),
3288 dbesc($datarray['tag']),
3289 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3291 intval($importer['importer_uid'])
3293 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3296 // update last-child if it changes
3298 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3299 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3300 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3301 intval($allow[0]['data']),
3302 dbesc(datetime_convert()),
3304 intval($importer['importer_uid'])
3310 // This is my contact on another system, but it's really me.
3311 // Turn this into a wall post.
3313 if($importer['remote_self'])
3314 $datarray['wall'] = 1;
3316 $datarray['parent-uri'] = $item_id;
3317 $datarray['uid'] = $importer['importer_uid'];
3318 $datarray['contact-id'] = $importer['id'];
3321 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3322 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3323 // but otherwise there's a possible data mixup on the sender's system.
3324 // the tgroup delivery code called from item_store will correct it if it's a forum,
3325 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3326 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3327 $datarray['owner-name'] = $importer['senderName'];
3328 $datarray['owner-link'] = $importer['url'];
3329 $datarray['owner-avatar'] = $importer['thumb'];
3332 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3335 $posted_id = item_store($datarray);
3337 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3338 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3341 $xo = parse_xml_string($datarray['object'],false);
3343 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3345 // somebody was poked/prodded. Was it me?
3347 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3349 foreach($links->link as $l) {
3350 $atts = $l->attributes();
3351 switch($atts['rel']) {
3353 $Blink = $atts['href'];
3359 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3361 // send a notification
3362 require_once('include/enotify.php');
3365 'type' => NOTIFY_POKE,
3366 'notify_flags' => $importer['notify-flags'],
3367 'language' => $importer['language'],
3368 'to_name' => $importer['username'],
3369 'to_email' => $importer['email'],
3370 'uid' => $importer['importer_uid'],
3371 'item' => $datarray,
3372 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3373 'source_name' => stripslashes($datarray['author-name']),
3374 'source_link' => $datarray['author-link'],
3375 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3376 ? $importer['thumb'] : $datarray['author-avatar']),
3377 'verb' => $datarray['verb'],
3378 'otype' => 'person',
3379 'activity' => $verb,
3396 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3397 $url = notags(trim($datarray['author-link']));
3398 $name = notags(trim($datarray['author-name']));
3399 $photo = notags(trim($datarray['author-avatar']));
3401 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3402 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3403 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3405 if(is_array($contact)) {
3406 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3407 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3408 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
3409 intval(CONTACT_IS_FRIEND),
3410 intval($contact['id']),
3411 intval($importer['uid'])
3414 // send email notification to owner?
3418 // create contact record
3420 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3421 `blocked`, `readonly`, `pending`, `writable` )
3422 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3423 intval($importer['uid']),
3424 dbesc(datetime_convert()),
3426 dbesc(normalise_link($url)),
3430 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3431 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3433 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3434 intval($importer['uid']),
3438 $contact_record = $r[0];
3440 // create notification
3441 $hash = random_string();
3443 if(is_array($contact_record)) {
3444 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3445 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3446 intval($importer['uid']),
3447 intval($contact_record['id']),
3449 dbesc(datetime_convert())
3452 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3453 intval($importer['uid'])
3458 if(intval($r[0]['def_gid'])) {
3459 require_once('include/group.php');
3460 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3463 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3464 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3465 $email = replace_macros($email_tpl, array(
3466 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3468 '$myname' => $r[0]['username'],
3469 '$siteurl' => $a->get_baseurl(),
3470 '$sitename' => $a->config['sitename']
3472 $res = mail($r[0]['email'],
3473 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'),
3475 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3476 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3477 . 'Content-transfer-encoding: 8bit' );
3484 function lose_follower($importer,$contact,$datarray,$item) {
3486 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3487 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3488 intval(CONTACT_IS_SHARING),
3489 intval($contact['id'])
3493 contact_remove($contact['id']);
3497 function lose_sharer($importer,$contact,$datarray,$item) {
3499 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3500 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3501 intval(CONTACT_IS_FOLLOWER),
3502 intval($contact['id'])
3506 contact_remove($contact['id']);
3511 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3515 if(is_array($importer)) {
3516 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3517 intval($importer['uid'])
3521 // Diaspora has different message-ids in feeds than they do
3522 // through the direct Diaspora protocol. If we try and use
3523 // the feed, we'll get duplicates. So don't.
3525 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3528 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3530 // Use a single verify token, even if multiple hubs
3532 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3534 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3536 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3538 if(! strlen($contact['hub-verify'])) {
3539 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d LIMIT 1",
3540 dbesc($verify_token),
3541 intval($contact['id'])
3545 post_url($url,$params);
3547 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3554 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3558 $name = xmlify($name);
3559 $uri = xmlify($uri);
3562 $photo = xmlify($photo);
3566 $o .= "<name>$name</name>\r\n";
3567 $o .= "<uri>$uri</uri>\r\n";
3568 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3569 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3571 call_hooks('atom_author', $o);
3573 $o .= "</$tag>\r\n";
3577 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3581 if(! $item['parent'])
3584 if($item['deleted'])
3585 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3588 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3589 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3591 $body = $item['body'];
3593 $o = "\r\n\r\n<entry>\r\n";
3595 if(is_array($author))
3596 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3598 $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']));
3599 if(strlen($item['owner-name']))
3600 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3602 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3603 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3604 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3607 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3608 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3609 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3610 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3611 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3612 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3613 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3615 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3617 if($item['location']) {
3618 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3619 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3623 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3625 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3626 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3629 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3630 if($item['bookmark'])
3631 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3634 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3637 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3639 if($item['signed_text']) {
3640 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3641 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3644 $verb = construct_verb($item);
3645 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3646 $actobj = construct_activity_object($item);
3649 $actarg = construct_activity_target($item);
3653 $tags = item_getfeedtags($item);
3655 foreach($tags as $t) {
3656 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3660 $o .= item_getfeedattach($item);
3662 $mentioned = get_mentions($item);
3666 call_hooks('atom_entry', $o);
3668 $o .= '</entry>' . "\r\n";
3673 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3675 if(get_config('system','disable_embedded'))
3680 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3681 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3686 $img_start = strpos($orig_body, '[img');
3687 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3688 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3689 while( ($img_st_close !== false) && ($img_len !== false) ) {
3691 $img_st_close++; // make it point to AFTER the closing bracket
3692 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3694 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3697 if(stristr($image , $site . '/photo/')) {
3698 // Only embed locally hosted photos
3700 $i = basename($image);
3701 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3702 $x = strpos($i,'-');
3705 $res = substr($i,$x+1);
3706 $i = substr($i,0,$x);
3707 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3714 // Check to see if we should replace this photo link with an embedded image
3715 // 1. No need to do so if the photo is public
3716 // 2. If there's a contact-id provided, see if they're in the access list
3717 // for the photo. If so, embed it.
3718 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3719 // permissions, regardless of order but first check to see if they're an exact
3720 // match to save some processing overhead.
3722 if(has_permissions($r[0])) {
3724 $recips = enumerate_permissions($r[0]);
3725 if(in_array($cid, $recips)) {
3730 if(compare_permissions($item,$r[0]))
3735 $data = $r[0]['data'];
3736 $type = $r[0]['type'];
3738 // If a custom width and height were specified, apply before embedding
3739 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3740 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3742 $width = intval($match[1]);
3743 $height = intval($match[2]);
3745 $ph = new Photo($data, $type);
3746 if($ph->is_valid()) {
3747 $ph->scaleImage(max($width, $height));
3748 $data = $ph->imageString();
3749 $type = $ph->getType();
3753 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3754 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3755 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3761 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3762 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3763 if($orig_body === false)
3766 $img_start = strpos($orig_body, '[img');
3767 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3768 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3771 $new_body = $new_body . $orig_body;
3777 function has_permissions($obj) {
3778 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3783 function compare_permissions($obj1,$obj2) {
3784 // first part is easy. Check that these are exactly the same.
3785 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3786 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3787 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3788 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3791 // This is harder. Parse all the permissions and compare the resulting set.
3793 $recipients1 = enumerate_permissions($obj1);
3794 $recipients2 = enumerate_permissions($obj2);
3797 if($recipients1 == $recipients2)
3802 // returns an array of contact-ids that are allowed to see this object
3804 function enumerate_permissions($obj) {
3805 require_once('include/group.php');
3806 $allow_people = expand_acl($obj['allow_cid']);
3807 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3808 $deny_people = expand_acl($obj['deny_cid']);
3809 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3810 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3811 $deny = array_unique(array_merge($deny_people,$deny_groups));
3812 $recipients = array_diff($recipients,$deny);
3816 function item_getfeedtags($item) {
3819 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3821 for($x = 0; $x < $cnt; $x ++) {
3823 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3827 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3829 for($x = 0; $x < $cnt; $x ++) {
3831 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3837 function item_getfeedattach($item) {
3839 $arr = explode('[/attach],',$item['attach']);
3841 foreach($arr as $r) {
3843 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3845 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3846 if(intval($matches[2]))
3847 $ret .= 'length="' . intval($matches[2]) . '" ';
3848 if($matches[4] !== ' ')
3849 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3850 $ret .= ' />' . "\r\n";
3859 function item_expire($uid,$days) {
3861 if((! $uid) || ($days < 1))
3864 // $expire_network_only = save your own wall posts
3865 // and just expire conversations started by others
3867 $expire_network_only = get_pconfig($uid,'expire','network_only');
3868 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3870 $r = q("SELECT * FROM `item`
3872 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
3883 $expire_items = get_pconfig($uid, 'expire','items');
3884 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3886 $expire_notes = get_pconfig($uid, 'expire','notes');
3887 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3889 $expire_starred = get_pconfig($uid, 'expire','starred');
3890 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3892 $expire_photos = get_pconfig($uid, 'expire','photos');
3893 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3895 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3897 foreach($r as $item) {
3899 // don't expire filed items
3901 if(strpos($item['file'],'[') !== false)
3904 // Only expire posts, not photos and photo comments
3906 if($expire_photos==0 && strlen($item['resource-id']))
3908 if($expire_starred==0 && intval($item['starred']))
3910 if($expire_notes==0 && $item['type']=='note')
3912 if($expire_items==0 && $item['type']!='note')
3915 drop_item($item['id'],false);
3918 proc_run('php',"include/notifier.php","expire","$uid");
3923 function drop_items($items) {
3926 if(! local_user() && ! remote_user())
3930 foreach($items as $item) {
3931 $owner = drop_item($item,false);
3932 if($owner && ! $uid)
3937 // multiple threads may have been deleted, send an expire notification
3940 proc_run('php',"include/notifier.php","expire","$uid");
3944 function drop_item($id,$interactive = true) {
3948 // locate item to be deleted
3950 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3957 notice( t('Item not found.') . EOL);
3958 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3963 $owner = $item['uid'];
3967 // check if logged in user is either the author or owner of this item
3969 if(is_array($_SESSION['remote'])) {
3970 foreach($_SESSION['remote'] as $visitor) {
3971 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3972 $cid = $visitor['cid'];
3979 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3981 // Check if we should do HTML-based delete confirmation
3982 if($_REQUEST['confirm']) {
3983 // <form> can't take arguments in its "action" parameter
3984 // so add any arguments as hidden inputs
3985 $query = explode_querystring($a->query_string);
3987 foreach($query['args'] as $arg) {
3988 if(strpos($arg, 'confirm=') === false) {
3989 $arg_parts = explode('=', $arg);
3990 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3994 return replace_macros(get_markup_template('confirm.tpl'), array(
3996 '$message' => t('Do you really want to delete this item?'),
3997 '$extra_inputs' => $inputs,
3998 '$confirm' => t('Yes'),
3999 '$confirm_url' => $query['base'],
4000 '$confirm_name' => 'confirmed',
4001 '$cancel' => t('Cancel'),
4004 // Now check how the user responded to the confirmation query
4005 if($_REQUEST['canceled']) {
4006 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4009 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4012 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
4013 dbesc(datetime_convert()),
4014 dbesc(datetime_convert()),
4017 create_tags_from_item($item['id']);
4019 // clean up categories and tags so they don't end up as orphans
4022 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4024 foreach($matches as $mtch) {
4025 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4031 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4033 foreach($matches as $mtch) {
4034 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4038 // If item is a link to a photo resource, nuke all the associated photos
4039 // (visitors will not have photo resources)
4040 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4041 // generate a resource-id and therefore aren't intimately linked to the item.
4043 if(strlen($item['resource-id'])) {
4044 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4045 dbesc($item['resource-id']),
4046 intval($item['uid'])
4048 // ignore the result
4051 // If item is a link to an event, nuke the event record.
4053 if(intval($item['event-id'])) {
4054 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1",
4055 intval($item['event-id']),
4056 intval($item['uid'])
4058 // ignore the result
4061 // clean up item_id and sign meta-data tables
4063 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4064 intval($item['id']),
4065 intval($item['uid'])
4068 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4069 intval($item['id']),
4070 intval($item['uid'])
4073 // If it's the parent of a comment thread, kill all the kids
4075 if($item['uri'] == $item['parent-uri']) {
4076 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4077 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4078 dbesc(datetime_convert()),
4079 dbesc(datetime_convert()),
4080 dbesc($item['parent-uri']),
4081 intval($item['uid'])
4083 create_tags_from_item($item['parent-uri'], $item['uid']);
4084 // ignore the result
4087 // ensure that last-child is set in case the comment that had it just got wiped.
4088 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4089 dbesc(datetime_convert()),
4090 dbesc($item['parent-uri']),
4091 intval($item['uid'])
4093 // who is the last child now?
4094 $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",
4095 dbesc($item['parent-uri']),
4096 intval($item['uid'])
4099 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
4104 // Add a relayable_retraction signature for Diaspora.
4105 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4107 $drop_id = intval($item['id']);
4109 // send the notification upstream/downstream as the case may be
4111 proc_run('php',"include/notifier.php","drop","$drop_id");
4115 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4121 notice( t('Permission denied.') . EOL);
4122 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4129 function first_post_date($uid,$wall = false) {
4130 $r = q("select id, created from item
4131 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4133 order by created asc limit 1",
4135 intval($wall ? 1 : 0)
4138 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4139 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4144 function posted_dates($uid,$wall) {
4145 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4147 $dthen = first_post_date($uid,$wall);
4151 // If it's near the end of a long month, backup to the 28th so that in
4152 // consecutive loops we'll always get a whole month difference.
4154 if(intval(substr($dnow,8)) > 28)
4155 $dnow = substr($dnow,0,8) . '28';
4156 if(intval(substr($dthen,8)) > 28)
4157 $dnow = substr($dthen,0,8) . '28';
4160 // Starting with the current month, get the first and last days of every
4161 // month down to and including the month of the first post
4162 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4163 $dstart = substr($dnow,0,8) . '01';
4164 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4165 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4166 $end_month = datetime_convert('','',$dend,'Y-m-d');
4167 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4168 $ret[] = array($str,$end_month,$start_month);
4169 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4175 function posted_date_widget($url,$uid,$wall) {
4178 if(! feature_enabled($uid,'archives'))
4181 // For former Facebook folks that left because of "timeline"
4183 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4186 $ret = posted_dates($uid,$wall);
4190 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4191 '$title' => t('Archives'),
4192 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4199 function store_diaspora_retract_sig($item, $user, $baseurl) {
4200 // Note that we can't add a target_author_signature
4201 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4202 // the comment, that means we're the home of the post, and Diaspora will only
4203 // check the parent_author_signature of retractions that it doesn't have to relay further
4205 // I don't think this function gets called for an "unlike," but I'll check anyway
4207 $enabled = intval(get_config('system','diaspora_enabled'));
4209 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4213 logger('drop_item: storing diaspora retraction signature');
4215 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4217 if(local_user() == $item['uid']) {
4219 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4220 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4223 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4224 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4227 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4228 // only handles DFRN deletes
4229 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4230 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4231 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4237 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4238 intval($item['id']),
4239 dbesc($signed_text),