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','wall-to-wall_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());
986 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
988 if ($arr['network'] == "") {
989 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
990 intval($arr['contact-id']),
995 $arr['network'] = $r[0]["network"];
997 // Fallback to friendica (why is it empty in some cases?)
998 if ($arr['network'] == "")
999 $arr['network'] = NETWORK_DFRN;
1001 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1004 $arr['thr-parent'] = $arr['parent-uri'];
1005 if($arr['parent-uri'] === $arr['uri']) {
1007 $parent_deleted = 0;
1008 $allow_cid = $arr['allow_cid'];
1009 $allow_gid = $arr['allow_gid'];
1010 $deny_cid = $arr['deny_cid'];
1011 $deny_gid = $arr['deny_gid'];
1015 // find the parent and snarf the item id and ACLs
1016 // and anything else we need to inherit
1018 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1019 dbesc($arr['parent-uri']),
1025 // is the new message multi-level threaded?
1026 // even though we don't support it now, preserve the info
1027 // and re-attach to the conversation parent.
1029 if($r[0]['uri'] != $r[0]['parent-uri']) {
1030 $arr['parent-uri'] = $r[0]['parent-uri'];
1031 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1032 ORDER BY `id` ASC LIMIT 1",
1033 dbesc($r[0]['parent-uri']),
1034 dbesc($r[0]['parent-uri']),
1041 $parent_id = $r[0]['id'];
1042 $parent_deleted = $r[0]['deleted'];
1043 $allow_cid = $r[0]['allow_cid'];
1044 $allow_gid = $r[0]['allow_gid'];
1045 $deny_cid = $r[0]['deny_cid'];
1046 $deny_gid = $r[0]['deny_gid'];
1047 $arr['wall'] = $r[0]['wall'];
1049 // if the parent is private, force privacy for the entire conversation
1050 // This differs from the above settings as it subtly allows comments from
1051 // email correspondents to be private even if the overall thread is not.
1053 if($r[0]['private'])
1054 $arr['private'] = $r[0]['private'];
1056 // Edge case. We host a public forum that was originally posted to privately.
1057 // The original author commented, but as this is a comment, the permissions
1058 // weren't fixed up so it will still show the comment as private unless we fix it here.
1060 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1061 $arr['private'] = 0;
1065 // Allow one to see reply tweets from status.net even when
1066 // we don't have or can't see the original post.
1069 logger('item_store: $force_parent=true, reply converted to top-level post.');
1071 $arr['parent-uri'] = $arr['uri'];
1072 $arr['gravity'] = 0;
1075 logger('item_store: item parent was not found - ignoring item');
1079 $parent_deleted = 0;
1083 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1087 if($r && count($r)) {
1088 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1092 call_hooks('post_remote',$arr);
1094 if(x($arr,'cancel')) {
1095 logger('item_store: post cancelled by plugin.');
1101 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1103 $r = dbq("INSERT INTO `item` (`"
1104 . implode("`, `", array_keys($arr))
1106 . implode("', '", array_values($arr))
1109 // find the item we just created
1111 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1112 $arr['uri'], // already dbesc'd
1117 $current_post = $r[0]['id'];
1118 logger('item_store: created item ' . $current_post);
1119 create_tags_from_item($r[0]['id']);
1121 logger('item_store: could not locate created item');
1125 logger('item_store: duplicated post occurred. Removing duplicates.');
1126 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1128 intval($arr['uid']),
1129 intval($current_post)
1133 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1134 $parent_id = $current_post;
1136 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1139 $private = $arr['private'];
1141 // Set parent id - and also make sure to inherit the parent's ACL's.
1143 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1144 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d LIMIT 1",
1151 intval($parent_deleted),
1152 intval($current_post)
1154 create_tags_from_item($current_post);
1156 // Complete ostatus threads
1157 if ($ostatus_conversation)
1158 complete_conversation($current_post, $ostatus_conversation);
1160 $arr['id'] = $current_post;
1161 $arr['parent'] = $parent_id;
1162 $arr['allow_cid'] = $allow_cid;
1163 $arr['allow_gid'] = $allow_gid;
1164 $arr['deny_cid'] = $deny_cid;
1165 $arr['deny_gid'] = $deny_gid;
1166 $arr['private'] = $private;
1167 $arr['deleted'] = $parent_deleted;
1169 // update the commented timestamp on the parent
1171 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
1172 dbesc(datetime_convert()),
1173 dbesc(datetime_convert()),
1178 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1179 intval($current_post),
1180 dbesc($dsprsig->signed_text),
1181 dbesc($dsprsig->signature),
1182 dbesc($dsprsig->signer)
1188 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1191 if($arr['last-child']) {
1192 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1194 intval($arr['uid']),
1195 intval($current_post)
1199 $deleted = tag_deliver($arr['uid'],$current_post);
1201 // current post can be deleted if is for a communuty page and no mention are
1205 // Store the fresh generated item into the cache
1206 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1208 if (($cachefile != '') AND !file_exists($cachefile)) {
1209 $s = prepare_text($arr['body']);
1211 $stamp1 = microtime(true);
1212 file_put_contents($cachefile, $s);
1213 $a->save_timestamp($stamp1, "file");
1214 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1217 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1218 if (count($r) == 1) {
1219 call_hooks('post_remote_end', $r[0]);
1222 logger('item_store: new item not found in DB, id ' . $current_post);
1225 return $current_post;
1228 function get_item_contact($item,$contacts) {
1229 if(! count($contacts) || (! is_array($item)))
1231 foreach($contacts as $contact) {
1232 if($contact['id'] == $item['contact-id']) {
1234 break; // NOTREACHED
1241 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1243 * @param int $item_id
1244 * @return bool true if item was deleted, else false
1246 function tag_deliver($uid,$item_id) {
1254 $u = q("select * from user where uid = %d limit 1",
1260 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1261 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1264 $i = q("select * from item where id = %d and uid = %d limit 1",
1273 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1275 // Diaspora uses their own hardwired link URL in @-tags
1276 // instead of the one we supply with webfinger
1278 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1280 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1282 foreach($matches as $mtch) {
1283 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1285 logger('tag_deliver: mention found: ' . $mtch[2]);
1291 if ( ($community_page || $prvgroup) &&
1292 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1293 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1295 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1296 q("DELETE FROM item WHERE id = %d and uid = %d limit 1",
1306 // send a notification
1308 // use a local photo if we have one
1310 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1311 intval($u[0]['uid']),
1312 dbesc(normalise_link($item['author-link']))
1314 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1317 require_once('include/enotify.php');
1319 'type' => NOTIFY_TAGSELF,
1320 'notify_flags' => $u[0]['notify-flags'],
1321 'language' => $u[0]['language'],
1322 'to_name' => $u[0]['username'],
1323 'to_email' => $u[0]['email'],
1324 'uid' => $u[0]['uid'],
1326 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1327 'source_name' => $item['author-name'],
1328 'source_link' => $item['author-link'],
1329 'source_photo' => $photo,
1330 'verb' => ACTIVITY_TAG,
1335 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1337 call_hooks('tagged', $arr);
1339 if((! $community_page) && (! $prvgroup))
1343 // tgroup delivery - setup a second delivery chain
1344 // prevent delivery looping - only proceed
1345 // if the message originated elsewhere and is a top-level post
1347 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1350 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1353 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1354 intval($u[0]['uid'])
1359 // also reset all the privacy bits to the forum default permissions
1361 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1363 $forum_mode = (($prvgroup) ? 2 : 1);
1365 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1366 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d limit 1",
1367 intval($forum_mode),
1368 dbesc($c[0]['name']),
1369 dbesc($c[0]['url']),
1370 dbesc($c[0]['thumb']),
1372 dbesc($u[0]['allow_cid']),
1373 dbesc($u[0]['allow_gid']),
1374 dbesc($u[0]['deny_cid']),
1375 dbesc($u[0]['deny_gid']),
1379 proc_run('php','include/notifier.php','tgroup',$item_id);
1385 function tgroup_check($uid,$item) {
1391 // check that the message originated elsewhere and is a top-level post
1393 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1397 $u = q("select * from user where uid = %d limit 1",
1403 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1404 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1407 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1409 // Diaspora uses their own hardwired link URL in @-tags
1410 // instead of the one we supply with webfinger
1412 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1414 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1416 foreach($matches as $mtch) {
1417 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1419 logger('tgroup_check: mention found: ' . $mtch[2]);
1427 if((! $community_page) && (! $prvgroup))
1441 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1445 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1447 if($contact['duplex'] && $contact['dfrn-id'])
1448 $idtosend = '0:' . $orig_id;
1449 if($contact['duplex'] && $contact['issued-id'])
1450 $idtosend = '1:' . $orig_id;
1452 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1454 $rino_enable = get_config('system','rino_encrypt');
1459 $ssl_val = intval(get_config('system','ssl_policy'));
1463 case SSL_POLICY_FULL:
1464 $ssl_policy = 'full';
1466 case SSL_POLICY_SELFSIGN:
1467 $ssl_policy = 'self';
1469 case SSL_POLICY_NONE:
1471 $ssl_policy = 'none';
1475 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1477 logger('dfrn_deliver: ' . $url);
1479 $xml = fetch_url($url);
1481 $curl_stat = $a->get_curl_code();
1483 return(-1); // timed out
1485 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1490 if(strpos($xml,'<?xml') === false) {
1491 logger('dfrn_deliver: no valid XML returned');
1492 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1496 $res = parse_xml_string($xml);
1498 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1499 return (($res->status) ? $res->status : 3);
1501 $postvars = array();
1502 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1503 $challenge = hex2bin((string) $res->challenge);
1504 $perm = (($res->perm) ? $res->perm : null);
1505 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1506 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1507 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1509 if($owner['page-flags'] == PAGE_PRVGROUP)
1512 $final_dfrn_id = '';
1515 if((($perm == 'rw') && (! intval($contact['writable'])))
1516 || (($perm == 'r') && (intval($contact['writable'])))) {
1517 q("update contact set writable = %d where id = %d limit 1",
1518 intval(($perm == 'rw') ? 1 : 0),
1519 intval($contact['id'])
1521 $contact['writable'] = (string) 1 - intval($contact['writable']);
1525 if(($contact['duplex'] && strlen($contact['pubkey']))
1526 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1527 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1528 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1529 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1532 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1533 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1536 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1538 if(strpos($final_dfrn_id,':') == 1)
1539 $final_dfrn_id = substr($final_dfrn_id,2);
1541 if($final_dfrn_id != $orig_id) {
1542 logger('dfrn_deliver: wrong dfrn_id.');
1543 // did not decode properly - cannot trust this site
1547 $postvars['dfrn_id'] = $idtosend;
1548 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1550 $postvars['dissolve'] = '1';
1553 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1554 $postvars['data'] = $atom;
1555 $postvars['perm'] = 'rw';
1558 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1559 $postvars['perm'] = 'r';
1562 $postvars['ssl_policy'] = $ssl_policy;
1565 $postvars['page'] = $page;
1567 if($rino && $rino_allowed && (! $dissolve)) {
1568 $key = substr(random_string(),0,16);
1569 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1570 $postvars['data'] = $data;
1571 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1574 if($dfrn_version >= 2.1) {
1575 if(($contact['duplex'] && strlen($contact['pubkey']))
1576 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1577 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1579 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1582 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1586 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1587 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1590 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1594 logger('md5 rawkey ' . md5($postvars['key']));
1596 $postvars['key'] = bin2hex($postvars['key']);
1599 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1601 $xml = post_url($contact['notify'],$postvars);
1603 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1605 $curl_stat = $a->get_curl_code();
1606 if((! $curl_stat) || (! strlen($xml)))
1607 return(-1); // timed out
1609 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1612 if(strpos($xml,'<?xml') === false) {
1613 logger('dfrn_deliver: phase 2: no valid XML returned');
1614 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1618 if($contact['term-date'] != '0000-00-00 00:00:00') {
1619 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1620 require_once('include/Contact.php');
1621 unmark_for_death($contact);
1624 $res = parse_xml_string($xml);
1626 return $res->status;
1631 This function returns true if $update has an edited timestamp newer
1632 than $existing, i.e. $update contains new data which should override
1633 what's already there. If there is no timestamp yet, the update is
1634 assumed to be newer. If the update has no timestamp, the existing
1635 item is assumed to be up-to-date. If the timestamps are equal it
1636 assumes the update has been seen before and should be ignored.
1638 function edited_timestamp_is_newer($existing, $update) {
1639 if (!x($existing,'edited') || !$existing['edited']) {
1642 if (!x($update,'edited') || !$update['edited']) {
1645 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1646 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1647 return (strcmp($existing_edited, $update_edited) < 0);
1652 * consume_feed - process atom feed and update anything/everything we might need to update
1654 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1656 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1657 * It is this person's stuff that is going to be updated.
1658 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1659 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1660 * have a contact record.
1661 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1662 * might not) try and subscribe to it.
1663 * $datedir sorts in reverse order
1664 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1665 * imported prior to its children being seen in the stream unless we are certain
1666 * of how the feed is arranged/ordered.
1667 * With $pass = 1, we only pull parent items out of the stream.
1668 * With $pass = 2, we only pull children (comments/likes).
1670 * So running this twice, first with pass 1 and then with pass 2 will do the right
1671 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1672 * model where comments can have sub-threads. That would require some massive sorting
1673 * to get all the feed items into a mostly linear ordering, and might still require
1677 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1679 require_once('library/simplepie/simplepie.inc');
1681 if(! strlen($xml)) {
1682 logger('consume_feed: empty input');
1686 $feed = new SimplePie();
1687 $feed->set_raw_data($xml);
1689 $feed->enable_order_by_date(true);
1691 $feed->enable_order_by_date(false);
1695 logger('consume_feed: Error parsing XML: ' . $feed->error());
1697 $permalink = $feed->get_permalink();
1699 // Check at the feed level for updated contact name and/or photo
1703 $photo_timestamp = '';
1707 $hubs = $feed->get_links('hub');
1708 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1711 $hub = implode(',', $hubs);
1713 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1715 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1717 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1718 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1719 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1720 $new_name = $elems['name'][0]['data'];
1722 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1723 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1724 $photo_url = $elems['link'][0]['attribs']['']['href'];
1727 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1728 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1732 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1733 logger('consume_feed: Updating photo for ' . $contact['name']);
1734 require_once("include/Photo.php");
1735 $photo_failure = false;
1736 $have_photo = false;
1738 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1739 intval($contact['id']),
1740 intval($contact['uid'])
1743 $resource_id = $r[0]['resource-id'];
1747 $resource_id = photo_new_resource();
1750 $img_str = fetch_url($photo_url,true);
1751 // guess mimetype from headers or filename
1752 $type = guess_image_type($photo_url,true);
1755 $img = new Photo($img_str, $type);
1756 if($img->is_valid()) {
1758 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1759 dbesc($resource_id),
1760 intval($contact['id']),
1761 intval($contact['uid'])
1765 $img->scaleImageSquare(175);
1767 $hash = $resource_id;
1768 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1770 $img->scaleImage(80);
1771 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1773 $img->scaleImage(48);
1774 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1778 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1779 WHERE `uid` = %d AND `id` = %d LIMIT 1",
1780 dbesc(datetime_convert()),
1781 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1782 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1783 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1784 intval($contact['uid']),
1785 intval($contact['id'])
1790 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1791 $r = q("select * from contact where uid = %d and id = %d limit 1",
1792 intval($contact['uid']),
1793 intval($contact['id'])
1796 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1797 dbesc(notags(trim($new_name))),
1798 dbesc(datetime_convert()),
1799 intval($contact['uid']),
1800 intval($contact['id'])
1803 // do our best to update the name on content items
1806 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1807 dbesc(notags(trim($new_name))),
1808 dbesc($r[0]['name']),
1809 dbesc($r[0]['url']),
1810 intval($contact['uid'])
1815 if(strlen($birthday)) {
1816 if(substr($birthday,0,4) != $contact['bdyear']) {
1817 logger('consume_feed: updating birthday: ' . $birthday);
1821 * Add new birthday event for this person
1823 * $bdtext is just a readable placeholder in case the event is shared
1824 * with others. We will replace it during presentation to our $importer
1825 * to contain a sparkle link and perhaps a photo.
1829 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1830 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1833 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1834 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1835 intval($contact['uid']),
1836 intval($contact['id']),
1837 dbesc(datetime_convert()),
1838 dbesc(datetime_convert()),
1839 dbesc(datetime_convert('UTC','UTC', $birthday)),
1840 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1849 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
1850 dbesc(substr($birthday,0,4)),
1851 intval($contact['uid']),
1852 intval($contact['id'])
1855 // This function is called twice without reloading the contact
1856 // Make sure we only create one event. This is why &$contact
1857 // is a reference var in this function
1859 $contact['bdyear'] = substr($birthday,0,4);
1864 $community_page = 0;
1865 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1867 $community_page = intval($rawtags[0]['data']);
1869 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1870 q("update contact set forum = %d where id = %d limit 1",
1871 intval($community_page),
1872 intval($contact['id'])
1874 $contact['forum'] = (string) $community_page;
1878 // process any deleted entries
1880 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1881 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1882 foreach($del_entries as $dentry) {
1884 if(isset($dentry['attribs']['']['ref'])) {
1885 $uri = $dentry['attribs']['']['ref'];
1887 if(isset($dentry['attribs']['']['when'])) {
1888 $when = $dentry['attribs']['']['when'];
1889 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1892 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1894 if($deleted && is_array($contact)) {
1895 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id`
1896 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1898 intval($importer['uid']),
1899 intval($contact['id'])
1904 if(! $item['deleted'])
1905 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1907 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1908 $xo = parse_xml_string($item['object'],false);
1909 $xt = parse_xml_string($item['target'],false);
1910 if($xt->type === ACTIVITY_OBJ_NOTE) {
1911 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1913 intval($importer['importer_uid'])
1917 // For tags, the owner cannot remove the tag on the author's copy of the post.
1919 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1920 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1921 $author_copy = (($item['origin']) ? true : false);
1923 if($owner_remove && $author_copy)
1925 if($author_remove || $owner_remove) {
1926 $tags = explode(',',$i[0]['tag']);
1929 foreach($tags as $tag)
1930 if(trim($tag) !== trim($xo->body))
1931 $newtags[] = trim($tag);
1933 q("update item set tag = '%s' where id = %d limit 1",
1934 dbesc(implode(',',$newtags)),
1937 create_tags_from_item($i[0]['id']);
1943 if($item['uri'] == $item['parent-uri']) {
1944 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1945 `body` = '', `title` = ''
1946 WHERE `parent-uri` = '%s' AND `uid` = %d",
1948 dbesc(datetime_convert()),
1949 dbesc($item['uri']),
1950 intval($importer['uid'])
1952 create_tags_from_itemuri($item['uri'], $importer['uid']);
1955 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1956 `body` = '', `title` = ''
1957 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1959 dbesc(datetime_convert()),
1961 intval($importer['uid'])
1963 create_tags_from_itemuri($uri, $importer['uid']);
1964 if($item['last-child']) {
1965 // ensure that last-child is set in case the comment that had it just got wiped.
1966 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1967 dbesc(datetime_convert()),
1968 dbesc($item['parent-uri']),
1969 intval($item['uid'])
1971 // who is the last child now?
1972 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1973 ORDER BY `created` DESC LIMIT 1",
1974 dbesc($item['parent-uri']),
1975 intval($importer['uid'])
1978 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
1989 // Now process the feed
1991 if($feed->get_item_quantity()) {
1993 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1995 // in inverse date order
1997 $items = array_reverse($feed->get_items());
1999 $items = $feed->get_items();
2002 foreach($items as $item) {
2005 $item_id = $item->get_id();
2006 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2007 if(isset($rawthread[0]['attribs']['']['ref'])) {
2009 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2012 if(($is_reply) && is_array($contact)) {
2017 // not allowed to post
2019 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2023 // Have we seen it? If not, import it.
2025 $item_id = $item->get_id();
2026 $datarray = get_atom_elements($feed,$item);
2028 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2029 $datarray['author-name'] = $contact['name'];
2030 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2031 $datarray['author-link'] = $contact['url'];
2032 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2033 $datarray['author-avatar'] = $contact['thumb'];
2035 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2036 logger('consume_feed: no author information! ' . print_r($datarray,true));
2040 $force_parent = false;
2041 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2042 if($contact['network'] === NETWORK_OSTATUS)
2043 $force_parent = true;
2044 if(strlen($datarray['title']))
2045 unset($datarray['title']);
2046 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2047 dbesc(datetime_convert()),
2049 intval($importer['uid'])
2051 $datarray['last-child'] = 1;
2055 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2057 intval($importer['uid'])
2060 // Update content if 'updated' changes
2063 if (edited_timestamp_is_newer($r[0], $datarray)) {
2065 // do not accept (ignore) an earlier edit than one we currently have.
2066 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2069 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2070 dbesc($datarray['title']),
2071 dbesc($datarray['body']),
2072 dbesc($datarray['tag']),
2073 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2075 intval($importer['uid'])
2077 create_tags_from_itemuri($item_id, $importer['uid']);
2080 // update last-child if it changes
2082 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2083 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2084 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2085 dbesc(datetime_convert()),
2087 intval($importer['uid'])
2089 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2090 intval($allow[0]['data']),
2091 dbesc(datetime_convert()),
2093 intval($importer['uid'])
2100 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2101 // one way feed - no remote comment ability
2102 $datarray['last-child'] = 0;
2104 $datarray['parent-uri'] = $parent_uri;
2105 $datarray['uid'] = $importer['uid'];
2106 $datarray['contact-id'] = $contact['id'];
2107 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2108 $datarray['type'] = 'activity';
2109 $datarray['gravity'] = GRAVITY_LIKE;
2110 // only one like or dislike per person
2111 $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",
2112 intval($datarray['uid']),
2113 intval($datarray['contact-id']),
2114 dbesc($datarray['verb']),
2122 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2123 $xo = parse_xml_string($datarray['object'],false);
2124 $xt = parse_xml_string($datarray['target'],false);
2126 if($xt->type == ACTIVITY_OBJ_NOTE) {
2127 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2129 intval($importer['importer_uid'])
2134 // extract tag, if not duplicate, add to parent item
2135 if($xo->id && $xo->content) {
2136 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2137 if(! (stristr($r[0]['tag'],$newtag))) {
2138 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
2139 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2142 create_tags_from_item($r[0]['id']);
2148 $r = item_store($datarray,$force_parent);
2154 // Head post of a conversation. Have we seen it? If not, import it.
2156 $item_id = $item->get_id();
2158 $datarray = get_atom_elements($feed,$item);
2160 if(is_array($contact)) {
2161 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2162 $datarray['author-name'] = $contact['name'];
2163 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2164 $datarray['author-link'] = $contact['url'];
2165 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2166 $datarray['author-avatar'] = $contact['thumb'];
2169 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2170 logger('consume_feed: no author information! ' . print_r($datarray,true));
2174 // special handling for events
2176 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2177 $ev = bbtoevent($datarray['body']);
2178 if(x($ev,'desc') && x($ev,'start')) {
2179 $ev['uid'] = $importer['uid'];
2180 $ev['uri'] = $item_id;
2181 $ev['edited'] = $datarray['edited'];
2182 $ev['private'] = $datarray['private'];
2184 if(is_array($contact))
2185 $ev['cid'] = $contact['id'];
2186 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2188 intval($importer['uid'])
2191 $ev['id'] = $r[0]['id'];
2192 $xyz = event_store($ev);
2197 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2198 if(strlen($datarray['title']))
2199 unset($datarray['title']);
2200 $datarray['last-child'] = 1;
2204 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2206 intval($importer['uid'])
2209 // Update content if 'updated' changes
2212 if (edited_timestamp_is_newer($r[0], $datarray)) {
2214 // do not accept (ignore) an earlier edit than one we currently have.
2215 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2218 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2219 dbesc($datarray['title']),
2220 dbesc($datarray['body']),
2221 dbesc($datarray['tag']),
2222 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2224 intval($importer['uid'])
2226 create_tags_from_itemuri($item_id, $importer['uid']);
2229 // update last-child if it changes
2231 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2232 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2233 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2234 intval($allow[0]['data']),
2235 dbesc(datetime_convert()),
2237 intval($importer['uid'])
2243 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2244 logger('consume-feed: New follower');
2245 new_follower($importer,$contact,$datarray,$item);
2248 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2249 lose_follower($importer,$contact,$datarray,$item);
2253 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2254 logger('consume-feed: New friend request');
2255 new_follower($importer,$contact,$datarray,$item,true);
2258 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2259 lose_sharer($importer,$contact,$datarray,$item);
2264 if(! is_array($contact))
2268 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2269 // one way feed - no remote comment ability
2270 $datarray['last-child'] = 0;
2272 if($contact['network'] === NETWORK_FEED)
2273 $datarray['private'] = 2;
2275 // This is my contact on another system, but it's really me.
2276 // Turn this into a wall post.
2278 if($contact['remote_self']) {
2279 $datarray['wall'] = 1;
2280 if($contact['network'] === NETWORK_FEED) {
2281 $datarray['private'] = 0;
2285 $datarray['parent-uri'] = $item_id;
2286 $datarray['uid'] = $importer['uid'];
2287 $datarray['contact-id'] = $contact['id'];
2289 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2290 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2291 // but otherwise there's a possible data mixup on the sender's system.
2292 // the tgroup delivery code called from item_store will correct it if it's a forum,
2293 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2294 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2295 $datarray['owner-name'] = $contact['name'];
2296 $datarray['owner-link'] = $contact['url'];
2297 $datarray['owner-avatar'] = $contact['thumb'];
2300 // We've allowed "followers" to reach this point so we can decide if they are
2301 // posting an @-tag delivery, which followers are allowed to do for certain
2302 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2304 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2308 $r = item_store($datarray);
2316 function local_delivery($importer,$data) {
2319 logger(__function__, LOGGER_TRACE);
2321 if($importer['readonly']) {
2322 // We aren't receiving stuff from this person. But we will quietly ignore them
2323 // rather than a blatant "go away" message.
2324 logger('local_delivery: ignoring');
2329 // Consume notification feed. This may differ from consuming a public feed in several ways
2330 // - might contain email or friend suggestions
2331 // - might contain remote followup to our message
2332 // - in which case we need to accept it and then notify other conversants
2333 // - we may need to send various email notifications
2335 $feed = new SimplePie();
2336 $feed->set_raw_data($data);
2337 $feed->enable_order_by_date(false);
2342 logger('local_delivery: Error parsing XML: ' . $feed->error());
2345 // Check at the feed level for updated contact name and/or photo
2349 $photo_timestamp = '';
2353 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2355 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2357 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2360 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2361 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2362 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2363 $new_name = $elems['name'][0]['data'];
2365 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2366 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2367 $photo_url = $elems['link'][0]['attribs']['']['href'];
2371 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2372 logger('local_delivery: Updating photo for ' . $importer['name']);
2373 require_once("include/Photo.php");
2374 $photo_failure = false;
2375 $have_photo = false;
2377 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2378 intval($importer['id']),
2379 intval($importer['importer_uid'])
2382 $resource_id = $r[0]['resource-id'];
2386 $resource_id = photo_new_resource();
2389 $img_str = fetch_url($photo_url,true);
2390 // guess mimetype from headers or filename
2391 $type = guess_image_type($photo_url,true);
2394 $img = new Photo($img_str, $type);
2395 if($img->is_valid()) {
2397 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2398 dbesc($resource_id),
2399 intval($importer['id']),
2400 intval($importer['importer_uid'])
2404 $img->scaleImageSquare(175);
2406 $hash = $resource_id;
2407 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2409 $img->scaleImage(80);
2410 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2412 $img->scaleImage(48);
2413 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2417 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2418 WHERE `uid` = %d AND `id` = %d LIMIT 1",
2419 dbesc(datetime_convert()),
2420 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2421 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2422 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2423 intval($importer['importer_uid']),
2424 intval($importer['id'])
2429 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2430 $r = q("select * from contact where uid = %d and id = %d limit 1",
2431 intval($importer['importer_uid']),
2432 intval($importer['id'])
2435 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
2436 dbesc(notags(trim($new_name))),
2437 dbesc(datetime_convert()),
2438 intval($importer['importer_uid']),
2439 intval($importer['id'])
2442 // do our best to update the name on content items
2445 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2446 dbesc(notags(trim($new_name))),
2447 dbesc($r[0]['name']),
2448 dbesc($r[0]['url']),
2449 intval($importer['importer_uid'])
2456 // Currently unsupported - needs a lot of work
2457 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2458 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2459 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2461 $newloc['uid'] = $importer['importer_uid'];
2462 $newloc['cid'] = $importer['id'];
2463 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2464 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2465 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2466 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2467 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2468 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2469 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2470 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2471 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2472 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2473 /** relocated user must have original key pair */
2474 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2475 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2477 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2480 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2481 intval($importer['id']),
2482 intval($importer['importer_uid']));
2487 $x = q("UPDATE contact SET
2497 `site-pubkey` = '%s'
2498 WHERE id=%d AND uid=%d;",
2499 dbesc($newloc['name']),
2500 dbesc($newloc['photo']),
2501 dbesc($newloc['thumb']),
2502 dbesc($newloc['micro']),
2503 dbesc($newloc['url']),
2504 dbesc($newloc['request']),
2505 dbesc($newloc['confirm']),
2506 dbesc($newloc['notify']),
2507 dbesc($newloc['poll']),
2508 dbesc($newloc['sitepubkey']),
2509 intval($importer['id']),
2510 intval($importer['importer_uid']));
2516 'owner-link' => array($old['url'], $newloc['url']),
2517 'author-link' => array($old['url'], $newloc['url']),
2518 'owner-avatar' => array($old['photo'], $newloc['photo']),
2519 'author-avatar' => array($old['photo'], $newloc['photo']),
2521 foreach ($fields as $n=>$f){
2522 $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2525 intval($importer['importer_uid']));
2531 // merge with current record, current contents have priority
2532 // update record, set url-updated
2533 // update profile photos
2539 // handle friend suggestion notification
2541 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2542 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2543 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2545 $fsugg['uid'] = $importer['importer_uid'];
2546 $fsugg['cid'] = $importer['id'];
2547 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2548 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2549 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2550 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2551 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2553 // Does our member already have a friend matching this description?
2555 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2556 dbesc($fsugg['name']),
2557 dbesc(normalise_link($fsugg['url'])),
2558 intval($fsugg['uid'])
2563 // Do we already have an fcontact record for this person?
2566 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2567 dbesc($fsugg['url']),
2568 dbesc($fsugg['name']),
2569 dbesc($fsugg['request'])
2574 // OK, we do. Do we already have an introduction for this person ?
2575 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2576 intval($fsugg['uid']),
2583 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2584 dbesc($fsugg['name']),
2585 dbesc($fsugg['url']),
2586 dbesc($fsugg['photo']),
2587 dbesc($fsugg['request'])
2589 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2590 dbesc($fsugg['url']),
2591 dbesc($fsugg['name']),
2592 dbesc($fsugg['request'])
2597 // database record did not get created. Quietly give up.
2602 $hash = random_string();
2604 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2605 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2606 intval($fsugg['uid']),
2608 intval($fsugg['cid']),
2609 dbesc($fsugg['body']),
2611 dbesc(datetime_convert()),
2616 'type' => NOTIFY_SUGGEST,
2617 'notify_flags' => $importer['notify-flags'],
2618 'language' => $importer['language'],
2619 'to_name' => $importer['username'],
2620 'to_email' => $importer['email'],
2621 'uid' => $importer['importer_uid'],
2623 'link' => $a->get_baseurl() . '/notifications/intros',
2624 'source_name' => $importer['name'],
2625 'source_link' => $importer['url'],
2626 'source_photo' => $importer['photo'],
2627 'verb' => ACTIVITY_REQ_FRIEND,
2636 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2637 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2639 logger('local_delivery: private message received');
2642 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2645 $msg['uid'] = $importer['importer_uid'];
2646 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2647 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2648 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2649 $msg['contact-id'] = $importer['id'];
2650 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2651 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2653 $msg['replied'] = 0;
2654 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2655 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2656 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2660 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2661 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2663 // send notifications.
2665 require_once('include/enotify.php');
2667 $notif_params = array(
2668 'type' => NOTIFY_MAIL,
2669 'notify_flags' => $importer['notify-flags'],
2670 'language' => $importer['language'],
2671 'to_name' => $importer['username'],
2672 'to_email' => $importer['email'],
2673 'uid' => $importer['importer_uid'],
2675 'source_name' => $msg['from-name'],
2676 'source_link' => $importer['url'],
2677 'source_photo' => $importer['thumb'],
2678 'verb' => ACTIVITY_POST,
2682 notification($notif_params);
2688 $community_page = 0;
2689 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2691 $community_page = intval($rawtags[0]['data']);
2693 if(intval($importer['forum']) != $community_page) {
2694 q("update contact set forum = %d where id = %d limit 1",
2695 intval($community_page),
2696 intval($importer['id'])
2698 $importer['forum'] = (string) $community_page;
2701 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2703 // process any deleted entries
2705 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2706 if(is_array($del_entries) && count($del_entries)) {
2707 foreach($del_entries as $dentry) {
2709 if(isset($dentry['attribs']['']['ref'])) {
2710 $uri = $dentry['attribs']['']['ref'];
2712 if(isset($dentry['attribs']['']['when'])) {
2713 $when = $dentry['attribs']['']['when'];
2714 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2717 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2721 // check for relayed deletes to our conversation
2724 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2726 intval($importer['importer_uid'])
2729 $parent_uri = $r[0]['parent-uri'];
2730 if($r[0]['id'] != $r[0]['parent'])
2737 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2740 logger('local_delivery: possible community delete');
2743 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2745 // was the top-level post for this reply written by somebody on this site?
2746 // Specifically, the recipient?
2748 $is_a_remote_delete = false;
2750 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2751 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2752 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2753 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2754 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2755 AND `item`.`uid` = %d
2761 intval($importer['importer_uid'])
2764 $is_a_remote_delete = true;
2766 // Does this have the characteristics of a community or private group comment?
2767 // If it's a reply to a wall post on a community/prvgroup page it's a
2768 // valid community comment. Also forum_mode makes it valid for sure.
2769 // If neither, it's not.
2771 if($is_a_remote_delete && $community) {
2772 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2773 $is_a_remote_delete = false;
2774 logger('local_delivery: not a community delete');
2778 if($is_a_remote_delete) {
2779 logger('local_delivery: received remote delete');
2783 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2784 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2786 intval($importer['importer_uid']),
2787 intval($importer['id'])
2793 if($item['deleted'])
2796 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2798 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2799 $xo = parse_xml_string($item['object'],false);
2800 $xt = parse_xml_string($item['target'],false);
2802 if($xt->type === ACTIVITY_OBJ_NOTE) {
2803 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2805 intval($importer['importer_uid'])
2809 // For tags, the owner cannot remove the tag on the author's copy of the post.
2811 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2812 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2813 $author_copy = (($item['origin']) ? true : false);
2815 if($owner_remove && $author_copy)
2817 if($author_remove || $owner_remove) {
2818 $tags = explode(',',$i[0]['tag']);
2821 foreach($tags as $tag)
2822 if(trim($tag) !== trim($xo->body))
2823 $newtags[] = trim($tag);
2825 q("update item set tag = '%s' where id = %d limit 1",
2826 dbesc(implode(',',$newtags)),
2829 create_tags_from_item($i[0]['id']);
2835 if($item['uri'] == $item['parent-uri']) {
2836 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2837 `body` = '', `title` = ''
2838 WHERE `parent-uri` = '%s' AND `uid` = %d",
2840 dbesc(datetime_convert()),
2841 dbesc($item['uri']),
2842 intval($importer['importer_uid'])
2844 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2847 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2848 `body` = '', `title` = ''
2849 WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2851 dbesc(datetime_convert()),
2853 intval($importer['importer_uid'])
2855 create_tags_from_itemuri($uri, $importer['importer_uid']);
2856 if($item['last-child']) {
2857 // ensure that last-child is set in case the comment that had it just got wiped.
2858 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2859 dbesc(datetime_convert()),
2860 dbesc($item['parent-uri']),
2861 intval($item['uid'])
2863 // who is the last child now?
2864 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2865 ORDER BY `created` DESC LIMIT 1",
2866 dbesc($item['parent-uri']),
2867 intval($importer['importer_uid'])
2870 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
2875 // if this is a relayed delete, propagate it to other recipients
2877 if($is_a_remote_delete)
2878 proc_run('php',"include/notifier.php","drop",$item['id']);
2886 foreach($feed->get_items() as $item) {
2889 $item_id = $item->get_id();
2890 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2891 if(isset($rawthread[0]['attribs']['']['ref'])) {
2893 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2899 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2902 logger('local_delivery: possible community reply');
2905 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2907 // was the top-level post for this reply written by somebody on this site?
2908 // Specifically, the recipient?
2910 $is_a_remote_comment = false;
2911 $top_uri = $parent_uri;
2913 $r = q("select `item`.`parent-uri` from `item`
2914 WHERE `item`.`uri` = '%s'
2918 if($r && count($r)) {
2919 $top_uri = $r[0]['parent-uri'];
2921 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2922 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2923 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2924 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2925 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2926 AND `item`.`uid` = %d
2932 intval($importer['importer_uid'])
2935 $is_a_remote_comment = true;
2938 // Does this have the characteristics of a community or private group comment?
2939 // If it's a reply to a wall post on a community/prvgroup page it's a
2940 // valid community comment. Also forum_mode makes it valid for sure.
2941 // If neither, it's not.
2943 if($is_a_remote_comment && $community) {
2944 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2945 $is_a_remote_comment = false;
2946 logger('local_delivery: not a community reply');
2950 if($is_a_remote_comment) {
2951 logger('local_delivery: received remote comment');
2953 // remote reply to our post. Import and then notify everybody else.
2955 $datarray = get_atom_elements($feed,$item);
2957 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2959 intval($importer['importer_uid'])
2962 // Update content if 'updated' changes
2966 if (edited_timestamp_is_newer($r[0], $datarray)) {
2968 // do not accept (ignore) an earlier edit than one we currently have.
2969 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2972 logger('received updated comment' , LOGGER_DEBUG);
2973 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2974 dbesc($datarray['title']),
2975 dbesc($datarray['body']),
2976 dbesc($datarray['tag']),
2977 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2979 intval($importer['importer_uid'])
2981 create_tags_from_itemuri($item_id, $importer['importer_uid']);
2983 proc_run('php',"include/notifier.php","comment-import",$iid);
2992 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
2993 intval($importer['importer_uid'])
2997 $datarray['type'] = 'remote-comment';
2998 $datarray['wall'] = 1;
2999 $datarray['parent-uri'] = $parent_uri;
3000 $datarray['uid'] = $importer['importer_uid'];
3001 $datarray['owner-name'] = $own[0]['name'];
3002 $datarray['owner-link'] = $own[0]['url'];
3003 $datarray['owner-avatar'] = $own[0]['thumb'];
3004 $datarray['contact-id'] = $importer['id'];
3006 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3008 $datarray['type'] = 'activity';
3009 $datarray['gravity'] = GRAVITY_LIKE;
3010 $datarray['last-child'] = 0;
3011 // only one like or dislike per person
3012 $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",
3013 intval($datarray['uid']),
3014 intval($datarray['contact-id']),
3015 dbesc($datarray['verb']),
3016 dbesc($datarray['parent-uri']),
3017 dbesc($datarray['parent-uri'])
3024 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3026 $xo = parse_xml_string($datarray['object'],false);
3027 $xt = parse_xml_string($datarray['target'],false);
3029 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3031 // fetch the parent item
3033 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3035 intval($importer['importer_uid'])
3040 // extract tag, if not duplicate, and this user allows tags, add to parent item
3042 if($xo->id && $xo->content) {
3043 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3044 if(! (stristr($tagp[0]['tag'],$newtag))) {
3045 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3046 intval($importer['importer_uid'])
3048 if(count($i) && ! intval($i[0]['blocktags'])) {
3049 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d LIMIT 1",
3050 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3051 intval($tagp[0]['id']),
3052 dbesc(datetime_convert())
3054 create_tags_from_item($tagp[0]['id']);
3062 $posted_id = item_store($datarray);
3066 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3068 intval($importer['importer_uid'])
3071 $parent = $r[0]['parent'];
3072 $parent_uri = $r[0]['parent-uri'];
3076 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3077 dbesc(datetime_convert()),
3078 intval($importer['importer_uid']),
3079 intval($r[0]['parent'])
3082 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1",
3083 dbesc(datetime_convert()),
3084 intval($importer['importer_uid']),
3089 if($posted_id && $parent) {
3091 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3093 if((! $is_like) && (! $importer['self'])) {
3095 require_once('include/enotify.php');
3098 'type' => NOTIFY_COMMENT,
3099 'notify_flags' => $importer['notify-flags'],
3100 'language' => $importer['language'],
3101 'to_name' => $importer['username'],
3102 'to_email' => $importer['email'],
3103 'uid' => $importer['importer_uid'],
3104 'item' => $datarray,
3105 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3106 'source_name' => stripslashes($datarray['author-name']),
3107 'source_link' => $datarray['author-link'],
3108 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3109 ? $importer['thumb'] : $datarray['author-avatar']),
3110 'verb' => ACTIVITY_POST,
3112 'parent' => $parent,
3113 'parent_uri' => $parent_uri,
3125 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3127 $item_id = $item->get_id();
3128 $datarray = get_atom_elements($feed,$item);
3130 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3133 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3135 intval($importer['importer_uid'])
3138 // Update content if 'updated' changes
3141 if (edited_timestamp_is_newer($r[0], $datarray)) {
3143 // do not accept (ignore) an earlier edit than one we currently have.
3144 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3147 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3148 dbesc($datarray['title']),
3149 dbesc($datarray['body']),
3150 dbesc($datarray['tag']),
3151 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3153 intval($importer['importer_uid'])
3155 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3158 // update last-child if it changes
3160 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3161 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3162 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3163 dbesc(datetime_convert()),
3165 intval($importer['importer_uid'])
3167 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3168 intval($allow[0]['data']),
3169 dbesc(datetime_convert()),
3171 intval($importer['importer_uid'])
3177 $datarray['parent-uri'] = $parent_uri;
3178 $datarray['uid'] = $importer['importer_uid'];
3179 $datarray['contact-id'] = $importer['id'];
3180 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3181 $datarray['type'] = 'activity';
3182 $datarray['gravity'] = GRAVITY_LIKE;
3183 // only one like or dislike per person
3184 $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",
3185 intval($datarray['uid']),
3186 intval($datarray['contact-id']),
3187 dbesc($datarray['verb']),
3196 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3198 $xo = parse_xml_string($datarray['object'],false);
3199 $xt = parse_xml_string($datarray['target'],false);
3201 if($xt->type == ACTIVITY_OBJ_NOTE) {
3202 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3204 intval($importer['importer_uid'])
3209 // extract tag, if not duplicate, add to parent item
3211 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3212 q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1",
3213 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3216 create_tags_from_item($r[0]['id']);
3222 $posted_id = item_store($datarray);
3224 // find out if our user is involved in this conversation and wants to be notified.
3226 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3228 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3230 intval($importer['importer_uid'])
3233 if(count($myconv)) {
3234 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3236 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3237 if(! link_compare($datarray['author-link'],$importer_url)) {
3240 foreach($myconv as $conv) {
3242 // now if we find a match, it means we're in this conversation
3244 if(! link_compare($conv['author-link'],$importer_url))
3247 require_once('include/enotify.php');
3249 $conv_parent = $conv['parent'];
3252 'type' => NOTIFY_COMMENT,
3253 'notify_flags' => $importer['notify-flags'],
3254 'language' => $importer['language'],
3255 'to_name' => $importer['username'],
3256 'to_email' => $importer['email'],
3257 'uid' => $importer['importer_uid'],
3258 'item' => $datarray,
3259 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3260 'source_name' => stripslashes($datarray['author-name']),
3261 'source_link' => $datarray['author-link'],
3262 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3263 ? $importer['thumb'] : $datarray['author-avatar']),
3264 'verb' => ACTIVITY_POST,
3266 'parent' => $conv_parent,
3267 'parent_uri' => $parent_uri
3271 // only send one notification
3283 // Head post of a conversation. Have we seen it? If not, import it.
3286 $item_id = $item->get_id();
3287 $datarray = get_atom_elements($feed,$item);
3289 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3290 $ev = bbtoevent($datarray['body']);
3291 if(x($ev,'desc') && x($ev,'start')) {
3292 $ev['cid'] = $importer['id'];
3293 $ev['uid'] = $importer['uid'];
3294 $ev['uri'] = $item_id;
3295 $ev['edited'] = $datarray['edited'];
3296 $ev['private'] = $datarray['private'];
3298 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3300 intval($importer['uid'])
3303 $ev['id'] = $r[0]['id'];
3304 $xyz = event_store($ev);
3309 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3311 intval($importer['importer_uid'])
3314 // Update content if 'updated' changes
3317 if (edited_timestamp_is_newer($r[0], $datarray)) {
3319 // do not accept (ignore) an earlier edit than one we currently have.
3320 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3323 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3324 dbesc($datarray['title']),
3325 dbesc($datarray['body']),
3326 dbesc($datarray['tag']),
3327 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3329 intval($importer['importer_uid'])
3331 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3334 // update last-child if it changes
3336 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3337 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3338 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3339 intval($allow[0]['data']),
3340 dbesc(datetime_convert()),
3342 intval($importer['importer_uid'])
3348 // This is my contact on another system, but it's really me.
3349 // Turn this into a wall post.
3351 if($importer['remote_self'])
3352 $datarray['wall'] = 1;
3354 $datarray['parent-uri'] = $item_id;
3355 $datarray['uid'] = $importer['importer_uid'];
3356 $datarray['contact-id'] = $importer['id'];
3359 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3360 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3361 // but otherwise there's a possible data mixup on the sender's system.
3362 // the tgroup delivery code called from item_store will correct it if it's a forum,
3363 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3364 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3365 $datarray['owner-name'] = $importer['senderName'];
3366 $datarray['owner-link'] = $importer['url'];
3367 $datarray['owner-avatar'] = $importer['thumb'];
3370 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3373 $posted_id = item_store($datarray);
3375 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3376 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3379 $xo = parse_xml_string($datarray['object'],false);
3381 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3383 // somebody was poked/prodded. Was it me?
3385 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3387 foreach($links->link as $l) {
3388 $atts = $l->attributes();
3389 switch($atts['rel']) {
3391 $Blink = $atts['href'];
3397 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3399 // send a notification
3400 require_once('include/enotify.php');
3403 'type' => NOTIFY_POKE,
3404 'notify_flags' => $importer['notify-flags'],
3405 'language' => $importer['language'],
3406 'to_name' => $importer['username'],
3407 'to_email' => $importer['email'],
3408 'uid' => $importer['importer_uid'],
3409 'item' => $datarray,
3410 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3411 'source_name' => stripslashes($datarray['author-name']),
3412 'source_link' => $datarray['author-link'],
3413 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3414 ? $importer['thumb'] : $datarray['author-avatar']),
3415 'verb' => $datarray['verb'],
3416 'otype' => 'person',
3417 'activity' => $verb,
3434 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3435 $url = notags(trim($datarray['author-link']));
3436 $name = notags(trim($datarray['author-name']));
3437 $photo = notags(trim($datarray['author-avatar']));
3439 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3440 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3441 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3443 if(is_array($contact)) {
3444 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3445 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3446 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
3447 intval(CONTACT_IS_FRIEND),
3448 intval($contact['id']),
3449 intval($importer['uid'])
3452 // send email notification to owner?
3456 // create contact record
3458 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3459 `blocked`, `readonly`, `pending`, `writable` )
3460 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3461 intval($importer['uid']),
3462 dbesc(datetime_convert()),
3464 dbesc(normalise_link($url)),
3468 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3469 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3471 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3472 intval($importer['uid']),
3476 $contact_record = $r[0];
3478 // create notification
3479 $hash = random_string();
3481 if(is_array($contact_record)) {
3482 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3483 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3484 intval($importer['uid']),
3485 intval($contact_record['id']),
3487 dbesc(datetime_convert())
3490 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3491 intval($importer['uid'])
3496 if(intval($r[0]['def_gid'])) {
3497 require_once('include/group.php');
3498 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3501 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3502 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3503 $email = replace_macros($email_tpl, array(
3504 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3506 '$myname' => $r[0]['username'],
3507 '$siteurl' => $a->get_baseurl(),
3508 '$sitename' => $a->config['sitename']
3510 $res = mail($r[0]['email'],
3511 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'),
3513 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3514 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3515 . 'Content-transfer-encoding: 8bit' );
3522 function lose_follower($importer,$contact,$datarray,$item) {
3524 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3525 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3526 intval(CONTACT_IS_SHARING),
3527 intval($contact['id'])
3531 contact_remove($contact['id']);
3535 function lose_sharer($importer,$contact,$datarray,$item) {
3537 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3538 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
3539 intval(CONTACT_IS_FOLLOWER),
3540 intval($contact['id'])
3544 contact_remove($contact['id']);
3549 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3553 if(is_array($importer)) {
3554 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3555 intval($importer['uid'])
3559 // Diaspora has different message-ids in feeds than they do
3560 // through the direct Diaspora protocol. If we try and use
3561 // the feed, we'll get duplicates. So don't.
3563 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3566 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3568 // Use a single verify token, even if multiple hubs
3570 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3572 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3574 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3576 if(! strlen($contact['hub-verify'])) {
3577 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d LIMIT 1",
3578 dbesc($verify_token),
3579 intval($contact['id'])
3583 post_url($url,$params);
3585 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3592 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3596 $name = xmlify($name);
3597 $uri = xmlify($uri);
3600 $photo = xmlify($photo);
3604 $o .= "<name>$name</name>\r\n";
3605 $o .= "<uri>$uri</uri>\r\n";
3606 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3607 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3609 call_hooks('atom_author', $o);
3611 $o .= "</$tag>\r\n";
3615 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3619 if(! $item['parent'])
3622 if($item['deleted'])
3623 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3626 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3627 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3629 $body = $item['body'];
3631 $o = "\r\n\r\n<entry>\r\n";
3633 if(is_array($author))
3634 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3636 $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']));
3637 if(strlen($item['owner-name']))
3638 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3640 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3641 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3642 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3645 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3646 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3647 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3648 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3649 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3650 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3651 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3653 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3655 if($item['location']) {
3656 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3657 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3661 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3663 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3664 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3667 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3668 if($item['bookmark'])
3669 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3672 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3675 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3677 if($item['signed_text']) {
3678 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3679 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3682 $verb = construct_verb($item);
3683 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3684 $actobj = construct_activity_object($item);
3687 $actarg = construct_activity_target($item);
3691 $tags = item_getfeedtags($item);
3693 foreach($tags as $t) {
3694 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3698 $o .= item_getfeedattach($item);
3700 $mentioned = get_mentions($item);
3704 call_hooks('atom_entry', $o);
3706 $o .= '</entry>' . "\r\n";
3711 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3713 if(get_config('system','disable_embedded'))
3718 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3719 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3724 $img_start = strpos($orig_body, '[img');
3725 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3726 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3727 while( ($img_st_close !== false) && ($img_len !== false) ) {
3729 $img_st_close++; // make it point to AFTER the closing bracket
3730 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3732 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3735 if(stristr($image , $site . '/photo/')) {
3736 // Only embed locally hosted photos
3738 $i = basename($image);
3739 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3740 $x = strpos($i,'-');
3743 $res = substr($i,$x+1);
3744 $i = substr($i,0,$x);
3745 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3752 // Check to see if we should replace this photo link with an embedded image
3753 // 1. No need to do so if the photo is public
3754 // 2. If there's a contact-id provided, see if they're in the access list
3755 // for the photo. If so, embed it.
3756 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3757 // permissions, regardless of order but first check to see if they're an exact
3758 // match to save some processing overhead.
3760 if(has_permissions($r[0])) {
3762 $recips = enumerate_permissions($r[0]);
3763 if(in_array($cid, $recips)) {
3768 if(compare_permissions($item,$r[0]))
3773 $data = $r[0]['data'];
3774 $type = $r[0]['type'];
3776 // If a custom width and height were specified, apply before embedding
3777 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3778 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3780 $width = intval($match[1]);
3781 $height = intval($match[2]);
3783 $ph = new Photo($data, $type);
3784 if($ph->is_valid()) {
3785 $ph->scaleImage(max($width, $height));
3786 $data = $ph->imageString();
3787 $type = $ph->getType();
3791 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3792 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3793 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3799 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3800 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3801 if($orig_body === false)
3804 $img_start = strpos($orig_body, '[img');
3805 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3806 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3809 $new_body = $new_body . $orig_body;
3815 function has_permissions($obj) {
3816 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3821 function compare_permissions($obj1,$obj2) {
3822 // first part is easy. Check that these are exactly the same.
3823 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3824 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3825 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3826 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3829 // This is harder. Parse all the permissions and compare the resulting set.
3831 $recipients1 = enumerate_permissions($obj1);
3832 $recipients2 = enumerate_permissions($obj2);
3835 if($recipients1 == $recipients2)
3840 // returns an array of contact-ids that are allowed to see this object
3842 function enumerate_permissions($obj) {
3843 require_once('include/group.php');
3844 $allow_people = expand_acl($obj['allow_cid']);
3845 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3846 $deny_people = expand_acl($obj['deny_cid']);
3847 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3848 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3849 $deny = array_unique(array_merge($deny_people,$deny_groups));
3850 $recipients = array_diff($recipients,$deny);
3854 function item_getfeedtags($item) {
3857 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3859 for($x = 0; $x < $cnt; $x ++) {
3861 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3865 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3867 for($x = 0; $x < $cnt; $x ++) {
3869 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3875 function item_getfeedattach($item) {
3877 $arr = explode('[/attach],',$item['attach']);
3879 foreach($arr as $r) {
3881 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3883 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3884 if(intval($matches[2]))
3885 $ret .= 'length="' . intval($matches[2]) . '" ';
3886 if($matches[4] !== ' ')
3887 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3888 $ret .= ' />' . "\r\n";
3897 function item_expire($uid,$days) {
3899 if((! $uid) || ($days < 1))
3902 // $expire_network_only = save your own wall posts
3903 // and just expire conversations started by others
3905 $expire_network_only = get_pconfig($uid,'expire','network_only');
3906 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3908 $r = q("SELECT * FROM `item`
3910 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
3921 $expire_items = get_pconfig($uid, 'expire','items');
3922 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3924 $expire_notes = get_pconfig($uid, 'expire','notes');
3925 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3927 $expire_starred = get_pconfig($uid, 'expire','starred');
3928 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3930 $expire_photos = get_pconfig($uid, 'expire','photos');
3931 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3933 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3935 foreach($r as $item) {
3937 // don't expire filed items
3939 if(strpos($item['file'],'[') !== false)
3942 // Only expire posts, not photos and photo comments
3944 if($expire_photos==0 && strlen($item['resource-id']))
3946 if($expire_starred==0 && intval($item['starred']))
3948 if($expire_notes==0 && $item['type']=='note')
3950 if($expire_items==0 && $item['type']!='note')
3953 drop_item($item['id'],false);
3956 proc_run('php',"include/notifier.php","expire","$uid");
3961 function drop_items($items) {
3964 if(! local_user() && ! remote_user())
3968 foreach($items as $item) {
3969 $owner = drop_item($item,false);
3970 if($owner && ! $uid)
3975 // multiple threads may have been deleted, send an expire notification
3978 proc_run('php',"include/notifier.php","expire","$uid");
3982 function drop_item($id,$interactive = true) {
3986 // locate item to be deleted
3988 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3995 notice( t('Item not found.') . EOL);
3996 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4001 $owner = $item['uid'];
4005 // check if logged in user is either the author or owner of this item
4007 if(is_array($_SESSION['remote'])) {
4008 foreach($_SESSION['remote'] as $visitor) {
4009 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4010 $cid = $visitor['cid'];
4017 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4019 // Check if we should do HTML-based delete confirmation
4020 if($_REQUEST['confirm']) {
4021 // <form> can't take arguments in its "action" parameter
4022 // so add any arguments as hidden inputs
4023 $query = explode_querystring($a->query_string);
4025 foreach($query['args'] as $arg) {
4026 if(strpos($arg, 'confirm=') === false) {
4027 $arg_parts = explode('=', $arg);
4028 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4032 return replace_macros(get_markup_template('confirm.tpl'), array(
4034 '$message' => t('Do you really want to delete this item?'),
4035 '$extra_inputs' => $inputs,
4036 '$confirm' => t('Yes'),
4037 '$confirm_url' => $query['base'],
4038 '$confirm_name' => 'confirmed',
4039 '$cancel' => t('Cancel'),
4042 // Now check how the user responded to the confirmation query
4043 if($_REQUEST['canceled']) {
4044 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4047 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4050 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
4051 dbesc(datetime_convert()),
4052 dbesc(datetime_convert()),
4055 create_tags_from_item($item['id']);
4057 // clean up categories and tags so they don't end up as orphans
4060 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4062 foreach($matches as $mtch) {
4063 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4069 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4071 foreach($matches as $mtch) {
4072 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4076 // If item is a link to a photo resource, nuke all the associated photos
4077 // (visitors will not have photo resources)
4078 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4079 // generate a resource-id and therefore aren't intimately linked to the item.
4081 if(strlen($item['resource-id'])) {
4082 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4083 dbesc($item['resource-id']),
4084 intval($item['uid'])
4086 // ignore the result
4089 // If item is a link to an event, nuke the event record.
4091 if(intval($item['event-id'])) {
4092 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1",
4093 intval($item['event-id']),
4094 intval($item['uid'])
4096 // ignore the result
4099 // clean up item_id and sign meta-data tables
4102 // Old code - caused very long queries and warning entries in the mysql logfiles:
4104 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4105 intval($item['id']),
4106 intval($item['uid'])
4109 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4110 intval($item['id']),
4111 intval($item['uid'])
4115 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4117 // Creating list of parents
4118 $r = q("select id from item where parent = %d and uid = %d",
4119 intval($item['id']),
4120 intval($item['uid'])
4125 foreach ($r AS $row) {
4126 if ($parentid != "")
4129 $parentid .= $row["id"];
4133 if ($parentid != "") {
4134 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4136 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4139 // If it's the parent of a comment thread, kill all the kids
4141 if($item['uri'] == $item['parent-uri']) {
4142 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4143 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4144 dbesc(datetime_convert()),
4145 dbesc(datetime_convert()),
4146 dbesc($item['parent-uri']),
4147 intval($item['uid'])
4149 create_tags_from_item($item['parent-uri'], $item['uid']);
4150 // ignore the result
4153 // ensure that last-child is set in case the comment that had it just got wiped.
4154 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4155 dbesc(datetime_convert()),
4156 dbesc($item['parent-uri']),
4157 intval($item['uid'])
4159 // who is the last child now?
4160 $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",
4161 dbesc($item['parent-uri']),
4162 intval($item['uid'])
4165 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
4170 // Add a relayable_retraction signature for Diaspora.
4171 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4173 $drop_id = intval($item['id']);
4175 // send the notification upstream/downstream as the case may be
4177 proc_run('php',"include/notifier.php","drop","$drop_id");
4181 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4187 notice( t('Permission denied.') . EOL);
4188 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4195 function first_post_date($uid,$wall = false) {
4196 $r = q("select id, created from item
4197 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4199 order by created asc limit 1",
4201 intval($wall ? 1 : 0)
4204 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4205 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4210 function posted_dates($uid,$wall) {
4211 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4213 $dthen = first_post_date($uid,$wall);
4217 // If it's near the end of a long month, backup to the 28th so that in
4218 // consecutive loops we'll always get a whole month difference.
4220 if(intval(substr($dnow,8)) > 28)
4221 $dnow = substr($dnow,0,8) . '28';
4222 if(intval(substr($dthen,8)) > 28)
4223 $dnow = substr($dthen,0,8) . '28';
4226 // Starting with the current month, get the first and last days of every
4227 // month down to and including the month of the first post
4228 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4229 $dstart = substr($dnow,0,8) . '01';
4230 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4231 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4232 $end_month = datetime_convert('','',$dend,'Y-m-d');
4233 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4234 $ret[] = array($str,$end_month,$start_month);
4235 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4241 function posted_date_widget($url,$uid,$wall) {
4244 if(! feature_enabled($uid,'archives'))
4247 // For former Facebook folks that left because of "timeline"
4249 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4252 $ret = posted_dates($uid,$wall);
4256 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4257 '$title' => t('Archives'),
4258 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4265 function store_diaspora_retract_sig($item, $user, $baseurl) {
4266 // Note that we can't add a target_author_signature
4267 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4268 // the comment, that means we're the home of the post, and Diaspora will only
4269 // check the parent_author_signature of retractions that it doesn't have to relay further
4271 // I don't think this function gets called for an "unlike," but I'll check anyway
4273 $enabled = intval(get_config('system','diaspora_enabled'));
4275 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4279 logger('drop_item: storing diaspora retraction signature');
4281 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4283 if(local_user() == $item['uid']) {
4285 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4286 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4289 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4290 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4293 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4294 // only handles DFRN deletes
4295 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4296 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4297 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4303 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4304 intval($item['id']),
4305 dbesc($signed_text),