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, $contact = array()) {
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 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
850 $res["body"] = $res["title"]."\n\n[class=type-link]".fetch_siteinfo($res['plink'])."[/class]";
854 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
856 call_hooks('parse_atom', $arr);
858 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
859 //if (strpos($res["body"], "RT @") !== false) {
860 /*if (strpos($res["body"], "@") !== false) {
861 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
862 file_put_contents($debugfile, serialize($arr));
868 function fetch_siteinfo($url) {
869 require_once("mod/parse_url.php");
871 // Fetch site infos - but only from the meta data
872 $data = parseurl_getsiteinfo($url, true);
876 if (!is_string($data["text"]) AND (sizeof($data["images"]) == 0) AND ($data["title"] == $url))
879 if (is_string($data["title"]))
880 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]\n";
882 if (sizeof($data["images"]) > 0) {
883 $imagedata = $data["images"][0];
884 $text .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]' . "\n";
887 if (is_string($data["text"]))
888 $text .= "[quote]".$data["text"]."[/quote]";
893 function encode_rel_links($links) {
895 if(! ((is_array($links)) && (count($links))))
897 foreach($links as $link) {
899 if($link['attribs']['']['rel'])
900 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
901 if($link['attribs']['']['type'])
902 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
903 if($link['attribs']['']['href'])
904 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
905 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
906 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
907 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
908 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
916 function item_store($arr,$force_parent = false) {
918 // If a Diaspora signature structure was passed in, pull it out of the
919 // item array and set it aside for later storage.
922 if(x($arr,'dsprsig')) {
923 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
924 unset($arr['dsprsig']);
927 // if an OStatus conversation url was passed in, it is stored and then
928 // removed from the array.
929 $ostatus_conversation = null;
931 if (isset($arr["ostatus_conversation"])) {
932 $ostatus_conversation = $arr["ostatus_conversation"];
933 unset($arr["ostatus_conversation"]);
936 if(x($arr, 'gravity'))
937 $arr['gravity'] = intval($arr['gravity']);
938 elseif($arr['parent-uri'] === $arr['uri'])
940 elseif(activity_match($arr['verb'],ACTIVITY_POST))
943 $arr['gravity'] = 6; // extensible catchall
946 $arr['type'] = 'remote';
948 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
950 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
951 $arr['body'] = strip_tags($arr['body']);
954 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
955 require_once('library/langdet/Text/LanguageDetect.php');
956 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
957 $l = new Text_LanguageDetect;
958 //$lng = $l->detectConfidence($naked_body);
959 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
960 $lng = $l->detect($naked_body, 3);
962 if (sizeof($lng) > 0) {
965 foreach ($lng as $language => $score) {
971 $postopts .= $language.";".$score;
973 $arr['postopts'] = $postopts;
977 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
978 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
979 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
980 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
981 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
982 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
983 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
984 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
985 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
986 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
987 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
988 $arr['commented'] = datetime_convert();
989 $arr['received'] = datetime_convert();
990 $arr['changed'] = datetime_convert();
991 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
992 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
993 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
994 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
995 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
997 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
998 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
999 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1000 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1001 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1002 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1003 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1004 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1005 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1006 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1007 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1008 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1009 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1010 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1011 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1012 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1013 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1014 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1015 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1016 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1018 if ($arr['network'] == "") {
1019 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1020 intval($arr['contact-id']),
1025 $arr['network'] = $r[0]["network"];
1027 // Fallback to friendica (why is it empty in some cases?)
1028 if ($arr['network'] == "")
1029 $arr['network'] = NETWORK_DFRN;
1031 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1034 $arr['thr-parent'] = $arr['parent-uri'];
1035 if($arr['parent-uri'] === $arr['uri']) {
1037 $parent_deleted = 0;
1038 $allow_cid = $arr['allow_cid'];
1039 $allow_gid = $arr['allow_gid'];
1040 $deny_cid = $arr['deny_cid'];
1041 $deny_gid = $arr['deny_gid'];
1045 // find the parent and snarf the item id and ACLs
1046 // and anything else we need to inherit
1048 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1049 dbesc($arr['parent-uri']),
1055 // is the new message multi-level threaded?
1056 // even though we don't support it now, preserve the info
1057 // and re-attach to the conversation parent.
1059 if($r[0]['uri'] != $r[0]['parent-uri']) {
1060 $arr['parent-uri'] = $r[0]['parent-uri'];
1061 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1062 ORDER BY `id` ASC LIMIT 1",
1063 dbesc($r[0]['parent-uri']),
1064 dbesc($r[0]['parent-uri']),
1071 $parent_id = $r[0]['id'];
1072 $parent_deleted = $r[0]['deleted'];
1073 $allow_cid = $r[0]['allow_cid'];
1074 $allow_gid = $r[0]['allow_gid'];
1075 $deny_cid = $r[0]['deny_cid'];
1076 $deny_gid = $r[0]['deny_gid'];
1077 $arr['wall'] = $r[0]['wall'];
1079 // if the parent is private, force privacy for the entire conversation
1080 // This differs from the above settings as it subtly allows comments from
1081 // email correspondents to be private even if the overall thread is not.
1083 if($r[0]['private'])
1084 $arr['private'] = $r[0]['private'];
1086 // Edge case. We host a public forum that was originally posted to privately.
1087 // The original author commented, but as this is a comment, the permissions
1088 // weren't fixed up so it will still show the comment as private unless we fix it here.
1090 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1091 $arr['private'] = 0;
1095 // Allow one to see reply tweets from status.net even when
1096 // we don't have or can't see the original post.
1099 logger('item_store: $force_parent=true, reply converted to top-level post.');
1101 $arr['parent-uri'] = $arr['uri'];
1102 $arr['gravity'] = 0;
1105 logger('item_store: item parent was not found - ignoring item');
1109 $parent_deleted = 0;
1113 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1117 if($r && count($r)) {
1118 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1122 call_hooks('post_remote',$arr);
1124 if(x($arr,'cancel')) {
1125 logger('item_store: post cancelled by plugin.');
1131 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1133 $r = dbq("INSERT INTO `item` (`"
1134 . implode("`, `", array_keys($arr))
1136 . implode("', '", array_values($arr))
1139 // find the item we just created
1141 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1142 $arr['uri'], // already dbesc'd
1147 $current_post = $r[0]['id'];
1148 logger('item_store: created item ' . $current_post);
1149 create_tags_from_item($r[0]['id']);
1151 // Only check for notifications on start posts
1152 if ($arr['parent-uri'] === $arr['uri']) {
1153 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1155 // Send a notification for every new post?
1156 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1157 intval($arr['contact-id']),
1162 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1163 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1164 intval($arr['uid']));
1166 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1167 intval($current_post),
1173 require_once('include/enotify.php');
1175 'type' => NOTIFY_SHARE,
1176 'notify_flags' => $u[0]['notify-flags'],
1177 'language' => $u[0]['language'],
1178 'to_name' => $u[0]['username'],
1179 'to_email' => $u[0]['email'],
1180 'uid' => $u[0]['uid'],
1182 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1183 'source_name' => $item[0]['author-name'],
1184 'source_link' => $item[0]['author-link'],
1185 'source_photo' => $item[0]['author-avatar'],
1186 'verb' => ACTIVITY_TAG,
1189 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1194 logger('item_store: could not locate created item');
1198 logger('item_store: duplicated post occurred. Removing duplicates.');
1199 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1201 intval($arr['uid']),
1202 intval($current_post)
1206 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1207 $parent_id = $current_post;
1209 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1212 $private = $arr['private'];
1214 // Set parent id - and also make sure to inherit the parent's ACLs.
1216 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1217 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1224 intval($parent_deleted),
1225 intval($current_post)
1227 create_tags_from_item($current_post);
1229 // Complete ostatus threads
1230 if ($ostatus_conversation)
1231 complete_conversation($current_post, $ostatus_conversation);
1233 $arr['id'] = $current_post;
1234 $arr['parent'] = $parent_id;
1235 $arr['allow_cid'] = $allow_cid;
1236 $arr['allow_gid'] = $allow_gid;
1237 $arr['deny_cid'] = $deny_cid;
1238 $arr['deny_gid'] = $deny_gid;
1239 $arr['private'] = $private;
1240 $arr['deleted'] = $parent_deleted;
1242 // update the commented timestamp on the parent
1244 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1245 dbesc(datetime_convert()),
1246 dbesc(datetime_convert()),
1251 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1252 intval($current_post),
1253 dbesc($dsprsig->signed_text),
1254 dbesc($dsprsig->signature),
1255 dbesc($dsprsig->signer)
1261 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1264 if($arr['last-child']) {
1265 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1267 intval($arr['uid']),
1268 intval($current_post)
1272 $deleted = tag_deliver($arr['uid'],$current_post);
1274 // current post can be deleted if is for a communuty page and no mention are
1278 // Store the fresh generated item into the cache
1279 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1281 if (($cachefile != '') AND !file_exists($cachefile)) {
1282 $s = prepare_text($arr['body']);
1284 $stamp1 = microtime(true);
1285 file_put_contents($cachefile, $s);
1286 $a->save_timestamp($stamp1, "file");
1287 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1290 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1291 if (count($r) == 1) {
1292 call_hooks('post_remote_end', $r[0]);
1295 logger('item_store: new item not found in DB, id ' . $current_post);
1298 return $current_post;
1301 function get_item_contact($item,$contacts) {
1302 if(! count($contacts) || (! is_array($item)))
1304 foreach($contacts as $contact) {
1305 if($contact['id'] == $item['contact-id']) {
1307 break; // NOTREACHED
1314 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1316 * @param int $item_id
1317 * @return bool true if item was deleted, else false
1319 function tag_deliver($uid,$item_id) {
1327 $u = q("select * from user where uid = %d limit 1",
1333 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1334 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1337 $i = q("select * from item where id = %d and uid = %d limit 1",
1346 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1348 // Diaspora uses their own hardwired link URL in @-tags
1349 // instead of the one we supply with webfinger
1351 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1353 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1355 foreach($matches as $mtch) {
1356 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1358 logger('tag_deliver: mention found: ' . $mtch[2]);
1364 if ( ($community_page || $prvgroup) &&
1365 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1366 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1368 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1369 q("DELETE FROM item WHERE id = %d and uid = %d",
1379 // send a notification
1381 // use a local photo if we have one
1383 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1384 intval($u[0]['uid']),
1385 dbesc(normalise_link($item['author-link']))
1387 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1390 require_once('include/enotify.php');
1392 'type' => NOTIFY_TAGSELF,
1393 'notify_flags' => $u[0]['notify-flags'],
1394 'language' => $u[0]['language'],
1395 'to_name' => $u[0]['username'],
1396 'to_email' => $u[0]['email'],
1397 'uid' => $u[0]['uid'],
1399 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1400 'source_name' => $item['author-name'],
1401 'source_link' => $item['author-link'],
1402 'source_photo' => $photo,
1403 'verb' => ACTIVITY_TAG,
1408 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1410 call_hooks('tagged', $arr);
1412 if((! $community_page) && (! $prvgroup))
1416 // tgroup delivery - setup a second delivery chain
1417 // prevent delivery looping - only proceed
1418 // if the message originated elsewhere and is a top-level post
1420 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1423 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1426 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1427 intval($u[0]['uid'])
1432 // also reset all the privacy bits to the forum default permissions
1434 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1436 $forum_mode = (($prvgroup) ? 2 : 1);
1438 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1439 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1440 intval($forum_mode),
1441 dbesc($c[0]['name']),
1442 dbesc($c[0]['url']),
1443 dbesc($c[0]['thumb']),
1445 dbesc($u[0]['allow_cid']),
1446 dbesc($u[0]['allow_gid']),
1447 dbesc($u[0]['deny_cid']),
1448 dbesc($u[0]['deny_gid']),
1452 proc_run('php','include/notifier.php','tgroup',$item_id);
1458 function tgroup_check($uid,$item) {
1464 // check that the message originated elsewhere and is a top-level post
1466 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1470 $u = q("select * from user where uid = %d limit 1",
1476 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1477 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1480 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1482 // Diaspora uses their own hardwired link URL in @-tags
1483 // instead of the one we supply with webfinger
1485 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1487 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1489 foreach($matches as $mtch) {
1490 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1492 logger('tgroup_check: mention found: ' . $mtch[2]);
1500 if((! $community_page) && (! $prvgroup))
1514 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1518 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1520 if($contact['duplex'] && $contact['dfrn-id'])
1521 $idtosend = '0:' . $orig_id;
1522 if($contact['duplex'] && $contact['issued-id'])
1523 $idtosend = '1:' . $orig_id;
1525 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1527 $rino_enable = get_config('system','rino_encrypt');
1532 $ssl_val = intval(get_config('system','ssl_policy'));
1536 case SSL_POLICY_FULL:
1537 $ssl_policy = 'full';
1539 case SSL_POLICY_SELFSIGN:
1540 $ssl_policy = 'self';
1542 case SSL_POLICY_NONE:
1544 $ssl_policy = 'none';
1548 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1550 logger('dfrn_deliver: ' . $url);
1552 $xml = fetch_url($url);
1554 $curl_stat = $a->get_curl_code();
1556 return(-1); // timed out
1558 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1563 if(strpos($xml,'<?xml') === false) {
1564 logger('dfrn_deliver: no valid XML returned');
1565 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1569 $res = parse_xml_string($xml);
1571 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1572 return (($res->status) ? $res->status : 3);
1574 $postvars = array();
1575 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1576 $challenge = hex2bin((string) $res->challenge);
1577 $perm = (($res->perm) ? $res->perm : null);
1578 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1579 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1580 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1582 if($owner['page-flags'] == PAGE_PRVGROUP)
1585 $final_dfrn_id = '';
1588 if((($perm == 'rw') && (! intval($contact['writable'])))
1589 || (($perm == 'r') && (intval($contact['writable'])))) {
1590 q("update contact set writable = %d where id = %d",
1591 intval(($perm == 'rw') ? 1 : 0),
1592 intval($contact['id'])
1594 $contact['writable'] = (string) 1 - intval($contact['writable']);
1598 if(($contact['duplex'] && strlen($contact['pubkey']))
1599 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1600 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1601 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1602 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1605 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1606 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1609 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1611 if(strpos($final_dfrn_id,':') == 1)
1612 $final_dfrn_id = substr($final_dfrn_id,2);
1614 if($final_dfrn_id != $orig_id) {
1615 logger('dfrn_deliver: wrong dfrn_id.');
1616 // did not decode properly - cannot trust this site
1620 $postvars['dfrn_id'] = $idtosend;
1621 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1623 $postvars['dissolve'] = '1';
1626 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1627 $postvars['data'] = $atom;
1628 $postvars['perm'] = 'rw';
1631 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1632 $postvars['perm'] = 'r';
1635 $postvars['ssl_policy'] = $ssl_policy;
1638 $postvars['page'] = $page;
1640 if($rino && $rino_allowed && (! $dissolve)) {
1641 $key = substr(random_string(),0,16);
1642 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1643 $postvars['data'] = $data;
1644 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1647 if($dfrn_version >= 2.1) {
1648 if(($contact['duplex'] && strlen($contact['pubkey']))
1649 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1650 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1652 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1655 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1659 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1660 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1663 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1667 logger('md5 rawkey ' . md5($postvars['key']));
1669 $postvars['key'] = bin2hex($postvars['key']);
1672 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1674 $xml = post_url($contact['notify'],$postvars);
1676 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1678 $curl_stat = $a->get_curl_code();
1679 if((! $curl_stat) || (! strlen($xml)))
1680 return(-1); // timed out
1682 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1685 if(strpos($xml,'<?xml') === false) {
1686 logger('dfrn_deliver: phase 2: no valid XML returned');
1687 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1691 if($contact['term-date'] != '0000-00-00 00:00:00') {
1692 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1693 require_once('include/Contact.php');
1694 unmark_for_death($contact);
1697 $res = parse_xml_string($xml);
1699 return $res->status;
1704 This function returns true if $update has an edited timestamp newer
1705 than $existing, i.e. $update contains new data which should override
1706 what's already there. If there is no timestamp yet, the update is
1707 assumed to be newer. If the update has no timestamp, the existing
1708 item is assumed to be up-to-date. If the timestamps are equal it
1709 assumes the update has been seen before and should be ignored.
1711 function edited_timestamp_is_newer($existing, $update) {
1712 if (!x($existing,'edited') || !$existing['edited']) {
1715 if (!x($update,'edited') || !$update['edited']) {
1718 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1719 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1720 return (strcmp($existing_edited, $update_edited) < 0);
1725 * consume_feed - process atom feed and update anything/everything we might need to update
1727 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1729 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1730 * It is this person's stuff that is going to be updated.
1731 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1732 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1733 * have a contact record.
1734 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1735 * might not) try and subscribe to it.
1736 * $datedir sorts in reverse order
1737 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1738 * imported prior to its children being seen in the stream unless we are certain
1739 * of how the feed is arranged/ordered.
1740 * With $pass = 1, we only pull parent items out of the stream.
1741 * With $pass = 2, we only pull children (comments/likes).
1743 * So running this twice, first with pass 1 and then with pass 2 will do the right
1744 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1745 * model where comments can have sub-threads. That would require some massive sorting
1746 * to get all the feed items into a mostly linear ordering, and might still require
1750 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1752 require_once('library/simplepie/simplepie.inc');
1754 if(! strlen($xml)) {
1755 logger('consume_feed: empty input');
1759 $feed = new SimplePie();
1760 $feed->set_raw_data($xml);
1762 $feed->enable_order_by_date(true);
1764 $feed->enable_order_by_date(false);
1768 logger('consume_feed: Error parsing XML: ' . $feed->error());
1770 $permalink = $feed->get_permalink();
1772 // Check at the feed level for updated contact name and/or photo
1776 $photo_timestamp = '';
1780 $hubs = $feed->get_links('hub');
1781 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1784 $hub = implode(',', $hubs);
1786 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1788 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1790 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1791 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1792 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1793 $new_name = $elems['name'][0]['data'];
1795 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1796 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1797 $photo_url = $elems['link'][0]['attribs']['']['href'];
1800 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1801 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1805 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1806 logger('consume_feed: Updating photo for ' . $contact['name']);
1807 require_once("include/Photo.php");
1808 $photo_failure = false;
1809 $have_photo = false;
1811 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1812 intval($contact['id']),
1813 intval($contact['uid'])
1816 $resource_id = $r[0]['resource-id'];
1820 $resource_id = photo_new_resource();
1823 $img_str = fetch_url($photo_url,true);
1824 // guess mimetype from headers or filename
1825 $type = guess_image_type($photo_url,true);
1828 $img = new Photo($img_str, $type);
1829 if($img->is_valid()) {
1831 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1832 dbesc($resource_id),
1833 intval($contact['id']),
1834 intval($contact['uid'])
1838 $img->scaleImageSquare(175);
1840 $hash = $resource_id;
1841 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1843 $img->scaleImage(80);
1844 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1846 $img->scaleImage(48);
1847 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1851 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1852 WHERE `uid` = %d AND `id` = %d",
1853 dbesc(datetime_convert()),
1854 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1855 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1856 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1857 intval($contact['uid']),
1858 intval($contact['id'])
1863 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1864 $r = q("select * from contact where uid = %d and id = %d limit 1",
1865 intval($contact['uid']),
1866 intval($contact['id'])
1869 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1870 dbesc(notags(trim($new_name))),
1871 dbesc(datetime_convert()),
1872 intval($contact['uid']),
1873 intval($contact['id'])
1876 // do our best to update the name on content items
1879 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1880 dbesc(notags(trim($new_name))),
1881 dbesc($r[0]['name']),
1882 dbesc($r[0]['url']),
1883 intval($contact['uid'])
1888 if(strlen($birthday)) {
1889 if(substr($birthday,0,4) != $contact['bdyear']) {
1890 logger('consume_feed: updating birthday: ' . $birthday);
1894 * Add new birthday event for this person
1896 * $bdtext is just a readable placeholder in case the event is shared
1897 * with others. We will replace it during presentation to our $importer
1898 * to contain a sparkle link and perhaps a photo.
1902 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1903 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1906 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1907 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1908 intval($contact['uid']),
1909 intval($contact['id']),
1910 dbesc(datetime_convert()),
1911 dbesc(datetime_convert()),
1912 dbesc(datetime_convert('UTC','UTC', $birthday)),
1913 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1922 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1923 dbesc(substr($birthday,0,4)),
1924 intval($contact['uid']),
1925 intval($contact['id'])
1928 // This function is called twice without reloading the contact
1929 // Make sure we only create one event. This is why &$contact
1930 // is a reference var in this function
1932 $contact['bdyear'] = substr($birthday,0,4);
1937 $community_page = 0;
1938 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1940 $community_page = intval($rawtags[0]['data']);
1942 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1943 q("update contact set forum = %d where id = %d",
1944 intval($community_page),
1945 intval($contact['id'])
1947 $contact['forum'] = (string) $community_page;
1951 // process any deleted entries
1953 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1954 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1955 foreach($del_entries as $dentry) {
1957 if(isset($dentry['attribs']['']['ref'])) {
1958 $uri = $dentry['attribs']['']['ref'];
1960 if(isset($dentry['attribs']['']['when'])) {
1961 $when = $dentry['attribs']['']['when'];
1962 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1965 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1967 if($deleted && is_array($contact)) {
1968 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id`
1969 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1971 intval($importer['uid']),
1972 intval($contact['id'])
1977 if(! $item['deleted'])
1978 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1980 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1981 $xo = parse_xml_string($item['object'],false);
1982 $xt = parse_xml_string($item['target'],false);
1983 if($xt->type === ACTIVITY_OBJ_NOTE) {
1984 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1986 intval($importer['importer_uid'])
1990 // For tags, the owner cannot remove the tag on the author's copy of the post.
1992 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1993 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1994 $author_copy = (($item['origin']) ? true : false);
1996 if($owner_remove && $author_copy)
1998 if($author_remove || $owner_remove) {
1999 $tags = explode(',',$i[0]['tag']);
2002 foreach($tags as $tag)
2003 if(trim($tag) !== trim($xo->body))
2004 $newtags[] = trim($tag);
2006 q("update item set tag = '%s' where id = %d",
2007 dbesc(implode(',',$newtags)),
2010 create_tags_from_item($i[0]['id']);
2016 if($item['uri'] == $item['parent-uri']) {
2017 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2018 `body` = '', `title` = ''
2019 WHERE `parent-uri` = '%s' AND `uid` = %d",
2021 dbesc(datetime_convert()),
2022 dbesc($item['uri']),
2023 intval($importer['uid'])
2025 create_tags_from_itemuri($item['uri'], $importer['uid']);
2028 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2029 `body` = '', `title` = ''
2030 WHERE `uri` = '%s' AND `uid` = %d",
2032 dbesc(datetime_convert()),
2034 intval($importer['uid'])
2036 create_tags_from_itemuri($uri, $importer['uid']);
2037 if($item['last-child']) {
2038 // ensure that last-child is set in case the comment that had it just got wiped.
2039 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2040 dbesc(datetime_convert()),
2041 dbesc($item['parent-uri']),
2042 intval($item['uid'])
2044 // who is the last child now?
2045 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2046 ORDER BY `created` DESC LIMIT 1",
2047 dbesc($item['parent-uri']),
2048 intval($importer['uid'])
2051 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2062 // Now process the feed
2064 if($feed->get_item_quantity()) {
2066 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2068 // in inverse date order
2070 $items = array_reverse($feed->get_items());
2072 $items = $feed->get_items();
2075 foreach($items as $item) {
2078 $item_id = $item->get_id();
2079 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2080 if(isset($rawthread[0]['attribs']['']['ref'])) {
2082 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2085 if(($is_reply) && is_array($contact)) {
2090 // not allowed to post
2092 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2096 // Have we seen it? If not, import it.
2098 $item_id = $item->get_id();
2099 $datarray = get_atom_elements($feed, $item, $contact);
2101 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2102 $datarray['author-name'] = $contact['name'];
2103 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2104 $datarray['author-link'] = $contact['url'];
2105 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2106 $datarray['author-avatar'] = $contact['thumb'];
2108 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2109 logger('consume_feed: no author information! ' . print_r($datarray,true));
2113 $force_parent = false;
2114 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2115 if($contact['network'] === NETWORK_OSTATUS)
2116 $force_parent = true;
2117 if(strlen($datarray['title']))
2118 unset($datarray['title']);
2119 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2120 dbesc(datetime_convert()),
2122 intval($importer['uid'])
2124 $datarray['last-child'] = 1;
2128 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2130 intval($importer['uid'])
2133 // Update content if 'updated' changes
2136 if (edited_timestamp_is_newer($r[0], $datarray)) {
2138 // do not accept (ignore) an earlier edit than one we currently have.
2139 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2142 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2143 dbesc($datarray['title']),
2144 dbesc($datarray['body']),
2145 dbesc($datarray['tag']),
2146 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2148 intval($importer['uid'])
2150 create_tags_from_itemuri($item_id, $importer['uid']);
2153 // update last-child if it changes
2155 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2156 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2157 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2158 dbesc(datetime_convert()),
2160 intval($importer['uid'])
2162 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2163 intval($allow[0]['data']),
2164 dbesc(datetime_convert()),
2166 intval($importer['uid'])
2173 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2174 // one way feed - no remote comment ability
2175 $datarray['last-child'] = 0;
2177 $datarray['parent-uri'] = $parent_uri;
2178 $datarray['uid'] = $importer['uid'];
2179 $datarray['contact-id'] = $contact['id'];
2180 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2181 $datarray['type'] = 'activity';
2182 $datarray['gravity'] = GRAVITY_LIKE;
2183 // only one like or dislike per person
2184 // splitted into two queries for performance issues
2185 $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",
2186 intval($datarray['uid']),
2187 intval($datarray['contact-id']),
2188 dbesc($datarray['verb']),
2194 $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",
2195 intval($datarray['uid']),
2196 intval($datarray['contact-id']),
2197 dbesc($datarray['verb']),
2204 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2205 $xo = parse_xml_string($datarray['object'],false);
2206 $xt = parse_xml_string($datarray['target'],false);
2208 if($xt->type == ACTIVITY_OBJ_NOTE) {
2209 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2211 intval($importer['importer_uid'])
2216 // extract tag, if not duplicate, add to parent item
2217 if($xo->id && $xo->content) {
2218 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2219 if(! (stristr($r[0]['tag'],$newtag))) {
2220 q("UPDATE item SET tag = '%s' WHERE id = %d",
2221 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2224 create_tags_from_item($r[0]['id']);
2230 $r = item_store($datarray,$force_parent);
2236 // Head post of a conversation. Have we seen it? If not, import it.
2238 $item_id = $item->get_id();
2240 $datarray = get_atom_elements($feed, $item, $contact);
2242 if(is_array($contact)) {
2243 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2244 $datarray['author-name'] = $contact['name'];
2245 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2246 $datarray['author-link'] = $contact['url'];
2247 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2248 $datarray['author-avatar'] = $contact['thumb'];
2251 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2252 logger('consume_feed: no author information! ' . print_r($datarray,true));
2256 // special handling for events
2258 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2259 $ev = bbtoevent($datarray['body']);
2260 if(x($ev,'desc') && x($ev,'start')) {
2261 $ev['uid'] = $importer['uid'];
2262 $ev['uri'] = $item_id;
2263 $ev['edited'] = $datarray['edited'];
2264 $ev['private'] = $datarray['private'];
2266 if(is_array($contact))
2267 $ev['cid'] = $contact['id'];
2268 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2270 intval($importer['uid'])
2273 $ev['id'] = $r[0]['id'];
2274 $xyz = event_store($ev);
2279 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2280 if(strlen($datarray['title']))
2281 unset($datarray['title']);
2282 $datarray['last-child'] = 1;
2286 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2288 intval($importer['uid'])
2291 // Update content if 'updated' changes
2294 if (edited_timestamp_is_newer($r[0], $datarray)) {
2296 // do not accept (ignore) an earlier edit than one we currently have.
2297 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2300 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2301 dbesc($datarray['title']),
2302 dbesc($datarray['body']),
2303 dbesc($datarray['tag']),
2304 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2306 intval($importer['uid'])
2308 create_tags_from_itemuri($item_id, $importer['uid']);
2311 // update last-child if it changes
2313 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2314 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2315 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2316 intval($allow[0]['data']),
2317 dbesc(datetime_convert()),
2319 intval($importer['uid'])
2325 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2326 logger('consume-feed: New follower');
2327 new_follower($importer,$contact,$datarray,$item);
2330 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2331 lose_follower($importer,$contact,$datarray,$item);
2335 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2336 logger('consume-feed: New friend request');
2337 new_follower($importer,$contact,$datarray,$item,true);
2340 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2341 lose_sharer($importer,$contact,$datarray,$item);
2346 if(! is_array($contact))
2350 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2351 // one way feed - no remote comment ability
2352 $datarray['last-child'] = 0;
2354 if($contact['network'] === NETWORK_FEED)
2355 $datarray['private'] = 2;
2357 // This is my contact on another system, but it's really me.
2358 // Turn this into a wall post.
2360 if($contact['remote_self']) {
2361 $datarray['wall'] = 1;
2362 if($contact['network'] === NETWORK_FEED) {
2363 $datarray['private'] = 0;
2367 $datarray['parent-uri'] = $item_id;
2368 $datarray['uid'] = $importer['uid'];
2369 $datarray['contact-id'] = $contact['id'];
2371 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2372 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2373 // but otherwise there's a possible data mixup on the sender's system.
2374 // the tgroup delivery code called from item_store will correct it if it's a forum,
2375 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2376 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2377 $datarray['owner-name'] = $contact['name'];
2378 $datarray['owner-link'] = $contact['url'];
2379 $datarray['owner-avatar'] = $contact['thumb'];
2382 // We've allowed "followers" to reach this point so we can decide if they are
2383 // posting an @-tag delivery, which followers are allowed to do for certain
2384 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2386 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2390 $r = item_store($datarray);
2398 function local_delivery($importer,$data) {
2401 logger(__function__, LOGGER_TRACE);
2403 if($importer['readonly']) {
2404 // We aren't receiving stuff from this person. But we will quietly ignore them
2405 // rather than a blatant "go away" message.
2406 logger('local_delivery: ignoring');
2411 // Consume notification feed. This may differ from consuming a public feed in several ways
2412 // - might contain email or friend suggestions
2413 // - might contain remote followup to our message
2414 // - in which case we need to accept it and then notify other conversants
2415 // - we may need to send various email notifications
2417 $feed = new SimplePie();
2418 $feed->set_raw_data($data);
2419 $feed->enable_order_by_date(false);
2424 logger('local_delivery: Error parsing XML: ' . $feed->error());
2427 // Check at the feed level for updated contact name and/or photo
2431 $photo_timestamp = '';
2435 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2437 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2439 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2442 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2443 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2444 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2445 $new_name = $elems['name'][0]['data'];
2447 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2448 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2449 $photo_url = $elems['link'][0]['attribs']['']['href'];
2453 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2454 logger('local_delivery: Updating photo for ' . $importer['name']);
2455 require_once("include/Photo.php");
2456 $photo_failure = false;
2457 $have_photo = false;
2459 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2460 intval($importer['id']),
2461 intval($importer['importer_uid'])
2464 $resource_id = $r[0]['resource-id'];
2468 $resource_id = photo_new_resource();
2471 $img_str = fetch_url($photo_url,true);
2472 // guess mimetype from headers or filename
2473 $type = guess_image_type($photo_url,true);
2476 $img = new Photo($img_str, $type);
2477 if($img->is_valid()) {
2479 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2480 dbesc($resource_id),
2481 intval($importer['id']),
2482 intval($importer['importer_uid'])
2486 $img->scaleImageSquare(175);
2488 $hash = $resource_id;
2489 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2491 $img->scaleImage(80);
2492 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2494 $img->scaleImage(48);
2495 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2499 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2500 WHERE `uid` = %d AND `id` = %d",
2501 dbesc(datetime_convert()),
2502 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2503 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2504 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2505 intval($importer['importer_uid']),
2506 intval($importer['id'])
2511 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2512 $r = q("select * from contact where uid = %d and id = %d limit 1",
2513 intval($importer['importer_uid']),
2514 intval($importer['id'])
2517 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2518 dbesc(notags(trim($new_name))),
2519 dbesc(datetime_convert()),
2520 intval($importer['importer_uid']),
2521 intval($importer['id'])
2524 // do our best to update the name on content items
2527 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2528 dbesc(notags(trim($new_name))),
2529 dbesc($r[0]['name']),
2530 dbesc($r[0]['url']),
2531 intval($importer['importer_uid'])
2538 // Currently unsupported - needs a lot of work
2539 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2540 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2541 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2543 $newloc['uid'] = $importer['importer_uid'];
2544 $newloc['cid'] = $importer['id'];
2545 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2546 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2547 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2548 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2549 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2550 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2551 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2552 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2553 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2554 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2555 /** relocated user must have original key pair */
2556 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2557 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2559 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2562 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2563 intval($importer['id']),
2564 intval($importer['importer_uid']));
2569 $x = q("UPDATE contact SET
2579 `site-pubkey` = '%s'
2580 WHERE id=%d AND uid=%d;",
2581 dbesc($newloc['name']),
2582 dbesc($newloc['photo']),
2583 dbesc($newloc['thumb']),
2584 dbesc($newloc['micro']),
2585 dbesc($newloc['url']),
2586 dbesc($newloc['request']),
2587 dbesc($newloc['confirm']),
2588 dbesc($newloc['notify']),
2589 dbesc($newloc['poll']),
2590 dbesc($newloc['sitepubkey']),
2591 intval($importer['id']),
2592 intval($importer['importer_uid']));
2598 'owner-link' => array($old['url'], $newloc['url']),
2599 'author-link' => array($old['url'], $newloc['url']),
2600 'owner-avatar' => array($old['photo'], $newloc['photo']),
2601 'author-avatar' => array($old['photo'], $newloc['photo']),
2603 foreach ($fields as $n=>$f){
2604 $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2607 intval($importer['importer_uid']));
2613 // merge with current record, current contents have priority
2614 // update record, set url-updated
2615 // update profile photos
2621 // handle friend suggestion notification
2623 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2624 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2625 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2627 $fsugg['uid'] = $importer['importer_uid'];
2628 $fsugg['cid'] = $importer['id'];
2629 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2630 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2631 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2632 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2633 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2635 // Does our member already have a friend matching this description?
2637 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2638 dbesc($fsugg['name']),
2639 dbesc(normalise_link($fsugg['url'])),
2640 intval($fsugg['uid'])
2645 // Do we already have an fcontact record for this person?
2648 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2649 dbesc($fsugg['url']),
2650 dbesc($fsugg['name']),
2651 dbesc($fsugg['request'])
2656 // OK, we do. Do we already have an introduction for this person ?
2657 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2658 intval($fsugg['uid']),
2665 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2666 dbesc($fsugg['name']),
2667 dbesc($fsugg['url']),
2668 dbesc($fsugg['photo']),
2669 dbesc($fsugg['request'])
2671 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2672 dbesc($fsugg['url']),
2673 dbesc($fsugg['name']),
2674 dbesc($fsugg['request'])
2679 // database record did not get created. Quietly give up.
2684 $hash = random_string();
2686 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2687 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2688 intval($fsugg['uid']),
2690 intval($fsugg['cid']),
2691 dbesc($fsugg['body']),
2693 dbesc(datetime_convert()),
2698 'type' => NOTIFY_SUGGEST,
2699 'notify_flags' => $importer['notify-flags'],
2700 'language' => $importer['language'],
2701 'to_name' => $importer['username'],
2702 'to_email' => $importer['email'],
2703 'uid' => $importer['importer_uid'],
2705 'link' => $a->get_baseurl() . '/notifications/intros',
2706 'source_name' => $importer['name'],
2707 'source_link' => $importer['url'],
2708 'source_photo' => $importer['photo'],
2709 'verb' => ACTIVITY_REQ_FRIEND,
2718 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2719 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2721 logger('local_delivery: private message received');
2724 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2727 $msg['uid'] = $importer['importer_uid'];
2728 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2729 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2730 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2731 $msg['contact-id'] = $importer['id'];
2732 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2733 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2735 $msg['replied'] = 0;
2736 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2737 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2738 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2742 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2743 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2745 // send notifications.
2747 require_once('include/enotify.php');
2749 $notif_params = array(
2750 'type' => NOTIFY_MAIL,
2751 'notify_flags' => $importer['notify-flags'],
2752 'language' => $importer['language'],
2753 'to_name' => $importer['username'],
2754 'to_email' => $importer['email'],
2755 'uid' => $importer['importer_uid'],
2757 'source_name' => $msg['from-name'],
2758 'source_link' => $importer['url'],
2759 'source_photo' => $importer['thumb'],
2760 'verb' => ACTIVITY_POST,
2764 notification($notif_params);
2770 $community_page = 0;
2771 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2773 $community_page = intval($rawtags[0]['data']);
2775 if(intval($importer['forum']) != $community_page) {
2776 q("update contact set forum = %d where id = %d",
2777 intval($community_page),
2778 intval($importer['id'])
2780 $importer['forum'] = (string) $community_page;
2783 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2785 // process any deleted entries
2787 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2788 if(is_array($del_entries) && count($del_entries)) {
2789 foreach($del_entries as $dentry) {
2791 if(isset($dentry['attribs']['']['ref'])) {
2792 $uri = $dentry['attribs']['']['ref'];
2794 if(isset($dentry['attribs']['']['when'])) {
2795 $when = $dentry['attribs']['']['when'];
2796 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2799 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2803 // check for relayed deletes to our conversation
2806 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2808 intval($importer['importer_uid'])
2811 $parent_uri = $r[0]['parent-uri'];
2812 if($r[0]['id'] != $r[0]['parent'])
2819 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2822 logger('local_delivery: possible community delete');
2825 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2827 // was the top-level post for this reply written by somebody on this site?
2828 // Specifically, the recipient?
2830 $is_a_remote_delete = false;
2832 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2833 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2834 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2835 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2836 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2837 AND `item`.`uid` = %d
2843 intval($importer['importer_uid'])
2846 $is_a_remote_delete = true;
2848 // Does this have the characteristics of a community or private group comment?
2849 // If it's a reply to a wall post on a community/prvgroup page it's a
2850 // valid community comment. Also forum_mode makes it valid for sure.
2851 // If neither, it's not.
2853 if($is_a_remote_delete && $community) {
2854 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2855 $is_a_remote_delete = false;
2856 logger('local_delivery: not a community delete');
2860 if($is_a_remote_delete) {
2861 logger('local_delivery: received remote delete');
2865 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2866 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2868 intval($importer['importer_uid']),
2869 intval($importer['id'])
2875 if($item['deleted'])
2878 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2880 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2881 $xo = parse_xml_string($item['object'],false);
2882 $xt = parse_xml_string($item['target'],false);
2884 if($xt->type === ACTIVITY_OBJ_NOTE) {
2885 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2887 intval($importer['importer_uid'])
2891 // For tags, the owner cannot remove the tag on the author's copy of the post.
2893 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2894 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2895 $author_copy = (($item['origin']) ? true : false);
2897 if($owner_remove && $author_copy)
2899 if($author_remove || $owner_remove) {
2900 $tags = explode(',',$i[0]['tag']);
2903 foreach($tags as $tag)
2904 if(trim($tag) !== trim($xo->body))
2905 $newtags[] = trim($tag);
2907 q("update item set tag = '%s' where id = %d",
2908 dbesc(implode(',',$newtags)),
2911 create_tags_from_item($i[0]['id']);
2917 if($item['uri'] == $item['parent-uri']) {
2918 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2919 `body` = '', `title` = ''
2920 WHERE `parent-uri` = '%s' AND `uid` = %d",
2922 dbesc(datetime_convert()),
2923 dbesc($item['uri']),
2924 intval($importer['importer_uid'])
2926 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2929 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2930 `body` = '', `title` = ''
2931 WHERE `uri` = '%s' AND `uid` = %d",
2933 dbesc(datetime_convert()),
2935 intval($importer['importer_uid'])
2937 create_tags_from_itemuri($uri, $importer['importer_uid']);
2938 if($item['last-child']) {
2939 // ensure that last-child is set in case the comment that had it just got wiped.
2940 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2941 dbesc(datetime_convert()),
2942 dbesc($item['parent-uri']),
2943 intval($item['uid'])
2945 // who is the last child now?
2946 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2947 ORDER BY `created` DESC LIMIT 1",
2948 dbesc($item['parent-uri']),
2949 intval($importer['importer_uid'])
2952 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2957 // if this is a relayed delete, propagate it to other recipients
2959 if($is_a_remote_delete)
2960 proc_run('php',"include/notifier.php","drop",$item['id']);
2968 foreach($feed->get_items() as $item) {
2971 $item_id = $item->get_id();
2972 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2973 if(isset($rawthread[0]['attribs']['']['ref'])) {
2975 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2981 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2984 logger('local_delivery: possible community reply');
2987 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2989 // was the top-level post for this reply written by somebody on this site?
2990 // Specifically, the recipient?
2992 $is_a_remote_comment = false;
2993 $top_uri = $parent_uri;
2995 $r = q("select `item`.`parent-uri` from `item`
2996 WHERE `item`.`uri` = '%s'
3000 if($r && count($r)) {
3001 $top_uri = $r[0]['parent-uri'];
3003 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3004 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3005 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3006 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3007 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3008 AND `item`.`uid` = %d
3014 intval($importer['importer_uid'])
3017 $is_a_remote_comment = true;
3020 // Does this have the characteristics of a community or private group comment?
3021 // If it's a reply to a wall post on a community/prvgroup page it's a
3022 // valid community comment. Also forum_mode makes it valid for sure.
3023 // If neither, it's not.
3025 if($is_a_remote_comment && $community) {
3026 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3027 $is_a_remote_comment = false;
3028 logger('local_delivery: not a community reply');
3032 if($is_a_remote_comment) {
3033 logger('local_delivery: received remote comment');
3035 // remote reply to our post. Import and then notify everybody else.
3037 $datarray = get_atom_elements($feed, $item);
3039 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3041 intval($importer['importer_uid'])
3044 // Update content if 'updated' changes
3048 if (edited_timestamp_is_newer($r[0], $datarray)) {
3050 // do not accept (ignore) an earlier edit than one we currently have.
3051 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3054 logger('received updated comment' , LOGGER_DEBUG);
3055 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3056 dbesc($datarray['title']),
3057 dbesc($datarray['body']),
3058 dbesc($datarray['tag']),
3059 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3061 intval($importer['importer_uid'])
3063 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3065 proc_run('php',"include/notifier.php","comment-import",$iid);
3074 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3075 intval($importer['importer_uid'])
3079 $datarray['type'] = 'remote-comment';
3080 $datarray['wall'] = 1;
3081 $datarray['parent-uri'] = $parent_uri;
3082 $datarray['uid'] = $importer['importer_uid'];
3083 $datarray['owner-name'] = $own[0]['name'];
3084 $datarray['owner-link'] = $own[0]['url'];
3085 $datarray['owner-avatar'] = $own[0]['thumb'];
3086 $datarray['contact-id'] = $importer['id'];
3088 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3090 $datarray['type'] = 'activity';
3091 $datarray['gravity'] = GRAVITY_LIKE;
3092 $datarray['last-child'] = 0;
3093 // only one like or dislike per person
3094 // splitted into two queries for performance issues
3095 $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",
3096 intval($datarray['uid']),
3097 intval($datarray['contact-id']),
3098 dbesc($datarray['verb']),
3099 dbesc($datarray['parent-uri'])
3105 $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",
3106 intval($datarray['uid']),
3107 intval($datarray['contact-id']),
3108 dbesc($datarray['verb']),
3109 dbesc($datarray['parent-uri'])
3116 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3118 $xo = parse_xml_string($datarray['object'],false);
3119 $xt = parse_xml_string($datarray['target'],false);
3121 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3123 // fetch the parent item
3125 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3127 intval($importer['importer_uid'])
3132 // extract tag, if not duplicate, and this user allows tags, add to parent item
3134 if($xo->id && $xo->content) {
3135 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3136 if(! (stristr($tagp[0]['tag'],$newtag))) {
3137 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3138 intval($importer['importer_uid'])
3140 if(count($i) && ! intval($i[0]['blocktags'])) {
3141 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d",
3142 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3143 intval($tagp[0]['id']),
3144 dbesc(datetime_convert())
3146 create_tags_from_item($tagp[0]['id']);
3154 $posted_id = item_store($datarray);
3158 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3160 intval($importer['importer_uid'])
3163 $parent = $r[0]['parent'];
3164 $parent_uri = $r[0]['parent-uri'];
3168 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3169 dbesc(datetime_convert()),
3170 intval($importer['importer_uid']),
3171 intval($r[0]['parent'])
3174 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3175 dbesc(datetime_convert()),
3176 intval($importer['importer_uid']),
3181 if($posted_id && $parent) {
3183 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3185 if((! $is_like) && (! $importer['self'])) {
3187 require_once('include/enotify.php');
3190 'type' => NOTIFY_COMMENT,
3191 'notify_flags' => $importer['notify-flags'],
3192 'language' => $importer['language'],
3193 'to_name' => $importer['username'],
3194 'to_email' => $importer['email'],
3195 'uid' => $importer['importer_uid'],
3196 'item' => $datarray,
3197 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3198 'source_name' => stripslashes($datarray['author-name']),
3199 'source_link' => $datarray['author-link'],
3200 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3201 ? $importer['thumb'] : $datarray['author-avatar']),
3202 'verb' => ACTIVITY_POST,
3204 'parent' => $parent,
3205 'parent_uri' => $parent_uri,
3217 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3219 $item_id = $item->get_id();
3220 $datarray = get_atom_elements($feed,$item);
3222 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3225 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3227 intval($importer['importer_uid'])
3230 // Update content if 'updated' changes
3233 if (edited_timestamp_is_newer($r[0], $datarray)) {
3235 // do not accept (ignore) an earlier edit than one we currently have.
3236 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3239 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3240 dbesc($datarray['title']),
3241 dbesc($datarray['body']),
3242 dbesc($datarray['tag']),
3243 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3245 intval($importer['importer_uid'])
3247 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3250 // update last-child if it changes
3252 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3253 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3254 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3255 dbesc(datetime_convert()),
3257 intval($importer['importer_uid'])
3259 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3260 intval($allow[0]['data']),
3261 dbesc(datetime_convert()),
3263 intval($importer['importer_uid'])
3269 $datarray['parent-uri'] = $parent_uri;
3270 $datarray['uid'] = $importer['importer_uid'];
3271 $datarray['contact-id'] = $importer['id'];
3272 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3273 $datarray['type'] = 'activity';
3274 $datarray['gravity'] = GRAVITY_LIKE;
3275 // only one like or dislike per person
3276 // splitted into two queries for performance issues
3277 $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",
3278 intval($datarray['uid']),
3279 intval($datarray['contact-id']),
3280 dbesc($datarray['verb']),
3286 $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",
3287 intval($datarray['uid']),
3288 intval($datarray['contact-id']),
3289 dbesc($datarray['verb']),
3297 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3299 $xo = parse_xml_string($datarray['object'],false);
3300 $xt = parse_xml_string($datarray['target'],false);
3302 if($xt->type == ACTIVITY_OBJ_NOTE) {
3303 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3305 intval($importer['importer_uid'])
3310 // extract tag, if not duplicate, add to parent item
3312 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3313 q("UPDATE item SET tag = '%s' WHERE id = %d",
3314 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3317 create_tags_from_item($r[0]['id']);
3323 $posted_id = item_store($datarray);
3325 // find out if our user is involved in this conversation and wants to be notified.
3327 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3329 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3331 intval($importer['importer_uid'])
3334 if(count($myconv)) {
3335 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3337 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3338 if(! link_compare($datarray['author-link'],$importer_url)) {
3341 foreach($myconv as $conv) {
3343 // now if we find a match, it means we're in this conversation
3345 if(! link_compare($conv['author-link'],$importer_url))
3348 require_once('include/enotify.php');
3350 $conv_parent = $conv['parent'];
3353 'type' => NOTIFY_COMMENT,
3354 'notify_flags' => $importer['notify-flags'],
3355 'language' => $importer['language'],
3356 'to_name' => $importer['username'],
3357 'to_email' => $importer['email'],
3358 'uid' => $importer['importer_uid'],
3359 'item' => $datarray,
3360 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3361 'source_name' => stripslashes($datarray['author-name']),
3362 'source_link' => $datarray['author-link'],
3363 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3364 ? $importer['thumb'] : $datarray['author-avatar']),
3365 'verb' => ACTIVITY_POST,
3367 'parent' => $conv_parent,
3368 'parent_uri' => $parent_uri
3372 // only send one notification
3384 // Head post of a conversation. Have we seen it? If not, import it.
3387 $item_id = $item->get_id();
3388 $datarray = get_atom_elements($feed,$item);
3390 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3391 $ev = bbtoevent($datarray['body']);
3392 if(x($ev,'desc') && x($ev,'start')) {
3393 $ev['cid'] = $importer['id'];
3394 $ev['uid'] = $importer['uid'];
3395 $ev['uri'] = $item_id;
3396 $ev['edited'] = $datarray['edited'];
3397 $ev['private'] = $datarray['private'];
3399 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3401 intval($importer['uid'])
3404 $ev['id'] = $r[0]['id'];
3405 $xyz = event_store($ev);
3410 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3412 intval($importer['importer_uid'])
3415 // Update content if 'updated' changes
3418 if (edited_timestamp_is_newer($r[0], $datarray)) {
3420 // do not accept (ignore) an earlier edit than one we currently have.
3421 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3424 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3425 dbesc($datarray['title']),
3426 dbesc($datarray['body']),
3427 dbesc($datarray['tag']),
3428 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3430 intval($importer['importer_uid'])
3432 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3435 // update last-child if it changes
3437 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3438 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3439 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3440 intval($allow[0]['data']),
3441 dbesc(datetime_convert()),
3443 intval($importer['importer_uid'])
3449 // This is my contact on another system, but it's really me.
3450 // Turn this into a wall post.
3452 if($importer['remote_self'])
3453 $datarray['wall'] = 1;
3455 $datarray['parent-uri'] = $item_id;
3456 $datarray['uid'] = $importer['importer_uid'];
3457 $datarray['contact-id'] = $importer['id'];
3460 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3461 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3462 // but otherwise there's a possible data mixup on the sender's system.
3463 // the tgroup delivery code called from item_store will correct it if it's a forum,
3464 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3465 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3466 $datarray['owner-name'] = $importer['senderName'];
3467 $datarray['owner-link'] = $importer['url'];
3468 $datarray['owner-avatar'] = $importer['thumb'];
3471 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3474 $posted_id = item_store($datarray);
3476 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3477 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3480 $xo = parse_xml_string($datarray['object'],false);
3482 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3484 // somebody was poked/prodded. Was it me?
3486 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3488 foreach($links->link as $l) {
3489 $atts = $l->attributes();
3490 switch($atts['rel']) {
3492 $Blink = $atts['href'];
3498 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3500 // send a notification
3501 require_once('include/enotify.php');
3504 'type' => NOTIFY_POKE,
3505 'notify_flags' => $importer['notify-flags'],
3506 'language' => $importer['language'],
3507 'to_name' => $importer['username'],
3508 'to_email' => $importer['email'],
3509 'uid' => $importer['importer_uid'],
3510 'item' => $datarray,
3511 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3512 'source_name' => stripslashes($datarray['author-name']),
3513 'source_link' => $datarray['author-link'],
3514 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3515 ? $importer['thumb'] : $datarray['author-avatar']),
3516 'verb' => $datarray['verb'],
3517 'otype' => 'person',
3518 'activity' => $verb,
3535 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3536 $url = notags(trim($datarray['author-link']));
3537 $name = notags(trim($datarray['author-name']));
3538 $photo = notags(trim($datarray['author-avatar']));
3540 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3541 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3542 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3544 if(is_array($contact)) {
3545 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3546 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3547 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3548 intval(CONTACT_IS_FRIEND),
3549 intval($contact['id']),
3550 intval($importer['uid'])
3553 // send email notification to owner?
3557 // create contact record
3559 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3560 `blocked`, `readonly`, `pending`, `writable` )
3561 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3562 intval($importer['uid']),
3563 dbesc(datetime_convert()),
3565 dbesc(normalise_link($url)),
3569 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3570 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3572 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3573 intval($importer['uid']),
3577 $contact_record = $r[0];
3579 // create notification
3580 $hash = random_string();
3582 if(is_array($contact_record)) {
3583 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3584 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3585 intval($importer['uid']),
3586 intval($contact_record['id']),
3588 dbesc(datetime_convert())
3591 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3592 intval($importer['uid'])
3597 if(intval($r[0]['def_gid'])) {
3598 require_once('include/group.php');
3599 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3602 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3603 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3604 $email = replace_macros($email_tpl, array(
3605 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3607 '$myname' => $r[0]['username'],
3608 '$siteurl' => $a->get_baseurl(),
3609 '$sitename' => $a->config['sitename']
3611 $res = mail($r[0]['email'],
3612 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'),
3614 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3615 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3616 . 'Content-transfer-encoding: 8bit' );
3623 function lose_follower($importer,$contact,$datarray,$item) {
3625 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3626 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3627 intval(CONTACT_IS_SHARING),
3628 intval($contact['id'])
3632 contact_remove($contact['id']);
3636 function lose_sharer($importer,$contact,$datarray,$item) {
3638 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3639 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3640 intval(CONTACT_IS_FOLLOWER),
3641 intval($contact['id'])
3645 contact_remove($contact['id']);
3650 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3654 if(is_array($importer)) {
3655 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3656 intval($importer['uid'])
3660 // Diaspora has different message-ids in feeds than they do
3661 // through the direct Diaspora protocol. If we try and use
3662 // the feed, we'll get duplicates. So don't.
3664 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3667 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3669 // Use a single verify token, even if multiple hubs
3671 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3673 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3675 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3677 if(! strlen($contact['hub-verify'])) {
3678 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3679 dbesc($verify_token),
3680 intval($contact['id'])
3684 post_url($url,$params);
3686 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3693 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3697 $name = xmlify($name);
3698 $uri = xmlify($uri);
3701 $photo = xmlify($photo);
3705 $o .= "<name>$name</name>\r\n";
3706 $o .= "<uri>$uri</uri>\r\n";
3707 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3708 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3710 call_hooks('atom_author', $o);
3712 $o .= "</$tag>\r\n";
3716 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3720 if(! $item['parent'])
3723 if($item['deleted'])
3724 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3727 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3728 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3730 $body = $item['body'];
3732 $o = "\r\n\r\n<entry>\r\n";
3734 if(is_array($author))
3735 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3737 $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']));
3738 if(strlen($item['owner-name']))
3739 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3741 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3742 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3743 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3746 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3747 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3748 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3749 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3750 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3751 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3752 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3754 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3756 if($item['location']) {
3757 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3758 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3762 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3764 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3765 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3768 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3769 if($item['bookmark'])
3770 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3773 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3776 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3778 if($item['signed_text']) {
3779 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3780 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3783 $verb = construct_verb($item);
3784 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3785 $actobj = construct_activity_object($item);
3788 $actarg = construct_activity_target($item);
3792 $tags = item_getfeedtags($item);
3794 foreach($tags as $t) {
3795 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3799 $o .= item_getfeedattach($item);
3801 $mentioned = get_mentions($item);
3805 call_hooks('atom_entry', $o);
3807 $o .= '</entry>' . "\r\n";
3812 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3814 if(get_config('system','disable_embedded'))
3819 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3820 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3825 $img_start = strpos($orig_body, '[img');
3826 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3827 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3828 while( ($img_st_close !== false) && ($img_len !== false) ) {
3830 $img_st_close++; // make it point to AFTER the closing bracket
3831 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3833 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3836 if(stristr($image , $site . '/photo/')) {
3837 // Only embed locally hosted photos
3839 $i = basename($image);
3840 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3841 $x = strpos($i,'-');
3844 $res = substr($i,$x+1);
3845 $i = substr($i,0,$x);
3846 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3853 // Check to see if we should replace this photo link with an embedded image
3854 // 1. No need to do so if the photo is public
3855 // 2. If there's a contact-id provided, see if they're in the access list
3856 // for the photo. If so, embed it.
3857 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3858 // permissions, regardless of order but first check to see if they're an exact
3859 // match to save some processing overhead.
3861 if(has_permissions($r[0])) {
3863 $recips = enumerate_permissions($r[0]);
3864 if(in_array($cid, $recips)) {
3869 if(compare_permissions($item,$r[0]))
3874 $data = $r[0]['data'];
3875 $type = $r[0]['type'];
3877 // If a custom width and height were specified, apply before embedding
3878 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3879 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3881 $width = intval($match[1]);
3882 $height = intval($match[2]);
3884 $ph = new Photo($data, $type);
3885 if($ph->is_valid()) {
3886 $ph->scaleImage(max($width, $height));
3887 $data = $ph->imageString();
3888 $type = $ph->getType();
3892 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3893 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3894 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3900 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3901 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3902 if($orig_body === false)
3905 $img_start = strpos($orig_body, '[img');
3906 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3907 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3910 $new_body = $new_body . $orig_body;
3916 function has_permissions($obj) {
3917 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3922 function compare_permissions($obj1,$obj2) {
3923 // first part is easy. Check that these are exactly the same.
3924 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3925 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3926 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3927 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3930 // This is harder. Parse all the permissions and compare the resulting set.
3932 $recipients1 = enumerate_permissions($obj1);
3933 $recipients2 = enumerate_permissions($obj2);
3936 if($recipients1 == $recipients2)
3941 // returns an array of contact-ids that are allowed to see this object
3943 function enumerate_permissions($obj) {
3944 require_once('include/group.php');
3945 $allow_people = expand_acl($obj['allow_cid']);
3946 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3947 $deny_people = expand_acl($obj['deny_cid']);
3948 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3949 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3950 $deny = array_unique(array_merge($deny_people,$deny_groups));
3951 $recipients = array_diff($recipients,$deny);
3955 function item_getfeedtags($item) {
3958 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3960 for($x = 0; $x < $cnt; $x ++) {
3962 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3966 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3968 for($x = 0; $x < $cnt; $x ++) {
3970 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3976 function item_getfeedattach($item) {
3978 $arr = explode('[/attach],',$item['attach']);
3980 foreach($arr as $r) {
3982 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3984 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3985 if(intval($matches[2]))
3986 $ret .= 'length="' . intval($matches[2]) . '" ';
3987 if($matches[4] !== ' ')
3988 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3989 $ret .= ' />' . "\r\n";
3998 function item_expire($uid,$days) {
4000 if((! $uid) || ($days < 1))
4003 // $expire_network_only = save your own wall posts
4004 // and just expire conversations started by others
4006 $expire_network_only = get_pconfig($uid,'expire','network_only');
4007 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4009 $r = q("SELECT * FROM `item`
4011 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4022 $expire_items = get_pconfig($uid, 'expire','items');
4023 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4025 $expire_notes = get_pconfig($uid, 'expire','notes');
4026 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4028 $expire_starred = get_pconfig($uid, 'expire','starred');
4029 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4031 $expire_photos = get_pconfig($uid, 'expire','photos');
4032 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4034 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4036 foreach($r as $item) {
4038 // don't expire filed items
4040 if(strpos($item['file'],'[') !== false)
4043 // Only expire posts, not photos and photo comments
4045 if($expire_photos==0 && strlen($item['resource-id']))
4047 if($expire_starred==0 && intval($item['starred']))
4049 if($expire_notes==0 && $item['type']=='note')
4051 if($expire_items==0 && $item['type']!='note')
4054 drop_item($item['id'],false);
4057 proc_run('php',"include/notifier.php","expire","$uid");
4062 function drop_items($items) {
4065 if(! local_user() && ! remote_user())
4069 foreach($items as $item) {
4070 $owner = drop_item($item,false);
4071 if($owner && ! $uid)
4076 // multiple threads may have been deleted, send an expire notification
4079 proc_run('php',"include/notifier.php","expire","$uid");
4083 function drop_item($id,$interactive = true) {
4087 // locate item to be deleted
4089 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4096 notice( t('Item not found.') . EOL);
4097 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4102 $owner = $item['uid'];
4106 // check if logged in user is either the author or owner of this item
4108 if(is_array($_SESSION['remote'])) {
4109 foreach($_SESSION['remote'] as $visitor) {
4110 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4111 $cid = $visitor['cid'];
4118 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4120 // Check if we should do HTML-based delete confirmation
4121 if($_REQUEST['confirm']) {
4122 // <form> can't take arguments in its "action" parameter
4123 // so add any arguments as hidden inputs
4124 $query = explode_querystring($a->query_string);
4126 foreach($query['args'] as $arg) {
4127 if(strpos($arg, 'confirm=') === false) {
4128 $arg_parts = explode('=', $arg);
4129 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4133 return replace_macros(get_markup_template('confirm.tpl'), array(
4135 '$message' => t('Do you really want to delete this item?'),
4136 '$extra_inputs' => $inputs,
4137 '$confirm' => t('Yes'),
4138 '$confirm_url' => $query['base'],
4139 '$confirm_name' => 'confirmed',
4140 '$cancel' => t('Cancel'),
4143 // Now check how the user responded to the confirmation query
4144 if($_REQUEST['canceled']) {
4145 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4148 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4151 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4152 dbesc(datetime_convert()),
4153 dbesc(datetime_convert()),
4156 create_tags_from_item($item['id']);
4158 // clean up categories and tags so they don't end up as orphans
4161 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4163 foreach($matches as $mtch) {
4164 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4170 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4172 foreach($matches as $mtch) {
4173 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4177 // If item is a link to a photo resource, nuke all the associated photos
4178 // (visitors will not have photo resources)
4179 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4180 // generate a resource-id and therefore aren't intimately linked to the item.
4182 if(strlen($item['resource-id'])) {
4183 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4184 dbesc($item['resource-id']),
4185 intval($item['uid'])
4187 // ignore the result
4190 // If item is a link to an event, nuke the event record.
4192 if(intval($item['event-id'])) {
4193 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4194 intval($item['event-id']),
4195 intval($item['uid'])
4197 // ignore the result
4200 // clean up item_id and sign meta-data tables
4203 // Old code - caused very long queries and warning entries in the mysql logfiles:
4205 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4206 intval($item['id']),
4207 intval($item['uid'])
4210 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4211 intval($item['id']),
4212 intval($item['uid'])
4216 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4218 // Creating list of parents
4219 $r = q("select id from item where parent = %d and uid = %d",
4220 intval($item['id']),
4221 intval($item['uid'])
4226 foreach ($r AS $row) {
4227 if ($parentid != "")
4230 $parentid .= $row["id"];
4234 if ($parentid != "") {
4235 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4237 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4240 // If it's the parent of a comment thread, kill all the kids
4242 if($item['uri'] == $item['parent-uri']) {
4243 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4244 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4245 dbesc(datetime_convert()),
4246 dbesc(datetime_convert()),
4247 dbesc($item['parent-uri']),
4248 intval($item['uid'])
4250 create_tags_from_item($item['parent-uri'], $item['uid']);
4251 // ignore the result
4254 // ensure that last-child is set in case the comment that had it just got wiped.
4255 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4256 dbesc(datetime_convert()),
4257 dbesc($item['parent-uri']),
4258 intval($item['uid'])
4260 // who is the last child now?
4261 $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",
4262 dbesc($item['parent-uri']),
4263 intval($item['uid'])
4266 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4271 // Add a relayable_retraction signature for Diaspora.
4272 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4274 $drop_id = intval($item['id']);
4276 // send the notification upstream/downstream as the case may be
4278 proc_run('php',"include/notifier.php","drop","$drop_id");
4282 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4288 notice( t('Permission denied.') . EOL);
4289 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4296 function first_post_date($uid,$wall = false) {
4297 $r = q("select id, created from item
4298 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4300 order by created asc limit 1",
4302 intval($wall ? 1 : 0)
4305 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4306 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4311 function posted_dates($uid,$wall) {
4312 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4314 $dthen = first_post_date($uid,$wall);
4318 // If it's near the end of a long month, backup to the 28th so that in
4319 // consecutive loops we'll always get a whole month difference.
4321 if(intval(substr($dnow,8)) > 28)
4322 $dnow = substr($dnow,0,8) . '28';
4323 if(intval(substr($dthen,8)) > 28)
4324 $dnow = substr($dthen,0,8) . '28';
4327 // Starting with the current month, get the first and last days of every
4328 // month down to and including the month of the first post
4329 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4330 $dstart = substr($dnow,0,8) . '01';
4331 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4332 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4333 $end_month = datetime_convert('','',$dend,'Y-m-d');
4334 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4335 $ret[] = array($str,$end_month,$start_month);
4336 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4342 function posted_date_widget($url,$uid,$wall) {
4345 if(! feature_enabled($uid,'archives'))
4348 // For former Facebook folks that left because of "timeline"
4350 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4353 $ret = posted_dates($uid,$wall);
4357 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4358 '$title' => t('Archives'),
4359 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4366 function store_diaspora_retract_sig($item, $user, $baseurl) {
4367 // Note that we can't add a target_author_signature
4368 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4369 // the comment, that means we're the home of the post, and Diaspora will only
4370 // check the parent_author_signature of retractions that it doesn't have to relay further
4372 // I don't think this function gets called for an "unlike," but I'll check anyway
4374 $enabled = intval(get_config('system','diaspora_enabled'));
4376 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4380 logger('drop_item: storing diaspora retraction signature');
4382 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4384 if(local_user() == $item['uid']) {
4386 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4387 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4390 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4391 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4394 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4395 // only handles DFRN deletes
4396 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4397 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4398 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4404 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4405 intval($item['id']),
4406 dbesc($signed_text),