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 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
442 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
444 $res['body'] = nl2br($res['body']);
447 // removing the content of the title if its identically to the body
448 // This helps with auto generated titles e.g. from tumblr
449 if (title_is_body($res["title"], $res["body"]))
453 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
457 // look for a photo. We should check media size and find the best one,
458 // but for now let's just find any author photo
460 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
462 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
463 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
464 foreach($base as $link) {
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
472 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
474 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
475 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
476 if($base && count($base)) {
477 foreach($base as $link) {
478 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
479 $res['author-link'] = unxmlify($link['attribs']['']['href']);
480 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
481 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
482 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
488 // No photo/profile-link on the item - look at the feed level
490 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
491 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
492 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
493 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 foreach($base as $link) {
495 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
496 $res['author-link'] = unxmlify($link['attribs']['']['href']);
497 if(! $res['author-avatar']) {
498 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
499 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
504 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
506 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
507 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
509 if($base && count($base)) {
510 foreach($base as $link) {
511 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
512 $res['author-link'] = unxmlify($link['attribs']['']['href']);
513 if(! (x($res,'author-avatar'))) {
514 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
515 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
522 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
523 if($apps && $apps[0]['attribs']['']['source']) {
524 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
525 if($res['app'] === 'web')
526 $res['app'] = 'OStatus';
529 // base64 encoded json structure representing Diaspora signature
531 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
533 $res['dsprsig'] = unxmlify($dsig[0]['data']);
536 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
538 $res['guid'] = unxmlify($dguid[0]['data']);
540 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
542 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
546 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
549 $have_real_body = false;
551 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
553 $have_real_body = true;
554 $res['body'] = $rawenv[0]['data'];
555 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
556 // make sure nobody is trying to sneak some html tags by us
557 $res['body'] = notags(base64url_decode($res['body']));
561 $res['body'] = limit_body_size($res['body']);
563 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
564 // the content type. Our own network only emits text normally, though it might have been converted to
565 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
566 // have to assume it is all html and needs to be purified.
568 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
569 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
570 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
573 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
575 $res['body'] = reltoabs($res['body'],$base_url);
577 $res['body'] = html2bb_video($res['body']);
579 $res['body'] = oembed_html2bbcode($res['body']);
581 $config = HTMLPurifier_Config::createDefault();
582 $config->set('Cache.DefinitionImpl', null);
584 // we shouldn't need a whitelist, because the bbcode converter
585 // will strip out any unsupported tags.
587 $purifier = new HTMLPurifier($config);
588 $res['body'] = $purifier->purify($res['body']);
590 $res['body'] = @html2bbcode($res['body']);
594 elseif(! $have_real_body) {
596 // it's not one of our messages and it has no tags
597 // so it's probably just text. We'll escape it just to be safe.
599 $res['body'] = escape_tags($res['body']);
603 // this tag is obsolete but we keep it for really old sites
605 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
606 if($allow && $allow[0]['data'] == 1)
607 $res['last-child'] = 1;
609 $res['last-child'] = 0;
611 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
612 if($private && intval($private[0]['data']) > 0)
613 $res['private'] = intval($private[0]['data']);
617 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
618 if($extid && $extid[0]['data'])
619 $res['extid'] = $extid[0]['data'];
621 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
623 $res['location'] = unxmlify($rawlocation[0]['data']);
626 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
628 $res['created'] = unxmlify($rawcreated[0]['data']);
631 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
633 $res['edited'] = unxmlify($rawedited[0]['data']);
635 if((x($res,'edited')) && (! (x($res,'created'))))
636 $res['created'] = $res['edited'];
638 if(! $res['created'])
639 $res['created'] = $item->get_date('c');
642 $res['edited'] = $item->get_date('c');
645 // Disallow time travelling posts
647 $d1 = strtotime($res['created']);
648 $d2 = strtotime($res['edited']);
649 $d3 = strtotime('now');
652 $res['created'] = datetime_convert();
654 $res['edited'] = datetime_convert();
656 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
657 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
658 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
659 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
660 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
661 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
662 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
663 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
664 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
666 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
667 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
669 foreach($base as $link) {
670 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
671 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
672 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
677 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
679 $res['coord'] = unxmlify($rawgeo[0]['data']);
682 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
684 // select between supported verbs
687 $res['verb'] = unxmlify($rawverb[0]['data']);
690 // translate OStatus unfollow to activity streams if it happened to get selected
692 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
693 $res['verb'] = ACTIVITY_UNFOLLOW;
695 $cats = $item->get_categories();
698 foreach($cats as $cat) {
699 $term = $cat->get_term();
701 $term = $cat->get_label();
702 $scheme = $cat->get_scheme();
703 if($scheme && $term && stristr($scheme,'X-DFRN:'))
704 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
706 $tag_arr[] = notags(trim($term));
708 $res['tag'] = implode(',', $tag_arr);
711 $attach = $item->get_enclosures();
714 foreach($attach as $att) {
715 $len = intval($att->get_length());
716 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
717 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
718 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
719 if(strpos($type,';'))
720 $type = substr($type,0,strpos($type,';'));
721 if((! $link) || (strpos($link,'http') !== 0))
727 $type = 'application/octet-stream';
729 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
731 $res['attach'] = implode(',', $att_arr);
734 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
737 $res['object'] = '<object>' . "\n";
738 $child = $rawobj[0]['child'];
739 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
740 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
741 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
743 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
744 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
745 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
746 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
748 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
750 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
752 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
753 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
754 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
755 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
757 $body = html2bb_video($body);
759 $config = HTMLPurifier_Config::createDefault();
760 $config->set('Cache.DefinitionImpl', null);
762 $purifier = new HTMLPurifier($config);
763 $body = $purifier->purify($body);
764 $body = html2bbcode($body);
767 $res['object'] .= '<content>' . $body . '</content>' . "\n";
770 $res['object'] .= '</object>' . "\n";
773 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
776 $res['target'] = '<target>' . "\n";
777 $child = $rawobj[0]['child'];
778 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
779 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
781 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
782 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
783 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
784 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
786 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
788 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
790 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
791 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
792 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
793 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
795 $body = html2bb_video($body);
797 $config = HTMLPurifier_Config::createDefault();
798 $config->set('Cache.DefinitionImpl', null);
800 $purifier = new HTMLPurifier($config);
801 $body = $purifier->purify($body);
802 $body = html2bbcode($body);
805 $res['target'] .= '<content>' . $body . '</content>' . "\n";
808 $res['target'] .= '</target>' . "\n";
811 // This is some experimental stuff. By now retweets are shown with "RT:"
812 // But: There is data so that the message could be shown similar to native retweets
813 // There is some better way to parse this array - but it didn't worked for me.
814 $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"];
815 if (is_array($child)) {
816 logger('get_atom_elements: Looking for status.net repeated message');
818 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
819 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
820 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
821 $uri = $author["uri"][0]["data"];
822 $name = $author["name"][0]["data"];
823 $avatar = @array_shift($author["link"][2]["attribs"]);
824 $avatar = $avatar["href"];
826 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
827 logger('get_atom_elements: fixing sender of repeated message.');
829 if (!intval(get_config('system','wall-to-wall_share'))) {
830 $prefix = "[share author='".str_replace("'", "'",$name).
832 "' avatar='".$avatar.
833 "' link='".$orig_uri."']";
835 $res["body"] = $prefix.html2bbcode($message)."[/share]";
837 $res["owner-name"] = $res["author-name"];
838 $res["owner-link"] = $res["author-link"];
839 $res["owner-avatar"] = $res["author-avatar"];
841 $res["author-name"] = $name;
842 $res["author-link"] = $uri;
843 $res["author-avatar"] = $avatar;
845 $res["body"] = html2bbcode($message);
850 // Search for ostatus conversation url
851 $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"];
853 if (is_array($links)) {
854 foreach ($links as $link) {
855 $conversation = array_shift($link["attribs"]);
857 if ($conversation["rel"] == "ostatus:conversation") {
858 $res["ostatus_conversation"] = $conversation["href"];
859 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
864 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
865 $res["body"] = $res["title"].add_page_info($res['plink']);
867 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
868 $res["body"] = add_page_info_to_body($res["body"]);
869 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
870 $res["body"] = add_page_info_to_body($res["body"]);
873 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
875 call_hooks('parse_atom', $arr);
880 function add_page_info($url, $no_photos = false, $photo = "") {
881 require_once("mod/parse_url.php");
883 $data = parseurl_getsiteinfo($url, true);
885 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
887 // It maybe is a rich content, but if it does have everything that a link has,
888 // then treat it that way
889 if (($data["type"] == "rich") AND is_string($data["title"]) AND
890 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
891 $data["type"] = "link";
893 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
896 if ($no_photos AND ($data["type"] == "photo"))
899 if (($data["type"] != "photo") AND is_string($data["title"]))
900 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
902 if (($data["type"] != "video") AND ($photo != ""))
903 $text .= '[img]'.$photo.'[/img]';
904 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
905 $imagedata = $data["images"][0];
906 $text .= '[img]'.$imagedata["src"].'[/img]';
909 if (($data["type"] != "photo") AND is_string($data["text"]))
910 $text .= "[quote]".$data["text"]."[/quote]";
912 return("\n[class=type-".$data["type"]."]".$text."[/class]");
915 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
917 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
919 $URLSearchString = "^\[\]";
921 // Adding these spaces is a quick hack due to my problems with regular expressions :)
922 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
925 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
927 // Convert urls without bbcode elements
928 if (!$matches AND $texturl) {
929 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
931 // Yeah, a hack. I really hate regular expressions :)
933 $matches[1] = $matches[2];
937 $body .= add_page_info($matches[1], $no_photos);
942 function encode_rel_links($links) {
944 if(! ((is_array($links)) && (count($links))))
946 foreach($links as $link) {
948 if($link['attribs']['']['rel'])
949 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
950 if($link['attribs']['']['type'])
951 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
952 if($link['attribs']['']['href'])
953 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
954 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
955 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
956 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
957 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
965 function item_store($arr,$force_parent = false) {
967 // If a Diaspora signature structure was passed in, pull it out of the
968 // item array and set it aside for later storage.
971 if(x($arr,'dsprsig')) {
972 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
973 unset($arr['dsprsig']);
976 // if an OStatus conversation url was passed in, it is stored and then
977 // removed from the array.
978 $ostatus_conversation = null;
980 if (isset($arr["ostatus_conversation"])) {
981 $ostatus_conversation = $arr["ostatus_conversation"];
982 unset($arr["ostatus_conversation"]);
985 if(x($arr, 'gravity'))
986 $arr['gravity'] = intval($arr['gravity']);
987 elseif($arr['parent-uri'] === $arr['uri'])
989 elseif(activity_match($arr['verb'],ACTIVITY_POST))
992 $arr['gravity'] = 6; // extensible catchall
995 $arr['type'] = 'remote';
999 /* check for create date and expire time */
1000 $uid = intval($arr['uid']);
1001 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1003 $expire_interval = $r[0]['expire'];
1004 if ($expire_interval>0) {
1005 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1006 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1007 if ($created_date < $expire_date) {
1008 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1014 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1015 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1016 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1017 // $arr['body'] = strip_tags($arr['body']);
1020 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1021 require_once('library/langdet/Text/LanguageDetect.php');
1022 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1023 $l = new Text_LanguageDetect;
1024 //$lng = $l->detectConfidence($naked_body);
1025 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1026 $lng = $l->detect($naked_body, 3);
1028 if (sizeof($lng) > 0) {
1031 foreach ($lng as $language => $score) {
1032 if ($postopts == "")
1033 $postopts = "lang=";
1037 $postopts .= $language.";".$score;
1039 $arr['postopts'] = $postopts;
1043 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1044 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1045 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1046 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1047 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1048 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1049 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1050 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1051 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1052 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1053 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1054 $arr['commented'] = datetime_convert();
1055 $arr['received'] = datetime_convert();
1056 $arr['changed'] = datetime_convert();
1057 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1058 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1059 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1060 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1061 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1062 $arr['deleted'] = 0;
1063 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1064 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1065 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1066 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1067 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1068 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1069 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1070 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1071 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1072 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1073 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1074 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1075 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1076 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1077 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1078 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1079 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1080 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1081 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1082 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1084 if ($arr['network'] == "") {
1085 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1086 intval($arr['contact-id']),
1091 $arr['network'] = $r[0]["network"];
1093 // Fallback to friendica (why is it empty in some cases?)
1094 if ($arr['network'] == "")
1095 $arr['network'] = NETWORK_DFRN;
1097 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1100 $arr['thr-parent'] = $arr['parent-uri'];
1101 if($arr['parent-uri'] === $arr['uri']) {
1103 $parent_deleted = 0;
1104 $allow_cid = $arr['allow_cid'];
1105 $allow_gid = $arr['allow_gid'];
1106 $deny_cid = $arr['deny_cid'];
1107 $deny_gid = $arr['deny_gid'];
1111 // find the parent and snarf the item id and ACLs
1112 // and anything else we need to inherit
1114 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1115 dbesc($arr['parent-uri']),
1121 // is the new message multi-level threaded?
1122 // even though we don't support it now, preserve the info
1123 // and re-attach to the conversation parent.
1125 if($r[0]['uri'] != $r[0]['parent-uri']) {
1126 $arr['parent-uri'] = $r[0]['parent-uri'];
1127 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1128 ORDER BY `id` ASC LIMIT 1",
1129 dbesc($r[0]['parent-uri']),
1130 dbesc($r[0]['parent-uri']),
1137 $parent_id = $r[0]['id'];
1138 $parent_deleted = $r[0]['deleted'];
1139 $allow_cid = $r[0]['allow_cid'];
1140 $allow_gid = $r[0]['allow_gid'];
1141 $deny_cid = $r[0]['deny_cid'];
1142 $deny_gid = $r[0]['deny_gid'];
1143 $arr['wall'] = $r[0]['wall'];
1145 // if the parent is private, force privacy for the entire conversation
1146 // This differs from the above settings as it subtly allows comments from
1147 // email correspondents to be private even if the overall thread is not.
1149 if($r[0]['private'])
1150 $arr['private'] = $r[0]['private'];
1152 // Edge case. We host a public forum that was originally posted to privately.
1153 // The original author commented, but as this is a comment, the permissions
1154 // weren't fixed up so it will still show the comment as private unless we fix it here.
1156 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1157 $arr['private'] = 0;
1160 // If its a post from myself then tag the thread as "mention"
1161 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1162 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1165 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1166 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1167 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1168 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1169 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1175 // Allow one to see reply tweets from status.net even when
1176 // we don't have or can't see the original post.
1179 logger('item_store: $force_parent=true, reply converted to top-level post.');
1181 $arr['parent-uri'] = $arr['uri'];
1182 $arr['gravity'] = 0;
1185 logger('item_store: item parent was not found - ignoring item');
1189 $parent_deleted = 0;
1193 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1197 if($r && count($r)) {
1198 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1202 call_hooks('post_remote',$arr);
1204 if(x($arr,'cancel')) {
1205 logger('item_store: post cancelled by plugin.');
1211 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1213 $r = dbq("INSERT INTO `item` (`"
1214 . implode("`, `", array_keys($arr))
1216 . implode("', '", array_values($arr))
1219 // find the item we just created
1221 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1222 $arr['uri'], // already dbesc'd
1227 $current_post = $r[0]['id'];
1228 logger('item_store: created item ' . $current_post);
1230 // Only check for notifications on start posts
1231 if ($arr['parent-uri'] === $arr['uri']) {
1232 add_thread($r[0]['id']);
1233 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1235 // Send a notification for every new post?
1236 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1237 intval($arr['contact-id']),
1242 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1243 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1244 intval($arr['uid']));
1246 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1247 intval($current_post),
1253 require_once('include/enotify.php');
1255 'type' => NOTIFY_SHARE,
1256 'notify_flags' => $u[0]['notify-flags'],
1257 'language' => $u[0]['language'],
1258 'to_name' => $u[0]['username'],
1259 'to_email' => $u[0]['email'],
1260 'uid' => $u[0]['uid'],
1262 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1263 'source_name' => $item[0]['author-name'],
1264 'source_link' => $item[0]['author-link'],
1265 'source_photo' => $item[0]['author-avatar'],
1266 'verb' => ACTIVITY_TAG,
1269 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1274 logger('item_store: could not locate created item');
1278 logger('item_store: duplicated post occurred. Removing duplicates.');
1279 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1281 intval($arr['uid']),
1282 intval($current_post)
1286 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1287 $parent_id = $current_post;
1289 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1292 $private = $arr['private'];
1294 // Set parent id - and also make sure to inherit the parent's ACLs.
1296 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1297 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1304 intval($parent_deleted),
1305 intval($current_post)
1308 // Complete ostatus threads
1309 if ($ostatus_conversation)
1310 complete_conversation($current_post, $ostatus_conversation);
1312 $arr['id'] = $current_post;
1313 $arr['parent'] = $parent_id;
1314 $arr['allow_cid'] = $allow_cid;
1315 $arr['allow_gid'] = $allow_gid;
1316 $arr['deny_cid'] = $deny_cid;
1317 $arr['deny_gid'] = $deny_gid;
1318 $arr['private'] = $private;
1319 $arr['deleted'] = $parent_deleted;
1321 // update the commented timestamp on the parent
1323 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1324 dbesc(datetime_convert()),
1325 dbesc(datetime_convert()),
1328 update_thread($parent_id);
1331 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1332 intval($current_post),
1333 dbesc($dsprsig->signed_text),
1334 dbesc($dsprsig->signature),
1335 dbesc($dsprsig->signer)
1341 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1344 if($arr['last-child']) {
1345 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1347 intval($arr['uid']),
1348 intval($current_post)
1352 $deleted = tag_deliver($arr['uid'],$current_post);
1354 // current post can be deleted if is for a communuty page and no mention are
1358 // Store the fresh generated item into the cache
1359 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1361 if (($cachefile != '') AND !file_exists($cachefile)) {
1362 $s = prepare_text($arr['body']);
1364 $stamp1 = microtime(true);
1365 file_put_contents($cachefile, $s);
1366 $a->save_timestamp($stamp1, "file");
1367 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1370 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1371 if (count($r) == 1) {
1372 call_hooks('post_remote_end', $r[0]);
1374 logger('item_store: new item not found in DB, id ' . $current_post);
1378 create_tags_from_item($current_post);
1379 create_files_from_item($current_post);
1381 return $current_post;
1384 function get_item_contact($item,$contacts) {
1385 if(! count($contacts) || (! is_array($item)))
1387 foreach($contacts as $contact) {
1388 if($contact['id'] == $item['contact-id']) {
1390 break; // NOTREACHED
1397 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1399 * @param int $item_id
1400 * @return bool true if item was deleted, else false
1402 function tag_deliver($uid,$item_id) {
1410 $u = q("select * from user where uid = %d limit 1",
1416 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1417 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1420 $i = q("select * from item where id = %d and uid = %d limit 1",
1429 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1431 // Diaspora uses their own hardwired link URL in @-tags
1432 // instead of the one we supply with webfinger
1434 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1436 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1438 foreach($matches as $mtch) {
1439 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1441 logger('tag_deliver: mention found: ' . $mtch[2]);
1447 if ( ($community_page || $prvgroup) &&
1448 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1449 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1451 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1452 q("DELETE FROM item WHERE id = %d and uid = %d",
1462 // send a notification
1464 // use a local photo if we have one
1466 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1467 intval($u[0]['uid']),
1468 dbesc(normalise_link($item['author-link']))
1470 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1473 require_once('include/enotify.php');
1475 'type' => NOTIFY_TAGSELF,
1476 'notify_flags' => $u[0]['notify-flags'],
1477 'language' => $u[0]['language'],
1478 'to_name' => $u[0]['username'],
1479 'to_email' => $u[0]['email'],
1480 'uid' => $u[0]['uid'],
1482 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1483 'source_name' => $item['author-name'],
1484 'source_link' => $item['author-link'],
1485 'source_photo' => $photo,
1486 'verb' => ACTIVITY_TAG,
1491 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1493 call_hooks('tagged', $arr);
1495 if((! $community_page) && (! $prvgroup))
1499 // tgroup delivery - setup a second delivery chain
1500 // prevent delivery looping - only proceed
1501 // if the message originated elsewhere and is a top-level post
1503 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1506 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1509 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1510 intval($u[0]['uid'])
1515 // also reset all the privacy bits to the forum default permissions
1517 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1519 $forum_mode = (($prvgroup) ? 2 : 1);
1521 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1522 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1523 intval($forum_mode),
1524 dbesc($c[0]['name']),
1525 dbesc($c[0]['url']),
1526 dbesc($c[0]['thumb']),
1528 dbesc($u[0]['allow_cid']),
1529 dbesc($u[0]['allow_gid']),
1530 dbesc($u[0]['deny_cid']),
1531 dbesc($u[0]['deny_gid']),
1534 update_thread($item_id);
1536 proc_run('php','include/notifier.php','tgroup',$item_id);
1542 function tgroup_check($uid,$item) {
1548 // check that the message originated elsewhere and is a top-level post
1550 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1554 $u = q("select * from user where uid = %d limit 1",
1560 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1561 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1564 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1566 // Diaspora uses their own hardwired link URL in @-tags
1567 // instead of the one we supply with webfinger
1569 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1571 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1573 foreach($matches as $mtch) {
1574 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1576 logger('tgroup_check: mention found: ' . $mtch[2]);
1584 if((! $community_page) && (! $prvgroup))
1598 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1602 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1604 if($contact['duplex'] && $contact['dfrn-id'])
1605 $idtosend = '0:' . $orig_id;
1606 if($contact['duplex'] && $contact['issued-id'])
1607 $idtosend = '1:' . $orig_id;
1609 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1611 $rino_enable = get_config('system','rino_encrypt');
1616 $ssl_val = intval(get_config('system','ssl_policy'));
1620 case SSL_POLICY_FULL:
1621 $ssl_policy = 'full';
1623 case SSL_POLICY_SELFSIGN:
1624 $ssl_policy = 'self';
1626 case SSL_POLICY_NONE:
1628 $ssl_policy = 'none';
1632 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1634 logger('dfrn_deliver: ' . $url);
1636 $xml = fetch_url($url);
1638 $curl_stat = $a->get_curl_code();
1640 return(-1); // timed out
1642 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1647 if(strpos($xml,'<?xml') === false) {
1648 logger('dfrn_deliver: no valid XML returned');
1649 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1653 $res = parse_xml_string($xml);
1655 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1656 return (($res->status) ? $res->status : 3);
1658 $postvars = array();
1659 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1660 $challenge = hex2bin((string) $res->challenge);
1661 $perm = (($res->perm) ? $res->perm : null);
1662 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1663 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1664 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1666 if($owner['page-flags'] == PAGE_PRVGROUP)
1669 $final_dfrn_id = '';
1672 if((($perm == 'rw') && (! intval($contact['writable'])))
1673 || (($perm == 'r') && (intval($contact['writable'])))) {
1674 q("update contact set writable = %d where id = %d",
1675 intval(($perm == 'rw') ? 1 : 0),
1676 intval($contact['id'])
1678 $contact['writable'] = (string) 1 - intval($contact['writable']);
1682 if(($contact['duplex'] && strlen($contact['pubkey']))
1683 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1684 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1685 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1686 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1689 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1690 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1693 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1695 if(strpos($final_dfrn_id,':') == 1)
1696 $final_dfrn_id = substr($final_dfrn_id,2);
1698 if($final_dfrn_id != $orig_id) {
1699 logger('dfrn_deliver: wrong dfrn_id.');
1700 // did not decode properly - cannot trust this site
1704 $postvars['dfrn_id'] = $idtosend;
1705 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1707 $postvars['dissolve'] = '1';
1710 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1711 $postvars['data'] = $atom;
1712 $postvars['perm'] = 'rw';
1715 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1716 $postvars['perm'] = 'r';
1719 $postvars['ssl_policy'] = $ssl_policy;
1722 $postvars['page'] = $page;
1724 if($rino && $rino_allowed && (! $dissolve)) {
1725 $key = substr(random_string(),0,16);
1726 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1727 $postvars['data'] = $data;
1728 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1731 if($dfrn_version >= 2.1) {
1732 if(($contact['duplex'] && strlen($contact['pubkey']))
1733 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1734 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1736 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1739 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1743 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1744 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1747 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1751 logger('md5 rawkey ' . md5($postvars['key']));
1753 $postvars['key'] = bin2hex($postvars['key']);
1756 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1758 $xml = post_url($contact['notify'],$postvars);
1760 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1762 $curl_stat = $a->get_curl_code();
1763 if((! $curl_stat) || (! strlen($xml)))
1764 return(-1); // timed out
1766 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1769 if(strpos($xml,'<?xml') === false) {
1770 logger('dfrn_deliver: phase 2: no valid XML returned');
1771 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1775 if($contact['term-date'] != '0000-00-00 00:00:00') {
1776 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1777 require_once('include/Contact.php');
1778 unmark_for_death($contact);
1781 $res = parse_xml_string($xml);
1783 return $res->status;
1788 This function returns true if $update has an edited timestamp newer
1789 than $existing, i.e. $update contains new data which should override
1790 what's already there. If there is no timestamp yet, the update is
1791 assumed to be newer. If the update has no timestamp, the existing
1792 item is assumed to be up-to-date. If the timestamps are equal it
1793 assumes the update has been seen before and should be ignored.
1795 function edited_timestamp_is_newer($existing, $update) {
1796 if (!x($existing,'edited') || !$existing['edited']) {
1799 if (!x($update,'edited') || !$update['edited']) {
1802 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1803 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1804 return (strcmp($existing_edited, $update_edited) < 0);
1809 * consume_feed - process atom feed and update anything/everything we might need to update
1811 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1813 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1814 * It is this person's stuff that is going to be updated.
1815 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1816 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1817 * have a contact record.
1818 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1819 * might not) try and subscribe to it.
1820 * $datedir sorts in reverse order
1821 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1822 * imported prior to its children being seen in the stream unless we are certain
1823 * of how the feed is arranged/ordered.
1824 * With $pass = 1, we only pull parent items out of the stream.
1825 * With $pass = 2, we only pull children (comments/likes).
1827 * So running this twice, first with pass 1 and then with pass 2 will do the right
1828 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1829 * model where comments can have sub-threads. That would require some massive sorting
1830 * to get all the feed items into a mostly linear ordering, and might still require
1834 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1836 require_once('library/simplepie/simplepie.inc');
1838 if(! strlen($xml)) {
1839 logger('consume_feed: empty input');
1843 $feed = new SimplePie();
1844 $feed->set_raw_data($xml);
1846 $feed->enable_order_by_date(true);
1848 $feed->enable_order_by_date(false);
1852 logger('consume_feed: Error parsing XML: ' . $feed->error());
1854 $permalink = $feed->get_permalink();
1856 // Check at the feed level for updated contact name and/or photo
1860 $photo_timestamp = '';
1864 $hubs = $feed->get_links('hub');
1865 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1868 $hub = implode(',', $hubs);
1870 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1872 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1874 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1875 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1876 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1877 $new_name = $elems['name'][0]['data'];
1879 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1880 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1881 $photo_url = $elems['link'][0]['attribs']['']['href'];
1884 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1885 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1889 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1890 logger('consume_feed: Updating photo for ' . $contact['name']);
1891 require_once("include/Photo.php");
1892 $photo_failure = false;
1893 $have_photo = false;
1895 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1896 intval($contact['id']),
1897 intval($contact['uid'])
1900 $resource_id = $r[0]['resource-id'];
1904 $resource_id = photo_new_resource();
1907 $img_str = fetch_url($photo_url,true);
1908 // guess mimetype from headers or filename
1909 $type = guess_image_type($photo_url,true);
1912 $img = new Photo($img_str, $type);
1913 if($img->is_valid()) {
1915 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1916 dbesc($resource_id),
1917 intval($contact['id']),
1918 intval($contact['uid'])
1922 $img->scaleImageSquare(175);
1924 $hash = $resource_id;
1925 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1927 $img->scaleImage(80);
1928 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1930 $img->scaleImage(48);
1931 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1935 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1936 WHERE `uid` = %d AND `id` = %d",
1937 dbesc(datetime_convert()),
1938 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1939 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1940 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1941 intval($contact['uid']),
1942 intval($contact['id'])
1947 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1948 $r = q("select * from contact where uid = %d and id = %d limit 1",
1949 intval($contact['uid']),
1950 intval($contact['id'])
1953 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1954 dbesc(notags(trim($new_name))),
1955 dbesc(datetime_convert()),
1956 intval($contact['uid']),
1957 intval($contact['id'])
1960 // do our best to update the name on content items
1963 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1964 dbesc(notags(trim($new_name))),
1965 dbesc($r[0]['name']),
1966 dbesc($r[0]['url']),
1967 intval($contact['uid'])
1972 if(strlen($birthday)) {
1973 if(substr($birthday,0,4) != $contact['bdyear']) {
1974 logger('consume_feed: updating birthday: ' . $birthday);
1978 * Add new birthday event for this person
1980 * $bdtext is just a readable placeholder in case the event is shared
1981 * with others. We will replace it during presentation to our $importer
1982 * to contain a sparkle link and perhaps a photo.
1986 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1987 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1990 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1991 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1992 intval($contact['uid']),
1993 intval($contact['id']),
1994 dbesc(datetime_convert()),
1995 dbesc(datetime_convert()),
1996 dbesc(datetime_convert('UTC','UTC', $birthday)),
1997 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2006 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2007 dbesc(substr($birthday,0,4)),
2008 intval($contact['uid']),
2009 intval($contact['id'])
2012 // This function is called twice without reloading the contact
2013 // Make sure we only create one event. This is why &$contact
2014 // is a reference var in this function
2016 $contact['bdyear'] = substr($birthday,0,4);
2021 $community_page = 0;
2022 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2024 $community_page = intval($rawtags[0]['data']);
2026 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2027 q("update contact set forum = %d where id = %d",
2028 intval($community_page),
2029 intval($contact['id'])
2031 $contact['forum'] = (string) $community_page;
2035 // process any deleted entries
2037 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2038 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2039 foreach($del_entries as $dentry) {
2041 if(isset($dentry['attribs']['']['ref'])) {
2042 $uri = $dentry['attribs']['']['ref'];
2044 if(isset($dentry['attribs']['']['when'])) {
2045 $when = $dentry['attribs']['']['when'];
2046 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2049 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2051 if($deleted && is_array($contact)) {
2052 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2053 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2055 intval($importer['uid']),
2056 intval($contact['id'])
2061 if(! $item['deleted'])
2062 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2064 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2065 $xo = parse_xml_string($item['object'],false);
2066 $xt = parse_xml_string($item['target'],false);
2067 if($xt->type === ACTIVITY_OBJ_NOTE) {
2068 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2070 intval($importer['importer_uid'])
2074 // For tags, the owner cannot remove the tag on the author's copy of the post.
2076 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2077 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2078 $author_copy = (($item['origin']) ? true : false);
2080 if($owner_remove && $author_copy)
2082 if($author_remove || $owner_remove) {
2083 $tags = explode(',',$i[0]['tag']);
2086 foreach($tags as $tag)
2087 if(trim($tag) !== trim($xo->body))
2088 $newtags[] = trim($tag);
2090 q("update item set tag = '%s' where id = %d",
2091 dbesc(implode(',',$newtags)),
2094 create_tags_from_item($i[0]['id']);
2100 if($item['uri'] == $item['parent-uri']) {
2101 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2102 `body` = '', `title` = ''
2103 WHERE `parent-uri` = '%s' AND `uid` = %d",
2105 dbesc(datetime_convert()),
2106 dbesc($item['uri']),
2107 intval($importer['uid'])
2109 create_tags_from_itemuri($item['uri'], $importer['uid']);
2110 create_files_from_itemuri($item['uri'], $importer['uid']);
2111 update_thread_uri($item['uri'], $importer['uid']);
2114 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2115 `body` = '', `title` = ''
2116 WHERE `uri` = '%s' AND `uid` = %d",
2118 dbesc(datetime_convert()),
2120 intval($importer['uid'])
2122 create_tags_from_itemuri($uri, $importer['uid']);
2123 create_files_from_itemuri($uri, $importer['uid']);
2124 if($item['last-child']) {
2125 // ensure that last-child is set in case the comment that had it just got wiped.
2126 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2127 dbesc(datetime_convert()),
2128 dbesc($item['parent-uri']),
2129 intval($item['uid'])
2131 // who is the last child now?
2132 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2133 ORDER BY `created` DESC LIMIT 1",
2134 dbesc($item['parent-uri']),
2135 intval($importer['uid'])
2138 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2149 // Now process the feed
2151 if($feed->get_item_quantity()) {
2153 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2155 // in inverse date order
2157 $items = array_reverse($feed->get_items());
2159 $items = $feed->get_items();
2162 foreach($items as $item) {
2165 $item_id = $item->get_id();
2166 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2167 if(isset($rawthread[0]['attribs']['']['ref'])) {
2169 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2172 if(($is_reply) && is_array($contact)) {
2177 // not allowed to post
2179 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2183 // Have we seen it? If not, import it.
2185 $item_id = $item->get_id();
2186 $datarray = get_atom_elements($feed, $item, $contact);
2188 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2189 $datarray['author-name'] = $contact['name'];
2190 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2191 $datarray['author-link'] = $contact['url'];
2192 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2193 $datarray['author-avatar'] = $contact['thumb'];
2195 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2196 logger('consume_feed: no author information! ' . print_r($datarray,true));
2200 $force_parent = false;
2201 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2202 if($contact['network'] === NETWORK_OSTATUS)
2203 $force_parent = true;
2204 if(strlen($datarray['title']))
2205 unset($datarray['title']);
2206 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2207 dbesc(datetime_convert()),
2209 intval($importer['uid'])
2211 $datarray['last-child'] = 1;
2212 update_thread_uri($parent_uri, $importer['uid']);
2216 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2218 intval($importer['uid'])
2221 // Update content if 'updated' changes
2224 if (edited_timestamp_is_newer($r[0], $datarray)) {
2226 // do not accept (ignore) an earlier edit than one we currently have.
2227 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2230 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2231 dbesc($datarray['title']),
2232 dbesc($datarray['body']),
2233 dbesc($datarray['tag']),
2234 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2235 dbesc(datetime_convert()),
2237 intval($importer['uid'])
2239 create_tags_from_itemuri($item_id, $importer['uid']);
2240 update_thread_uri($item_id, $importer['uid']);
2243 // update last-child if it changes
2245 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2246 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2247 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2248 dbesc(datetime_convert()),
2250 intval($importer['uid'])
2252 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2253 intval($allow[0]['data']),
2254 dbesc(datetime_convert()),
2256 intval($importer['uid'])
2258 update_thread_uri($item_id, $importer['uid']);
2264 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2265 // one way feed - no remote comment ability
2266 $datarray['last-child'] = 0;
2268 $datarray['parent-uri'] = $parent_uri;
2269 $datarray['uid'] = $importer['uid'];
2270 $datarray['contact-id'] = $contact['id'];
2271 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2272 $datarray['type'] = 'activity';
2273 $datarray['gravity'] = GRAVITY_LIKE;
2274 // only one like or dislike per person
2275 // splitted into two queries for performance issues
2276 $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",
2277 intval($datarray['uid']),
2278 intval($datarray['contact-id']),
2279 dbesc($datarray['verb']),
2285 $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",
2286 intval($datarray['uid']),
2287 intval($datarray['contact-id']),
2288 dbesc($datarray['verb']),
2295 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2296 $xo = parse_xml_string($datarray['object'],false);
2297 $xt = parse_xml_string($datarray['target'],false);
2299 if($xt->type == ACTIVITY_OBJ_NOTE) {
2300 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2302 intval($importer['importer_uid'])
2307 // extract tag, if not duplicate, add to parent item
2308 if($xo->id && $xo->content) {
2309 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2310 if(! (stristr($r[0]['tag'],$newtag))) {
2311 q("UPDATE item SET tag = '%s' WHERE id = %d",
2312 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2315 create_tags_from_item($r[0]['id']);
2321 $r = item_store($datarray,$force_parent);
2327 // Head post of a conversation. Have we seen it? If not, import it.
2329 $item_id = $item->get_id();
2331 $datarray = get_atom_elements($feed, $item, $contact);
2333 if(is_array($contact)) {
2334 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2335 $datarray['author-name'] = $contact['name'];
2336 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2337 $datarray['author-link'] = $contact['url'];
2338 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2339 $datarray['author-avatar'] = $contact['thumb'];
2342 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2343 logger('consume_feed: no author information! ' . print_r($datarray,true));
2347 // special handling for events
2349 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2350 $ev = bbtoevent($datarray['body']);
2351 if(x($ev,'desc') && x($ev,'start')) {
2352 $ev['uid'] = $importer['uid'];
2353 $ev['uri'] = $item_id;
2354 $ev['edited'] = $datarray['edited'];
2355 $ev['private'] = $datarray['private'];
2357 if(is_array($contact))
2358 $ev['cid'] = $contact['id'];
2359 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2361 intval($importer['uid'])
2364 $ev['id'] = $r[0]['id'];
2365 $xyz = event_store($ev);
2370 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2371 if(strlen($datarray['title']))
2372 unset($datarray['title']);
2373 $datarray['last-child'] = 1;
2377 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2379 intval($importer['uid'])
2382 // Update content if 'updated' changes
2385 if (edited_timestamp_is_newer($r[0], $datarray)) {
2387 // do not accept (ignore) an earlier edit than one we currently have.
2388 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2391 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2392 dbesc($datarray['title']),
2393 dbesc($datarray['body']),
2394 dbesc($datarray['tag']),
2395 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2396 dbesc(datetime_convert()),
2398 intval($importer['uid'])
2400 create_tags_from_itemuri($item_id, $importer['uid']);
2401 update_thread_uri($item_id, $importer['uid']);
2404 // update last-child if it changes
2406 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2407 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2408 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2409 intval($allow[0]['data']),
2410 dbesc(datetime_convert()),
2412 intval($importer['uid'])
2414 update_thread_uri($item_id, $importer['uid']);
2419 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2420 logger('consume-feed: New follower');
2421 new_follower($importer,$contact,$datarray,$item);
2424 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2425 lose_follower($importer,$contact,$datarray,$item);
2429 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2430 logger('consume-feed: New friend request');
2431 new_follower($importer,$contact,$datarray,$item,true);
2434 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2435 lose_sharer($importer,$contact,$datarray,$item);
2440 if(! is_array($contact))
2444 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2445 // one way feed - no remote comment ability
2446 $datarray['last-child'] = 0;
2448 if($contact['network'] === NETWORK_FEED)
2449 $datarray['private'] = 2;
2451 // This is my contact on another system, but it's really me.
2452 // Turn this into a wall post.
2454 if($contact['remote_self']) {
2455 $datarray['wall'] = 1;
2456 if($contact['network'] === NETWORK_FEED) {
2457 $datarray['private'] = 0;
2461 $datarray['parent-uri'] = $item_id;
2462 $datarray['uid'] = $importer['uid'];
2463 $datarray['contact-id'] = $contact['id'];
2465 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2466 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2467 // but otherwise there's a possible data mixup on the sender's system.
2468 // the tgroup delivery code called from item_store will correct it if it's a forum,
2469 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2470 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2471 $datarray['owner-name'] = $contact['name'];
2472 $datarray['owner-link'] = $contact['url'];
2473 $datarray['owner-avatar'] = $contact['thumb'];
2476 // We've allowed "followers" to reach this point so we can decide if they are
2477 // posting an @-tag delivery, which followers are allowed to do for certain
2478 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2480 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2484 $r = item_store($datarray);
2492 function local_delivery($importer,$data) {
2495 logger(__function__, LOGGER_TRACE);
2497 if($importer['readonly']) {
2498 // We aren't receiving stuff from this person. But we will quietly ignore them
2499 // rather than a blatant "go away" message.
2500 logger('local_delivery: ignoring');
2505 // Consume notification feed. This may differ from consuming a public feed in several ways
2506 // - might contain email or friend suggestions
2507 // - might contain remote followup to our message
2508 // - in which case we need to accept it and then notify other conversants
2509 // - we may need to send various email notifications
2511 $feed = new SimplePie();
2512 $feed->set_raw_data($data);
2513 $feed->enable_order_by_date(false);
2518 logger('local_delivery: Error parsing XML: ' . $feed->error());
2521 // Check at the feed level for updated contact name and/or photo
2525 $photo_timestamp = '';
2529 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2531 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2533 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2536 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2537 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2538 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2539 $new_name = $elems['name'][0]['data'];
2541 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2542 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2543 $photo_url = $elems['link'][0]['attribs']['']['href'];
2547 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2548 logger('local_delivery: Updating photo for ' . $importer['name']);
2549 require_once("include/Photo.php");
2550 $photo_failure = false;
2551 $have_photo = false;
2553 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2554 intval($importer['id']),
2555 intval($importer['importer_uid'])
2558 $resource_id = $r[0]['resource-id'];
2562 $resource_id = photo_new_resource();
2565 $img_str = fetch_url($photo_url,true);
2566 // guess mimetype from headers or filename
2567 $type = guess_image_type($photo_url,true);
2570 $img = new Photo($img_str, $type);
2571 if($img->is_valid()) {
2573 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2574 dbesc($resource_id),
2575 intval($importer['id']),
2576 intval($importer['importer_uid'])
2580 $img->scaleImageSquare(175);
2582 $hash = $resource_id;
2583 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2585 $img->scaleImage(80);
2586 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2588 $img->scaleImage(48);
2589 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2593 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2594 WHERE `uid` = %d AND `id` = %d",
2595 dbesc(datetime_convert()),
2596 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2597 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2598 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2599 intval($importer['importer_uid']),
2600 intval($importer['id'])
2605 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2606 $r = q("select * from contact where uid = %d and id = %d limit 1",
2607 intval($importer['importer_uid']),
2608 intval($importer['id'])
2611 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2612 dbesc(notags(trim($new_name))),
2613 dbesc(datetime_convert()),
2614 intval($importer['importer_uid']),
2615 intval($importer['id'])
2618 // do our best to update the name on content items
2621 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2622 dbesc(notags(trim($new_name))),
2623 dbesc($r[0]['name']),
2624 dbesc($r[0]['url']),
2625 intval($importer['importer_uid'])
2632 // Currently unsupported - needs a lot of work
2633 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2634 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2635 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2637 $newloc['uid'] = $importer['importer_uid'];
2638 $newloc['cid'] = $importer['id'];
2639 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2640 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2641 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2642 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2643 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2644 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2645 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2646 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2647 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2648 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2649 /** relocated user must have original key pair */
2650 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2651 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2653 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2656 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2657 intval($importer['id']),
2658 intval($importer['importer_uid']));
2663 $x = q("UPDATE contact SET
2673 `site-pubkey` = '%s'
2674 WHERE id=%d AND uid=%d;",
2675 dbesc($newloc['name']),
2676 dbesc($newloc['photo']),
2677 dbesc($newloc['thumb']),
2678 dbesc($newloc['micro']),
2679 dbesc($newloc['url']),
2680 dbesc($newloc['request']),
2681 dbesc($newloc['confirm']),
2682 dbesc($newloc['notify']),
2683 dbesc($newloc['poll']),
2684 dbesc($newloc['sitepubkey']),
2685 intval($importer['id']),
2686 intval($importer['importer_uid']));
2692 'owner-link' => array($old['url'], $newloc['url']),
2693 'author-link' => array($old['url'], $newloc['url']),
2694 'owner-avatar' => array($old['photo'], $newloc['photo']),
2695 'author-avatar' => array($old['photo'], $newloc['photo']),
2697 foreach ($fields as $n=>$f){
2698 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2701 intval($importer['importer_uid']));
2707 // merge with current record, current contents have priority
2708 // update record, set url-updated
2709 // update profile photos
2715 // handle friend suggestion notification
2717 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2718 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2719 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2721 $fsugg['uid'] = $importer['importer_uid'];
2722 $fsugg['cid'] = $importer['id'];
2723 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2724 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2725 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2726 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2727 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2729 // Does our member already have a friend matching this description?
2731 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2732 dbesc($fsugg['name']),
2733 dbesc(normalise_link($fsugg['url'])),
2734 intval($fsugg['uid'])
2739 // Do we already have an fcontact record for this person?
2742 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2743 dbesc($fsugg['url']),
2744 dbesc($fsugg['name']),
2745 dbesc($fsugg['request'])
2750 // OK, we do. Do we already have an introduction for this person ?
2751 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2752 intval($fsugg['uid']),
2759 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2760 dbesc($fsugg['name']),
2761 dbesc($fsugg['url']),
2762 dbesc($fsugg['photo']),
2763 dbesc($fsugg['request'])
2765 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2766 dbesc($fsugg['url']),
2767 dbesc($fsugg['name']),
2768 dbesc($fsugg['request'])
2773 // database record did not get created. Quietly give up.
2778 $hash = random_string();
2780 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2781 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2782 intval($fsugg['uid']),
2784 intval($fsugg['cid']),
2785 dbesc($fsugg['body']),
2787 dbesc(datetime_convert()),
2792 'type' => NOTIFY_SUGGEST,
2793 'notify_flags' => $importer['notify-flags'],
2794 'language' => $importer['language'],
2795 'to_name' => $importer['username'],
2796 'to_email' => $importer['email'],
2797 'uid' => $importer['importer_uid'],
2799 'link' => $a->get_baseurl() . '/notifications/intros',
2800 'source_name' => $importer['name'],
2801 'source_link' => $importer['url'],
2802 'source_photo' => $importer['photo'],
2803 'verb' => ACTIVITY_REQ_FRIEND,
2812 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2813 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2815 logger('local_delivery: private message received');
2818 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2821 $msg['uid'] = $importer['importer_uid'];
2822 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2823 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2824 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2825 $msg['contact-id'] = $importer['id'];
2826 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2827 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2829 $msg['replied'] = 0;
2830 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2831 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2832 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2836 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2837 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2839 // send notifications.
2841 require_once('include/enotify.php');
2843 $notif_params = array(
2844 'type' => NOTIFY_MAIL,
2845 'notify_flags' => $importer['notify-flags'],
2846 'language' => $importer['language'],
2847 'to_name' => $importer['username'],
2848 'to_email' => $importer['email'],
2849 'uid' => $importer['importer_uid'],
2851 'source_name' => $msg['from-name'],
2852 'source_link' => $importer['url'],
2853 'source_photo' => $importer['thumb'],
2854 'verb' => ACTIVITY_POST,
2858 notification($notif_params);
2864 $community_page = 0;
2865 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2867 $community_page = intval($rawtags[0]['data']);
2869 if(intval($importer['forum']) != $community_page) {
2870 q("update contact set forum = %d where id = %d",
2871 intval($community_page),
2872 intval($importer['id'])
2874 $importer['forum'] = (string) $community_page;
2877 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2879 // process any deleted entries
2881 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2882 if(is_array($del_entries) && count($del_entries)) {
2883 foreach($del_entries as $dentry) {
2885 if(isset($dentry['attribs']['']['ref'])) {
2886 $uri = $dentry['attribs']['']['ref'];
2888 if(isset($dentry['attribs']['']['when'])) {
2889 $when = $dentry['attribs']['']['when'];
2890 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2893 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2897 // check for relayed deletes to our conversation
2900 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2902 intval($importer['importer_uid'])
2905 $parent_uri = $r[0]['parent-uri'];
2906 if($r[0]['id'] != $r[0]['parent'])
2913 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2916 logger('local_delivery: possible community delete');
2919 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2921 // was the top-level post for this reply written by somebody on this site?
2922 // Specifically, the recipient?
2924 $is_a_remote_delete = false;
2926 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2927 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2928 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2929 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2930 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2931 AND `item`.`uid` = %d
2937 intval($importer['importer_uid'])
2940 $is_a_remote_delete = true;
2942 // Does this have the characteristics of a community or private group comment?
2943 // If it's a reply to a wall post on a community/prvgroup page it's a
2944 // valid community comment. Also forum_mode makes it valid for sure.
2945 // If neither, it's not.
2947 if($is_a_remote_delete && $community) {
2948 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2949 $is_a_remote_delete = false;
2950 logger('local_delivery: not a community delete');
2954 if($is_a_remote_delete) {
2955 logger('local_delivery: received remote delete');
2959 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2960 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2962 intval($importer['importer_uid']),
2963 intval($importer['id'])
2969 if($item['deleted'])
2972 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2974 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2975 $xo = parse_xml_string($item['object'],false);
2976 $xt = parse_xml_string($item['target'],false);
2978 if($xt->type === ACTIVITY_OBJ_NOTE) {
2979 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2981 intval($importer['importer_uid'])
2985 // For tags, the owner cannot remove the tag on the author's copy of the post.
2987 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2988 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2989 $author_copy = (($item['origin']) ? true : false);
2991 if($owner_remove && $author_copy)
2993 if($author_remove || $owner_remove) {
2994 $tags = explode(',',$i[0]['tag']);
2997 foreach($tags as $tag)
2998 if(trim($tag) !== trim($xo->body))
2999 $newtags[] = trim($tag);
3001 q("update item set tag = '%s' where id = %d",
3002 dbesc(implode(',',$newtags)),
3005 create_tags_from_item($i[0]['id']);
3011 if($item['uri'] == $item['parent-uri']) {
3012 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3013 `body` = '', `title` = ''
3014 WHERE `parent-uri` = '%s' AND `uid` = %d",
3016 dbesc(datetime_convert()),
3017 dbesc($item['uri']),
3018 intval($importer['importer_uid'])
3020 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3021 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3022 update_thread_uri($item['uri'], $importer['importer_uid']);
3025 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3026 `body` = '', `title` = ''
3027 WHERE `uri` = '%s' AND `uid` = %d",
3029 dbesc(datetime_convert()),
3031 intval($importer['importer_uid'])
3033 create_tags_from_itemuri($uri, $importer['importer_uid']);
3034 create_files_from_itemuri($uri, $importer['importer_uid']);
3035 update_thread_uri($uri, $importer['importer_uid']);
3036 if($item['last-child']) {
3037 // ensure that last-child is set in case the comment that had it just got wiped.
3038 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3039 dbesc(datetime_convert()),
3040 dbesc($item['parent-uri']),
3041 intval($item['uid'])
3043 // who is the last child now?
3044 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3045 ORDER BY `created` DESC LIMIT 1",
3046 dbesc($item['parent-uri']),
3047 intval($importer['importer_uid'])
3050 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3055 // if this is a relayed delete, propagate it to other recipients
3057 if($is_a_remote_delete)
3058 proc_run('php',"include/notifier.php","drop",$item['id']);
3066 foreach($feed->get_items() as $item) {
3069 $item_id = $item->get_id();
3070 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3071 if(isset($rawthread[0]['attribs']['']['ref'])) {
3073 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3079 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3082 logger('local_delivery: possible community reply');
3085 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3087 // was the top-level post for this reply written by somebody on this site?
3088 // Specifically, the recipient?
3090 $is_a_remote_comment = false;
3091 $top_uri = $parent_uri;
3093 $r = q("select `item`.`parent-uri` from `item`
3094 WHERE `item`.`uri` = '%s'
3098 if($r && count($r)) {
3099 $top_uri = $r[0]['parent-uri'];
3101 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3102 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3103 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3104 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3105 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3106 AND `item`.`uid` = %d
3112 intval($importer['importer_uid'])
3115 $is_a_remote_comment = true;
3118 // Does this have the characteristics of a community or private group comment?
3119 // If it's a reply to a wall post on a community/prvgroup page it's a
3120 // valid community comment. Also forum_mode makes it valid for sure.
3121 // If neither, it's not.
3123 if($is_a_remote_comment && $community) {
3124 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3125 $is_a_remote_comment = false;
3126 logger('local_delivery: not a community reply');
3130 if($is_a_remote_comment) {
3131 logger('local_delivery: received remote comment');
3133 // remote reply to our post. Import and then notify everybody else.
3135 $datarray = get_atom_elements($feed, $item);
3137 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3139 intval($importer['importer_uid'])
3142 // Update content if 'updated' changes
3146 if (edited_timestamp_is_newer($r[0], $datarray)) {
3148 // do not accept (ignore) an earlier edit than one we currently have.
3149 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3152 logger('received updated comment' , LOGGER_DEBUG);
3153 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3154 dbesc($datarray['title']),
3155 dbesc($datarray['body']),
3156 dbesc($datarray['tag']),
3157 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3158 dbesc(datetime_convert()),
3160 intval($importer['importer_uid'])
3162 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3164 proc_run('php',"include/notifier.php","comment-import",$iid);
3173 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3174 intval($importer['importer_uid'])
3178 $datarray['type'] = 'remote-comment';
3179 $datarray['wall'] = 1;
3180 $datarray['parent-uri'] = $parent_uri;
3181 $datarray['uid'] = $importer['importer_uid'];
3182 $datarray['owner-name'] = $own[0]['name'];
3183 $datarray['owner-link'] = $own[0]['url'];
3184 $datarray['owner-avatar'] = $own[0]['thumb'];
3185 $datarray['contact-id'] = $importer['id'];
3187 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3189 $datarray['type'] = 'activity';
3190 $datarray['gravity'] = GRAVITY_LIKE;
3191 $datarray['last-child'] = 0;
3192 // only one like or dislike per person
3193 // splitted into two queries for performance issues
3194 $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",
3195 intval($datarray['uid']),
3196 intval($datarray['contact-id']),
3197 dbesc($datarray['verb']),
3198 dbesc($datarray['parent-uri'])
3204 $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",
3205 intval($datarray['uid']),
3206 intval($datarray['contact-id']),
3207 dbesc($datarray['verb']),
3208 dbesc($datarray['parent-uri'])
3215 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3217 $xo = parse_xml_string($datarray['object'],false);
3218 $xt = parse_xml_string($datarray['target'],false);
3220 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3222 // fetch the parent item
3224 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3226 intval($importer['importer_uid'])
3231 // extract tag, if not duplicate, and this user allows tags, add to parent item
3233 if($xo->id && $xo->content) {
3234 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3235 if(! (stristr($tagp[0]['tag'],$newtag))) {
3236 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3237 intval($importer['importer_uid'])
3239 if(count($i) && ! intval($i[0]['blocktags'])) {
3240 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3241 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3242 intval($tagp[0]['id']),
3243 dbesc(datetime_convert()),
3244 dbesc(datetime_convert())
3246 create_tags_from_item($tagp[0]['id']);
3254 $posted_id = item_store($datarray);
3258 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3260 intval($importer['importer_uid'])
3263 $parent = $r[0]['parent'];
3264 $parent_uri = $r[0]['parent-uri'];
3268 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3269 dbesc(datetime_convert()),
3270 intval($importer['importer_uid']),
3271 intval($r[0]['parent'])
3274 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3275 dbesc(datetime_convert()),
3276 intval($importer['importer_uid']),
3281 if($posted_id && $parent) {
3283 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3285 if((! $is_like) && (! $importer['self'])) {
3287 require_once('include/enotify.php');
3290 'type' => NOTIFY_COMMENT,
3291 'notify_flags' => $importer['notify-flags'],
3292 'language' => $importer['language'],
3293 'to_name' => $importer['username'],
3294 'to_email' => $importer['email'],
3295 'uid' => $importer['importer_uid'],
3296 'item' => $datarray,
3297 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3298 'source_name' => stripslashes($datarray['author-name']),
3299 'source_link' => $datarray['author-link'],
3300 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3301 ? $importer['thumb'] : $datarray['author-avatar']),
3302 'verb' => ACTIVITY_POST,
3304 'parent' => $parent,
3305 'parent_uri' => $parent_uri,
3317 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3319 $item_id = $item->get_id();
3320 $datarray = get_atom_elements($feed,$item);
3322 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3325 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3327 intval($importer['importer_uid'])
3330 // Update content if 'updated' changes
3333 if (edited_timestamp_is_newer($r[0], $datarray)) {
3335 // do not accept (ignore) an earlier edit than one we currently have.
3336 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3339 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3340 dbesc($datarray['title']),
3341 dbesc($datarray['body']),
3342 dbesc($datarray['tag']),
3343 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3344 dbesc(datetime_convert()),
3346 intval($importer['importer_uid'])
3348 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3351 // update last-child if it changes
3353 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3354 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3355 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3356 dbesc(datetime_convert()),
3358 intval($importer['importer_uid'])
3360 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3361 intval($allow[0]['data']),
3362 dbesc(datetime_convert()),
3364 intval($importer['importer_uid'])
3370 $datarray['parent-uri'] = $parent_uri;
3371 $datarray['uid'] = $importer['importer_uid'];
3372 $datarray['contact-id'] = $importer['id'];
3373 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3374 $datarray['type'] = 'activity';
3375 $datarray['gravity'] = GRAVITY_LIKE;
3376 // only one like or dislike per person
3377 // splitted into two queries for performance issues
3378 $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",
3379 intval($datarray['uid']),
3380 intval($datarray['contact-id']),
3381 dbesc($datarray['verb']),
3387 $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",
3388 intval($datarray['uid']),
3389 intval($datarray['contact-id']),
3390 dbesc($datarray['verb']),
3398 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3400 $xo = parse_xml_string($datarray['object'],false);
3401 $xt = parse_xml_string($datarray['target'],false);
3403 if($xt->type == ACTIVITY_OBJ_NOTE) {
3404 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3406 intval($importer['importer_uid'])
3411 // extract tag, if not duplicate, add to parent item
3413 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3414 q("UPDATE item SET tag = '%s' WHERE id = %d",
3415 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3418 create_tags_from_item($r[0]['id']);
3424 $posted_id = item_store($datarray);
3426 // find out if our user is involved in this conversation and wants to be notified.
3428 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3430 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3432 intval($importer['importer_uid'])
3435 if(count($myconv)) {
3436 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3438 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3439 if(! link_compare($datarray['author-link'],$importer_url)) {
3442 foreach($myconv as $conv) {
3444 // now if we find a match, it means we're in this conversation
3446 if(! link_compare($conv['author-link'],$importer_url))
3449 require_once('include/enotify.php');
3451 $conv_parent = $conv['parent'];
3454 'type' => NOTIFY_COMMENT,
3455 'notify_flags' => $importer['notify-flags'],
3456 'language' => $importer['language'],
3457 'to_name' => $importer['username'],
3458 'to_email' => $importer['email'],
3459 'uid' => $importer['importer_uid'],
3460 'item' => $datarray,
3461 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3462 'source_name' => stripslashes($datarray['author-name']),
3463 'source_link' => $datarray['author-link'],
3464 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3465 ? $importer['thumb'] : $datarray['author-avatar']),
3466 'verb' => ACTIVITY_POST,
3468 'parent' => $conv_parent,
3469 'parent_uri' => $parent_uri
3473 // only send one notification
3485 // Head post of a conversation. Have we seen it? If not, import it.
3488 $item_id = $item->get_id();
3489 $datarray = get_atom_elements($feed,$item);
3491 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3492 $ev = bbtoevent($datarray['body']);
3493 if(x($ev,'desc') && x($ev,'start')) {
3494 $ev['cid'] = $importer['id'];
3495 $ev['uid'] = $importer['uid'];
3496 $ev['uri'] = $item_id;
3497 $ev['edited'] = $datarray['edited'];
3498 $ev['private'] = $datarray['private'];
3500 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3502 intval($importer['uid'])
3505 $ev['id'] = $r[0]['id'];
3506 $xyz = event_store($ev);
3511 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3513 intval($importer['importer_uid'])
3516 // Update content if 'updated' changes
3519 if (edited_timestamp_is_newer($r[0], $datarray)) {
3521 // do not accept (ignore) an earlier edit than one we currently have.
3522 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3525 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3526 dbesc($datarray['title']),
3527 dbesc($datarray['body']),
3528 dbesc($datarray['tag']),
3529 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3530 dbesc(datetime_convert()),
3532 intval($importer['importer_uid'])
3534 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3535 update_thread_uri($item_id, $importer['importer_uid']);
3538 // update last-child if it changes
3540 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3541 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3542 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3543 intval($allow[0]['data']),
3544 dbesc(datetime_convert()),
3546 intval($importer['importer_uid'])
3552 // This is my contact on another system, but it's really me.
3553 // Turn this into a wall post.
3555 if($importer['remote_self'])
3556 $datarray['wall'] = 1;
3558 $datarray['parent-uri'] = $item_id;
3559 $datarray['uid'] = $importer['importer_uid'];
3560 $datarray['contact-id'] = $importer['id'];
3563 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3564 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3565 // but otherwise there's a possible data mixup on the sender's system.
3566 // the tgroup delivery code called from item_store will correct it if it's a forum,
3567 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3568 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3569 $datarray['owner-name'] = $importer['senderName'];
3570 $datarray['owner-link'] = $importer['url'];
3571 $datarray['owner-avatar'] = $importer['thumb'];
3574 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3577 $posted_id = item_store($datarray);
3579 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3580 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3583 $xo = parse_xml_string($datarray['object'],false);
3585 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3587 // somebody was poked/prodded. Was it me?
3589 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3591 foreach($links->link as $l) {
3592 $atts = $l->attributes();
3593 switch($atts['rel']) {
3595 $Blink = $atts['href'];
3601 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3603 // send a notification
3604 require_once('include/enotify.php');
3607 'type' => NOTIFY_POKE,
3608 'notify_flags' => $importer['notify-flags'],
3609 'language' => $importer['language'],
3610 'to_name' => $importer['username'],
3611 'to_email' => $importer['email'],
3612 'uid' => $importer['importer_uid'],
3613 'item' => $datarray,
3614 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3615 'source_name' => stripslashes($datarray['author-name']),
3616 'source_link' => $datarray['author-link'],
3617 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3618 ? $importer['thumb'] : $datarray['author-avatar']),
3619 'verb' => $datarray['verb'],
3620 'otype' => 'person',
3621 'activity' => $verb,
3638 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3639 $url = notags(trim($datarray['author-link']));
3640 $name = notags(trim($datarray['author-name']));
3641 $photo = notags(trim($datarray['author-avatar']));
3643 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3644 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3645 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3647 if(is_array($contact)) {
3648 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3649 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3650 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3651 intval(CONTACT_IS_FRIEND),
3652 intval($contact['id']),
3653 intval($importer['uid'])
3656 // send email notification to owner?
3660 // create contact record
3662 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3663 `blocked`, `readonly`, `pending`, `writable` )
3664 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3665 intval($importer['uid']),
3666 dbesc(datetime_convert()),
3668 dbesc(normalise_link($url)),
3672 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3673 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3675 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3676 intval($importer['uid']),
3680 $contact_record = $r[0];
3682 // create notification
3683 $hash = random_string();
3685 if(is_array($contact_record)) {
3686 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3687 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3688 intval($importer['uid']),
3689 intval($contact_record['id']),
3691 dbesc(datetime_convert())
3694 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3695 intval($importer['uid'])
3700 if(intval($r[0]['def_gid'])) {
3701 require_once('include/group.php');
3702 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3705 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3706 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3707 $email = replace_macros($email_tpl, array(
3708 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3710 '$myname' => $r[0]['username'],
3711 '$siteurl' => $a->get_baseurl(),
3712 '$sitename' => $a->config['sitename']
3714 $res = mail($r[0]['email'],
3715 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'),
3717 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3718 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3719 . 'Content-transfer-encoding: 8bit' );
3726 function lose_follower($importer,$contact,$datarray,$item) {
3728 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3729 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3730 intval(CONTACT_IS_SHARING),
3731 intval($contact['id'])
3735 contact_remove($contact['id']);
3739 function lose_sharer($importer,$contact,$datarray,$item) {
3741 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3742 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3743 intval(CONTACT_IS_FOLLOWER),
3744 intval($contact['id'])
3748 contact_remove($contact['id']);
3753 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3757 if(is_array($importer)) {
3758 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3759 intval($importer['uid'])
3763 // Diaspora has different message-ids in feeds than they do
3764 // through the direct Diaspora protocol. If we try and use
3765 // the feed, we'll get duplicates. So don't.
3767 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3770 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3772 // Use a single verify token, even if multiple hubs
3774 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3776 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3778 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3780 if(! strlen($contact['hub-verify'])) {
3781 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3782 dbesc($verify_token),
3783 intval($contact['id'])
3787 post_url($url,$params);
3789 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3796 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3800 $name = xmlify($name);
3801 $uri = xmlify($uri);
3804 $photo = xmlify($photo);
3808 $o .= "<name>$name</name>\r\n";
3809 $o .= "<uri>$uri</uri>\r\n";
3810 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3811 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3813 call_hooks('atom_author', $o);
3815 $o .= "</$tag>\r\n";
3819 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3823 if(! $item['parent'])
3826 if($item['deleted'])
3827 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3830 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3831 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3833 $body = $item['body'];
3835 $o = "\r\n\r\n<entry>\r\n";
3837 if(is_array($author))
3838 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3840 $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']));
3841 if(strlen($item['owner-name']))
3842 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3844 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3845 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3846 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3849 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3850 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3851 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3852 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3853 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3854 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3855 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3857 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3859 if($item['location']) {
3860 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3861 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3865 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3867 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3868 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3871 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3872 if($item['bookmark'])
3873 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3876 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3879 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3881 if($item['signed_text']) {
3882 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3883 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3886 $verb = construct_verb($item);
3887 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3888 $actobj = construct_activity_object($item);
3891 $actarg = construct_activity_target($item);
3895 $tags = item_getfeedtags($item);
3897 foreach($tags as $t) {
3898 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3902 $o .= item_getfeedattach($item);
3904 $mentioned = get_mentions($item);
3908 call_hooks('atom_entry', $o);
3910 $o .= '</entry>' . "\r\n";
3915 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3917 if(get_config('system','disable_embedded'))
3922 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3923 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3928 $img_start = strpos($orig_body, '[img');
3929 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3930 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3931 while( ($img_st_close !== false) && ($img_len !== false) ) {
3933 $img_st_close++; // make it point to AFTER the closing bracket
3934 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3936 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3939 if(stristr($image , $site . '/photo/')) {
3940 // Only embed locally hosted photos
3942 $i = basename($image);
3943 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3944 $x = strpos($i,'-');
3947 $res = substr($i,$x+1);
3948 $i = substr($i,0,$x);
3949 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3956 // Check to see if we should replace this photo link with an embedded image
3957 // 1. No need to do so if the photo is public
3958 // 2. If there's a contact-id provided, see if they're in the access list
3959 // for the photo. If so, embed it.
3960 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3961 // permissions, regardless of order but first check to see if they're an exact
3962 // match to save some processing overhead.
3964 if(has_permissions($r[0])) {
3966 $recips = enumerate_permissions($r[0]);
3967 if(in_array($cid, $recips)) {
3972 if(compare_permissions($item,$r[0]))
3977 $data = $r[0]['data'];
3978 $type = $r[0]['type'];
3980 // If a custom width and height were specified, apply before embedding
3981 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3982 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3984 $width = intval($match[1]);
3985 $height = intval($match[2]);
3987 $ph = new Photo($data, $type);
3988 if($ph->is_valid()) {
3989 $ph->scaleImage(max($width, $height));
3990 $data = $ph->imageString();
3991 $type = $ph->getType();
3995 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3996 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3997 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4003 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4004 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4005 if($orig_body === false)
4008 $img_start = strpos($orig_body, '[img');
4009 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4010 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4013 $new_body = $new_body . $orig_body;
4019 function has_permissions($obj) {
4020 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4025 function compare_permissions($obj1,$obj2) {
4026 // first part is easy. Check that these are exactly the same.
4027 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4028 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4029 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4030 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4033 // This is harder. Parse all the permissions and compare the resulting set.
4035 $recipients1 = enumerate_permissions($obj1);
4036 $recipients2 = enumerate_permissions($obj2);
4039 if($recipients1 == $recipients2)
4044 // returns an array of contact-ids that are allowed to see this object
4046 function enumerate_permissions($obj) {
4047 require_once('include/group.php');
4048 $allow_people = expand_acl($obj['allow_cid']);
4049 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4050 $deny_people = expand_acl($obj['deny_cid']);
4051 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4052 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4053 $deny = array_unique(array_merge($deny_people,$deny_groups));
4054 $recipients = array_diff($recipients,$deny);
4058 function item_getfeedtags($item) {
4061 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4063 for($x = 0; $x < $cnt; $x ++) {
4065 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4069 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4071 for($x = 0; $x < $cnt; $x ++) {
4073 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4079 function item_getfeedattach($item) {
4081 $arr = explode('[/attach],',$item['attach']);
4083 foreach($arr as $r) {
4085 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4087 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4088 if(intval($matches[2]))
4089 $ret .= 'length="' . intval($matches[2]) . '" ';
4090 if($matches[4] !== ' ')
4091 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4092 $ret .= ' />' . "\r\n";
4101 function item_expire($uid, $days, $network = "", $force = false) {
4103 if((! $uid) || ($days < 1))
4106 // $expire_network_only = save your own wall posts
4107 // and just expire conversations started by others
4109 $expire_network_only = get_pconfig($uid,'expire','network_only');
4110 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4112 if ($network != "") {
4113 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4114 // There is an index "uid_network_received" but not "uid_network_created"
4115 // This avoids the creation of another index just for one purpose.
4116 // And it doesn't really matter wether to look at "received" or "created"
4117 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4119 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4121 $r = q("SELECT * FROM `item`
4122 WHERE `uid` = %d $range
4133 $expire_items = get_pconfig($uid, 'expire','items');
4134 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4136 // Forcing expiring of items - but not notes and marked items
4138 $expire_items = true;
4140 $expire_notes = get_pconfig($uid, 'expire','notes');
4141 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4143 $expire_starred = get_pconfig($uid, 'expire','starred');
4144 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4146 $expire_photos = get_pconfig($uid, 'expire','photos');
4147 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4149 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4151 foreach($r as $item) {
4153 // don't expire filed items
4155 if(strpos($item['file'],'[') !== false)
4158 // Only expire posts, not photos and photo comments
4160 if($expire_photos==0 && strlen($item['resource-id']))
4162 if($expire_starred==0 && intval($item['starred']))
4164 if($expire_notes==0 && $item['type']=='note')
4166 if($expire_items==0 && $item['type']!='note')
4169 drop_item($item['id'],false);
4172 proc_run('php',"include/notifier.php","expire","$uid");
4177 function drop_items($items) {
4180 if(! local_user() && ! remote_user())
4184 foreach($items as $item) {
4185 $owner = drop_item($item,false);
4186 if($owner && ! $uid)
4191 // multiple threads may have been deleted, send an expire notification
4194 proc_run('php',"include/notifier.php","expire","$uid");
4198 function drop_item($id,$interactive = true) {
4202 // locate item to be deleted
4204 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4211 notice( t('Item not found.') . EOL);
4212 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4217 $owner = $item['uid'];
4221 // check if logged in user is either the author or owner of this item
4223 if(is_array($_SESSION['remote'])) {
4224 foreach($_SESSION['remote'] as $visitor) {
4225 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4226 $cid = $visitor['cid'];
4233 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4235 // Check if we should do HTML-based delete confirmation
4236 if($_REQUEST['confirm']) {
4237 // <form> can't take arguments in its "action" parameter
4238 // so add any arguments as hidden inputs
4239 $query = explode_querystring($a->query_string);
4241 foreach($query['args'] as $arg) {
4242 if(strpos($arg, 'confirm=') === false) {
4243 $arg_parts = explode('=', $arg);
4244 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4248 return replace_macros(get_markup_template('confirm.tpl'), array(
4250 '$message' => t('Do you really want to delete this item?'),
4251 '$extra_inputs' => $inputs,
4252 '$confirm' => t('Yes'),
4253 '$confirm_url' => $query['base'],
4254 '$confirm_name' => 'confirmed',
4255 '$cancel' => t('Cancel'),
4258 // Now check how the user responded to the confirmation query
4259 if($_REQUEST['canceled']) {
4260 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4263 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4266 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4267 dbesc(datetime_convert()),
4268 dbesc(datetime_convert()),
4271 create_tags_from_item($item['id']);
4272 create_files_from_item($item['id']);
4273 delete_thread($item['id']);
4275 // clean up categories and tags so they don't end up as orphans
4278 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4280 foreach($matches as $mtch) {
4281 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4287 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4289 foreach($matches as $mtch) {
4290 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4294 // If item is a link to a photo resource, nuke all the associated photos
4295 // (visitors will not have photo resources)
4296 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4297 // generate a resource-id and therefore aren't intimately linked to the item.
4299 if(strlen($item['resource-id'])) {
4300 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4301 dbesc($item['resource-id']),
4302 intval($item['uid'])
4304 // ignore the result
4307 // If item is a link to an event, nuke the event record.
4309 if(intval($item['event-id'])) {
4310 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4311 intval($item['event-id']),
4312 intval($item['uid'])
4314 // ignore the result
4317 // clean up item_id and sign meta-data tables
4320 // Old code - caused very long queries and warning entries in the mysql logfiles:
4322 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4323 intval($item['id']),
4324 intval($item['uid'])
4327 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4328 intval($item['id']),
4329 intval($item['uid'])
4333 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4335 // Creating list of parents
4336 $r = q("select id from item where parent = %d and uid = %d",
4337 intval($item['id']),
4338 intval($item['uid'])
4343 foreach ($r AS $row) {
4344 if ($parentid != "")
4347 $parentid .= $row["id"];
4351 if ($parentid != "") {
4352 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4354 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4357 // If it's the parent of a comment thread, kill all the kids
4359 if($item['uri'] == $item['parent-uri']) {
4360 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4361 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4362 dbesc(datetime_convert()),
4363 dbesc(datetime_convert()),
4364 dbesc($item['parent-uri']),
4365 intval($item['uid'])
4367 create_tags_from_item($item['parent-uri'], $item['uid']);
4368 create_files_from_item($item['parent-uri'], $item['uid']);
4369 delete_thread_uri($item['parent-uri'], $item['uid']);
4370 // ignore the result
4373 // ensure that last-child is set in case the comment that had it just got wiped.
4374 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4375 dbesc(datetime_convert()),
4376 dbesc($item['parent-uri']),
4377 intval($item['uid'])
4379 // who is the last child now?
4380 $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",
4381 dbesc($item['parent-uri']),
4382 intval($item['uid'])
4385 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4390 // Add a relayable_retraction signature for Diaspora.
4391 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4393 $drop_id = intval($item['id']);
4395 // send the notification upstream/downstream as the case may be
4397 proc_run('php',"include/notifier.php","drop","$drop_id");
4401 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4407 notice( t('Permission denied.') . EOL);
4408 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4415 function first_post_date($uid,$wall = false) {
4416 $r = q("select id, created from item
4417 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4419 order by created asc limit 1",
4421 intval($wall ? 1 : 0)
4424 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4425 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4430 function posted_dates($uid,$wall) {
4431 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4433 $dthen = first_post_date($uid,$wall);
4437 // If it's near the end of a long month, backup to the 28th so that in
4438 // consecutive loops we'll always get a whole month difference.
4440 if(intval(substr($dnow,8)) > 28)
4441 $dnow = substr($dnow,0,8) . '28';
4442 if(intval(substr($dthen,8)) > 28)
4443 $dnow = substr($dthen,0,8) . '28';
4446 // Starting with the current month, get the first and last days of every
4447 // month down to and including the month of the first post
4448 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4449 $dstart = substr($dnow,0,8) . '01';
4450 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4451 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4452 $end_month = datetime_convert('','',$dend,'Y-m-d');
4453 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4454 $ret[] = array($str,$end_month,$start_month);
4455 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4461 function posted_date_widget($url,$uid,$wall) {
4464 if(! feature_enabled($uid,'archives'))
4467 // For former Facebook folks that left because of "timeline"
4469 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4472 $ret = posted_dates($uid,$wall);
4476 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4477 '$title' => t('Archives'),
4478 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4485 function store_diaspora_retract_sig($item, $user, $baseurl) {
4486 // Note that we can't add a target_author_signature
4487 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4488 // the comment, that means we're the home of the post, and Diaspora will only
4489 // check the parent_author_signature of retractions that it doesn't have to relay further
4491 // I don't think this function gets called for an "unlike," but I'll check anyway
4493 $enabled = intval(get_config('system','diaspora_enabled'));
4495 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4499 logger('drop_item: storing diaspora retraction signature');
4501 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4503 if(local_user() == $item['uid']) {
4505 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4506 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4509 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4510 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4513 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4514 // only handles DFRN deletes
4515 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4516 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4517 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4523 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4524 intval($item['id']),
4525 dbesc($signed_text),