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",
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",
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",
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",
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",
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",
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",
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",
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",
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",
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",
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",
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",
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",
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 // splitted into two queries for performance issues
2112 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2113 intval($datarray['uid']),
2114 intval($datarray['contact-id']),
2115 dbesc($datarray['verb']),
2121 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2122 intval($datarray['uid']),
2123 intval($datarray['contact-id']),
2124 dbesc($datarray['verb']),
2131 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2132 $xo = parse_xml_string($datarray['object'],false);
2133 $xt = parse_xml_string($datarray['target'],false);
2135 if($xt->type == ACTIVITY_OBJ_NOTE) {
2136 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2138 intval($importer['importer_uid'])
2143 // extract tag, if not duplicate, add to parent item
2144 if($xo->id && $xo->content) {
2145 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2146 if(! (stristr($r[0]['tag'],$newtag))) {
2147 q("UPDATE item SET tag = '%s' WHERE id = %d",
2148 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2151 create_tags_from_item($r[0]['id']);
2157 $r = item_store($datarray,$force_parent);
2163 // Head post of a conversation. Have we seen it? If not, import it.
2165 $item_id = $item->get_id();
2167 $datarray = get_atom_elements($feed,$item);
2169 if(is_array($contact)) {
2170 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2171 $datarray['author-name'] = $contact['name'];
2172 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2173 $datarray['author-link'] = $contact['url'];
2174 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2175 $datarray['author-avatar'] = $contact['thumb'];
2178 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2179 logger('consume_feed: no author information! ' . print_r($datarray,true));
2183 // special handling for events
2185 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2186 $ev = bbtoevent($datarray['body']);
2187 if(x($ev,'desc') && x($ev,'start')) {
2188 $ev['uid'] = $importer['uid'];
2189 $ev['uri'] = $item_id;
2190 $ev['edited'] = $datarray['edited'];
2191 $ev['private'] = $datarray['private'];
2193 if(is_array($contact))
2194 $ev['cid'] = $contact['id'];
2195 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2197 intval($importer['uid'])
2200 $ev['id'] = $r[0]['id'];
2201 $xyz = event_store($ev);
2206 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2207 if(strlen($datarray['title']))
2208 unset($datarray['title']);
2209 $datarray['last-child'] = 1;
2213 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2215 intval($importer['uid'])
2218 // Update content if 'updated' changes
2221 if (edited_timestamp_is_newer($r[0], $datarray)) {
2223 // do not accept (ignore) an earlier edit than one we currently have.
2224 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2227 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2228 dbesc($datarray['title']),
2229 dbesc($datarray['body']),
2230 dbesc($datarray['tag']),
2231 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2233 intval($importer['uid'])
2235 create_tags_from_itemuri($item_id, $importer['uid']);
2238 // update last-child if it changes
2240 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2241 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2242 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2243 intval($allow[0]['data']),
2244 dbesc(datetime_convert()),
2246 intval($importer['uid'])
2252 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2253 logger('consume-feed: New follower');
2254 new_follower($importer,$contact,$datarray,$item);
2257 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2258 lose_follower($importer,$contact,$datarray,$item);
2262 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2263 logger('consume-feed: New friend request');
2264 new_follower($importer,$contact,$datarray,$item,true);
2267 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2268 lose_sharer($importer,$contact,$datarray,$item);
2273 if(! is_array($contact))
2277 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2278 // one way feed - no remote comment ability
2279 $datarray['last-child'] = 0;
2281 if($contact['network'] === NETWORK_FEED)
2282 $datarray['private'] = 2;
2284 // This is my contact on another system, but it's really me.
2285 // Turn this into a wall post.
2287 if($contact['remote_self']) {
2288 $datarray['wall'] = 1;
2289 if($contact['network'] === NETWORK_FEED) {
2290 $datarray['private'] = 0;
2294 $datarray['parent-uri'] = $item_id;
2295 $datarray['uid'] = $importer['uid'];
2296 $datarray['contact-id'] = $contact['id'];
2298 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2299 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2300 // but otherwise there's a possible data mixup on the sender's system.
2301 // the tgroup delivery code called from item_store will correct it if it's a forum,
2302 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2303 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2304 $datarray['owner-name'] = $contact['name'];
2305 $datarray['owner-link'] = $contact['url'];
2306 $datarray['owner-avatar'] = $contact['thumb'];
2309 // We've allowed "followers" to reach this point so we can decide if they are
2310 // posting an @-tag delivery, which followers are allowed to do for certain
2311 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2313 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2317 $r = item_store($datarray);
2325 function local_delivery($importer,$data) {
2328 logger(__function__, LOGGER_TRACE);
2330 if($importer['readonly']) {
2331 // We aren't receiving stuff from this person. But we will quietly ignore them
2332 // rather than a blatant "go away" message.
2333 logger('local_delivery: ignoring');
2338 // Consume notification feed. This may differ from consuming a public feed in several ways
2339 // - might contain email or friend suggestions
2340 // - might contain remote followup to our message
2341 // - in which case we need to accept it and then notify other conversants
2342 // - we may need to send various email notifications
2344 $feed = new SimplePie();
2345 $feed->set_raw_data($data);
2346 $feed->enable_order_by_date(false);
2351 logger('local_delivery: Error parsing XML: ' . $feed->error());
2354 // Check at the feed level for updated contact name and/or photo
2358 $photo_timestamp = '';
2362 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2364 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2366 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2369 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2370 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2371 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2372 $new_name = $elems['name'][0]['data'];
2374 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2375 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2376 $photo_url = $elems['link'][0]['attribs']['']['href'];
2380 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2381 logger('local_delivery: Updating photo for ' . $importer['name']);
2382 require_once("include/Photo.php");
2383 $photo_failure = false;
2384 $have_photo = false;
2386 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2387 intval($importer['id']),
2388 intval($importer['importer_uid'])
2391 $resource_id = $r[0]['resource-id'];
2395 $resource_id = photo_new_resource();
2398 $img_str = fetch_url($photo_url,true);
2399 // guess mimetype from headers or filename
2400 $type = guess_image_type($photo_url,true);
2403 $img = new Photo($img_str, $type);
2404 if($img->is_valid()) {
2406 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2407 dbesc($resource_id),
2408 intval($importer['id']),
2409 intval($importer['importer_uid'])
2413 $img->scaleImageSquare(175);
2415 $hash = $resource_id;
2416 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2418 $img->scaleImage(80);
2419 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2421 $img->scaleImage(48);
2422 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2426 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2427 WHERE `uid` = %d AND `id` = %d",
2428 dbesc(datetime_convert()),
2429 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2430 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2431 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2432 intval($importer['importer_uid']),
2433 intval($importer['id'])
2438 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2439 $r = q("select * from contact where uid = %d and id = %d limit 1",
2440 intval($importer['importer_uid']),
2441 intval($importer['id'])
2444 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2445 dbesc(notags(trim($new_name))),
2446 dbesc(datetime_convert()),
2447 intval($importer['importer_uid']),
2448 intval($importer['id'])
2451 // do our best to update the name on content items
2454 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2455 dbesc(notags(trim($new_name))),
2456 dbesc($r[0]['name']),
2457 dbesc($r[0]['url']),
2458 intval($importer['importer_uid'])
2465 // Currently unsupported - needs a lot of work
2466 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2467 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2468 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2470 $newloc['uid'] = $importer['importer_uid'];
2471 $newloc['cid'] = $importer['id'];
2472 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2473 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2474 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2475 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2476 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2477 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2478 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2479 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2480 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2481 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2482 /** relocated user must have original key pair */
2483 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2484 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2486 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2489 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2490 intval($importer['id']),
2491 intval($importer['importer_uid']));
2496 $x = q("UPDATE contact SET
2506 `site-pubkey` = '%s'
2507 WHERE id=%d AND uid=%d;",
2508 dbesc($newloc['name']),
2509 dbesc($newloc['photo']),
2510 dbesc($newloc['thumb']),
2511 dbesc($newloc['micro']),
2512 dbesc($newloc['url']),
2513 dbesc($newloc['request']),
2514 dbesc($newloc['confirm']),
2515 dbesc($newloc['notify']),
2516 dbesc($newloc['poll']),
2517 dbesc($newloc['sitepubkey']),
2518 intval($importer['id']),
2519 intval($importer['importer_uid']));
2525 'owner-link' => array($old['url'], $newloc['url']),
2526 'author-link' => array($old['url'], $newloc['url']),
2527 'owner-avatar' => array($old['photo'], $newloc['photo']),
2528 'author-avatar' => array($old['photo'], $newloc['photo']),
2530 foreach ($fields as $n=>$f){
2531 $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2534 intval($importer['importer_uid']));
2540 // merge with current record, current contents have priority
2541 // update record, set url-updated
2542 // update profile photos
2548 // handle friend suggestion notification
2550 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2551 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2552 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2554 $fsugg['uid'] = $importer['importer_uid'];
2555 $fsugg['cid'] = $importer['id'];
2556 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2557 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2558 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2559 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2560 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2562 // Does our member already have a friend matching this description?
2564 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2565 dbesc($fsugg['name']),
2566 dbesc(normalise_link($fsugg['url'])),
2567 intval($fsugg['uid'])
2572 // Do we already have an fcontact record for this person?
2575 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2576 dbesc($fsugg['url']),
2577 dbesc($fsugg['name']),
2578 dbesc($fsugg['request'])
2583 // OK, we do. Do we already have an introduction for this person ?
2584 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2585 intval($fsugg['uid']),
2592 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2593 dbesc($fsugg['name']),
2594 dbesc($fsugg['url']),
2595 dbesc($fsugg['photo']),
2596 dbesc($fsugg['request'])
2598 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2599 dbesc($fsugg['url']),
2600 dbesc($fsugg['name']),
2601 dbesc($fsugg['request'])
2606 // database record did not get created. Quietly give up.
2611 $hash = random_string();
2613 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2614 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2615 intval($fsugg['uid']),
2617 intval($fsugg['cid']),
2618 dbesc($fsugg['body']),
2620 dbesc(datetime_convert()),
2625 'type' => NOTIFY_SUGGEST,
2626 'notify_flags' => $importer['notify-flags'],
2627 'language' => $importer['language'],
2628 'to_name' => $importer['username'],
2629 'to_email' => $importer['email'],
2630 'uid' => $importer['importer_uid'],
2632 'link' => $a->get_baseurl() . '/notifications/intros',
2633 'source_name' => $importer['name'],
2634 'source_link' => $importer['url'],
2635 'source_photo' => $importer['photo'],
2636 'verb' => ACTIVITY_REQ_FRIEND,
2645 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2646 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2648 logger('local_delivery: private message received');
2651 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2654 $msg['uid'] = $importer['importer_uid'];
2655 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2656 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2657 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2658 $msg['contact-id'] = $importer['id'];
2659 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2660 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2662 $msg['replied'] = 0;
2663 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2664 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2665 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2669 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2670 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2672 // send notifications.
2674 require_once('include/enotify.php');
2676 $notif_params = array(
2677 'type' => NOTIFY_MAIL,
2678 'notify_flags' => $importer['notify-flags'],
2679 'language' => $importer['language'],
2680 'to_name' => $importer['username'],
2681 'to_email' => $importer['email'],
2682 'uid' => $importer['importer_uid'],
2684 'source_name' => $msg['from-name'],
2685 'source_link' => $importer['url'],
2686 'source_photo' => $importer['thumb'],
2687 'verb' => ACTIVITY_POST,
2691 notification($notif_params);
2697 $community_page = 0;
2698 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2700 $community_page = intval($rawtags[0]['data']);
2702 if(intval($importer['forum']) != $community_page) {
2703 q("update contact set forum = %d where id = %d",
2704 intval($community_page),
2705 intval($importer['id'])
2707 $importer['forum'] = (string) $community_page;
2710 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2712 // process any deleted entries
2714 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2715 if(is_array($del_entries) && count($del_entries)) {
2716 foreach($del_entries as $dentry) {
2718 if(isset($dentry['attribs']['']['ref'])) {
2719 $uri = $dentry['attribs']['']['ref'];
2721 if(isset($dentry['attribs']['']['when'])) {
2722 $when = $dentry['attribs']['']['when'];
2723 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2726 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2730 // check for relayed deletes to our conversation
2733 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2735 intval($importer['importer_uid'])
2738 $parent_uri = $r[0]['parent-uri'];
2739 if($r[0]['id'] != $r[0]['parent'])
2746 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2749 logger('local_delivery: possible community delete');
2752 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2754 // was the top-level post for this reply written by somebody on this site?
2755 // Specifically, the recipient?
2757 $is_a_remote_delete = false;
2759 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2760 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2761 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2762 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2763 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2764 AND `item`.`uid` = %d
2770 intval($importer['importer_uid'])
2773 $is_a_remote_delete = true;
2775 // Does this have the characteristics of a community or private group comment?
2776 // If it's a reply to a wall post on a community/prvgroup page it's a
2777 // valid community comment. Also forum_mode makes it valid for sure.
2778 // If neither, it's not.
2780 if($is_a_remote_delete && $community) {
2781 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2782 $is_a_remote_delete = false;
2783 logger('local_delivery: not a community delete');
2787 if($is_a_remote_delete) {
2788 logger('local_delivery: received remote delete');
2792 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2793 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2795 intval($importer['importer_uid']),
2796 intval($importer['id'])
2802 if($item['deleted'])
2805 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2807 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2808 $xo = parse_xml_string($item['object'],false);
2809 $xt = parse_xml_string($item['target'],false);
2811 if($xt->type === ACTIVITY_OBJ_NOTE) {
2812 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2814 intval($importer['importer_uid'])
2818 // For tags, the owner cannot remove the tag on the author's copy of the post.
2820 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2821 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2822 $author_copy = (($item['origin']) ? true : false);
2824 if($owner_remove && $author_copy)
2826 if($author_remove || $owner_remove) {
2827 $tags = explode(',',$i[0]['tag']);
2830 foreach($tags as $tag)
2831 if(trim($tag) !== trim($xo->body))
2832 $newtags[] = trim($tag);
2834 q("update item set tag = '%s' where id = %d",
2835 dbesc(implode(',',$newtags)),
2838 create_tags_from_item($i[0]['id']);
2844 if($item['uri'] == $item['parent-uri']) {
2845 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2846 `body` = '', `title` = ''
2847 WHERE `parent-uri` = '%s' AND `uid` = %d",
2849 dbesc(datetime_convert()),
2850 dbesc($item['uri']),
2851 intval($importer['importer_uid'])
2853 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2856 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2857 `body` = '', `title` = ''
2858 WHERE `uri` = '%s' AND `uid` = %d",
2860 dbesc(datetime_convert()),
2862 intval($importer['importer_uid'])
2864 create_tags_from_itemuri($uri, $importer['importer_uid']);
2865 if($item['last-child']) {
2866 // ensure that last-child is set in case the comment that had it just got wiped.
2867 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2868 dbesc(datetime_convert()),
2869 dbesc($item['parent-uri']),
2870 intval($item['uid'])
2872 // who is the last child now?
2873 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2874 ORDER BY `created` DESC LIMIT 1",
2875 dbesc($item['parent-uri']),
2876 intval($importer['importer_uid'])
2879 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2884 // if this is a relayed delete, propagate it to other recipients
2886 if($is_a_remote_delete)
2887 proc_run('php',"include/notifier.php","drop",$item['id']);
2895 foreach($feed->get_items() as $item) {
2898 $item_id = $item->get_id();
2899 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2900 if(isset($rawthread[0]['attribs']['']['ref'])) {
2902 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2908 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2911 logger('local_delivery: possible community reply');
2914 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2916 // was the top-level post for this reply written by somebody on this site?
2917 // Specifically, the recipient?
2919 $is_a_remote_comment = false;
2920 $top_uri = $parent_uri;
2922 $r = q("select `item`.`parent-uri` from `item`
2923 WHERE `item`.`uri` = '%s'
2927 if($r && count($r)) {
2928 $top_uri = $r[0]['parent-uri'];
2930 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2931 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2932 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2933 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2934 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2935 AND `item`.`uid` = %d
2941 intval($importer['importer_uid'])
2944 $is_a_remote_comment = true;
2947 // Does this have the characteristics of a community or private group comment?
2948 // If it's a reply to a wall post on a community/prvgroup page it's a
2949 // valid community comment. Also forum_mode makes it valid for sure.
2950 // If neither, it's not.
2952 if($is_a_remote_comment && $community) {
2953 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2954 $is_a_remote_comment = false;
2955 logger('local_delivery: not a community reply');
2959 if($is_a_remote_comment) {
2960 logger('local_delivery: received remote comment');
2962 // remote reply to our post. Import and then notify everybody else.
2964 $datarray = get_atom_elements($feed,$item);
2966 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2968 intval($importer['importer_uid'])
2971 // Update content if 'updated' changes
2975 if (edited_timestamp_is_newer($r[0], $datarray)) {
2977 // do not accept (ignore) an earlier edit than one we currently have.
2978 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2981 logger('received updated comment' , LOGGER_DEBUG);
2982 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2983 dbesc($datarray['title']),
2984 dbesc($datarray['body']),
2985 dbesc($datarray['tag']),
2986 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2988 intval($importer['importer_uid'])
2990 create_tags_from_itemuri($item_id, $importer['importer_uid']);
2992 proc_run('php',"include/notifier.php","comment-import",$iid);
3001 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3002 intval($importer['importer_uid'])
3006 $datarray['type'] = 'remote-comment';
3007 $datarray['wall'] = 1;
3008 $datarray['parent-uri'] = $parent_uri;
3009 $datarray['uid'] = $importer['importer_uid'];
3010 $datarray['owner-name'] = $own[0]['name'];
3011 $datarray['owner-link'] = $own[0]['url'];
3012 $datarray['owner-avatar'] = $own[0]['thumb'];
3013 $datarray['contact-id'] = $importer['id'];
3015 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3017 $datarray['type'] = 'activity';
3018 $datarray['gravity'] = GRAVITY_LIKE;
3019 $datarray['last-child'] = 0;
3020 // only one like or dislike per person
3021 // splitted into two queries for performance issues
3022 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3023 intval($datarray['uid']),
3024 intval($datarray['contact-id']),
3025 dbesc($datarray['verb']),
3026 dbesc($datarray['parent-uri'])
3032 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3033 intval($datarray['uid']),
3034 intval($datarray['contact-id']),
3035 dbesc($datarray['verb']),
3036 dbesc($datarray['parent-uri'])
3043 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3045 $xo = parse_xml_string($datarray['object'],false);
3046 $xt = parse_xml_string($datarray['target'],false);
3048 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3050 // fetch the parent item
3052 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3054 intval($importer['importer_uid'])
3059 // extract tag, if not duplicate, and this user allows tags, add to parent item
3061 if($xo->id && $xo->content) {
3062 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3063 if(! (stristr($tagp[0]['tag'],$newtag))) {
3064 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3065 intval($importer['importer_uid'])
3067 if(count($i) && ! intval($i[0]['blocktags'])) {
3068 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d",
3069 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3070 intval($tagp[0]['id']),
3071 dbesc(datetime_convert())
3073 create_tags_from_item($tagp[0]['id']);
3081 $posted_id = item_store($datarray);
3085 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3087 intval($importer['importer_uid'])
3090 $parent = $r[0]['parent'];
3091 $parent_uri = $r[0]['parent-uri'];
3095 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3096 dbesc(datetime_convert()),
3097 intval($importer['importer_uid']),
3098 intval($r[0]['parent'])
3101 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3102 dbesc(datetime_convert()),
3103 intval($importer['importer_uid']),
3108 if($posted_id && $parent) {
3110 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3112 if((! $is_like) && (! $importer['self'])) {
3114 require_once('include/enotify.php');
3117 'type' => NOTIFY_COMMENT,
3118 'notify_flags' => $importer['notify-flags'],
3119 'language' => $importer['language'],
3120 'to_name' => $importer['username'],
3121 'to_email' => $importer['email'],
3122 'uid' => $importer['importer_uid'],
3123 'item' => $datarray,
3124 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3125 'source_name' => stripslashes($datarray['author-name']),
3126 'source_link' => $datarray['author-link'],
3127 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3128 ? $importer['thumb'] : $datarray['author-avatar']),
3129 'verb' => ACTIVITY_POST,
3131 'parent' => $parent,
3132 'parent_uri' => $parent_uri,
3144 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3146 $item_id = $item->get_id();
3147 $datarray = get_atom_elements($feed,$item);
3149 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3152 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3154 intval($importer['importer_uid'])
3157 // Update content if 'updated' changes
3160 if (edited_timestamp_is_newer($r[0], $datarray)) {
3162 // do not accept (ignore) an earlier edit than one we currently have.
3163 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3166 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3167 dbesc($datarray['title']),
3168 dbesc($datarray['body']),
3169 dbesc($datarray['tag']),
3170 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3172 intval($importer['importer_uid'])
3174 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3177 // update last-child if it changes
3179 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3180 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3181 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3182 dbesc(datetime_convert()),
3184 intval($importer['importer_uid'])
3186 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3187 intval($allow[0]['data']),
3188 dbesc(datetime_convert()),
3190 intval($importer['importer_uid'])
3196 $datarray['parent-uri'] = $parent_uri;
3197 $datarray['uid'] = $importer['importer_uid'];
3198 $datarray['contact-id'] = $importer['id'];
3199 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3200 $datarray['type'] = 'activity';
3201 $datarray['gravity'] = GRAVITY_LIKE;
3202 // only one like or dislike per person
3203 // splitted into two queries for performance issues
3204 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3205 intval($datarray['uid']),
3206 intval($datarray['contact-id']),
3207 dbesc($datarray['verb']),
3213 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3214 intval($datarray['uid']),
3215 intval($datarray['contact-id']),
3216 dbesc($datarray['verb']),
3224 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3226 $xo = parse_xml_string($datarray['object'],false);
3227 $xt = parse_xml_string($datarray['target'],false);
3229 if($xt->type == ACTIVITY_OBJ_NOTE) {
3230 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3232 intval($importer['importer_uid'])
3237 // extract tag, if not duplicate, add to parent item
3239 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3240 q("UPDATE item SET tag = '%s' WHERE id = %d",
3241 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3244 create_tags_from_item($r[0]['id']);
3250 $posted_id = item_store($datarray);
3252 // find out if our user is involved in this conversation and wants to be notified.
3254 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3256 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3258 intval($importer['importer_uid'])
3261 if(count($myconv)) {
3262 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3264 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3265 if(! link_compare($datarray['author-link'],$importer_url)) {
3268 foreach($myconv as $conv) {
3270 // now if we find a match, it means we're in this conversation
3272 if(! link_compare($conv['author-link'],$importer_url))
3275 require_once('include/enotify.php');
3277 $conv_parent = $conv['parent'];
3280 'type' => NOTIFY_COMMENT,
3281 'notify_flags' => $importer['notify-flags'],
3282 'language' => $importer['language'],
3283 'to_name' => $importer['username'],
3284 'to_email' => $importer['email'],
3285 'uid' => $importer['importer_uid'],
3286 'item' => $datarray,
3287 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3288 'source_name' => stripslashes($datarray['author-name']),
3289 'source_link' => $datarray['author-link'],
3290 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3291 ? $importer['thumb'] : $datarray['author-avatar']),
3292 'verb' => ACTIVITY_POST,
3294 'parent' => $conv_parent,
3295 'parent_uri' => $parent_uri
3299 // only send one notification
3311 // Head post of a conversation. Have we seen it? If not, import it.
3314 $item_id = $item->get_id();
3315 $datarray = get_atom_elements($feed,$item);
3317 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3318 $ev = bbtoevent($datarray['body']);
3319 if(x($ev,'desc') && x($ev,'start')) {
3320 $ev['cid'] = $importer['id'];
3321 $ev['uid'] = $importer['uid'];
3322 $ev['uri'] = $item_id;
3323 $ev['edited'] = $datarray['edited'];
3324 $ev['private'] = $datarray['private'];
3326 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3328 intval($importer['uid'])
3331 $ev['id'] = $r[0]['id'];
3332 $xyz = event_store($ev);
3337 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3339 intval($importer['importer_uid'])
3342 // Update content if 'updated' changes
3345 if (edited_timestamp_is_newer($r[0], $datarray)) {
3347 // do not accept (ignore) an earlier edit than one we currently have.
3348 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3351 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3352 dbesc($datarray['title']),
3353 dbesc($datarray['body']),
3354 dbesc($datarray['tag']),
3355 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3357 intval($importer['importer_uid'])
3359 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3362 // update last-child if it changes
3364 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3365 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3366 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3367 intval($allow[0]['data']),
3368 dbesc(datetime_convert()),
3370 intval($importer['importer_uid'])
3376 // This is my contact on another system, but it's really me.
3377 // Turn this into a wall post.
3379 if($importer['remote_self'])
3380 $datarray['wall'] = 1;
3382 $datarray['parent-uri'] = $item_id;
3383 $datarray['uid'] = $importer['importer_uid'];
3384 $datarray['contact-id'] = $importer['id'];
3387 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3388 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3389 // but otherwise there's a possible data mixup on the sender's system.
3390 // the tgroup delivery code called from item_store will correct it if it's a forum,
3391 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3392 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3393 $datarray['owner-name'] = $importer['senderName'];
3394 $datarray['owner-link'] = $importer['url'];
3395 $datarray['owner-avatar'] = $importer['thumb'];
3398 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3401 $posted_id = item_store($datarray);
3403 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3404 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3407 $xo = parse_xml_string($datarray['object'],false);
3409 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3411 // somebody was poked/prodded. Was it me?
3413 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3415 foreach($links->link as $l) {
3416 $atts = $l->attributes();
3417 switch($atts['rel']) {
3419 $Blink = $atts['href'];
3425 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3427 // send a notification
3428 require_once('include/enotify.php');
3431 'type' => NOTIFY_POKE,
3432 'notify_flags' => $importer['notify-flags'],
3433 'language' => $importer['language'],
3434 'to_name' => $importer['username'],
3435 'to_email' => $importer['email'],
3436 'uid' => $importer['importer_uid'],
3437 'item' => $datarray,
3438 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3439 'source_name' => stripslashes($datarray['author-name']),
3440 'source_link' => $datarray['author-link'],
3441 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3442 ? $importer['thumb'] : $datarray['author-avatar']),
3443 'verb' => $datarray['verb'],
3444 'otype' => 'person',
3445 'activity' => $verb,
3462 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3463 $url = notags(trim($datarray['author-link']));
3464 $name = notags(trim($datarray['author-name']));
3465 $photo = notags(trim($datarray['author-avatar']));
3467 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3468 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3469 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3471 if(is_array($contact)) {
3472 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3473 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3474 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3475 intval(CONTACT_IS_FRIEND),
3476 intval($contact['id']),
3477 intval($importer['uid'])
3480 // send email notification to owner?
3484 // create contact record
3486 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3487 `blocked`, `readonly`, `pending`, `writable` )
3488 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3489 intval($importer['uid']),
3490 dbesc(datetime_convert()),
3492 dbesc(normalise_link($url)),
3496 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3497 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3499 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3500 intval($importer['uid']),
3504 $contact_record = $r[0];
3506 // create notification
3507 $hash = random_string();
3509 if(is_array($contact_record)) {
3510 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3511 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3512 intval($importer['uid']),
3513 intval($contact_record['id']),
3515 dbesc(datetime_convert())
3518 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3519 intval($importer['uid'])
3524 if(intval($r[0]['def_gid'])) {
3525 require_once('include/group.php');
3526 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3529 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3530 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3531 $email = replace_macros($email_tpl, array(
3532 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3534 '$myname' => $r[0]['username'],
3535 '$siteurl' => $a->get_baseurl(),
3536 '$sitename' => $a->config['sitename']
3538 $res = mail($r[0]['email'],
3539 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'),
3541 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3542 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3543 . 'Content-transfer-encoding: 8bit' );
3550 function lose_follower($importer,$contact,$datarray,$item) {
3552 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3553 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3554 intval(CONTACT_IS_SHARING),
3555 intval($contact['id'])
3559 contact_remove($contact['id']);
3563 function lose_sharer($importer,$contact,$datarray,$item) {
3565 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3566 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3567 intval(CONTACT_IS_FOLLOWER),
3568 intval($contact['id'])
3572 contact_remove($contact['id']);
3577 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3581 if(is_array($importer)) {
3582 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3583 intval($importer['uid'])
3587 // Diaspora has different message-ids in feeds than they do
3588 // through the direct Diaspora protocol. If we try and use
3589 // the feed, we'll get duplicates. So don't.
3591 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3594 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3596 // Use a single verify token, even if multiple hubs
3598 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3600 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3602 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3604 if(! strlen($contact['hub-verify'])) {
3605 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3606 dbesc($verify_token),
3607 intval($contact['id'])
3611 post_url($url,$params);
3613 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3620 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3624 $name = xmlify($name);
3625 $uri = xmlify($uri);
3628 $photo = xmlify($photo);
3632 $o .= "<name>$name</name>\r\n";
3633 $o .= "<uri>$uri</uri>\r\n";
3634 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3635 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3637 call_hooks('atom_author', $o);
3639 $o .= "</$tag>\r\n";
3643 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3647 if(! $item['parent'])
3650 if($item['deleted'])
3651 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3654 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3655 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3657 $body = $item['body'];
3659 $o = "\r\n\r\n<entry>\r\n";
3661 if(is_array($author))
3662 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3664 $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']));
3665 if(strlen($item['owner-name']))
3666 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3668 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3669 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3670 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3673 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3674 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3675 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3676 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3677 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3678 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3679 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3681 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3683 if($item['location']) {
3684 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3685 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3689 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3691 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3692 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3695 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3696 if($item['bookmark'])
3697 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3700 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3703 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3705 if($item['signed_text']) {
3706 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3707 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3710 $verb = construct_verb($item);
3711 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3712 $actobj = construct_activity_object($item);
3715 $actarg = construct_activity_target($item);
3719 $tags = item_getfeedtags($item);
3721 foreach($tags as $t) {
3722 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3726 $o .= item_getfeedattach($item);
3728 $mentioned = get_mentions($item);
3732 call_hooks('atom_entry', $o);
3734 $o .= '</entry>' . "\r\n";
3739 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3741 if(get_config('system','disable_embedded'))
3746 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3747 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3752 $img_start = strpos($orig_body, '[img');
3753 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3754 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3755 while( ($img_st_close !== false) && ($img_len !== false) ) {
3757 $img_st_close++; // make it point to AFTER the closing bracket
3758 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3760 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3763 if(stristr($image , $site . '/photo/')) {
3764 // Only embed locally hosted photos
3766 $i = basename($image);
3767 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3768 $x = strpos($i,'-');
3771 $res = substr($i,$x+1);
3772 $i = substr($i,0,$x);
3773 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3780 // Check to see if we should replace this photo link with an embedded image
3781 // 1. No need to do so if the photo is public
3782 // 2. If there's a contact-id provided, see if they're in the access list
3783 // for the photo. If so, embed it.
3784 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3785 // permissions, regardless of order but first check to see if they're an exact
3786 // match to save some processing overhead.
3788 if(has_permissions($r[0])) {
3790 $recips = enumerate_permissions($r[0]);
3791 if(in_array($cid, $recips)) {
3796 if(compare_permissions($item,$r[0]))
3801 $data = $r[0]['data'];
3802 $type = $r[0]['type'];
3804 // If a custom width and height were specified, apply before embedding
3805 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3806 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3808 $width = intval($match[1]);
3809 $height = intval($match[2]);
3811 $ph = new Photo($data, $type);
3812 if($ph->is_valid()) {
3813 $ph->scaleImage(max($width, $height));
3814 $data = $ph->imageString();
3815 $type = $ph->getType();
3819 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3820 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3821 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3827 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3828 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3829 if($orig_body === false)
3832 $img_start = strpos($orig_body, '[img');
3833 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3834 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3837 $new_body = $new_body . $orig_body;
3843 function has_permissions($obj) {
3844 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3849 function compare_permissions($obj1,$obj2) {
3850 // first part is easy. Check that these are exactly the same.
3851 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3852 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3853 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3854 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3857 // This is harder. Parse all the permissions and compare the resulting set.
3859 $recipients1 = enumerate_permissions($obj1);
3860 $recipients2 = enumerate_permissions($obj2);
3863 if($recipients1 == $recipients2)
3868 // returns an array of contact-ids that are allowed to see this object
3870 function enumerate_permissions($obj) {
3871 require_once('include/group.php');
3872 $allow_people = expand_acl($obj['allow_cid']);
3873 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3874 $deny_people = expand_acl($obj['deny_cid']);
3875 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3876 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3877 $deny = array_unique(array_merge($deny_people,$deny_groups));
3878 $recipients = array_diff($recipients,$deny);
3882 function item_getfeedtags($item) {
3885 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3887 for($x = 0; $x < $cnt; $x ++) {
3889 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3893 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3895 for($x = 0; $x < $cnt; $x ++) {
3897 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3903 function item_getfeedattach($item) {
3905 $arr = explode('[/attach],',$item['attach']);
3907 foreach($arr as $r) {
3909 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3911 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3912 if(intval($matches[2]))
3913 $ret .= 'length="' . intval($matches[2]) . '" ';
3914 if($matches[4] !== ' ')
3915 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3916 $ret .= ' />' . "\r\n";
3925 function item_expire($uid,$days) {
3927 if((! $uid) || ($days < 1))
3930 // $expire_network_only = save your own wall posts
3931 // and just expire conversations started by others
3933 $expire_network_only = get_pconfig($uid,'expire','network_only');
3934 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3936 $r = q("SELECT * FROM `item`
3938 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
3949 $expire_items = get_pconfig($uid, 'expire','items');
3950 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3952 $expire_notes = get_pconfig($uid, 'expire','notes');
3953 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3955 $expire_starred = get_pconfig($uid, 'expire','starred');
3956 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3958 $expire_photos = get_pconfig($uid, 'expire','photos');
3959 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3961 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3963 foreach($r as $item) {
3965 // don't expire filed items
3967 if(strpos($item['file'],'[') !== false)
3970 // Only expire posts, not photos and photo comments
3972 if($expire_photos==0 && strlen($item['resource-id']))
3974 if($expire_starred==0 && intval($item['starred']))
3976 if($expire_notes==0 && $item['type']=='note')
3978 if($expire_items==0 && $item['type']!='note')
3981 drop_item($item['id'],false);
3984 proc_run('php',"include/notifier.php","expire","$uid");
3989 function drop_items($items) {
3992 if(! local_user() && ! remote_user())
3996 foreach($items as $item) {
3997 $owner = drop_item($item,false);
3998 if($owner && ! $uid)
4003 // multiple threads may have been deleted, send an expire notification
4006 proc_run('php',"include/notifier.php","expire","$uid");
4010 function drop_item($id,$interactive = true) {
4014 // locate item to be deleted
4016 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4023 notice( t('Item not found.') . EOL);
4024 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4029 $owner = $item['uid'];
4033 // check if logged in user is either the author or owner of this item
4035 if(is_array($_SESSION['remote'])) {
4036 foreach($_SESSION['remote'] as $visitor) {
4037 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4038 $cid = $visitor['cid'];
4045 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4047 // Check if we should do HTML-based delete confirmation
4048 if($_REQUEST['confirm']) {
4049 // <form> can't take arguments in its "action" parameter
4050 // so add any arguments as hidden inputs
4051 $query = explode_querystring($a->query_string);
4053 foreach($query['args'] as $arg) {
4054 if(strpos($arg, 'confirm=') === false) {
4055 $arg_parts = explode('=', $arg);
4056 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4060 return replace_macros(get_markup_template('confirm.tpl'), array(
4062 '$message' => t('Do you really want to delete this item?'),
4063 '$extra_inputs' => $inputs,
4064 '$confirm' => t('Yes'),
4065 '$confirm_url' => $query['base'],
4066 '$confirm_name' => 'confirmed',
4067 '$cancel' => t('Cancel'),
4070 // Now check how the user responded to the confirmation query
4071 if($_REQUEST['canceled']) {
4072 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4075 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4078 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4079 dbesc(datetime_convert()),
4080 dbesc(datetime_convert()),
4083 create_tags_from_item($item['id']);
4085 // clean up categories and tags so they don't end up as orphans
4088 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4090 foreach($matches as $mtch) {
4091 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4097 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4099 foreach($matches as $mtch) {
4100 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4104 // If item is a link to a photo resource, nuke all the associated photos
4105 // (visitors will not have photo resources)
4106 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4107 // generate a resource-id and therefore aren't intimately linked to the item.
4109 if(strlen($item['resource-id'])) {
4110 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4111 dbesc($item['resource-id']),
4112 intval($item['uid'])
4114 // ignore the result
4117 // If item is a link to an event, nuke the event record.
4119 if(intval($item['event-id'])) {
4120 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4121 intval($item['event-id']),
4122 intval($item['uid'])
4124 // ignore the result
4127 // clean up item_id and sign meta-data tables
4130 // Old code - caused very long queries and warning entries in the mysql logfiles:
4132 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4133 intval($item['id']),
4134 intval($item['uid'])
4137 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4138 intval($item['id']),
4139 intval($item['uid'])
4143 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4145 // Creating list of parents
4146 $r = q("select id from item where parent = %d and uid = %d",
4147 intval($item['id']),
4148 intval($item['uid'])
4153 foreach ($r AS $row) {
4154 if ($parentid != "")
4157 $parentid .= $row["id"];
4161 if ($parentid != "") {
4162 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4164 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4167 // If it's the parent of a comment thread, kill all the kids
4169 if($item['uri'] == $item['parent-uri']) {
4170 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4171 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4172 dbesc(datetime_convert()),
4173 dbesc(datetime_convert()),
4174 dbesc($item['parent-uri']),
4175 intval($item['uid'])
4177 create_tags_from_item($item['parent-uri'], $item['uid']);
4178 // ignore the result
4181 // ensure that last-child is set in case the comment that had it just got wiped.
4182 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4183 dbesc(datetime_convert()),
4184 dbesc($item['parent-uri']),
4185 intval($item['uid'])
4187 // who is the last child now?
4188 $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",
4189 dbesc($item['parent-uri']),
4190 intval($item['uid'])
4193 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4198 // Add a relayable_retraction signature for Diaspora.
4199 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4201 $drop_id = intval($item['id']);
4203 // send the notification upstream/downstream as the case may be
4205 proc_run('php',"include/notifier.php","drop","$drop_id");
4209 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4215 notice( t('Permission denied.') . EOL);
4216 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4223 function first_post_date($uid,$wall = false) {
4224 $r = q("select id, created from item
4225 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4227 order by created asc limit 1",
4229 intval($wall ? 1 : 0)
4232 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4233 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4238 function posted_dates($uid,$wall) {
4239 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4241 $dthen = first_post_date($uid,$wall);
4245 // If it's near the end of a long month, backup to the 28th so that in
4246 // consecutive loops we'll always get a whole month difference.
4248 if(intval(substr($dnow,8)) > 28)
4249 $dnow = substr($dnow,0,8) . '28';
4250 if(intval(substr($dthen,8)) > 28)
4251 $dnow = substr($dthen,0,8) . '28';
4254 // Starting with the current month, get the first and last days of every
4255 // month down to and including the month of the first post
4256 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4257 $dstart = substr($dnow,0,8) . '01';
4258 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4259 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4260 $end_month = datetime_convert('','',$dend,'Y-m-d');
4261 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4262 $ret[] = array($str,$end_month,$start_month);
4263 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4269 function posted_date_widget($url,$uid,$wall) {
4272 if(! feature_enabled($uid,'archives'))
4275 // For former Facebook folks that left because of "timeline"
4277 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4280 $ret = posted_dates($uid,$wall);
4284 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4285 '$title' => t('Archives'),
4286 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4293 function store_diaspora_retract_sig($item, $user, $baseurl) {
4294 // Note that we can't add a target_author_signature
4295 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4296 // the comment, that means we're the home of the post, and Diaspora will only
4297 // check the parent_author_signature of retractions that it doesn't have to relay further
4299 // I don't think this function gets called for an "unlike," but I'll check anyway
4301 $enabled = intval(get_config('system','diaspora_enabled'));
4303 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4307 logger('drop_item: storing diaspora retraction signature');
4309 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4311 if(local_user() == $item['uid']) {
4313 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4314 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4317 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4318 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4321 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4322 // only handles DFRN deletes
4323 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4324 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4325 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4331 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4332 intval($item['id']),
4333 dbesc($signed_text),