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/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 // removing the content of the title if its identically to the body
442 // This helps with auto generated titles e.g. from tumblr
443 if (title_is_body($res["title"], $res["body"]))
447 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
451 // look for a photo. We should check media size and find the best one,
452 // but for now let's just find any author photo
454 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
456 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
457 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
458 foreach($base as $link) {
459 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
460 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
461 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
466 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
468 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
469 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
470 if($base && count($base)) {
471 foreach($base as $link) {
472 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
473 $res['author-link'] = unxmlify($link['attribs']['']['href']);
474 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
475 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
476 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
482 // No photo/profile-link on the item - look at the feed level
484 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
485 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
486 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
487 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
488 foreach($base as $link) {
489 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
490 $res['author-link'] = unxmlify($link['attribs']['']['href']);
491 if(! $res['author-avatar']) {
492 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
493 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
498 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
500 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
501 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
503 if($base && count($base)) {
504 foreach($base as $link) {
505 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
506 $res['author-link'] = unxmlify($link['attribs']['']['href']);
507 if(! (x($res,'author-avatar'))) {
508 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
509 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
516 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
517 if($apps && $apps[0]['attribs']['']['source']) {
518 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
519 if($res['app'] === 'web')
520 $res['app'] = 'OStatus';
523 // base64 encoded json structure representing Diaspora signature
525 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
527 $res['dsprsig'] = unxmlify($dsig[0]['data']);
530 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
532 $res['guid'] = unxmlify($dguid[0]['data']);
534 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
536 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
540 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
543 $have_real_body = false;
545 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
547 $have_real_body = true;
548 $res['body'] = $rawenv[0]['data'];
549 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
550 // make sure nobody is trying to sneak some html tags by us
551 $res['body'] = notags(base64url_decode($res['body']));
555 $res['body'] = limit_body_size($res['body']);
557 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
558 // the content type. Our own network only emits text normally, though it might have been converted to
559 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
560 // have to assume it is all html and needs to be purified.
562 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
563 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
564 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
567 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
569 $res['body'] = reltoabs($res['body'],$base_url);
571 $res['body'] = html2bb_video($res['body']);
573 $res['body'] = oembed_html2bbcode($res['body']);
575 $config = HTMLPurifier_Config::createDefault();
576 $config->set('Cache.DefinitionImpl', null);
578 // we shouldn't need a whitelist, because the bbcode converter
579 // will strip out any unsupported tags.
581 $purifier = new HTMLPurifier($config);
582 $res['body'] = $purifier->purify($res['body']);
584 $res['body'] = @html2bbcode($res['body']);
588 elseif(! $have_real_body) {
590 // it's not one of our messages and it has no tags
591 // so it's probably just text. We'll escape it just to be safe.
593 $res['body'] = escape_tags($res['body']);
597 // this tag is obsolete but we keep it for really old sites
599 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
600 if($allow && $allow[0]['data'] == 1)
601 $res['last-child'] = 1;
603 $res['last-child'] = 0;
605 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
606 if($private && intval($private[0]['data']) > 0)
607 $res['private'] = intval($private[0]['data']);
611 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
612 if($extid && $extid[0]['data'])
613 $res['extid'] = $extid[0]['data'];
615 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
617 $res['location'] = unxmlify($rawlocation[0]['data']);
620 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
622 $res['created'] = unxmlify($rawcreated[0]['data']);
625 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
627 $res['edited'] = unxmlify($rawedited[0]['data']);
629 if((x($res,'edited')) && (! (x($res,'created'))))
630 $res['created'] = $res['edited'];
632 if(! $res['created'])
633 $res['created'] = $item->get_date('c');
636 $res['edited'] = $item->get_date('c');
639 // Disallow time travelling posts
641 $d1 = strtotime($res['created']);
642 $d2 = strtotime($res['edited']);
643 $d3 = strtotime('now');
646 $res['created'] = datetime_convert();
648 $res['edited'] = datetime_convert();
650 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
651 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
652 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
653 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
654 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
655 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
656 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
657 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
658 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
660 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
661 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
663 foreach($base as $link) {
664 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
665 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
666 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
671 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
673 $res['coord'] = unxmlify($rawgeo[0]['data']);
676 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
678 // select between supported verbs
681 $res['verb'] = unxmlify($rawverb[0]['data']);
684 // translate OStatus unfollow to activity streams if it happened to get selected
686 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
687 $res['verb'] = ACTIVITY_UNFOLLOW;
689 $cats = $item->get_categories();
692 foreach($cats as $cat) {
693 $term = $cat->get_term();
695 $term = $cat->get_label();
696 $scheme = $cat->get_scheme();
697 if($scheme && $term && stristr($scheme,'X-DFRN:'))
698 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
700 $tag_arr[] = notags(trim($term));
702 $res['tag'] = implode(',', $tag_arr);
705 $attach = $item->get_enclosures();
708 foreach($attach as $att) {
709 $len = intval($att->get_length());
710 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
711 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
712 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
713 if(strpos($type,';'))
714 $type = substr($type,0,strpos($type,';'));
715 if((! $link) || (strpos($link,'http') !== 0))
721 $type = 'application/octet-stream';
723 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
725 $res['attach'] = implode(',', $att_arr);
728 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
731 $res['object'] = '<object>' . "\n";
732 $child = $rawobj[0]['child'];
733 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
734 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
735 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
737 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
738 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
739 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
740 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
741 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
742 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
743 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
744 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
746 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
747 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
748 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
749 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
751 $body = html2bb_video($body);
753 $config = HTMLPurifier_Config::createDefault();
754 $config->set('Cache.DefinitionImpl', null);
756 $purifier = new HTMLPurifier($config);
757 $body = $purifier->purify($body);
758 $body = html2bbcode($body);
761 $res['object'] .= '<content>' . $body . '</content>' . "\n";
764 $res['object'] .= '</object>' . "\n";
767 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
770 $res['target'] = '<target>' . "\n";
771 $child = $rawobj[0]['child'];
772 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
773 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
775 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
776 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
777 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
778 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
779 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
780 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
781 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
782 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
784 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
785 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
786 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
787 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
789 $body = html2bb_video($body);
791 $config = HTMLPurifier_Config::createDefault();
792 $config->set('Cache.DefinitionImpl', null);
794 $purifier = new HTMLPurifier($config);
795 $body = $purifier->purify($body);
796 $body = html2bbcode($body);
799 $res['target'] .= '<content>' . $body . '</content>' . "\n";
802 $res['target'] .= '</target>' . "\n";
805 // This is some experimental stuff. By now retweets are shown with "RT:"
806 // But: There is data so that the message could be shown similar to native retweets
807 // There is some better way to parse this array - but it didn't worked for me.
808 $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"];
809 if (is_array($child)) {
810 logger('get_atom_elements: Looking for status.net repeated message');
812 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
813 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
814 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
815 $uri = $author["uri"][0]["data"];
816 $name = $author["name"][0]["data"];
817 $avatar = @array_shift($author["link"][2]["attribs"]);
818 $avatar = $avatar["href"];
820 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
821 logger('get_atom_elements: fixing sender of repeated message.');
823 if (!intval(get_config('system','wall-to-wall_share'))) {
824 $prefix = "[share author='".str_replace("'", "'",$name).
826 "' avatar='".$avatar.
827 "' link='".$orig_uri."']";
829 $res["body"] = $prefix.html2bbcode($message)."[/share]";
831 $res["owner-name"] = $res["author-name"];
832 $res["owner-link"] = $res["author-link"];
833 $res["owner-avatar"] = $res["author-avatar"];
835 $res["author-name"] = $name;
836 $res["author-link"] = $uri;
837 $res["author-avatar"] = $avatar;
839 $res["body"] = html2bbcode($message);
844 // Search for ostatus conversation url
845 $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"];
847 if (is_array($links)) {
848 foreach ($links as $link) {
849 $conversation = array_shift($link["attribs"]);
851 if ($conversation["rel"] == "ostatus:conversation") {
852 $res["ostatus_conversation"] = $conversation["href"];
853 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
858 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
859 $res["body"] = $res["title"].add_page_info($res['plink']);
861 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
862 $res["body"] = add_page_info_to_body($res["body"]);
864 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
866 call_hooks('parse_atom', $arr);
868 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
869 //if (strpos($res["body"], "RT @") !== false) {
870 /*if (strpos($res["body"], "@") !== false) {
871 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
872 file_put_contents($debugfile, serialize($arr));
878 function add_page_info($url, $no_photos = false) {
879 require_once("mod/parse_url.php");
880 $data = parseurl_getsiteinfo($url, true);
882 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
884 // It maybe is a rich content, but if it does have everything that a link has,
885 // then treat it that way
886 if (($data["type"] == "rich") AND is_string($data["title"]) AND
887 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
888 $data["type"] = "link";
890 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
893 if ($no_photos AND ($data["type"] == "photo"))
896 if (($data["type"] != "photo") AND is_string($data["title"]))
897 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
899 if (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
900 $imagedata = $data["images"][0];
901 $text .= '[img]'.$imagedata["src"].'[/img]';
904 if (($data["type"] != "photo") AND is_string($data["text"]))
905 $text .= "[quote]".$data["text"]."[/quote]";
907 return("\n[class=type-".$data["type"]."]".$text."[/class]");
910 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
912 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
914 $URLSearchString = "^\[\]";
916 // Adding these spaces is a quick hack due to my problems with regular expressions :)
917 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
920 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
922 // Convert urls without bbcode elements
923 if (!$matches AND $texturl) {
924 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
926 // Yeah, a hack. I really hate regular expressions :)
928 $matches[1] = $matches[2];
932 $body .= add_page_info($matches[1], $no_photos);
937 function encode_rel_links($links) {
939 if(! ((is_array($links)) && (count($links))))
941 foreach($links as $link) {
943 if($link['attribs']['']['rel'])
944 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
945 if($link['attribs']['']['type'])
946 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
947 if($link['attribs']['']['href'])
948 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
949 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
950 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
951 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
952 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
960 function item_store($arr,$force_parent = false) {
962 // If a Diaspora signature structure was passed in, pull it out of the
963 // item array and set it aside for later storage.
966 if(x($arr,'dsprsig')) {
967 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
968 unset($arr['dsprsig']);
971 // if an OStatus conversation url was passed in, it is stored and then
972 // removed from the array.
973 $ostatus_conversation = null;
975 if (isset($arr["ostatus_conversation"])) {
976 $ostatus_conversation = $arr["ostatus_conversation"];
977 unset($arr["ostatus_conversation"]);
980 if(x($arr, 'gravity'))
981 $arr['gravity'] = intval($arr['gravity']);
982 elseif($arr['parent-uri'] === $arr['uri'])
984 elseif(activity_match($arr['verb'],ACTIVITY_POST))
987 $arr['gravity'] = 6; // extensible catchall
990 $arr['type'] = 'remote';
994 /* check for create date and expire time */
995 $uid = intval($arr['uid']);
996 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
998 $expire_interval = $r[0]['expire'];
999 if ($expire_interval>0) {
1000 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1001 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1002 if ($created_date < $expire_date) {
1003 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1009 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1011 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1012 $arr['body'] = strip_tags($arr['body']);
1015 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1016 require_once('library/langdet/Text/LanguageDetect.php');
1017 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1018 $l = new Text_LanguageDetect;
1019 //$lng = $l->detectConfidence($naked_body);
1020 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1021 $lng = $l->detect($naked_body, 3);
1023 if (sizeof($lng) > 0) {
1026 foreach ($lng as $language => $score) {
1027 if ($postopts == "")
1028 $postopts = "lang=";
1032 $postopts .= $language.";".$score;
1034 $arr['postopts'] = $postopts;
1038 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1039 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1040 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1041 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1042 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1043 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1044 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1045 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1046 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1047 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1048 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1049 $arr['commented'] = datetime_convert();
1050 $arr['received'] = datetime_convert();
1051 $arr['changed'] = datetime_convert();
1052 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1053 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1054 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1055 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1056 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1057 $arr['deleted'] = 0;
1058 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1059 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1060 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1061 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1062 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1063 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1064 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1065 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1066 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1067 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1068 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1069 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1070 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1071 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1072 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1073 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1074 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1075 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1076 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1077 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1079 if ($arr['network'] == "") {
1080 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1081 intval($arr['contact-id']),
1086 $arr['network'] = $r[0]["network"];
1088 // Fallback to friendica (why is it empty in some cases?)
1089 if ($arr['network'] == "")
1090 $arr['network'] = NETWORK_DFRN;
1092 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1095 $arr['thr-parent'] = $arr['parent-uri'];
1096 if($arr['parent-uri'] === $arr['uri']) {
1098 $parent_deleted = 0;
1099 $allow_cid = $arr['allow_cid'];
1100 $allow_gid = $arr['allow_gid'];
1101 $deny_cid = $arr['deny_cid'];
1102 $deny_gid = $arr['deny_gid'];
1106 // find the parent and snarf the item id and ACLs
1107 // and anything else we need to inherit
1109 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1110 dbesc($arr['parent-uri']),
1116 // is the new message multi-level threaded?
1117 // even though we don't support it now, preserve the info
1118 // and re-attach to the conversation parent.
1120 if($r[0]['uri'] != $r[0]['parent-uri']) {
1121 $arr['parent-uri'] = $r[0]['parent-uri'];
1122 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1123 ORDER BY `id` ASC LIMIT 1",
1124 dbesc($r[0]['parent-uri']),
1125 dbesc($r[0]['parent-uri']),
1132 $parent_id = $r[0]['id'];
1133 $parent_deleted = $r[0]['deleted'];
1134 $allow_cid = $r[0]['allow_cid'];
1135 $allow_gid = $r[0]['allow_gid'];
1136 $deny_cid = $r[0]['deny_cid'];
1137 $deny_gid = $r[0]['deny_gid'];
1138 $arr['wall'] = $r[0]['wall'];
1140 // if the parent is private, force privacy for the entire conversation
1141 // This differs from the above settings as it subtly allows comments from
1142 // email correspondents to be private even if the overall thread is not.
1144 if($r[0]['private'])
1145 $arr['private'] = $r[0]['private'];
1147 // Edge case. We host a public forum that was originally posted to privately.
1148 // The original author commented, but as this is a comment, the permissions
1149 // weren't fixed up so it will still show the comment as private unless we fix it here.
1151 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1152 $arr['private'] = 0;
1155 // If its a post from myself then tag the thread as "mention"
1156 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1157 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1160 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1161 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1162 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1163 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1164 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1170 // Allow one to see reply tweets from status.net even when
1171 // we don't have or can't see the original post.
1174 logger('item_store: $force_parent=true, reply converted to top-level post.');
1176 $arr['parent-uri'] = $arr['uri'];
1177 $arr['gravity'] = 0;
1180 logger('item_store: item parent was not found - ignoring item');
1184 $parent_deleted = 0;
1188 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1192 if($r && count($r)) {
1193 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1197 call_hooks('post_remote',$arr);
1199 if(x($arr,'cancel')) {
1200 logger('item_store: post cancelled by plugin.');
1206 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1208 $r = dbq("INSERT INTO `item` (`"
1209 . implode("`, `", array_keys($arr))
1211 . implode("', '", array_values($arr))
1214 // find the item we just created
1216 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1217 $arr['uri'], // already dbesc'd
1222 $current_post = $r[0]['id'];
1223 logger('item_store: created item ' . $current_post);
1225 // Only check for notifications on start posts
1226 if ($arr['parent-uri'] === $arr['uri']) {
1227 add_thread($r[0]['id']);
1228 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1230 // Send a notification for every new post?
1231 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1232 intval($arr['contact-id']),
1237 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1238 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1239 intval($arr['uid']));
1241 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1242 intval($current_post),
1248 require_once('include/enotify.php');
1250 'type' => NOTIFY_SHARE,
1251 'notify_flags' => $u[0]['notify-flags'],
1252 'language' => $u[0]['language'],
1253 'to_name' => $u[0]['username'],
1254 'to_email' => $u[0]['email'],
1255 'uid' => $u[0]['uid'],
1257 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1258 'source_name' => $item[0]['author-name'],
1259 'source_link' => $item[0]['author-link'],
1260 'source_photo' => $item[0]['author-avatar'],
1261 'verb' => ACTIVITY_TAG,
1264 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1269 logger('item_store: could not locate created item');
1273 logger('item_store: duplicated post occurred. Removing duplicates.');
1274 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1276 intval($arr['uid']),
1277 intval($current_post)
1281 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1282 $parent_id = $current_post;
1284 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1287 $private = $arr['private'];
1289 // Set parent id - and also make sure to inherit the parent's ACLs.
1291 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1292 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1299 intval($parent_deleted),
1300 intval($current_post)
1303 // Complete ostatus threads
1304 if ($ostatus_conversation)
1305 complete_conversation($current_post, $ostatus_conversation);
1307 $arr['id'] = $current_post;
1308 $arr['parent'] = $parent_id;
1309 $arr['allow_cid'] = $allow_cid;
1310 $arr['allow_gid'] = $allow_gid;
1311 $arr['deny_cid'] = $deny_cid;
1312 $arr['deny_gid'] = $deny_gid;
1313 $arr['private'] = $private;
1314 $arr['deleted'] = $parent_deleted;
1316 // update the commented timestamp on the parent
1318 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1319 dbesc(datetime_convert()),
1320 dbesc(datetime_convert()),
1323 update_thread($parent_id);
1326 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1327 intval($current_post),
1328 dbesc($dsprsig->signed_text),
1329 dbesc($dsprsig->signature),
1330 dbesc($dsprsig->signer)
1336 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1339 if($arr['last-child']) {
1340 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1342 intval($arr['uid']),
1343 intval($current_post)
1347 $deleted = tag_deliver($arr['uid'],$current_post);
1349 // current post can be deleted if is for a communuty page and no mention are
1353 // Store the fresh generated item into the cache
1354 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1356 if (($cachefile != '') AND !file_exists($cachefile)) {
1357 $s = prepare_text($arr['body']);
1359 $stamp1 = microtime(true);
1360 file_put_contents($cachefile, $s);
1361 $a->save_timestamp($stamp1, "file");
1362 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1365 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1366 if (count($r) == 1) {
1367 call_hooks('post_remote_end', $r[0]);
1369 logger('item_store: new item not found in DB, id ' . $current_post);
1373 create_tags_from_item($current_post);
1374 create_files_from_item($current_post);
1376 return $current_post;
1379 function get_item_contact($item,$contacts) {
1380 if(! count($contacts) || (! is_array($item)))
1382 foreach($contacts as $contact) {
1383 if($contact['id'] == $item['contact-id']) {
1385 break; // NOTREACHED
1392 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1394 * @param int $item_id
1395 * @return bool true if item was deleted, else false
1397 function tag_deliver($uid,$item_id) {
1405 $u = q("select * from user where uid = %d limit 1",
1411 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1412 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1415 $i = q("select * from item where id = %d and uid = %d limit 1",
1424 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1426 // Diaspora uses their own hardwired link URL in @-tags
1427 // instead of the one we supply with webfinger
1429 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1431 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1433 foreach($matches as $mtch) {
1434 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1436 logger('tag_deliver: mention found: ' . $mtch[2]);
1442 if ( ($community_page || $prvgroup) &&
1443 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1444 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1446 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1447 q("DELETE FROM item WHERE id = %d and uid = %d",
1457 // send a notification
1459 // use a local photo if we have one
1461 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1462 intval($u[0]['uid']),
1463 dbesc(normalise_link($item['author-link']))
1465 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1468 require_once('include/enotify.php');
1470 'type' => NOTIFY_TAGSELF,
1471 'notify_flags' => $u[0]['notify-flags'],
1472 'language' => $u[0]['language'],
1473 'to_name' => $u[0]['username'],
1474 'to_email' => $u[0]['email'],
1475 'uid' => $u[0]['uid'],
1477 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1478 'source_name' => $item['author-name'],
1479 'source_link' => $item['author-link'],
1480 'source_photo' => $photo,
1481 'verb' => ACTIVITY_TAG,
1486 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1488 call_hooks('tagged', $arr);
1490 if((! $community_page) && (! $prvgroup))
1494 // tgroup delivery - setup a second delivery chain
1495 // prevent delivery looping - only proceed
1496 // if the message originated elsewhere and is a top-level post
1498 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1501 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1504 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1505 intval($u[0]['uid'])
1510 // also reset all the privacy bits to the forum default permissions
1512 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1514 $forum_mode = (($prvgroup) ? 2 : 1);
1516 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1517 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1518 intval($forum_mode),
1519 dbesc($c[0]['name']),
1520 dbesc($c[0]['url']),
1521 dbesc($c[0]['thumb']),
1523 dbesc($u[0]['allow_cid']),
1524 dbesc($u[0]['allow_gid']),
1525 dbesc($u[0]['deny_cid']),
1526 dbesc($u[0]['deny_gid']),
1529 update_thread($item_id);
1531 proc_run('php','include/notifier.php','tgroup',$item_id);
1537 function tgroup_check($uid,$item) {
1543 // check that the message originated elsewhere and is a top-level post
1545 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1549 $u = q("select * from user where uid = %d limit 1",
1555 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1556 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1559 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1561 // Diaspora uses their own hardwired link URL in @-tags
1562 // instead of the one we supply with webfinger
1564 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1566 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1568 foreach($matches as $mtch) {
1569 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1571 logger('tgroup_check: mention found: ' . $mtch[2]);
1579 if((! $community_page) && (! $prvgroup))
1593 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1597 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1599 if($contact['duplex'] && $contact['dfrn-id'])
1600 $idtosend = '0:' . $orig_id;
1601 if($contact['duplex'] && $contact['issued-id'])
1602 $idtosend = '1:' . $orig_id;
1604 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1606 $rino_enable = get_config('system','rino_encrypt');
1611 $ssl_val = intval(get_config('system','ssl_policy'));
1615 case SSL_POLICY_FULL:
1616 $ssl_policy = 'full';
1618 case SSL_POLICY_SELFSIGN:
1619 $ssl_policy = 'self';
1621 case SSL_POLICY_NONE:
1623 $ssl_policy = 'none';
1627 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1629 logger('dfrn_deliver: ' . $url);
1631 $xml = fetch_url($url);
1633 $curl_stat = $a->get_curl_code();
1635 return(-1); // timed out
1637 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1642 if(strpos($xml,'<?xml') === false) {
1643 logger('dfrn_deliver: no valid XML returned');
1644 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1648 $res = parse_xml_string($xml);
1650 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1651 return (($res->status) ? $res->status : 3);
1653 $postvars = array();
1654 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1655 $challenge = hex2bin((string) $res->challenge);
1656 $perm = (($res->perm) ? $res->perm : null);
1657 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1658 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1659 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1661 if($owner['page-flags'] == PAGE_PRVGROUP)
1664 $final_dfrn_id = '';
1667 if((($perm == 'rw') && (! intval($contact['writable'])))
1668 || (($perm == 'r') && (intval($contact['writable'])))) {
1669 q("update contact set writable = %d where id = %d",
1670 intval(($perm == 'rw') ? 1 : 0),
1671 intval($contact['id'])
1673 $contact['writable'] = (string) 1 - intval($contact['writable']);
1677 if(($contact['duplex'] && strlen($contact['pubkey']))
1678 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1679 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1680 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1681 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1684 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1685 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1688 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1690 if(strpos($final_dfrn_id,':') == 1)
1691 $final_dfrn_id = substr($final_dfrn_id,2);
1693 if($final_dfrn_id != $orig_id) {
1694 logger('dfrn_deliver: wrong dfrn_id.');
1695 // did not decode properly - cannot trust this site
1699 $postvars['dfrn_id'] = $idtosend;
1700 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1702 $postvars['dissolve'] = '1';
1705 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1706 $postvars['data'] = $atom;
1707 $postvars['perm'] = 'rw';
1710 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1711 $postvars['perm'] = 'r';
1714 $postvars['ssl_policy'] = $ssl_policy;
1717 $postvars['page'] = $page;
1719 if($rino && $rino_allowed && (! $dissolve)) {
1720 $key = substr(random_string(),0,16);
1721 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1722 $postvars['data'] = $data;
1723 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1726 if($dfrn_version >= 2.1) {
1727 if(($contact['duplex'] && strlen($contact['pubkey']))
1728 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1729 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1731 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1734 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1738 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1739 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1742 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1746 logger('md5 rawkey ' . md5($postvars['key']));
1748 $postvars['key'] = bin2hex($postvars['key']);
1751 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1753 $xml = post_url($contact['notify'],$postvars);
1755 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1757 $curl_stat = $a->get_curl_code();
1758 if((! $curl_stat) || (! strlen($xml)))
1759 return(-1); // timed out
1761 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1764 if(strpos($xml,'<?xml') === false) {
1765 logger('dfrn_deliver: phase 2: no valid XML returned');
1766 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1770 if($contact['term-date'] != '0000-00-00 00:00:00') {
1771 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1772 require_once('include/Contact.php');
1773 unmark_for_death($contact);
1776 $res = parse_xml_string($xml);
1778 return $res->status;
1783 This function returns true if $update has an edited timestamp newer
1784 than $existing, i.e. $update contains new data which should override
1785 what's already there. If there is no timestamp yet, the update is
1786 assumed to be newer. If the update has no timestamp, the existing
1787 item is assumed to be up-to-date. If the timestamps are equal it
1788 assumes the update has been seen before and should be ignored.
1790 function edited_timestamp_is_newer($existing, $update) {
1791 if (!x($existing,'edited') || !$existing['edited']) {
1794 if (!x($update,'edited') || !$update['edited']) {
1797 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1798 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1799 return (strcmp($existing_edited, $update_edited) < 0);
1804 * consume_feed - process atom feed and update anything/everything we might need to update
1806 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1808 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1809 * It is this person's stuff that is going to be updated.
1810 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1811 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1812 * have a contact record.
1813 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1814 * might not) try and subscribe to it.
1815 * $datedir sorts in reverse order
1816 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1817 * imported prior to its children being seen in the stream unless we are certain
1818 * of how the feed is arranged/ordered.
1819 * With $pass = 1, we only pull parent items out of the stream.
1820 * With $pass = 2, we only pull children (comments/likes).
1822 * So running this twice, first with pass 1 and then with pass 2 will do the right
1823 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1824 * model where comments can have sub-threads. That would require some massive sorting
1825 * to get all the feed items into a mostly linear ordering, and might still require
1829 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1831 require_once('library/simplepie/simplepie.inc');
1833 if(! strlen($xml)) {
1834 logger('consume_feed: empty input');
1838 $feed = new SimplePie();
1839 $feed->set_raw_data($xml);
1841 $feed->enable_order_by_date(true);
1843 $feed->enable_order_by_date(false);
1847 logger('consume_feed: Error parsing XML: ' . $feed->error());
1849 $permalink = $feed->get_permalink();
1851 // Check at the feed level for updated contact name and/or photo
1855 $photo_timestamp = '';
1859 $hubs = $feed->get_links('hub');
1860 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1863 $hub = implode(',', $hubs);
1865 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1867 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1869 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1870 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1871 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1872 $new_name = $elems['name'][0]['data'];
1874 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1875 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1876 $photo_url = $elems['link'][0]['attribs']['']['href'];
1879 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1880 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1884 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1885 logger('consume_feed: Updating photo for ' . $contact['name']);
1886 require_once("include/Photo.php");
1887 $photo_failure = false;
1888 $have_photo = false;
1890 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1891 intval($contact['id']),
1892 intval($contact['uid'])
1895 $resource_id = $r[0]['resource-id'];
1899 $resource_id = photo_new_resource();
1902 $img_str = fetch_url($photo_url,true);
1903 // guess mimetype from headers or filename
1904 $type = guess_image_type($photo_url,true);
1907 $img = new Photo($img_str, $type);
1908 if($img->is_valid()) {
1910 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1911 dbesc($resource_id),
1912 intval($contact['id']),
1913 intval($contact['uid'])
1917 $img->scaleImageSquare(175);
1919 $hash = $resource_id;
1920 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1922 $img->scaleImage(80);
1923 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1925 $img->scaleImage(48);
1926 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1930 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1931 WHERE `uid` = %d AND `id` = %d",
1932 dbesc(datetime_convert()),
1933 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1934 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1935 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1936 intval($contact['uid']),
1937 intval($contact['id'])
1942 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1943 $r = q("select * from contact where uid = %d and id = %d limit 1",
1944 intval($contact['uid']),
1945 intval($contact['id'])
1948 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1949 dbesc(notags(trim($new_name))),
1950 dbesc(datetime_convert()),
1951 intval($contact['uid']),
1952 intval($contact['id'])
1955 // do our best to update the name on content items
1958 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1959 dbesc(notags(trim($new_name))),
1960 dbesc($r[0]['name']),
1961 dbesc($r[0]['url']),
1962 intval($contact['uid'])
1967 if(strlen($birthday)) {
1968 if(substr($birthday,0,4) != $contact['bdyear']) {
1969 logger('consume_feed: updating birthday: ' . $birthday);
1973 * Add new birthday event for this person
1975 * $bdtext is just a readable placeholder in case the event is shared
1976 * with others. We will replace it during presentation to our $importer
1977 * to contain a sparkle link and perhaps a photo.
1981 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1982 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1985 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1986 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1987 intval($contact['uid']),
1988 intval($contact['id']),
1989 dbesc(datetime_convert()),
1990 dbesc(datetime_convert()),
1991 dbesc(datetime_convert('UTC','UTC', $birthday)),
1992 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2001 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2002 dbesc(substr($birthday,0,4)),
2003 intval($contact['uid']),
2004 intval($contact['id'])
2007 // This function is called twice without reloading the contact
2008 // Make sure we only create one event. This is why &$contact
2009 // is a reference var in this function
2011 $contact['bdyear'] = substr($birthday,0,4);
2016 $community_page = 0;
2017 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2019 $community_page = intval($rawtags[0]['data']);
2021 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2022 q("update contact set forum = %d where id = %d",
2023 intval($community_page),
2024 intval($contact['id'])
2026 $contact['forum'] = (string) $community_page;
2030 // process any deleted entries
2032 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2033 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2034 foreach($del_entries as $dentry) {
2036 if(isset($dentry['attribs']['']['ref'])) {
2037 $uri = $dentry['attribs']['']['ref'];
2039 if(isset($dentry['attribs']['']['when'])) {
2040 $when = $dentry['attribs']['']['when'];
2041 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2044 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2046 if($deleted && is_array($contact)) {
2047 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2048 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2050 intval($importer['uid']),
2051 intval($contact['id'])
2056 if(! $item['deleted'])
2057 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2059 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2060 $xo = parse_xml_string($item['object'],false);
2061 $xt = parse_xml_string($item['target'],false);
2062 if($xt->type === ACTIVITY_OBJ_NOTE) {
2063 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2065 intval($importer['importer_uid'])
2069 // For tags, the owner cannot remove the tag on the author's copy of the post.
2071 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2072 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2073 $author_copy = (($item['origin']) ? true : false);
2075 if($owner_remove && $author_copy)
2077 if($author_remove || $owner_remove) {
2078 $tags = explode(',',$i[0]['tag']);
2081 foreach($tags as $tag)
2082 if(trim($tag) !== trim($xo->body))
2083 $newtags[] = trim($tag);
2085 q("update item set tag = '%s' where id = %d",
2086 dbesc(implode(',',$newtags)),
2089 create_tags_from_item($i[0]['id']);
2095 if($item['uri'] == $item['parent-uri']) {
2096 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2097 `body` = '', `title` = ''
2098 WHERE `parent-uri` = '%s' AND `uid` = %d",
2100 dbesc(datetime_convert()),
2101 dbesc($item['uri']),
2102 intval($importer['uid'])
2104 create_tags_from_itemuri($item['uri'], $importer['uid']);
2105 create_files_from_itemuri($item['uri'], $importer['uid']);
2106 update_thread_uri($item['uri'], $importer['uid']);
2109 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2110 `body` = '', `title` = ''
2111 WHERE `uri` = '%s' AND `uid` = %d",
2113 dbesc(datetime_convert()),
2115 intval($importer['uid'])
2117 create_tags_from_itemuri($uri, $importer['uid']);
2118 create_files_from_itemuri($uri, $importer['uid']);
2119 if($item['last-child']) {
2120 // ensure that last-child is set in case the comment that had it just got wiped.
2121 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2122 dbesc(datetime_convert()),
2123 dbesc($item['parent-uri']),
2124 intval($item['uid'])
2126 // who is the last child now?
2127 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2128 ORDER BY `created` DESC LIMIT 1",
2129 dbesc($item['parent-uri']),
2130 intval($importer['uid'])
2133 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2144 // Now process the feed
2146 if($feed->get_item_quantity()) {
2148 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2150 // in inverse date order
2152 $items = array_reverse($feed->get_items());
2154 $items = $feed->get_items();
2157 foreach($items as $item) {
2160 $item_id = $item->get_id();
2161 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2162 if(isset($rawthread[0]['attribs']['']['ref'])) {
2164 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2167 if(($is_reply) && is_array($contact)) {
2172 // not allowed to post
2174 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2178 // Have we seen it? If not, import it.
2180 $item_id = $item->get_id();
2181 $datarray = get_atom_elements($feed, $item, $contact);
2183 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2184 $datarray['author-name'] = $contact['name'];
2185 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2186 $datarray['author-link'] = $contact['url'];
2187 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2188 $datarray['author-avatar'] = $contact['thumb'];
2190 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2191 logger('consume_feed: no author information! ' . print_r($datarray,true));
2195 $force_parent = false;
2196 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2197 if($contact['network'] === NETWORK_OSTATUS)
2198 $force_parent = true;
2199 if(strlen($datarray['title']))
2200 unset($datarray['title']);
2201 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2202 dbesc(datetime_convert()),
2204 intval($importer['uid'])
2206 $datarray['last-child'] = 1;
2207 update_thread_uri($parent_uri, $importer['uid']);
2211 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2213 intval($importer['uid'])
2216 // Update content if 'updated' changes
2219 if (edited_timestamp_is_newer($r[0], $datarray)) {
2221 // do not accept (ignore) an earlier edit than one we currently have.
2222 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2225 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2226 dbesc($datarray['title']),
2227 dbesc($datarray['body']),
2228 dbesc($datarray['tag']),
2229 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2230 dbesc(datetime_convert()),
2232 intval($importer['uid'])
2234 create_tags_from_itemuri($item_id, $importer['uid']);
2235 update_thread_uri($item_id, $importer['uid']);
2238 // update last-child if it changes
2240 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2241 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2242 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2243 dbesc(datetime_convert()),
2245 intval($importer['uid'])
2247 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2248 intval($allow[0]['data']),
2249 dbesc(datetime_convert()),
2251 intval($importer['uid'])
2253 update_thread_uri($item_id, $importer['uid']);
2259 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2260 // one way feed - no remote comment ability
2261 $datarray['last-child'] = 0;
2263 $datarray['parent-uri'] = $parent_uri;
2264 $datarray['uid'] = $importer['uid'];
2265 $datarray['contact-id'] = $contact['id'];
2266 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2267 $datarray['type'] = 'activity';
2268 $datarray['gravity'] = GRAVITY_LIKE;
2269 // only one like or dislike per person
2270 // splitted into two queries for performance issues
2271 $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",
2272 intval($datarray['uid']),
2273 intval($datarray['contact-id']),
2274 dbesc($datarray['verb']),
2280 $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",
2281 intval($datarray['uid']),
2282 intval($datarray['contact-id']),
2283 dbesc($datarray['verb']),
2290 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2291 $xo = parse_xml_string($datarray['object'],false);
2292 $xt = parse_xml_string($datarray['target'],false);
2294 if($xt->type == ACTIVITY_OBJ_NOTE) {
2295 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2297 intval($importer['importer_uid'])
2302 // extract tag, if not duplicate, add to parent item
2303 if($xo->id && $xo->content) {
2304 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2305 if(! (stristr($r[0]['tag'],$newtag))) {
2306 q("UPDATE item SET tag = '%s' WHERE id = %d",
2307 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2310 create_tags_from_item($r[0]['id']);
2316 $r = item_store($datarray,$force_parent);
2322 // Head post of a conversation. Have we seen it? If not, import it.
2324 $item_id = $item->get_id();
2326 $datarray = get_atom_elements($feed, $item, $contact);
2328 if(is_array($contact)) {
2329 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2330 $datarray['author-name'] = $contact['name'];
2331 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2332 $datarray['author-link'] = $contact['url'];
2333 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2334 $datarray['author-avatar'] = $contact['thumb'];
2337 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2338 logger('consume_feed: no author information! ' . print_r($datarray,true));
2342 // special handling for events
2344 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2345 $ev = bbtoevent($datarray['body']);
2346 if(x($ev,'desc') && x($ev,'start')) {
2347 $ev['uid'] = $importer['uid'];
2348 $ev['uri'] = $item_id;
2349 $ev['edited'] = $datarray['edited'];
2350 $ev['private'] = $datarray['private'];
2352 if(is_array($contact))
2353 $ev['cid'] = $contact['id'];
2354 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2356 intval($importer['uid'])
2359 $ev['id'] = $r[0]['id'];
2360 $xyz = event_store($ev);
2365 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2366 if(strlen($datarray['title']))
2367 unset($datarray['title']);
2368 $datarray['last-child'] = 1;
2372 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2374 intval($importer['uid'])
2377 // Update content if 'updated' changes
2380 if (edited_timestamp_is_newer($r[0], $datarray)) {
2382 // do not accept (ignore) an earlier edit than one we currently have.
2383 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2386 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2387 dbesc($datarray['title']),
2388 dbesc($datarray['body']),
2389 dbesc($datarray['tag']),
2390 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2391 dbesc(datetime_convert()),
2393 intval($importer['uid'])
2395 create_tags_from_itemuri($item_id, $importer['uid']);
2396 update_thread_uri($item_id, $importer['uid']);
2399 // update last-child if it changes
2401 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2402 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2403 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2404 intval($allow[0]['data']),
2405 dbesc(datetime_convert()),
2407 intval($importer['uid'])
2409 update_thread_uri($item_id, $importer['uid']);
2414 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2415 logger('consume-feed: New follower');
2416 new_follower($importer,$contact,$datarray,$item);
2419 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2420 lose_follower($importer,$contact,$datarray,$item);
2424 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2425 logger('consume-feed: New friend request');
2426 new_follower($importer,$contact,$datarray,$item,true);
2429 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2430 lose_sharer($importer,$contact,$datarray,$item);
2435 if(! is_array($contact))
2439 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2440 // one way feed - no remote comment ability
2441 $datarray['last-child'] = 0;
2443 if($contact['network'] === NETWORK_FEED)
2444 $datarray['private'] = 2;
2446 // This is my contact on another system, but it's really me.
2447 // Turn this into a wall post.
2449 if($contact['remote_self']) {
2450 $datarray['wall'] = 1;
2451 if($contact['network'] === NETWORK_FEED) {
2452 $datarray['private'] = 0;
2456 $datarray['parent-uri'] = $item_id;
2457 $datarray['uid'] = $importer['uid'];
2458 $datarray['contact-id'] = $contact['id'];
2460 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2461 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2462 // but otherwise there's a possible data mixup on the sender's system.
2463 // the tgroup delivery code called from item_store will correct it if it's a forum,
2464 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2465 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2466 $datarray['owner-name'] = $contact['name'];
2467 $datarray['owner-link'] = $contact['url'];
2468 $datarray['owner-avatar'] = $contact['thumb'];
2471 // We've allowed "followers" to reach this point so we can decide if they are
2472 // posting an @-tag delivery, which followers are allowed to do for certain
2473 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2475 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2479 $r = item_store($datarray);
2487 function local_delivery($importer,$data) {
2490 logger(__function__, LOGGER_TRACE);
2492 if($importer['readonly']) {
2493 // We aren't receiving stuff from this person. But we will quietly ignore them
2494 // rather than a blatant "go away" message.
2495 logger('local_delivery: ignoring');
2500 // Consume notification feed. This may differ from consuming a public feed in several ways
2501 // - might contain email or friend suggestions
2502 // - might contain remote followup to our message
2503 // - in which case we need to accept it and then notify other conversants
2504 // - we may need to send various email notifications
2506 $feed = new SimplePie();
2507 $feed->set_raw_data($data);
2508 $feed->enable_order_by_date(false);
2513 logger('local_delivery: Error parsing XML: ' . $feed->error());
2516 // Check at the feed level for updated contact name and/or photo
2520 $photo_timestamp = '';
2524 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2526 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2528 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2531 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2532 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2533 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2534 $new_name = $elems['name'][0]['data'];
2536 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2537 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2538 $photo_url = $elems['link'][0]['attribs']['']['href'];
2542 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2543 logger('local_delivery: Updating photo for ' . $importer['name']);
2544 require_once("include/Photo.php");
2545 $photo_failure = false;
2546 $have_photo = false;
2548 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2549 intval($importer['id']),
2550 intval($importer['importer_uid'])
2553 $resource_id = $r[0]['resource-id'];
2557 $resource_id = photo_new_resource();
2560 $img_str = fetch_url($photo_url,true);
2561 // guess mimetype from headers or filename
2562 $type = guess_image_type($photo_url,true);
2565 $img = new Photo($img_str, $type);
2566 if($img->is_valid()) {
2568 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2569 dbesc($resource_id),
2570 intval($importer['id']),
2571 intval($importer['importer_uid'])
2575 $img->scaleImageSquare(175);
2577 $hash = $resource_id;
2578 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2580 $img->scaleImage(80);
2581 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2583 $img->scaleImage(48);
2584 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2588 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2589 WHERE `uid` = %d AND `id` = %d",
2590 dbesc(datetime_convert()),
2591 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2592 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2593 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2594 intval($importer['importer_uid']),
2595 intval($importer['id'])
2600 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2601 $r = q("select * from contact where uid = %d and id = %d limit 1",
2602 intval($importer['importer_uid']),
2603 intval($importer['id'])
2606 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2607 dbesc(notags(trim($new_name))),
2608 dbesc(datetime_convert()),
2609 intval($importer['importer_uid']),
2610 intval($importer['id'])
2613 // do our best to update the name on content items
2616 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2617 dbesc(notags(trim($new_name))),
2618 dbesc($r[0]['name']),
2619 dbesc($r[0]['url']),
2620 intval($importer['importer_uid'])
2627 // Currently unsupported - needs a lot of work
2628 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2629 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2630 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2632 $newloc['uid'] = $importer['importer_uid'];
2633 $newloc['cid'] = $importer['id'];
2634 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2635 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2636 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2637 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2638 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2639 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2640 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2641 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2642 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2643 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2644 /** relocated user must have original key pair */
2645 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2646 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2648 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2651 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2652 intval($importer['id']),
2653 intval($importer['importer_uid']));
2658 $x = q("UPDATE contact SET
2668 `site-pubkey` = '%s'
2669 WHERE id=%d AND uid=%d;",
2670 dbesc($newloc['name']),
2671 dbesc($newloc['photo']),
2672 dbesc($newloc['thumb']),
2673 dbesc($newloc['micro']),
2674 dbesc($newloc['url']),
2675 dbesc($newloc['request']),
2676 dbesc($newloc['confirm']),
2677 dbesc($newloc['notify']),
2678 dbesc($newloc['poll']),
2679 dbesc($newloc['sitepubkey']),
2680 intval($importer['id']),
2681 intval($importer['importer_uid']));
2687 'owner-link' => array($old['url'], $newloc['url']),
2688 'author-link' => array($old['url'], $newloc['url']),
2689 'owner-avatar' => array($old['photo'], $newloc['photo']),
2690 'author-avatar' => array($old['photo'], $newloc['photo']),
2692 foreach ($fields as $n=>$f){
2693 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2696 intval($importer['importer_uid']));
2702 // merge with current record, current contents have priority
2703 // update record, set url-updated
2704 // update profile photos
2710 // handle friend suggestion notification
2712 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2713 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2714 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2716 $fsugg['uid'] = $importer['importer_uid'];
2717 $fsugg['cid'] = $importer['id'];
2718 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2719 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2720 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2721 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2722 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2724 // Does our member already have a friend matching this description?
2726 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2727 dbesc($fsugg['name']),
2728 dbesc(normalise_link($fsugg['url'])),
2729 intval($fsugg['uid'])
2734 // Do we already have an fcontact record for this person?
2737 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2738 dbesc($fsugg['url']),
2739 dbesc($fsugg['name']),
2740 dbesc($fsugg['request'])
2745 // OK, we do. Do we already have an introduction for this person ?
2746 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2747 intval($fsugg['uid']),
2754 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2755 dbesc($fsugg['name']),
2756 dbesc($fsugg['url']),
2757 dbesc($fsugg['photo']),
2758 dbesc($fsugg['request'])
2760 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2761 dbesc($fsugg['url']),
2762 dbesc($fsugg['name']),
2763 dbesc($fsugg['request'])
2768 // database record did not get created. Quietly give up.
2773 $hash = random_string();
2775 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2776 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2777 intval($fsugg['uid']),
2779 intval($fsugg['cid']),
2780 dbesc($fsugg['body']),
2782 dbesc(datetime_convert()),
2787 'type' => NOTIFY_SUGGEST,
2788 'notify_flags' => $importer['notify-flags'],
2789 'language' => $importer['language'],
2790 'to_name' => $importer['username'],
2791 'to_email' => $importer['email'],
2792 'uid' => $importer['importer_uid'],
2794 'link' => $a->get_baseurl() . '/notifications/intros',
2795 'source_name' => $importer['name'],
2796 'source_link' => $importer['url'],
2797 'source_photo' => $importer['photo'],
2798 'verb' => ACTIVITY_REQ_FRIEND,
2807 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2808 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2810 logger('local_delivery: private message received');
2813 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2816 $msg['uid'] = $importer['importer_uid'];
2817 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2818 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2819 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2820 $msg['contact-id'] = $importer['id'];
2821 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2822 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2824 $msg['replied'] = 0;
2825 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2826 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2827 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2831 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2832 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2834 // send notifications.
2836 require_once('include/enotify.php');
2838 $notif_params = array(
2839 'type' => NOTIFY_MAIL,
2840 'notify_flags' => $importer['notify-flags'],
2841 'language' => $importer['language'],
2842 'to_name' => $importer['username'],
2843 'to_email' => $importer['email'],
2844 'uid' => $importer['importer_uid'],
2846 'source_name' => $msg['from-name'],
2847 'source_link' => $importer['url'],
2848 'source_photo' => $importer['thumb'],
2849 'verb' => ACTIVITY_POST,
2853 notification($notif_params);
2859 $community_page = 0;
2860 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2862 $community_page = intval($rawtags[0]['data']);
2864 if(intval($importer['forum']) != $community_page) {
2865 q("update contact set forum = %d where id = %d",
2866 intval($community_page),
2867 intval($importer['id'])
2869 $importer['forum'] = (string) $community_page;
2872 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2874 // process any deleted entries
2876 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2877 if(is_array($del_entries) && count($del_entries)) {
2878 foreach($del_entries as $dentry) {
2880 if(isset($dentry['attribs']['']['ref'])) {
2881 $uri = $dentry['attribs']['']['ref'];
2883 if(isset($dentry['attribs']['']['when'])) {
2884 $when = $dentry['attribs']['']['when'];
2885 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2888 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2892 // check for relayed deletes to our conversation
2895 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2897 intval($importer['importer_uid'])
2900 $parent_uri = $r[0]['parent-uri'];
2901 if($r[0]['id'] != $r[0]['parent'])
2908 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2911 logger('local_delivery: possible community delete');
2914 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2916 // was the top-level post for this reply written by somebody on this site?
2917 // Specifically, the recipient?
2919 $is_a_remote_delete = false;
2921 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2922 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2923 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2924 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2925 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2926 AND `item`.`uid` = %d
2932 intval($importer['importer_uid'])
2935 $is_a_remote_delete = true;
2937 // Does this have the characteristics of a community or private group comment?
2938 // If it's a reply to a wall post on a community/prvgroup page it's a
2939 // valid community comment. Also forum_mode makes it valid for sure.
2940 // If neither, it's not.
2942 if($is_a_remote_delete && $community) {
2943 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2944 $is_a_remote_delete = false;
2945 logger('local_delivery: not a community delete');
2949 if($is_a_remote_delete) {
2950 logger('local_delivery: received remote delete');
2954 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2955 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2957 intval($importer['importer_uid']),
2958 intval($importer['id'])
2964 if($item['deleted'])
2967 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2969 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2970 $xo = parse_xml_string($item['object'],false);
2971 $xt = parse_xml_string($item['target'],false);
2973 if($xt->type === ACTIVITY_OBJ_NOTE) {
2974 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2976 intval($importer['importer_uid'])
2980 // For tags, the owner cannot remove the tag on the author's copy of the post.
2982 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2983 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2984 $author_copy = (($item['origin']) ? true : false);
2986 if($owner_remove && $author_copy)
2988 if($author_remove || $owner_remove) {
2989 $tags = explode(',',$i[0]['tag']);
2992 foreach($tags as $tag)
2993 if(trim($tag) !== trim($xo->body))
2994 $newtags[] = trim($tag);
2996 q("update item set tag = '%s' where id = %d",
2997 dbesc(implode(',',$newtags)),
3000 create_tags_from_item($i[0]['id']);
3006 if($item['uri'] == $item['parent-uri']) {
3007 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3008 `body` = '', `title` = ''
3009 WHERE `parent-uri` = '%s' AND `uid` = %d",
3011 dbesc(datetime_convert()),
3012 dbesc($item['uri']),
3013 intval($importer['importer_uid'])
3015 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3016 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3017 update_thread_uri($item['uri'], $importer['importer_uid']);
3020 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3021 `body` = '', `title` = ''
3022 WHERE `uri` = '%s' AND `uid` = %d",
3024 dbesc(datetime_convert()),
3026 intval($importer['importer_uid'])
3028 create_tags_from_itemuri($uri, $importer['importer_uid']);
3029 create_files_from_itemuri($uri, $importer['importer_uid']);
3030 update_thread_uri($uri, $importer['importer_uid']);
3031 if($item['last-child']) {
3032 // ensure that last-child is set in case the comment that had it just got wiped.
3033 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3034 dbesc(datetime_convert()),
3035 dbesc($item['parent-uri']),
3036 intval($item['uid'])
3038 // who is the last child now?
3039 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3040 ORDER BY `created` DESC LIMIT 1",
3041 dbesc($item['parent-uri']),
3042 intval($importer['importer_uid'])
3045 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3050 // if this is a relayed delete, propagate it to other recipients
3052 if($is_a_remote_delete)
3053 proc_run('php',"include/notifier.php","drop",$item['id']);
3061 foreach($feed->get_items() as $item) {
3064 $item_id = $item->get_id();
3065 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3066 if(isset($rawthread[0]['attribs']['']['ref'])) {
3068 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3074 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3077 logger('local_delivery: possible community reply');
3080 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3082 // was the top-level post for this reply written by somebody on this site?
3083 // Specifically, the recipient?
3085 $is_a_remote_comment = false;
3086 $top_uri = $parent_uri;
3088 $r = q("select `item`.`parent-uri` from `item`
3089 WHERE `item`.`uri` = '%s'
3093 if($r && count($r)) {
3094 $top_uri = $r[0]['parent-uri'];
3096 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3097 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3098 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3099 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3100 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3101 AND `item`.`uid` = %d
3107 intval($importer['importer_uid'])
3110 $is_a_remote_comment = true;
3113 // Does this have the characteristics of a community or private group comment?
3114 // If it's a reply to a wall post on a community/prvgroup page it's a
3115 // valid community comment. Also forum_mode makes it valid for sure.
3116 // If neither, it's not.
3118 if($is_a_remote_comment && $community) {
3119 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3120 $is_a_remote_comment = false;
3121 logger('local_delivery: not a community reply');
3125 if($is_a_remote_comment) {
3126 logger('local_delivery: received remote comment');
3128 // remote reply to our post. Import and then notify everybody else.
3130 $datarray = get_atom_elements($feed, $item);
3132 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3134 intval($importer['importer_uid'])
3137 // Update content if 'updated' changes
3141 if (edited_timestamp_is_newer($r[0], $datarray)) {
3143 // do not accept (ignore) an earlier edit than one we currently have.
3144 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3147 logger('received updated comment' , LOGGER_DEBUG);
3148 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3149 dbesc($datarray['title']),
3150 dbesc($datarray['body']),
3151 dbesc($datarray['tag']),
3152 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3153 dbesc(datetime_convert()),
3155 intval($importer['importer_uid'])
3157 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3159 proc_run('php',"include/notifier.php","comment-import",$iid);
3168 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3169 intval($importer['importer_uid'])
3173 $datarray['type'] = 'remote-comment';
3174 $datarray['wall'] = 1;
3175 $datarray['parent-uri'] = $parent_uri;
3176 $datarray['uid'] = $importer['importer_uid'];
3177 $datarray['owner-name'] = $own[0]['name'];
3178 $datarray['owner-link'] = $own[0]['url'];
3179 $datarray['owner-avatar'] = $own[0]['thumb'];
3180 $datarray['contact-id'] = $importer['id'];
3182 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3184 $datarray['type'] = 'activity';
3185 $datarray['gravity'] = GRAVITY_LIKE;
3186 $datarray['last-child'] = 0;
3187 // only one like or dislike per person
3188 // splitted into two queries for performance issues
3189 $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",
3190 intval($datarray['uid']),
3191 intval($datarray['contact-id']),
3192 dbesc($datarray['verb']),
3193 dbesc($datarray['parent-uri'])
3199 $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",
3200 intval($datarray['uid']),
3201 intval($datarray['contact-id']),
3202 dbesc($datarray['verb']),
3203 dbesc($datarray['parent-uri'])
3210 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3212 $xo = parse_xml_string($datarray['object'],false);
3213 $xt = parse_xml_string($datarray['target'],false);
3215 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3217 // fetch the parent item
3219 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3221 intval($importer['importer_uid'])
3226 // extract tag, if not duplicate, and this user allows tags, add to parent item
3228 if($xo->id && $xo->content) {
3229 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3230 if(! (stristr($tagp[0]['tag'],$newtag))) {
3231 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3232 intval($importer['importer_uid'])
3234 if(count($i) && ! intval($i[0]['blocktags'])) {
3235 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3236 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3237 intval($tagp[0]['id']),
3238 dbesc(datetime_convert()),
3239 dbesc(datetime_convert())
3241 create_tags_from_item($tagp[0]['id']);
3249 $posted_id = item_store($datarray);
3253 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3255 intval($importer['importer_uid'])
3258 $parent = $r[0]['parent'];
3259 $parent_uri = $r[0]['parent-uri'];
3263 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3264 dbesc(datetime_convert()),
3265 intval($importer['importer_uid']),
3266 intval($r[0]['parent'])
3269 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3270 dbesc(datetime_convert()),
3271 intval($importer['importer_uid']),
3276 if($posted_id && $parent) {
3278 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3280 if((! $is_like) && (! $importer['self'])) {
3282 require_once('include/enotify.php');
3285 'type' => NOTIFY_COMMENT,
3286 'notify_flags' => $importer['notify-flags'],
3287 'language' => $importer['language'],
3288 'to_name' => $importer['username'],
3289 'to_email' => $importer['email'],
3290 'uid' => $importer['importer_uid'],
3291 'item' => $datarray,
3292 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3293 'source_name' => stripslashes($datarray['author-name']),
3294 'source_link' => $datarray['author-link'],
3295 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3296 ? $importer['thumb'] : $datarray['author-avatar']),
3297 'verb' => ACTIVITY_POST,
3299 'parent' => $parent,
3300 'parent_uri' => $parent_uri,
3312 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3314 $item_id = $item->get_id();
3315 $datarray = get_atom_elements($feed,$item);
3317 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3320 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3322 intval($importer['importer_uid'])
3325 // Update content if 'updated' changes
3328 if (edited_timestamp_is_newer($r[0], $datarray)) {
3330 // do not accept (ignore) an earlier edit than one we currently have.
3331 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3334 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3335 dbesc($datarray['title']),
3336 dbesc($datarray['body']),
3337 dbesc($datarray['tag']),
3338 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3339 dbesc(datetime_convert()),
3341 intval($importer['importer_uid'])
3343 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3346 // update last-child if it changes
3348 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3349 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3350 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3351 dbesc(datetime_convert()),
3353 intval($importer['importer_uid'])
3355 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3356 intval($allow[0]['data']),
3357 dbesc(datetime_convert()),
3359 intval($importer['importer_uid'])
3365 $datarray['parent-uri'] = $parent_uri;
3366 $datarray['uid'] = $importer['importer_uid'];
3367 $datarray['contact-id'] = $importer['id'];
3368 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3369 $datarray['type'] = 'activity';
3370 $datarray['gravity'] = GRAVITY_LIKE;
3371 // only one like or dislike per person
3372 // splitted into two queries for performance issues
3373 $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",
3374 intval($datarray['uid']),
3375 intval($datarray['contact-id']),
3376 dbesc($datarray['verb']),
3382 $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",
3383 intval($datarray['uid']),
3384 intval($datarray['contact-id']),
3385 dbesc($datarray['verb']),
3393 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3395 $xo = parse_xml_string($datarray['object'],false);
3396 $xt = parse_xml_string($datarray['target'],false);
3398 if($xt->type == ACTIVITY_OBJ_NOTE) {
3399 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3401 intval($importer['importer_uid'])
3406 // extract tag, if not duplicate, add to parent item
3408 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3409 q("UPDATE item SET tag = '%s' WHERE id = %d",
3410 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3413 create_tags_from_item($r[0]['id']);
3419 $posted_id = item_store($datarray);
3421 // find out if our user is involved in this conversation and wants to be notified.
3423 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3425 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3427 intval($importer['importer_uid'])
3430 if(count($myconv)) {
3431 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3433 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3434 if(! link_compare($datarray['author-link'],$importer_url)) {
3437 foreach($myconv as $conv) {
3439 // now if we find a match, it means we're in this conversation
3441 if(! link_compare($conv['author-link'],$importer_url))
3444 require_once('include/enotify.php');
3446 $conv_parent = $conv['parent'];
3449 'type' => NOTIFY_COMMENT,
3450 'notify_flags' => $importer['notify-flags'],
3451 'language' => $importer['language'],
3452 'to_name' => $importer['username'],
3453 'to_email' => $importer['email'],
3454 'uid' => $importer['importer_uid'],
3455 'item' => $datarray,
3456 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3457 'source_name' => stripslashes($datarray['author-name']),
3458 'source_link' => $datarray['author-link'],
3459 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3460 ? $importer['thumb'] : $datarray['author-avatar']),
3461 'verb' => ACTIVITY_POST,
3463 'parent' => $conv_parent,
3464 'parent_uri' => $parent_uri
3468 // only send one notification
3480 // Head post of a conversation. Have we seen it? If not, import it.
3483 $item_id = $item->get_id();
3484 $datarray = get_atom_elements($feed,$item);
3486 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3487 $ev = bbtoevent($datarray['body']);
3488 if(x($ev,'desc') && x($ev,'start')) {
3489 $ev['cid'] = $importer['id'];
3490 $ev['uid'] = $importer['uid'];
3491 $ev['uri'] = $item_id;
3492 $ev['edited'] = $datarray['edited'];
3493 $ev['private'] = $datarray['private'];
3495 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3497 intval($importer['uid'])
3500 $ev['id'] = $r[0]['id'];
3501 $xyz = event_store($ev);
3506 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3508 intval($importer['importer_uid'])
3511 // Update content if 'updated' changes
3514 if (edited_timestamp_is_newer($r[0], $datarray)) {
3516 // do not accept (ignore) an earlier edit than one we currently have.
3517 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3520 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3521 dbesc($datarray['title']),
3522 dbesc($datarray['body']),
3523 dbesc($datarray['tag']),
3524 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3525 dbesc(datetime_convert()),
3527 intval($importer['importer_uid'])
3529 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3530 update_thread_uri($item_id, $importer['importer_uid']);
3533 // update last-child if it changes
3535 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3536 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3537 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3538 intval($allow[0]['data']),
3539 dbesc(datetime_convert()),
3541 intval($importer['importer_uid'])
3547 // This is my contact on another system, but it's really me.
3548 // Turn this into a wall post.
3550 if($importer['remote_self'])
3551 $datarray['wall'] = 1;
3553 $datarray['parent-uri'] = $item_id;
3554 $datarray['uid'] = $importer['importer_uid'];
3555 $datarray['contact-id'] = $importer['id'];
3558 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3559 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3560 // but otherwise there's a possible data mixup on the sender's system.
3561 // the tgroup delivery code called from item_store will correct it if it's a forum,
3562 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3563 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3564 $datarray['owner-name'] = $importer['senderName'];
3565 $datarray['owner-link'] = $importer['url'];
3566 $datarray['owner-avatar'] = $importer['thumb'];
3569 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3572 $posted_id = item_store($datarray);
3574 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3575 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3578 $xo = parse_xml_string($datarray['object'],false);
3580 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3582 // somebody was poked/prodded. Was it me?
3584 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3586 foreach($links->link as $l) {
3587 $atts = $l->attributes();
3588 switch($atts['rel']) {
3590 $Blink = $atts['href'];
3596 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3598 // send a notification
3599 require_once('include/enotify.php');
3602 'type' => NOTIFY_POKE,
3603 'notify_flags' => $importer['notify-flags'],
3604 'language' => $importer['language'],
3605 'to_name' => $importer['username'],
3606 'to_email' => $importer['email'],
3607 'uid' => $importer['importer_uid'],
3608 'item' => $datarray,
3609 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3610 'source_name' => stripslashes($datarray['author-name']),
3611 'source_link' => $datarray['author-link'],
3612 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3613 ? $importer['thumb'] : $datarray['author-avatar']),
3614 'verb' => $datarray['verb'],
3615 'otype' => 'person',
3616 'activity' => $verb,
3633 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3634 $url = notags(trim($datarray['author-link']));
3635 $name = notags(trim($datarray['author-name']));
3636 $photo = notags(trim($datarray['author-avatar']));
3638 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3639 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3640 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3642 if(is_array($contact)) {
3643 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3644 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3645 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3646 intval(CONTACT_IS_FRIEND),
3647 intval($contact['id']),
3648 intval($importer['uid'])
3651 // send email notification to owner?
3655 // create contact record
3657 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3658 `blocked`, `readonly`, `pending`, `writable` )
3659 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3660 intval($importer['uid']),
3661 dbesc(datetime_convert()),
3663 dbesc(normalise_link($url)),
3667 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3668 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3670 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3671 intval($importer['uid']),
3675 $contact_record = $r[0];
3677 // create notification
3678 $hash = random_string();
3680 if(is_array($contact_record)) {
3681 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3682 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3683 intval($importer['uid']),
3684 intval($contact_record['id']),
3686 dbesc(datetime_convert())
3689 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3690 intval($importer['uid'])
3695 if(intval($r[0]['def_gid'])) {
3696 require_once('include/group.php');
3697 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3700 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3701 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3702 $email = replace_macros($email_tpl, array(
3703 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3705 '$myname' => $r[0]['username'],
3706 '$siteurl' => $a->get_baseurl(),
3707 '$sitename' => $a->config['sitename']
3709 $res = mail($r[0]['email'],
3710 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'),
3712 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3713 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3714 . 'Content-transfer-encoding: 8bit' );
3721 function lose_follower($importer,$contact,$datarray,$item) {
3723 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3724 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3725 intval(CONTACT_IS_SHARING),
3726 intval($contact['id'])
3730 contact_remove($contact['id']);
3734 function lose_sharer($importer,$contact,$datarray,$item) {
3736 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3737 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3738 intval(CONTACT_IS_FOLLOWER),
3739 intval($contact['id'])
3743 contact_remove($contact['id']);
3748 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3752 if(is_array($importer)) {
3753 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3754 intval($importer['uid'])
3758 // Diaspora has different message-ids in feeds than they do
3759 // through the direct Diaspora protocol. If we try and use
3760 // the feed, we'll get duplicates. So don't.
3762 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3765 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3767 // Use a single verify token, even if multiple hubs
3769 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3771 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3773 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3775 if(! strlen($contact['hub-verify'])) {
3776 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3777 dbesc($verify_token),
3778 intval($contact['id'])
3782 post_url($url,$params);
3784 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3791 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3795 $name = xmlify($name);
3796 $uri = xmlify($uri);
3799 $photo = xmlify($photo);
3803 $o .= "<name>$name</name>\r\n";
3804 $o .= "<uri>$uri</uri>\r\n";
3805 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3806 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3808 call_hooks('atom_author', $o);
3810 $o .= "</$tag>\r\n";
3814 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3818 if(! $item['parent'])
3821 if($item['deleted'])
3822 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3825 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3826 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3828 $body = $item['body'];
3830 $o = "\r\n\r\n<entry>\r\n";
3832 if(is_array($author))
3833 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3835 $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']));
3836 if(strlen($item['owner-name']))
3837 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3839 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3840 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3841 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3844 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3845 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3846 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3847 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3848 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3849 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3850 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3852 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3854 if($item['location']) {
3855 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3856 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3860 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3862 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3863 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3866 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3867 if($item['bookmark'])
3868 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3871 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3874 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3876 if($item['signed_text']) {
3877 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3878 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3881 $verb = construct_verb($item);
3882 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3883 $actobj = construct_activity_object($item);
3886 $actarg = construct_activity_target($item);
3890 $tags = item_getfeedtags($item);
3892 foreach($tags as $t) {
3893 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3897 $o .= item_getfeedattach($item);
3899 $mentioned = get_mentions($item);
3903 call_hooks('atom_entry', $o);
3905 $o .= '</entry>' . "\r\n";
3910 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3912 if(get_config('system','disable_embedded'))
3917 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3918 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3923 $img_start = strpos($orig_body, '[img');
3924 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3925 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3926 while( ($img_st_close !== false) && ($img_len !== false) ) {
3928 $img_st_close++; // make it point to AFTER the closing bracket
3929 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3931 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3934 if(stristr($image , $site . '/photo/')) {
3935 // Only embed locally hosted photos
3937 $i = basename($image);
3938 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3939 $x = strpos($i,'-');
3942 $res = substr($i,$x+1);
3943 $i = substr($i,0,$x);
3944 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3951 // Check to see if we should replace this photo link with an embedded image
3952 // 1. No need to do so if the photo is public
3953 // 2. If there's a contact-id provided, see if they're in the access list
3954 // for the photo. If so, embed it.
3955 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3956 // permissions, regardless of order but first check to see if they're an exact
3957 // match to save some processing overhead.
3959 if(has_permissions($r[0])) {
3961 $recips = enumerate_permissions($r[0]);
3962 if(in_array($cid, $recips)) {
3967 if(compare_permissions($item,$r[0]))
3972 $data = $r[0]['data'];
3973 $type = $r[0]['type'];
3975 // If a custom width and height were specified, apply before embedding
3976 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3977 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3979 $width = intval($match[1]);
3980 $height = intval($match[2]);
3982 $ph = new Photo($data, $type);
3983 if($ph->is_valid()) {
3984 $ph->scaleImage(max($width, $height));
3985 $data = $ph->imageString();
3986 $type = $ph->getType();
3990 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3991 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3992 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3998 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3999 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4000 if($orig_body === false)
4003 $img_start = strpos($orig_body, '[img');
4004 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4005 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4008 $new_body = $new_body . $orig_body;
4014 function has_permissions($obj) {
4015 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4020 function compare_permissions($obj1,$obj2) {
4021 // first part is easy. Check that these are exactly the same.
4022 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4023 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4024 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4025 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4028 // This is harder. Parse all the permissions and compare the resulting set.
4030 $recipients1 = enumerate_permissions($obj1);
4031 $recipients2 = enumerate_permissions($obj2);
4034 if($recipients1 == $recipients2)
4039 // returns an array of contact-ids that are allowed to see this object
4041 function enumerate_permissions($obj) {
4042 require_once('include/group.php');
4043 $allow_people = expand_acl($obj['allow_cid']);
4044 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4045 $deny_people = expand_acl($obj['deny_cid']);
4046 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4047 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4048 $deny = array_unique(array_merge($deny_people,$deny_groups));
4049 $recipients = array_diff($recipients,$deny);
4053 function item_getfeedtags($item) {
4056 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4058 for($x = 0; $x < $cnt; $x ++) {
4060 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4064 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4066 for($x = 0; $x < $cnt; $x ++) {
4068 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4074 function item_getfeedattach($item) {
4076 $arr = explode('[/attach],',$item['attach']);
4078 foreach($arr as $r) {
4080 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4082 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4083 if(intval($matches[2]))
4084 $ret .= 'length="' . intval($matches[2]) . '" ';
4085 if($matches[4] !== ' ')
4086 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4087 $ret .= ' />' . "\r\n";
4096 function item_expire($uid,$days) {
4098 if((! $uid) || ($days < 1))
4101 // $expire_network_only = save your own wall posts
4102 // and just expire conversations started by others
4104 $expire_network_only = get_pconfig($uid,'expire','network_only');
4105 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4107 $r = q("SELECT * FROM `item`
4109 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4120 $expire_items = get_pconfig($uid, 'expire','items');
4121 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4123 $expire_notes = get_pconfig($uid, 'expire','notes');
4124 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4126 $expire_starred = get_pconfig($uid, 'expire','starred');
4127 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4129 $expire_photos = get_pconfig($uid, 'expire','photos');
4130 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4132 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4134 foreach($r as $item) {
4136 // don't expire filed items
4138 if(strpos($item['file'],'[') !== false)
4141 // Only expire posts, not photos and photo comments
4143 if($expire_photos==0 && strlen($item['resource-id']))
4145 if($expire_starred==0 && intval($item['starred']))
4147 if($expire_notes==0 && $item['type']=='note')
4149 if($expire_items==0 && $item['type']!='note')
4152 drop_item($item['id'],false);
4155 proc_run('php',"include/notifier.php","expire","$uid");
4160 function drop_items($items) {
4163 if(! local_user() && ! remote_user())
4167 foreach($items as $item) {
4168 $owner = drop_item($item,false);
4169 if($owner && ! $uid)
4174 // multiple threads may have been deleted, send an expire notification
4177 proc_run('php',"include/notifier.php","expire","$uid");
4181 function drop_item($id,$interactive = true) {
4185 // locate item to be deleted
4187 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4194 notice( t('Item not found.') . EOL);
4195 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4200 $owner = $item['uid'];
4204 // check if logged in user is either the author or owner of this item
4206 if(is_array($_SESSION['remote'])) {
4207 foreach($_SESSION['remote'] as $visitor) {
4208 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4209 $cid = $visitor['cid'];
4216 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4218 // Check if we should do HTML-based delete confirmation
4219 if($_REQUEST['confirm']) {
4220 // <form> can't take arguments in its "action" parameter
4221 // so add any arguments as hidden inputs
4222 $query = explode_querystring($a->query_string);
4224 foreach($query['args'] as $arg) {
4225 if(strpos($arg, 'confirm=') === false) {
4226 $arg_parts = explode('=', $arg);
4227 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4231 return replace_macros(get_markup_template('confirm.tpl'), array(
4233 '$message' => t('Do you really want to delete this item?'),
4234 '$extra_inputs' => $inputs,
4235 '$confirm' => t('Yes'),
4236 '$confirm_url' => $query['base'],
4237 '$confirm_name' => 'confirmed',
4238 '$cancel' => t('Cancel'),
4241 // Now check how the user responded to the confirmation query
4242 if($_REQUEST['canceled']) {
4243 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4246 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4249 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4250 dbesc(datetime_convert()),
4251 dbesc(datetime_convert()),
4254 create_tags_from_item($item['id']);
4255 create_files_from_item($item['id']);
4256 delete_thread($item['id']);
4258 // clean up categories and tags so they don't end up as orphans
4261 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4263 foreach($matches as $mtch) {
4264 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4270 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4272 foreach($matches as $mtch) {
4273 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4277 // If item is a link to a photo resource, nuke all the associated photos
4278 // (visitors will not have photo resources)
4279 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4280 // generate a resource-id and therefore aren't intimately linked to the item.
4282 if(strlen($item['resource-id'])) {
4283 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4284 dbesc($item['resource-id']),
4285 intval($item['uid'])
4287 // ignore the result
4290 // If item is a link to an event, nuke the event record.
4292 if(intval($item['event-id'])) {
4293 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4294 intval($item['event-id']),
4295 intval($item['uid'])
4297 // ignore the result
4300 // clean up item_id and sign meta-data tables
4303 // Old code - caused very long queries and warning entries in the mysql logfiles:
4305 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4306 intval($item['id']),
4307 intval($item['uid'])
4310 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4311 intval($item['id']),
4312 intval($item['uid'])
4316 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4318 // Creating list of parents
4319 $r = q("select id from item where parent = %d and uid = %d",
4320 intval($item['id']),
4321 intval($item['uid'])
4326 foreach ($r AS $row) {
4327 if ($parentid != "")
4330 $parentid .= $row["id"];
4334 if ($parentid != "") {
4335 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4337 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4340 // If it's the parent of a comment thread, kill all the kids
4342 if($item['uri'] == $item['parent-uri']) {
4343 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4344 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4345 dbesc(datetime_convert()),
4346 dbesc(datetime_convert()),
4347 dbesc($item['parent-uri']),
4348 intval($item['uid'])
4350 create_tags_from_item($item['parent-uri'], $item['uid']);
4351 create_files_from_item($item['parent-uri'], $item['uid']);
4352 delete_thread_uri($item['parent-uri'], $item['uid']);
4353 // ignore the result
4356 // ensure that last-child is set in case the comment that had it just got wiped.
4357 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4358 dbesc(datetime_convert()),
4359 dbesc($item['parent-uri']),
4360 intval($item['uid'])
4362 // who is the last child now?
4363 $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",
4364 dbesc($item['parent-uri']),
4365 intval($item['uid'])
4368 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4373 // Add a relayable_retraction signature for Diaspora.
4374 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4376 $drop_id = intval($item['id']);
4378 // send the notification upstream/downstream as the case may be
4380 proc_run('php',"include/notifier.php","drop","$drop_id");
4384 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4390 notice( t('Permission denied.') . EOL);
4391 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4398 function first_post_date($uid,$wall = false) {
4399 $r = q("select id, created from item
4400 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4402 order by created asc limit 1",
4404 intval($wall ? 1 : 0)
4407 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4408 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4413 function posted_dates($uid,$wall) {
4414 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4416 $dthen = first_post_date($uid,$wall);
4420 // If it's near the end of a long month, backup to the 28th so that in
4421 // consecutive loops we'll always get a whole month difference.
4423 if(intval(substr($dnow,8)) > 28)
4424 $dnow = substr($dnow,0,8) . '28';
4425 if(intval(substr($dthen,8)) > 28)
4426 $dnow = substr($dthen,0,8) . '28';
4429 // Starting with the current month, get the first and last days of every
4430 // month down to and including the month of the first post
4431 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4432 $dstart = substr($dnow,0,8) . '01';
4433 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4434 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4435 $end_month = datetime_convert('','',$dend,'Y-m-d');
4436 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4437 $ret[] = array($str,$end_month,$start_month);
4438 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4444 function posted_date_widget($url,$uid,$wall) {
4447 if(! feature_enabled($uid,'archives'))
4450 // For former Facebook folks that left because of "timeline"
4452 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4455 $ret = posted_dates($uid,$wall);
4459 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4460 '$title' => t('Archives'),
4461 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4468 function store_diaspora_retract_sig($item, $user, $baseurl) {
4469 // Note that we can't add a target_author_signature
4470 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4471 // the comment, that means we're the home of the post, and Diaspora will only
4472 // check the parent_author_signature of retractions that it doesn't have to relay further
4474 // I don't think this function gets called for an "unlike," but I'll check anyway
4476 $enabled = intval(get_config('system','diaspora_enabled'));
4478 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4482 logger('drop_item: storing diaspora retraction signature');
4484 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4486 if(local_user() == $item['uid']) {
4488 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4489 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4492 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4493 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4496 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4497 // only handles DFRN deletes
4498 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4499 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4500 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4506 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4507 intval($item['id']),
4508 dbesc($signed_text),