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 $deleted = tag_deliver($arr['uid'],$current_post);
1185 // current post can be deleted if is for a communuty page and no mention are
1189 // Store the fresh generated item into the cache
1190 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1192 if (($cachefile != '') AND !file_exists($cachefile)) {
1193 $s = prepare_text($arr['body']);
1195 $stamp1 = microtime(true);
1196 file_put_contents($cachefile, $s);
1197 $a->save_timestamp($stamp1, "file");
1198 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1201 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1202 if (count($r) == 1) {
1203 call_hooks('post_remote_end', $r[0]);
1206 logger('item_store: new item not found in DB, id ' . $current_post);
1209 return $current_post;
1212 function get_item_contact($item,$contacts) {
1213 if(! count($contacts) || (! is_array($item)))
1215 foreach($contacts as $contact) {
1216 if($contact['id'] == $item['contact-id']) {
1218 break; // NOTREACHED
1225 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1227 * @param int $item_id
1228 * @return bool true if item was deleted, else false
1230 function tag_deliver($uid,$item_id) {
1238 $u = q("select * from user where uid = %d limit 1",
1244 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1245 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1248 $i = q("select * from item where id = %d and uid = %d limit 1",
1257 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1259 // Diaspora uses their own hardwired link URL in @-tags
1260 // instead of the one we supply with webfinger
1262 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1264 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1266 foreach($matches as $mtch) {
1267 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1269 logger('tag_deliver: mention found: ' . $mtch[2]);
1275 if ( ($community_page || $prvgroup) &&
1276 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1277 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1279 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1280 q("DELETE FROM item WHERE id = %d and uid = %d limit 1",
1290 // send a notification
1292 // use a local photo if we have one
1294 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1295 intval($u[0]['uid']),
1296 dbesc(normalise_link($item['author-link']))
1298 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1301 require_once('include/enotify.php');
1303 'type' => NOTIFY_TAGSELF,
1304 'notify_flags' => $u[0]['notify-flags'],
1305 'language' => $u[0]['language'],
1306 'to_name' => $u[0]['username'],
1307 'to_email' => $u[0]['email'],
1308 'uid' => $u[0]['uid'],
1310 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1311 'source_name' => $item['author-name'],
1312 'source_link' => $item['author-link'],
1313 'source_photo' => $photo,
1314 'verb' => ACTIVITY_TAG,
1319 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1321 call_hooks('tagged', $arr);
1323 if((! $community_page) && (! $prvgroup))
1327 // tgroup delivery - setup a second delivery chain
1328 // prevent delivery looping - only proceed
1329 // if the message originated elsewhere and is a top-level post
1331 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1334 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1337 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1338 intval($u[0]['uid'])
1343 // also reset all the privacy bits to the forum default permissions
1345 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1347 $forum_mode = (($prvgroup) ? 2 : 1);
1349 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1350 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d limit 1",
1351 intval($forum_mode),
1352 dbesc($c[0]['name']),
1353 dbesc($c[0]['url']),
1354 dbesc($c[0]['thumb']),
1356 dbesc($u[0]['allow_cid']),
1357 dbesc($u[0]['allow_gid']),
1358 dbesc($u[0]['deny_cid']),
1359 dbesc($u[0]['deny_gid']),
1363 proc_run('php','include/notifier.php','tgroup',$item_id);
1369 function tgroup_check($uid,$item) {
1375 // check that the message originated elsewhere and is a top-level post
1377 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1381 $u = q("select * from user where uid = %d limit 1",
1387 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1388 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1391 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1393 // Diaspora uses their own hardwired link URL in @-tags
1394 // instead of the one we supply with webfinger
1396 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1398 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1400 foreach($matches as $mtch) {
1401 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1403 logger('tgroup_check: mention found: ' . $mtch[2]);
1411 if((! $community_page) && (! $prvgroup))
1425 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1429 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1431 if($contact['duplex'] && $contact['dfrn-id'])
1432 $idtosend = '0:' . $orig_id;
1433 if($contact['duplex'] && $contact['issued-id'])
1434 $idtosend = '1:' . $orig_id;
1436 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1438 $rino_enable = get_config('system','rino_encrypt');
1443 $ssl_val = intval(get_config('system','ssl_policy'));
1447 case SSL_POLICY_FULL:
1448 $ssl_policy = 'full';
1450 case SSL_POLICY_SELFSIGN:
1451 $ssl_policy = 'self';
1453 case SSL_POLICY_NONE:
1455 $ssl_policy = 'none';
1459 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1461 logger('dfrn_deliver: ' . $url);
1463 $xml = fetch_url($url);
1465 $curl_stat = $a->get_curl_code();
1467 return(-1); // timed out
1469 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1474 if(strpos($xml,'<?xml') === false) {
1475 logger('dfrn_deliver: no valid XML returned');
1476 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1480 $res = parse_xml_string($xml);
1482 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1483 return (($res->status) ? $res->status : 3);
1485 $postvars = array();
1486 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1487 $challenge = hex2bin((string) $res->challenge);
1488 $perm = (($res->perm) ? $res->perm : null);
1489 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1490 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1491 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1493 if($owner['page-flags'] == PAGE_PRVGROUP)
1496 $final_dfrn_id = '';
1499 if((($perm == 'rw') && (! intval($contact['writable'])))
1500 || (($perm == 'r') && (intval($contact['writable'])))) {
1501 q("update contact set writable = %d where id = %d limit 1",
1502 intval(($perm == 'rw') ? 1 : 0),
1503 intval($contact['id'])
1505 $contact['writable'] = (string) 1 - intval($contact['writable']);
1509 if(($contact['duplex'] && strlen($contact['pubkey']))
1510 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1511 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1512 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1513 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1516 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1517 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1520 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1522 if(strpos($final_dfrn_id,':') == 1)
1523 $final_dfrn_id = substr($final_dfrn_id,2);
1525 if($final_dfrn_id != $orig_id) {
1526 logger('dfrn_deliver: wrong dfrn_id.');
1527 // did not decode properly - cannot trust this site
1531 $postvars['dfrn_id'] = $idtosend;
1532 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1534 $postvars['dissolve'] = '1';
1537 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1538 $postvars['data'] = $atom;
1539 $postvars['perm'] = 'rw';
1542 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1543 $postvars['perm'] = 'r';
1546 $postvars['ssl_policy'] = $ssl_policy;
1549 $postvars['page'] = $page;
1551 if($rino && $rino_allowed && (! $dissolve)) {
1552 $key = substr(random_string(),0,16);
1553 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1554 $postvars['data'] = $data;
1555 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1558 if($dfrn_version >= 2.1) {
1559 if(($contact['duplex'] && strlen($contact['pubkey']))
1560 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1561 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1563 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1566 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1570 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1571 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1574 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1578 logger('md5 rawkey ' . md5($postvars['key']));
1580 $postvars['key'] = bin2hex($postvars['key']);
1583 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1585 $xml = post_url($contact['notify'],$postvars);
1587 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1589 $curl_stat = $a->get_curl_code();
1590 if((! $curl_stat) || (! strlen($xml)))
1591 return(-1); // timed out
1593 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1596 if(strpos($xml,'<?xml') === false) {
1597 logger('dfrn_deliver: phase 2: no valid XML returned');
1598 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1602 if($contact['term-date'] != '0000-00-00 00:00:00') {
1603 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1604 require_once('include/Contact.php');
1605 unmark_for_death($contact);
1608 $res = parse_xml_string($xml);
1610 return $res->status;
1615 This function returns true if $update has an edited timestamp newer
1616 than $existing, i.e. $update contains new data which should override
1617 what's already there. If there is no timestamp yet, the update is
1618 assumed to be newer. If the update has no timestamp, the existing
1619 item is assumed to be up-to-date. If the timestamps are equal it
1620 assumes the update has been seen before and should be ignored.
1622 function edited_timestamp_is_newer($existing, $update) {
1623 if (!x($existing,'edited') || !$existing['edited']) {
1626 if (!x($update,'edited') || !$update['edited']) {
1629 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1630 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1631 return (strcmp($existing_edited, $update_edited) < 0);
1636 * consume_feed - process atom feed and update anything/everything we might need to update
1638 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1640 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1641 * It is this person's stuff that is going to be updated.
1642 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1643 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1644 * have a contact record.
1645 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1646 * might not) try and subscribe to it.
1647 * $datedir sorts in reverse order
1648 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1649 * imported prior to its children being seen in the stream unless we are certain
1650 * of how the feed is arranged/ordered.
1651 * With $pass = 1, we only pull parent items out of the stream.
1652 * With $pass = 2, we only pull children (comments/likes).
1654 * So running this twice, first with pass 1 and then with pass 2 will do the right
1655 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1656 * model where comments can have sub-threads. That would require some massive sorting
1657 * to get all the feed items into a mostly linear ordering, and might still require
1661 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1663 require_once('library/simplepie/simplepie.inc');
1665 if(! strlen($xml)) {
1666 logger('consume_feed: empty input');
1670 $feed = new SimplePie();
1671 $feed->set_raw_data($xml);
1673 $feed->enable_order_by_date(true);
1675 $feed->enable_order_by_date(false);
1679 logger('consume_feed: Error parsing XML: ' . $feed->error());
1681 $permalink = $feed->get_permalink();
1683 // Check at the feed level for updated contact name and/or photo
1687 $photo_timestamp = '';
1691 $hubs = $feed->get_links('hub');
1692 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1695 $hub = implode(',', $hubs);
1697 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1699 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1701 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1702 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1703 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1704 $new_name = $elems['name'][0]['data'];
1706 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1707 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1708 $photo_url = $elems['link'][0]['attribs']['']['href'];
1711 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1712 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1716 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1717 logger('consume_feed: Updating photo for ' . $contact['name']);
1718 require_once("include/Photo.php");
1719 $photo_failure = false;
1720 $have_photo = false;
1722 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1723 intval($contact['id']),
1724 intval($contact['uid'])
1727 $resource_id = $r[0]['resource-id'];
1731 $resource_id = photo_new_resource();
1734 $img_str = fetch_url($photo_url,true);
1735 // guess mimetype from headers or filename
1736 $type = guess_image_type($photo_url,true);
1739 $img = new Photo($img_str, $type);
1740 if($img->is_valid()) {
1742 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1743 dbesc($resource_id),
1744 intval($contact['id']),
1745 intval($contact['uid'])
1749 $img->scaleImageSquare(175);
1751 $hash = $resource_id;
1752 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1754 $img->scaleImage(80);
1755 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1757 $img->scaleImage(48);
1758 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1762 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1763 WHERE `uid` = %d AND `id` = %d LIMIT 1",
1764 dbesc(datetime_convert()),
1765 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1766 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1767 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1768 intval($contact['uid']),
1769 intval($contact['id'])
1774 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1775 $r = q("select * from contact where uid = %d and id = %d limit 1",
1776 intval($contact['uid']),
1777 intval($contact['id'])
1780 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1781 dbesc(notags(trim($new_name))),
1782 dbesc(datetime_convert()),
1783 intval($contact['uid']),
1784 intval($contact['id'])
1787 // do our best to update the name on content items
1790 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1791 dbesc(notags(trim($new_name))),
1792 dbesc($r[0]['name']),
1793 dbesc($r[0]['url']),
1794 intval($contact['uid'])
1799 if(strlen($birthday)) {
1800 if(substr($birthday,0,4) != $contact['bdyear']) {
1801 logger('consume_feed: updating birthday: ' . $birthday);
1805 * Add new birthday event for this person
1807 * $bdtext is just a readable placeholder in case the event is shared
1808 * with others. We will replace it during presentation to our $importer
1809 * to contain a sparkle link and perhaps a photo.
1813 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1814 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1817 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1818 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1819 intval($contact['uid']),
1820 intval($contact['id']),
1821 dbesc(datetime_convert()),
1822 dbesc(datetime_convert()),
1823 dbesc(datetime_convert('UTC','UTC', $birthday)),
1824 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1833 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1834 dbesc(substr($birthday,0,4)),
1835 intval($contact['uid']),
1836 intval($contact['id'])
1839 // This function is called twice without reloading the contact
1840 // Make sure we only create one event. This is why &$contact
1841 // is a reference var in this function
1843 $contact['bdyear'] = substr($birthday,0,4);
1848 $community_page = 0;
1849 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1851 $community_page = intval($rawtags[0]['data']);
1853 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1854 q("update contact set forum = %d where id = %d limit 1",
1855 intval($community_page),
1856 intval($contact['id'])
1858 $contact['forum'] = (string) $community_page;
1862 // process any deleted entries
1864 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1865 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1866 foreach($del_entries as $dentry) {
1868 if(isset($dentry['attribs']['']['ref'])) {
1869 $uri = $dentry['attribs']['']['ref'];
1871 if(isset($dentry['attribs']['']['when'])) {
1872 $when = $dentry['attribs']['']['when'];
1873 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1876 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1878 if($deleted && is_array($contact)) {
1879 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id`
1880 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1882 intval($importer['uid']),
1883 intval($contact['id'])
1888 if(! $item['deleted'])
1889 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1891 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1892 $xo = parse_xml_string($item['object'],false);
1893 $xt = parse_xml_string($item['target'],false);
1894 if($xt->type === ACTIVITY_OBJ_NOTE) {
1895 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1897 intval($importer['importer_uid'])
1901 // For tags, the owner cannot remove the tag on the author's copy of the post.
1903 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1904 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1905 $author_copy = (($item['origin']) ? true : false);
1907 if($owner_remove && $author_copy)
1909 if($author_remove || $owner_remove) {
1910 $tags = explode(',',$i[0]['tag']);
1913 foreach($tags as $tag)
1914 if(trim($tag) !== trim($xo->body))
1915 $newtags[] = trim($tag);
1917 q("update item set tag = '%s' where id = %d limit 1",
1918 dbesc(implode(',',$newtags)),
1921 create_tags_from_item($i[0]['id']);
1927 if($item['uri'] == $item['parent-uri']) {
1928 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1929 `body` = '', `title` = ''
1930 WHERE `parent-uri` = '%s' AND `uid` = %d",
1932 dbesc(datetime_convert()),
1933 dbesc($item['uri']),
1934 intval($importer['uid'])
1936 create_tags_from_itemuri($item['uri'], $importer['uid']);
1939 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1940 `body` = '', `title` = ''
1941 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1943 dbesc(datetime_convert()),
1945 intval($importer['uid'])
1947 create_tags_from_itemuri($uri, $importer['uid']);
1948 if($item['last-child']) {
1949 // ensure that last-child is set in case the comment that had it just got wiped.
1950 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1951 dbesc(datetime_convert()),
1952 dbesc($item['parent-uri']),
1953 intval($item['uid'])
1955 // who is the last child now?
1956 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1957 ORDER BY `created` DESC LIMIT 1",
1958 dbesc($item['parent-uri']),
1959 intval($importer['uid'])
1962 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
1973 // Now process the feed
1975 if($feed->get_item_quantity()) {
1977 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1979 // in inverse date order
1981 $items = array_reverse($feed->get_items());
1983 $items = $feed->get_items();
1986 foreach($items as $item) {
1989 $item_id = $item->get_id();
1990 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
1991 if(isset($rawthread[0]['attribs']['']['ref'])) {
1993 $parent_uri = $rawthread[0]['attribs']['']['ref'];
1996 if(($is_reply) && is_array($contact)) {
2001 // not allowed to post
2003 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2007 // Have we seen it? If not, import it.
2009 $item_id = $item->get_id();
2010 $datarray = get_atom_elements($feed,$item);
2012 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2013 $datarray['author-name'] = $contact['name'];
2014 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2015 $datarray['author-link'] = $contact['url'];
2016 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2017 $datarray['author-avatar'] = $contact['thumb'];
2019 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2020 logger('consume_feed: no author information! ' . print_r($datarray,true));
2024 $force_parent = false;
2025 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2026 if($contact['network'] === NETWORK_OSTATUS)
2027 $force_parent = true;
2028 if(strlen($datarray['title']))
2029 unset($datarray['title']);
2030 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2031 dbesc(datetime_convert()),
2033 intval($importer['uid'])
2035 $datarray['last-child'] = 1;
2039 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2041 intval($importer['uid'])
2044 // Update content if 'updated' changes
2047 if (edited_timestamp_is_newer($r[0], $datarray)) {
2049 // do not accept (ignore) an earlier edit than one we currently have.
2050 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2053 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2054 dbesc($datarray['title']),
2055 dbesc($datarray['body']),
2056 dbesc($datarray['tag']),
2057 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2059 intval($importer['uid'])
2061 create_tags_from_itemuri($item_id, $importer['uid']);
2064 // update last-child if it changes
2066 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2067 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2068 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2069 dbesc(datetime_convert()),
2071 intval($importer['uid'])
2073 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2074 intval($allow[0]['data']),
2075 dbesc(datetime_convert()),
2077 intval($importer['uid'])
2084 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2085 // one way feed - no remote comment ability
2086 $datarray['last-child'] = 0;
2088 $datarray['parent-uri'] = $parent_uri;
2089 $datarray['uid'] = $importer['uid'];
2090 $datarray['contact-id'] = $contact['id'];
2091 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2092 $datarray['type'] = 'activity';
2093 $datarray['gravity'] = GRAVITY_LIKE;
2094 // only one like or dislike per person
2095 $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",
2096 intval($datarray['uid']),
2097 intval($datarray['contact-id']),
2098 dbesc($datarray['verb']),
2106 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2107 $xo = parse_xml_string($datarray['object'],false);
2108 $xt = parse_xml_string($datarray['target'],false);
2110 if($xt->type == ACTIVITY_OBJ_NOTE) {
2111 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2113 intval($importer['importer_uid'])
2118 // extract tag, if not duplicate, add to parent item
2119 if($xo->id && $xo->content) {
2120 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2121 if(! (stristr($r[0]['tag'],$newtag))) {
2122 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
2123 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2126 create_tags_from_item($r[0]['id']);
2132 $r = item_store($datarray,$force_parent);
2138 // Head post of a conversation. Have we seen it? If not, import it.
2140 $item_id = $item->get_id();
2142 $datarray = get_atom_elements($feed,$item);
2144 if(is_array($contact)) {
2145 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2146 $datarray['author-name'] = $contact['name'];
2147 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2148 $datarray['author-link'] = $contact['url'];
2149 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2150 $datarray['author-avatar'] = $contact['thumb'];
2153 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2154 logger('consume_feed: no author information! ' . print_r($datarray,true));
2158 // special handling for events
2160 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2161 $ev = bbtoevent($datarray['body']);
2162 if(x($ev,'desc') && x($ev,'start')) {
2163 $ev['uid'] = $importer['uid'];
2164 $ev['uri'] = $item_id;
2165 $ev['edited'] = $datarray['edited'];
2166 $ev['private'] = $datarray['private'];
2168 if(is_array($contact))
2169 $ev['cid'] = $contact['id'];
2170 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2172 intval($importer['uid'])
2175 $ev['id'] = $r[0]['id'];
2176 $xyz = event_store($ev);
2181 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2182 if(strlen($datarray['title']))
2183 unset($datarray['title']);
2184 $datarray['last-child'] = 1;
2188 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2190 intval($importer['uid'])
2193 // Update content if 'updated' changes
2196 if (edited_timestamp_is_newer($r[0], $datarray)) {
2198 // do not accept (ignore) an earlier edit than one we currently have.
2199 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2202 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2203 dbesc($datarray['title']),
2204 dbesc($datarray['body']),
2205 dbesc($datarray['tag']),
2206 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2208 intval($importer['uid'])
2210 create_tags_from_itemuri($item_id, $importer['uid']);
2213 // update last-child if it changes
2215 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2216 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2217 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2218 intval($allow[0]['data']),
2219 dbesc(datetime_convert()),
2221 intval($importer['uid'])
2227 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2228 logger('consume-feed: New follower');
2229 new_follower($importer,$contact,$datarray,$item);
2232 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2233 lose_follower($importer,$contact,$datarray,$item);
2237 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2238 logger('consume-feed: New friend request');
2239 new_follower($importer,$contact,$datarray,$item,true);
2242 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2243 lose_sharer($importer,$contact,$datarray,$item);
2248 if(! is_array($contact))
2252 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2253 // one way feed - no remote comment ability
2254 $datarray['last-child'] = 0;
2256 if($contact['network'] === NETWORK_FEED)
2257 $datarray['private'] = 2;
2259 // This is my contact on another system, but it's really me.
2260 // Turn this into a wall post.
2262 if($contact['remote_self']) {
2263 $datarray['wall'] = 1;
2264 if($contact['network'] === NETWORK_FEED) {
2265 $datarray['private'] = 0;
2269 $datarray['parent-uri'] = $item_id;
2270 $datarray['uid'] = $importer['uid'];
2271 $datarray['contact-id'] = $contact['id'];
2273 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2274 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2275 // but otherwise there's a possible data mixup on the sender's system.
2276 // the tgroup delivery code called from item_store will correct it if it's a forum,
2277 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2278 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2279 $datarray['owner-name'] = $contact['name'];
2280 $datarray['owner-link'] = $contact['url'];
2281 $datarray['owner-avatar'] = $contact['thumb'];
2284 // We've allowed "followers" to reach this point so we can decide if they are
2285 // posting an @-tag delivery, which followers are allowed to do for certain
2286 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2288 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2292 $r = item_store($datarray);
2300 function local_delivery($importer,$data) {
2303 logger(__function__, LOGGER_TRACE);
2305 if($importer['readonly']) {
2306 // We aren't receiving stuff from this person. But we will quietly ignore them
2307 // rather than a blatant "go away" message.
2308 logger('local_delivery: ignoring');
2313 // Consume notification feed. This may differ from consuming a public feed in several ways
2314 // - might contain email or friend suggestions
2315 // - might contain remote followup to our message
2316 // - in which case we need to accept it and then notify other conversants
2317 // - we may need to send various email notifications
2319 $feed = new SimplePie();
2320 $feed->set_raw_data($data);
2321 $feed->enable_order_by_date(false);
2326 logger('local_delivery: Error parsing XML: ' . $feed->error());
2329 // Check at the feed level for updated contact name and/or photo
2333 $photo_timestamp = '';
2337 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2339 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2341 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2344 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2345 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2346 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2347 $new_name = $elems['name'][0]['data'];
2349 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2350 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2351 $photo_url = $elems['link'][0]['attribs']['']['href'];
2355 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2356 logger('local_delivery: Updating photo for ' . $importer['name']);
2357 require_once("include/Photo.php");
2358 $photo_failure = false;
2359 $have_photo = false;
2361 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2362 intval($importer['id']),
2363 intval($importer['importer_uid'])
2366 $resource_id = $r[0]['resource-id'];
2370 $resource_id = photo_new_resource();
2373 $img_str = fetch_url($photo_url,true);
2374 // guess mimetype from headers or filename
2375 $type = guess_image_type($photo_url,true);
2378 $img = new Photo($img_str, $type);
2379 if($img->is_valid()) {
2381 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2382 dbesc($resource_id),
2383 intval($importer['id']),
2384 intval($importer['importer_uid'])
2388 $img->scaleImageSquare(175);
2390 $hash = $resource_id;
2391 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2393 $img->scaleImage(80);
2394 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2396 $img->scaleImage(48);
2397 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2401 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2402 WHERE `uid` = %d AND `id` = %d LIMIT 1",
2403 dbesc(datetime_convert()),
2404 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2405 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2406 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2407 intval($importer['importer_uid']),
2408 intval($importer['id'])
2413 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2414 $r = q("select * from contact where uid = %d and id = %d limit 1",
2415 intval($importer['importer_uid']),
2416 intval($importer['id'])
2419 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
2420 dbesc(notags(trim($new_name))),
2421 dbesc(datetime_convert()),
2422 intval($importer['importer_uid']),
2423 intval($importer['id'])
2426 // do our best to update the name on content items
2429 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2430 dbesc(notags(trim($new_name))),
2431 dbesc($r[0]['name']),
2432 dbesc($r[0]['url']),
2433 intval($importer['importer_uid'])
2440 // Currently unsupported - needs a lot of work
2441 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2442 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2443 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2445 $newloc['uid'] = $importer['importer_uid'];
2446 $newloc['cid'] = $importer['id'];
2447 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2448 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2449 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2450 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2451 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2452 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2453 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2454 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2455 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2456 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2457 /** relocated user must have original key pair */
2458 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2459 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2461 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2464 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2465 intval($importer['id']),
2466 intval($importer['importer_uid']));
2471 $x = q("UPDATE contact SET
2481 `site-pubkey` = '%s'
2482 WHERE id=%d AND uid=%d;",
2483 dbesc($newloc['name']),
2484 dbesc($newloc['photo']),
2485 dbesc($newloc['thumb']),
2486 dbesc($newloc['micro']),
2487 dbesc($newloc['url']),
2488 dbesc($newloc['request']),
2489 dbesc($newloc['confirm']),
2490 dbesc($newloc['notify']),
2491 dbesc($newloc['poll']),
2492 dbesc($newloc['sitepubkey']),
2493 intval($importer['id']),
2494 intval($importer['importer_uid']));
2500 'owner-link' => array($old['url'], $newloc['url']),
2501 'author-link' => array($old['url'], $newloc['url']),
2502 'owner-avatar' => array($old['photo'], $newloc['photo']),
2503 'author-avatar' => array($old['photo'], $newloc['photo']),
2505 foreach ($fields as $n=>$f){
2506 $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2509 intval($importer['importer_uid']));
2515 // merge with current record, current contents have priority
2516 // update record, set url-updated
2517 // update profile photos
2523 // handle friend suggestion notification
2525 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2526 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2527 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2529 $fsugg['uid'] = $importer['importer_uid'];
2530 $fsugg['cid'] = $importer['id'];
2531 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2532 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2533 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2534 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2535 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2537 // Does our member already have a friend matching this description?
2539 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2540 dbesc($fsugg['name']),
2541 dbesc(normalise_link($fsugg['url'])),
2542 intval($fsugg['uid'])
2547 // Do we already have an fcontact record for this person?
2550 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2551 dbesc($fsugg['url']),
2552 dbesc($fsugg['name']),
2553 dbesc($fsugg['request'])
2558 // OK, we do. Do we already have an introduction for this person ?
2559 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2560 intval($fsugg['uid']),
2567 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2568 dbesc($fsugg['name']),
2569 dbesc($fsugg['url']),
2570 dbesc($fsugg['photo']),
2571 dbesc($fsugg['request'])
2573 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2574 dbesc($fsugg['url']),
2575 dbesc($fsugg['name']),
2576 dbesc($fsugg['request'])
2581 // database record did not get created. Quietly give up.
2586 $hash = random_string();
2588 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2589 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2590 intval($fsugg['uid']),
2592 intval($fsugg['cid']),
2593 dbesc($fsugg['body']),
2595 dbesc(datetime_convert()),
2600 'type' => NOTIFY_SUGGEST,
2601 'notify_flags' => $importer['notify-flags'],
2602 'language' => $importer['language'],
2603 'to_name' => $importer['username'],
2604 'to_email' => $importer['email'],
2605 'uid' => $importer['importer_uid'],
2607 'link' => $a->get_baseurl() . '/notifications/intros',
2608 'source_name' => $importer['name'],
2609 'source_link' => $importer['url'],
2610 'source_photo' => $importer['photo'],
2611 'verb' => ACTIVITY_REQ_FRIEND,
2620 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2621 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2623 logger('local_delivery: private message received');
2626 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2629 $msg['uid'] = $importer['importer_uid'];
2630 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2631 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2632 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2633 $msg['contact-id'] = $importer['id'];
2634 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2635 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2637 $msg['replied'] = 0;
2638 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2639 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2640 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2644 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2645 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2647 // send notifications.
2649 require_once('include/enotify.php');
2651 $notif_params = array(
2652 'type' => NOTIFY_MAIL,
2653 'notify_flags' => $importer['notify-flags'],
2654 'language' => $importer['language'],
2655 'to_name' => $importer['username'],
2656 'to_email' => $importer['email'],
2657 'uid' => $importer['importer_uid'],
2659 'source_name' => $msg['from-name'],
2660 'source_link' => $importer['url'],
2661 'source_photo' => $importer['thumb'],
2662 'verb' => ACTIVITY_POST,
2666 notification($notif_params);
2672 $community_page = 0;
2673 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2675 $community_page = intval($rawtags[0]['data']);
2677 if(intval($importer['forum']) != $community_page) {
2678 q("update contact set forum = %d where id = %d limit 1",
2679 intval($community_page),
2680 intval($importer['id'])
2682 $importer['forum'] = (string) $community_page;
2685 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2687 // process any deleted entries
2689 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2690 if(is_array($del_entries) && count($del_entries)) {
2691 foreach($del_entries as $dentry) {
2693 if(isset($dentry['attribs']['']['ref'])) {
2694 $uri = $dentry['attribs']['']['ref'];
2696 if(isset($dentry['attribs']['']['when'])) {
2697 $when = $dentry['attribs']['']['when'];
2698 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2701 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2705 // check for relayed deletes to our conversation
2708 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2710 intval($importer['importer_uid'])
2713 $parent_uri = $r[0]['parent-uri'];
2714 if($r[0]['id'] != $r[0]['parent'])
2721 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2724 logger('local_delivery: possible community delete');
2727 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2729 // was the top-level post for this reply written by somebody on this site?
2730 // Specifically, the recipient?
2732 $is_a_remote_delete = false;
2734 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2735 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2736 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2737 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2738 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2739 AND `item`.`uid` = %d
2745 intval($importer['importer_uid'])
2748 $is_a_remote_delete = true;
2750 // Does this have the characteristics of a community or private group comment?
2751 // If it's a reply to a wall post on a community/prvgroup page it's a
2752 // valid community comment. Also forum_mode makes it valid for sure.
2753 // If neither, it's not.
2755 if($is_a_remote_delete && $community) {
2756 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2757 $is_a_remote_delete = false;
2758 logger('local_delivery: not a community delete');
2762 if($is_a_remote_delete) {
2763 logger('local_delivery: received remote delete');
2767 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2768 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2770 intval($importer['importer_uid']),
2771 intval($importer['id'])
2777 if($item['deleted'])
2780 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2782 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2783 $xo = parse_xml_string($item['object'],false);
2784 $xt = parse_xml_string($item['target'],false);
2786 if($xt->type === ACTIVITY_OBJ_NOTE) {
2787 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2789 intval($importer['importer_uid'])
2793 // For tags, the owner cannot remove the tag on the author's copy of the post.
2795 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2796 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2797 $author_copy = (($item['origin']) ? true : false);
2799 if($owner_remove && $author_copy)
2801 if($author_remove || $owner_remove) {
2802 $tags = explode(',',$i[0]['tag']);
2805 foreach($tags as $tag)
2806 if(trim($tag) !== trim($xo->body))
2807 $newtags[] = trim($tag);
2809 q("update item set tag = '%s' where id = %d limit 1",
2810 dbesc(implode(',',$newtags)),
2813 create_tags_from_item($i[0]['id']);
2819 if($item['uri'] == $item['parent-uri']) {
2820 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2821 `body` = '', `title` = ''
2822 WHERE `parent-uri` = '%s' AND `uid` = %d",
2824 dbesc(datetime_convert()),
2825 dbesc($item['uri']),
2826 intval($importer['importer_uid'])
2828 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2831 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2832 `body` = '', `title` = ''
2833 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2835 dbesc(datetime_convert()),
2837 intval($importer['importer_uid'])
2839 create_tags_from_itemuri($uri, $importer['importer_uid']);
2840 if($item['last-child']) {
2841 // ensure that last-child is set in case the comment that had it just got wiped.
2842 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2843 dbesc(datetime_convert()),
2844 dbesc($item['parent-uri']),
2845 intval($item['uid'])
2847 // who is the last child now?
2848 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2849 ORDER BY `created` DESC LIMIT 1",
2850 dbesc($item['parent-uri']),
2851 intval($importer['importer_uid'])
2854 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
2859 // if this is a relayed delete, propagate it to other recipients
2861 if($is_a_remote_delete)
2862 proc_run('php',"include/notifier.php","drop",$item['id']);
2870 foreach($feed->get_items() as $item) {
2873 $item_id = $item->get_id();
2874 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2875 if(isset($rawthread[0]['attribs']['']['ref'])) {
2877 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2883 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2886 logger('local_delivery: possible community reply');
2889 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2891 // was the top-level post for this reply written by somebody on this site?
2892 // Specifically, the recipient?
2894 $is_a_remote_comment = false;
2895 $top_uri = $parent_uri;
2897 $r = q("select `item`.`parent-uri` from `item`
2898 WHERE `item`.`uri` = '%s'
2902 if($r && count($r)) {
2903 $top_uri = $r[0]['parent-uri'];
2905 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2906 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2907 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2908 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2909 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2910 AND `item`.`uid` = %d
2916 intval($importer['importer_uid'])
2919 $is_a_remote_comment = true;
2922 // Does this have the characteristics of a community or private group comment?
2923 // If it's a reply to a wall post on a community/prvgroup page it's a
2924 // valid community comment. Also forum_mode makes it valid for sure.
2925 // If neither, it's not.
2927 if($is_a_remote_comment && $community) {
2928 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2929 $is_a_remote_comment = false;
2930 logger('local_delivery: not a community reply');
2934 if($is_a_remote_comment) {
2935 logger('local_delivery: received remote comment');
2937 // remote reply to our post. Import and then notify everybody else.
2939 $datarray = get_atom_elements($feed,$item);
2941 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2943 intval($importer['importer_uid'])
2946 // Update content if 'updated' changes
2950 if (edited_timestamp_is_newer($r[0], $datarray)) {
2952 // do not accept (ignore) an earlier edit than one we currently have.
2953 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2956 logger('received updated comment' , LOGGER_DEBUG);
2957 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2958 dbesc($datarray['title']),
2959 dbesc($datarray['body']),
2960 dbesc($datarray['tag']),
2961 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2963 intval($importer['importer_uid'])
2965 create_tags_from_itemuri($item_id, $importer['importer_uid']);
2967 proc_run('php',"include/notifier.php","comment-import",$iid);
2976 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
2977 intval($importer['importer_uid'])
2981 $datarray['type'] = 'remote-comment';
2982 $datarray['wall'] = 1;
2983 $datarray['parent-uri'] = $parent_uri;
2984 $datarray['uid'] = $importer['importer_uid'];
2985 $datarray['owner-name'] = $own[0]['name'];
2986 $datarray['owner-link'] = $own[0]['url'];
2987 $datarray['owner-avatar'] = $own[0]['thumb'];
2988 $datarray['contact-id'] = $importer['id'];
2990 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
2992 $datarray['type'] = 'activity';
2993 $datarray['gravity'] = GRAVITY_LIKE;
2994 $datarray['last-child'] = 0;
2995 // only one like or dislike per person
2996 $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",
2997 intval($datarray['uid']),
2998 intval($datarray['contact-id']),
2999 dbesc($datarray['verb']),
3000 dbesc($datarray['parent-uri']),
3001 dbesc($datarray['parent-uri'])
3008 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3010 $xo = parse_xml_string($datarray['object'],false);
3011 $xt = parse_xml_string($datarray['target'],false);
3013 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3015 // fetch the parent item
3017 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3019 intval($importer['importer_uid'])
3024 // extract tag, if not duplicate, and this user allows tags, add to parent item
3026 if($xo->id && $xo->content) {
3027 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3028 if(! (stristr($tagp[0]['tag'],$newtag))) {
3029 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3030 intval($importer['importer_uid'])
3032 if(count($i) && ! intval($i[0]['blocktags'])) {
3033 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d LIMIT 1",
3034 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3035 intval($tagp[0]['id']),
3036 dbesc(datetime_convert())
3038 create_tags_from_item($tagp[0]['id']);
3046 $posted_id = item_store($datarray);
3050 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3052 intval($importer['importer_uid'])
3055 $parent = $r[0]['parent'];
3056 $parent_uri = $r[0]['parent-uri'];
3060 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3061 dbesc(datetime_convert()),
3062 intval($importer['importer_uid']),
3063 intval($r[0]['parent'])
3066 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
3067 dbesc(datetime_convert()),
3068 intval($importer['importer_uid']),
3073 if($posted_id && $parent) {
3075 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3077 if((! $is_like) && (! $importer['self'])) {
3079 require_once('include/enotify.php');
3082 'type' => NOTIFY_COMMENT,
3083 'notify_flags' => $importer['notify-flags'],
3084 'language' => $importer['language'],
3085 'to_name' => $importer['username'],
3086 'to_email' => $importer['email'],
3087 'uid' => $importer['importer_uid'],
3088 'item' => $datarray,
3089 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3090 'source_name' => stripslashes($datarray['author-name']),
3091 'source_link' => $datarray['author-link'],
3092 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3093 ? $importer['thumb'] : $datarray['author-avatar']),
3094 'verb' => ACTIVITY_POST,
3096 'parent' => $parent,
3097 'parent_uri' => $parent_uri,
3109 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3111 $item_id = $item->get_id();
3112 $datarray = get_atom_elements($feed,$item);
3114 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3117 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3119 intval($importer['importer_uid'])
3122 // Update content if 'updated' changes
3125 if (edited_timestamp_is_newer($r[0], $datarray)) {
3127 // do not accept (ignore) an earlier edit than one we currently have.
3128 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3131 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3132 dbesc($datarray['title']),
3133 dbesc($datarray['body']),
3134 dbesc($datarray['tag']),
3135 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3137 intval($importer['importer_uid'])
3139 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3142 // update last-child if it changes
3144 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3145 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3146 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3147 dbesc(datetime_convert()),
3149 intval($importer['importer_uid'])
3151 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3152 intval($allow[0]['data']),
3153 dbesc(datetime_convert()),
3155 intval($importer['importer_uid'])
3161 $datarray['parent-uri'] = $parent_uri;
3162 $datarray['uid'] = $importer['importer_uid'];
3163 $datarray['contact-id'] = $importer['id'];
3164 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3165 $datarray['type'] = 'activity';
3166 $datarray['gravity'] = GRAVITY_LIKE;
3167 // only one like or dislike per person
3168 $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",
3169 intval($datarray['uid']),
3170 intval($datarray['contact-id']),
3171 dbesc($datarray['verb']),
3180 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3182 $xo = parse_xml_string($datarray['object'],false);
3183 $xt = parse_xml_string($datarray['target'],false);
3185 if($xt->type == ACTIVITY_OBJ_NOTE) {
3186 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3188 intval($importer['importer_uid'])
3193 // extract tag, if not duplicate, add to parent item
3195 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3196 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
3197 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3200 create_tags_from_item($r[0]['id']);
3206 $posted_id = item_store($datarray);
3208 // find out if our user is involved in this conversation and wants to be notified.
3210 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3212 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3214 intval($importer['importer_uid'])
3217 if(count($myconv)) {
3218 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3220 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3221 if(! link_compare($datarray['author-link'],$importer_url)) {
3224 foreach($myconv as $conv) {
3226 // now if we find a match, it means we're in this conversation
3228 if(! link_compare($conv['author-link'],$importer_url))
3231 require_once('include/enotify.php');
3233 $conv_parent = $conv['parent'];
3236 'type' => NOTIFY_COMMENT,
3237 'notify_flags' => $importer['notify-flags'],
3238 'language' => $importer['language'],
3239 'to_name' => $importer['username'],
3240 'to_email' => $importer['email'],
3241 'uid' => $importer['importer_uid'],
3242 'item' => $datarray,
3243 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3244 'source_name' => stripslashes($datarray['author-name']),
3245 'source_link' => $datarray['author-link'],
3246 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3247 ? $importer['thumb'] : $datarray['author-avatar']),
3248 'verb' => ACTIVITY_POST,
3250 'parent' => $conv_parent,
3251 'parent_uri' => $parent_uri
3255 // only send one notification
3267 // Head post of a conversation. Have we seen it? If not, import it.
3270 $item_id = $item->get_id();
3271 $datarray = get_atom_elements($feed,$item);
3273 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3274 $ev = bbtoevent($datarray['body']);
3275 if(x($ev,'desc') && x($ev,'start')) {
3276 $ev['cid'] = $importer['id'];
3277 $ev['uid'] = $importer['uid'];
3278 $ev['uri'] = $item_id;
3279 $ev['edited'] = $datarray['edited'];
3280 $ev['private'] = $datarray['private'];
3282 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3284 intval($importer['uid'])
3287 $ev['id'] = $r[0]['id'];
3288 $xyz = event_store($ev);
3293 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3295 intval($importer['importer_uid'])
3298 // Update content if 'updated' changes
3301 if (edited_timestamp_is_newer($r[0], $datarray)) {
3303 // do not accept (ignore) an earlier edit than one we currently have.
3304 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3307 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3308 dbesc($datarray['title']),
3309 dbesc($datarray['body']),
3310 dbesc($datarray['tag']),
3311 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3313 intval($importer['importer_uid'])
3315 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3318 // update last-child if it changes
3320 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3321 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3322 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3323 intval($allow[0]['data']),
3324 dbesc(datetime_convert()),
3326 intval($importer['importer_uid'])
3332 // This is my contact on another system, but it's really me.
3333 // Turn this into a wall post.
3335 if($importer['remote_self'])
3336 $datarray['wall'] = 1;
3338 $datarray['parent-uri'] = $item_id;
3339 $datarray['uid'] = $importer['importer_uid'];
3340 $datarray['contact-id'] = $importer['id'];
3343 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3344 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3345 // but otherwise there's a possible data mixup on the sender's system.
3346 // the tgroup delivery code called from item_store will correct it if it's a forum,
3347 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3348 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3349 $datarray['owner-name'] = $importer['senderName'];
3350 $datarray['owner-link'] = $importer['url'];
3351 $datarray['owner-avatar'] = $importer['thumb'];
3354 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3357 $posted_id = item_store($datarray);
3359 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3360 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3363 $xo = parse_xml_string($datarray['object'],false);
3365 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3367 // somebody was poked/prodded. Was it me?
3369 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3371 foreach($links->link as $l) {
3372 $atts = $l->attributes();
3373 switch($atts['rel']) {
3375 $Blink = $atts['href'];
3381 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3383 // send a notification
3384 require_once('include/enotify.php');
3387 'type' => NOTIFY_POKE,
3388 'notify_flags' => $importer['notify-flags'],
3389 'language' => $importer['language'],
3390 'to_name' => $importer['username'],
3391 'to_email' => $importer['email'],
3392 'uid' => $importer['importer_uid'],
3393 'item' => $datarray,
3394 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3395 'source_name' => stripslashes($datarray['author-name']),
3396 'source_link' => $datarray['author-link'],
3397 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3398 ? $importer['thumb'] : $datarray['author-avatar']),
3399 'verb' => $datarray['verb'],
3400 'otype' => 'person',
3401 'activity' => $verb,
3418 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3419 $url = notags(trim($datarray['author-link']));
3420 $name = notags(trim($datarray['author-name']));
3421 $photo = notags(trim($datarray['author-avatar']));
3423 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3424 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3425 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3427 if(is_array($contact)) {
3428 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3429 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3430 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
3431 intval(CONTACT_IS_FRIEND),
3432 intval($contact['id']),
3433 intval($importer['uid'])
3436 // send email notification to owner?
3440 // create contact record
3442 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3443 `blocked`, `readonly`, `pending`, `writable` )
3444 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3445 intval($importer['uid']),
3446 dbesc(datetime_convert()),
3448 dbesc(normalise_link($url)),
3452 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3453 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3455 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3456 intval($importer['uid']),
3460 $contact_record = $r[0];
3462 // create notification
3463 $hash = random_string();
3465 if(is_array($contact_record)) {
3466 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3467 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3468 intval($importer['uid']),
3469 intval($contact_record['id']),
3471 dbesc(datetime_convert())
3474 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3475 intval($importer['uid'])
3480 if(intval($r[0]['def_gid'])) {
3481 require_once('include/group.php');
3482 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3485 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3486 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3487 $email = replace_macros($email_tpl, array(
3488 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3490 '$myname' => $r[0]['username'],
3491 '$siteurl' => $a->get_baseurl(),
3492 '$sitename' => $a->config['sitename']
3494 $res = mail($r[0]['email'],
3495 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'),
3497 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3498 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3499 . 'Content-transfer-encoding: 8bit' );
3506 function lose_follower($importer,$contact,$datarray,$item) {
3508 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3509 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3510 intval(CONTACT_IS_SHARING),
3511 intval($contact['id'])
3515 contact_remove($contact['id']);
3519 function lose_sharer($importer,$contact,$datarray,$item) {
3521 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3522 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3523 intval(CONTACT_IS_FOLLOWER),
3524 intval($contact['id'])
3528 contact_remove($contact['id']);
3533 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3537 if(is_array($importer)) {
3538 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3539 intval($importer['uid'])
3543 // Diaspora has different message-ids in feeds than they do
3544 // through the direct Diaspora protocol. If we try and use
3545 // the feed, we'll get duplicates. So don't.
3547 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3550 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3552 // Use a single verify token, even if multiple hubs
3554 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3556 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3558 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3560 if(! strlen($contact['hub-verify'])) {
3561 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d LIMIT 1",
3562 dbesc($verify_token),
3563 intval($contact['id'])
3567 post_url($url,$params);
3569 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3576 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3580 $name = xmlify($name);
3581 $uri = xmlify($uri);
3584 $photo = xmlify($photo);
3588 $o .= "<name>$name</name>\r\n";
3589 $o .= "<uri>$uri</uri>\r\n";
3590 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3591 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3593 call_hooks('atom_author', $o);
3595 $o .= "</$tag>\r\n";
3599 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3603 if(! $item['parent'])
3606 if($item['deleted'])
3607 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3610 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3611 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3613 $body = $item['body'];
3615 $o = "\r\n\r\n<entry>\r\n";
3617 if(is_array($author))
3618 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3620 $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']));
3621 if(strlen($item['owner-name']))
3622 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3624 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3625 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3626 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3629 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3630 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3631 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3632 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3633 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3634 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3635 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3637 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3639 if($item['location']) {
3640 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3641 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3645 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3647 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3648 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3651 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3652 if($item['bookmark'])
3653 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3656 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3659 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3661 if($item['signed_text']) {
3662 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3663 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3666 $verb = construct_verb($item);
3667 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3668 $actobj = construct_activity_object($item);
3671 $actarg = construct_activity_target($item);
3675 $tags = item_getfeedtags($item);
3677 foreach($tags as $t) {
3678 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3682 $o .= item_getfeedattach($item);
3684 $mentioned = get_mentions($item);
3688 call_hooks('atom_entry', $o);
3690 $o .= '</entry>' . "\r\n";
3695 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3697 if(get_config('system','disable_embedded'))
3702 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3703 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3708 $img_start = strpos($orig_body, '[img');
3709 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3710 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3711 while( ($img_st_close !== false) && ($img_len !== false) ) {
3713 $img_st_close++; // make it point to AFTER the closing bracket
3714 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3716 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3719 if(stristr($image , $site . '/photo/')) {
3720 // Only embed locally hosted photos
3722 $i = basename($image);
3723 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3724 $x = strpos($i,'-');
3727 $res = substr($i,$x+1);
3728 $i = substr($i,0,$x);
3729 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3736 // Check to see if we should replace this photo link with an embedded image
3737 // 1. No need to do so if the photo is public
3738 // 2. If there's a contact-id provided, see if they're in the access list
3739 // for the photo. If so, embed it.
3740 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3741 // permissions, regardless of order but first check to see if they're an exact
3742 // match to save some processing overhead.
3744 if(has_permissions($r[0])) {
3746 $recips = enumerate_permissions($r[0]);
3747 if(in_array($cid, $recips)) {
3752 if(compare_permissions($item,$r[0]))
3757 $data = $r[0]['data'];
3758 $type = $r[0]['type'];
3760 // If a custom width and height were specified, apply before embedding
3761 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3762 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3764 $width = intval($match[1]);
3765 $height = intval($match[2]);
3767 $ph = new Photo($data, $type);
3768 if($ph->is_valid()) {
3769 $ph->scaleImage(max($width, $height));
3770 $data = $ph->imageString();
3771 $type = $ph->getType();
3775 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3776 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3777 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3783 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3784 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3785 if($orig_body === false)
3788 $img_start = strpos($orig_body, '[img');
3789 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3790 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3793 $new_body = $new_body . $orig_body;
3799 function has_permissions($obj) {
3800 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3805 function compare_permissions($obj1,$obj2) {
3806 // first part is easy. Check that these are exactly the same.
3807 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3808 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3809 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3810 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3813 // This is harder. Parse all the permissions and compare the resulting set.
3815 $recipients1 = enumerate_permissions($obj1);
3816 $recipients2 = enumerate_permissions($obj2);
3819 if($recipients1 == $recipients2)
3824 // returns an array of contact-ids that are allowed to see this object
3826 function enumerate_permissions($obj) {
3827 require_once('include/group.php');
3828 $allow_people = expand_acl($obj['allow_cid']);
3829 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3830 $deny_people = expand_acl($obj['deny_cid']);
3831 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3832 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3833 $deny = array_unique(array_merge($deny_people,$deny_groups));
3834 $recipients = array_diff($recipients,$deny);
3838 function item_getfeedtags($item) {
3841 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3843 for($x = 0; $x < $cnt; $x ++) {
3845 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3849 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3851 for($x = 0; $x < $cnt; $x ++) {
3853 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3859 function item_getfeedattach($item) {
3861 $arr = explode('[/attach],',$item['attach']);
3863 foreach($arr as $r) {
3865 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3867 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3868 if(intval($matches[2]))
3869 $ret .= 'length="' . intval($matches[2]) . '" ';
3870 if($matches[4] !== ' ')
3871 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3872 $ret .= ' />' . "\r\n";
3881 function item_expire($uid,$days) {
3883 if((! $uid) || ($days < 1))
3886 // $expire_network_only = save your own wall posts
3887 // and just expire conversations started by others
3889 $expire_network_only = get_pconfig($uid,'expire','network_only');
3890 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3892 $r = q("SELECT * FROM `item`
3894 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
3905 $expire_items = get_pconfig($uid, 'expire','items');
3906 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3908 $expire_notes = get_pconfig($uid, 'expire','notes');
3909 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3911 $expire_starred = get_pconfig($uid, 'expire','starred');
3912 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3914 $expire_photos = get_pconfig($uid, 'expire','photos');
3915 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3917 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3919 foreach($r as $item) {
3921 // don't expire filed items
3923 if(strpos($item['file'],'[') !== false)
3926 // Only expire posts, not photos and photo comments
3928 if($expire_photos==0 && strlen($item['resource-id']))
3930 if($expire_starred==0 && intval($item['starred']))
3932 if($expire_notes==0 && $item['type']=='note')
3934 if($expire_items==0 && $item['type']!='note')
3937 drop_item($item['id'],false);
3940 proc_run('php',"include/notifier.php","expire","$uid");
3945 function drop_items($items) {
3948 if(! local_user() && ! remote_user())
3952 foreach($items as $item) {
3953 $owner = drop_item($item,false);
3954 if($owner && ! $uid)
3959 // multiple threads may have been deleted, send an expire notification
3962 proc_run('php',"include/notifier.php","expire","$uid");
3966 function drop_item($id,$interactive = true) {
3970 // locate item to be deleted
3972 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3979 notice( t('Item not found.') . EOL);
3980 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3985 $owner = $item['uid'];
3989 // check if logged in user is either the author or owner of this item
3991 if(is_array($_SESSION['remote'])) {
3992 foreach($_SESSION['remote'] as $visitor) {
3993 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3994 $cid = $visitor['cid'];
4001 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4003 // Check if we should do HTML-based delete confirmation
4004 if($_REQUEST['confirm']) {
4005 // <form> can't take arguments in its "action" parameter
4006 // so add any arguments as hidden inputs
4007 $query = explode_querystring($a->query_string);
4009 foreach($query['args'] as $arg) {
4010 if(strpos($arg, 'confirm=') === false) {
4011 $arg_parts = explode('=', $arg);
4012 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4016 return replace_macros(get_markup_template('confirm.tpl'), array(
4018 '$message' => t('Do you really want to delete this item?'),
4019 '$extra_inputs' => $inputs,
4020 '$confirm' => t('Yes'),
4021 '$confirm_url' => $query['base'],
4022 '$confirm_name' => 'confirmed',
4023 '$cancel' => t('Cancel'),
4026 // Now check how the user responded to the confirmation query
4027 if($_REQUEST['canceled']) {
4028 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4031 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4034 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
4035 dbesc(datetime_convert()),
4036 dbesc(datetime_convert()),
4039 create_tags_from_item($item['id']);
4041 // clean up categories and tags so they don't end up as orphans
4044 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4046 foreach($matches as $mtch) {
4047 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4053 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4055 foreach($matches as $mtch) {
4056 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4060 // If item is a link to a photo resource, nuke all the associated photos
4061 // (visitors will not have photo resources)
4062 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4063 // generate a resource-id and therefore aren't intimately linked to the item.
4065 if(strlen($item['resource-id'])) {
4066 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4067 dbesc($item['resource-id']),
4068 intval($item['uid'])
4070 // ignore the result
4073 // If item is a link to an event, nuke the event record.
4075 if(intval($item['event-id'])) {
4076 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1",
4077 intval($item['event-id']),
4078 intval($item['uid'])
4080 // ignore the result
4083 // clean up item_id and sign meta-data tables
4085 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4086 intval($item['id']),
4087 intval($item['uid'])
4090 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4091 intval($item['id']),
4092 intval($item['uid'])
4095 // If it's the parent of a comment thread, kill all the kids
4097 if($item['uri'] == $item['parent-uri']) {
4098 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4099 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4100 dbesc(datetime_convert()),
4101 dbesc(datetime_convert()),
4102 dbesc($item['parent-uri']),
4103 intval($item['uid'])
4105 create_tags_from_item($item['parent-uri'], $item['uid']);
4106 // ignore the result
4109 // ensure that last-child is set in case the comment that had it just got wiped.
4110 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4111 dbesc(datetime_convert()),
4112 dbesc($item['parent-uri']),
4113 intval($item['uid'])
4115 // who is the last child now?
4116 $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",
4117 dbesc($item['parent-uri']),
4118 intval($item['uid'])
4121 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
4126 // Add a relayable_retraction signature for Diaspora.
4127 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4129 $drop_id = intval($item['id']);
4131 // send the notification upstream/downstream as the case may be
4133 proc_run('php',"include/notifier.php","drop","$drop_id");
4137 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4143 notice( t('Permission denied.') . EOL);
4144 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4151 function first_post_date($uid,$wall = false) {
4152 $r = q("select id, created from item
4153 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4155 order by created asc limit 1",
4157 intval($wall ? 1 : 0)
4160 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4161 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4166 function posted_dates($uid,$wall) {
4167 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4169 $dthen = first_post_date($uid,$wall);
4173 // If it's near the end of a long month, backup to the 28th so that in
4174 // consecutive loops we'll always get a whole month difference.
4176 if(intval(substr($dnow,8)) > 28)
4177 $dnow = substr($dnow,0,8) . '28';
4178 if(intval(substr($dthen,8)) > 28)
4179 $dnow = substr($dthen,0,8) . '28';
4182 // Starting with the current month, get the first and last days of every
4183 // month down to and including the month of the first post
4184 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4185 $dstart = substr($dnow,0,8) . '01';
4186 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4187 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4188 $end_month = datetime_convert('','',$dend,'Y-m-d');
4189 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4190 $ret[] = array($str,$end_month,$start_month);
4191 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4197 function posted_date_widget($url,$uid,$wall) {
4200 if(! feature_enabled($uid,'archives'))
4203 // For former Facebook folks that left because of "timeline"
4205 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4208 $ret = posted_dates($uid,$wall);
4212 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4213 '$title' => t('Archives'),
4214 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4221 function store_diaspora_retract_sig($item, $user, $baseurl) {
4222 // Note that we can't add a target_author_signature
4223 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4224 // the comment, that means we're the home of the post, and Diaspora will only
4225 // check the parent_author_signature of retractions that it doesn't have to relay further
4227 // I don't think this function gets called for an "unlike," but I'll check anyway
4229 $enabled = intval(get_config('system','diaspora_enabled'));
4231 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4235 logger('drop_item: storing diaspora retraction signature');
4237 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4239 if(local_user() == $item['uid']) {
4241 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4242 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4245 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4246 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4249 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4250 // only handles DFRN deletes
4251 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4252 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4253 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4259 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4260 intval($item['id']),
4261 dbesc($signed_text),