if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
$category = $a->argv[$x+1];
}
-
-
}
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
- `contact`.`name`, `contact`.`photo`, `contact`.`url`,
+ `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
`contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
foreach($items as $item) {
+ // prevent private email from leaking.
+ if($item['network'] === NETWORK_MAIL)
+ continue;
+
// public feeds get html, our own nodes use bbcode
if($public_feed) {
$res['body'] = $purifier->purify($res['body']);
$res['body'] = @html2bbcode($res['body']);
+
+
}
elseif(! $have_real_body) {
return xmlify($o);
}
+
+
function item_store($arr,$force_parent = false) {
// If a Diaspora signature structure was passed in, pull it out of the
$deny_cid = $r[0]['deny_cid'];
$deny_gid = $r[0]['deny_gid'];
$arr['wall'] = $r[0]['wall'];
+
+ // if the parent is private, force privacy for the entire conversation
+ // This differs from the above settings as it subtly allows comments from
+ // email correspondents to be private even if the overall thread is not.
+
+ if($r[0]['private'])
+ $arr['private'] = 1;
+
+ // Edge case. We host a public forum that was originally posted to privately.
+ // The original author commented, but as this is a comment, the permissions
+ // weren't fixed up so it will still show the comment as private unless we fix it here.
+
+ if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
+ $arr['private'] = 0;
}
else {
intval($current_post)
);
+ $arr['id'] = $current_post;
+ $arr['parent'] = $parent_id;
+ $arr['allow_cid'] = $allow_cid;
+ $arr['allow_gid'] = $allow_gid;
+ $arr['deny_cid'] = $deny_cid;
+ $arr['deny_gid'] = $deny_gid;
+ $arr['private'] = $private;
+ $arr['deleted'] = $parent_deleted;
+ call_hooks('post_remote_end',$arr);
+
// update the commented timestamp on the parent
q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
$mention = false;
- $u = q("select uid, nickname, language, username, email, `page-flags`, `notify-flags` from user where uid = %d limit 1",
+ $u = q("select * from user where uid = %d limit 1",
intval($uid)
);
if(! count($u))
return;
$community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
+ $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
+
$i = q("select * from item where id = %d and uid = %d limit 1",
intval($item_id),
'otype' => 'item'
));
- if(! $community_page)
+ if((! $community_page) && (! $prvgroup))
return;
+
// tgroup delivery - setup a second delivery chain
// prevent delivery looping - only proceed
// if the message originated elsewhere and is a top-level post
if(! count($c))
return;
- q("update item set wall = 1, origin = 1, forum_mode = 1, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s' where id = %d limit 1",
+ // also reset all the privacy bits to the forum default permissions
+
+ $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
+
+ $forum_mode = (($prvgroup) ? 2 : 1);
+
+ q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
+ `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d limit 1",
+ intval($forum_mode),
dbesc($c[0]['name']),
dbesc($c[0]['url']),
dbesc($c[0]['thumb']),
+ intval($private),
+ dbesc($u[0]['allow_cid']),
+ dbesc($u[0]['allow_gid']),
+ dbesc($u[0]['deny_cid']),
+ dbesc($u[0]['deny_gid']),
intval($item_id)
);
$a = get_app();
-// if((! strlen($contact['issued-id'])) && (! $contact['duplex']) && (! ($owner['page-flags'] == PAGE_COMMUNITY)))
-// return 3;
-
$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
if($contact['duplex'] && $contact['dfrn-id'])
$rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
$page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
+ if($owner['page-flags'] == PAGE_PRVGROUP)
+ $page = 2;
+
$final_dfrn_id = '';
if($perm) {
$postvars['ssl_policy'] = $ssl_policy;
if($page)
- $postvars['page'] = '1';
+ $postvars['page'] = $page;
if($rino && $rino_allowed && (! $dissolve)) {
$key = substr(random_string(),0,16);
return 3;
}
+ if($contact['term-date'] != '0000-00-00 00:00:00') {
+ logger("dfrn_deliver: $url back from the dead - removing mark for death");
+ require_once('include/Contact.php');
+ unmark_for_death($contact);
+ }
+
$res = parse_xml_string($xml);
return $res->status;
$birthday = '';
$hubs = $feed->get_links('hub');
+ logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
if(count($hubs))
$hub = implode(',', $hubs);
}
$img_str = fetch_url($photo_url,true);
- $img = new Photo($img_str);
+ // guess mimetype from headers or filename
+ $type = guess_image_type($photo_url,true);
+
+
+ $img = new Photo($img_str, $type);
if($img->is_valid()) {
if($have_photo) {
q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
WHERE `uid` = %d AND `id` = %d LIMIT 1",
dbesc(datetime_convert()),
- dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.jpg'),
- dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.jpg'),
- dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.jpg'),
+ dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
+ dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
+ dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
intval($contact['uid']),
intval($contact['id'])
);
if(count($r)) {
if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+
+ // do not accept (ignore) an earlier edit than one we currently have.
+ if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
+ continue;
+
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($datarray['title']),
dbesc($datarray['body']),
if(count($r)) {
if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+
+ // do not accept (ignore) an earlier edit than one we currently have.
+ if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
+ continue;
+
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($datarray['title']),
dbesc($datarray['body']),
}
if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
- // one way feed - no remote comment ability
- $datarray['last-child'] = 0;
+ // one way feed - no remote comment ability
+ $datarray['last-child'] = 0;
}
+ if($contact['network'] === NETWORK_FEED)
+ $datarray['private'] = 1;
// This is my contact on another system, but it's really me.
// Turn this into a wall post.
if($is_reply) {
$community = false;
- if($importer['page-flags'] == PAGE_COMMUNITY) {
+ if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
$sql_extra = '';
$community = true;
logger('local_delivery: possible community reply');
if($r && count($r))
$is_a_remote_comment = true;
- // Does this have the characteristics of a community comment?
- // If it's a reply to a wall post on a community page it's a
+ // Does this have the characteristics of a community or private group comment?
+ // If it's a reply to a wall post on a community/prvgroup page it's a
// valid community comment. Also forum_mode makes it valid for sure.
// If neither, it's not.
if(count($r)) {
$iid = $r[0]['id'];
- if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+ if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+
+ // do not accept (ignore) an earlier edit than one we currently have.
+ if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
+ continue;
+
logger('received updated comment' , LOGGER_DEBUG);
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($datarray['title']),
if(count($r)) {
if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+
+ // do not accept (ignore) an earlier edit than one we currently have.
+ if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
+ continue;
+
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($datarray['title']),
dbesc($datarray['body']),
if(count($r)) {
if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
+
+ // do not accept (ignore) an earlier edit than one we currently have.
+ if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
+ continue;
+
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($datarray['title']),
dbesc($datarray['body']),
);
$a = get_app();
if(count($r)) {
+
+ if(intval($r[0]['def_gid'])) {
+ require_once('include/group.php');
+ group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
+ }
+
if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
$email_tpl = get_intltext_template('follow_notify_eml.tpl');
$email = replace_macros($email_tpl, array(
function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
+ $a = get_app();
+
if(is_array($importer)) {
$r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
intval($importer['uid'])
);
}
- post_url($url,$params);
+ post_url($url,$params);
+
+ logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
+
return;
}
return $o;
}
-function atom_entry($item,$type,$author,$owner,$comment = false) {
+function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
$a = get_app();
if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
- $body = fix_private_photos($item['body'],$owner['uid']);
+ $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
else
$body = $item['body'];
return $o;
}
-function fix_private_photos($s,$uid) {
+function fix_private_photos($s,$uid, $item = null, $cid = 0) {
$a = get_app();
- logger('fix_private_photos');
- if(preg_match("/\[img\](.*?)\[\/img\]/is",$s,$matches)) {
- $image = $matches[1];
- logger('fix_private_photos: found photo ' . $image);
- if(stristr($image ,$a->get_baseurl() . '/photo/')) {
+ logger('fix_private_photos', LOGGER_DEBUG);
+ $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
+
+ if(preg_match("/\[img(.*?)\](.*?)\[\/img\]/is",$s,$matches)) {
+ $image = $matches[2];
+ logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
+ if(stristr($image , $site . '/photo/')) {
+ $replace = false;
$i = basename($image);
- $i = str_replace('.jpg','',$i);
+ $i = str_replace(array('.jpg','.png'),array('',''),$i);
$x = strpos($i,'-');
if($x) {
$res = substr($i,$x+1);
intval($uid)
);
if(count($r)) {
- logger('replacing photo');
- $s = str_replace($image, 'data:image/jpg;base64,' . base64_encode($r[0]['data']), $s);
+
+ // Check to see if we should replace this photo link with an embedded image
+ // 1. No need to do so if the photo is public
+ // 2. If there's a contact-id provided, see if they're in the access list
+ // for the photo. If so, embed it.
+ // 3. Otherwise, if we have an item, see if the item permissions match the photo
+ // permissions, regardless of order but first check to see if they're an exact
+ // match to save some processing overhead.
+
+ // Currently we only embed one private photo per message so as not to hit import
+ // size limits at the receiving end.
+
+ // To embed multiples, we would need to parse out the embedded photos on message
+ // receipt and limit size based only on the text component. Would also need to
+ // ignore all photos during bbcode translation and item localisation, as these
+ // will hit internal regex backtrace limits.
+
+ if(has_permissions($r[0])) {
+ if($cid) {
+ $recips = enumerate_permissions($r[0]);
+ if(in_array($cid, $recips)) {
+ $replace = true;
+ }
+ }
+ elseif($item) {
+ if(compare_permissions($item,$r[0]))
+ $replace = true;
+ }
+ }
+ if($replace) {
+ logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
+ $s = str_replace($image, 'data:' . $r[0]['type'] . ';base64,' . base64_encode($r[0]['data']), $s);
+ logger('fix_private_photos: replaced: ' . $s, LOGGER_DATA);
+ }
}
}
- logger('fix_private_photos: replaced: ' . $s, LOGGER_DATA);
}
}
return($s);
}
+function has_permissions($obj) {
+ if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
+ return true;
+ return false;
+}
+
+function compare_permissions($obj1,$obj2) {
+ // first part is easy. Check that these are exactly the same.
+ if(($obj1['allow_cid'] == $obj2['allow_cid'])
+ && ($obj1['allow_gid'] == $obj2['allow_gid'])
+ && ($obj1['deny_cid'] == $obj2['deny_cid'])
+ && ($obj1['deny_gid'] == $obj2['deny_gid']))
+ return true;
+
+ // This is harder. Parse all the permissions and compare the resulting set.
+
+ $recipients1 = enumerate_permissions($obj1);
+ $recipients2 = enumerate_permissions($obj2);
+ sort($recipients1);
+ sort($recipients2);
+ if($recipients1 == $recipients2)
+ return true;
+ return false;
+}
+
+// returns an array of contact-ids that are allowed to see this object
+
+function enumerate_permissions($obj) {
+ require_once('include/group.php');
+ $allow_people = expand_acl($obj['allow_cid']);
+ $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
+ $deny_people = expand_acl($obj['deny_cid']);
+ $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
+ $recipients = array_unique(array_merge($allow_people,$allow_groups));
+ $deny = array_unique(array_merge($deny_people,$deny_groups));
+ $recipients = array_diff($recipients,$deny);
+ return $recipients;
+}
function item_getfeedtags($item) {
$ret = array();
function item_expire($uid,$days) {
- if((! $uid) || (! $days))
+ if((! $uid) || ($days < 1))
return;
+ // $expire_network_only = save your own wall posts
+ // and just expire conversations started by others
+
+ $expire_network_only = get_pconfig($uid,'expire','network_only');
+ $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
+
$r = q("SELECT * FROM `item`
WHERE `uid` = %d
AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
AND `id` = `parent`
+ $sql_extra
AND `deleted` = 0",
intval($uid),
intval($days)
if($expire_items==0 && $item['type']!='note')
continue;
-
- $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1",
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
- intval($item['id'])
- );
-
- $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d) and uid = %d",
- intval($item['id']),
- intval($uid)
- );
-
- $r = q("DELETE FROM sign where iid in (select id from item where parent = %d) and uid = %d",
- intval($item['id']),
- intval($uid)
- );
-
- // kill the kids
-
- $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
- dbesc($item['parent-uri']),
- intval($item['uid'])
- );
-
+ drop_item($item['id'],false);
}
proc_run('php',"include/notifier.php","expire","$uid");
intval($item['id'])
);
+ // clean up categories and tags so they don't end up as orphans
+
+ $matches = false;
+ $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
+ if($cnt) {
+ foreach($matches as $mtch) {
+ file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
+ }
+ }
+
+ $matches = false;
+
+ $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
+ if($cnt) {
+ foreach($matches as $mtch) {
+ file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
+ }
+ }
+
// If item is a link to a photo resource, nuke all the associated photos
// (visitors will not have photo resources)
// This only applies to photos uploaded from the photos page. Photos inserted into a post do not
// ignore the result
}
+ // clean up item_id and sign meta-data tables
+
+ $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
+ intval($item['id']),
+ intval($item['uid'])
+ );
+
+ $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
+ intval($item['id']),
+ intval($item['uid'])
+ );
// If it's the parent of a comment thread, kill all the kids
q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1",
intval($r[0]['id'])
);
- }
+ }
+
+ // Add a relayable_retraction signature for Diaspora. Note that we can't add a target_author_signature
+ // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
+ // the comment, that means we're the home of the post, and Diaspora will only
+ // check the parent_author_signature of retractions that it doesn't have to relay further
+ //
+ // I don't think this function gets called for an "unlike," but I'll check anyway
+ $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
+
+ if(local_user() == $item['uid']) {
+
+ $handle = $a->user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+ $authorsig = base64_encode(rsa_sign($signed_text,$a->user['prvkey'],'sha256'));
+ }
+ else {
+ $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
+ $item['contact-id']
+ );
+ if(count($r)) {
+ // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
+ // only handles DFRN deletes
+ $handle_baseurl_start = strpos($r['url'],'://') + 3;
+ $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
+ $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
+ $authorsig = '';
+ }
+ }
+
+ if(isset($handle))
+ q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
+ intval($item['id']),
+ dbesc($signed_text),
+ dbesc($authorsig),
+ dbesc($handle)
+ );
}
$drop_id = intval($item['id']);
-
+
// send the notification upstream/downstream as the case may be
if(! $interactive)
}
}
+
+
+function first_post_date($uid,$wall = false) {
+ $r = q("select id, created from item
+ where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
+ and id = parent
+ order by created asc limit 1",
+ intval($uid),
+ intval($wall ? 1 : 0)
+ );
+ if(count($r)) {
+// logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
+ return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
+ }
+ return false;
+}
+
+function posted_dates($uid,$wall) {
+ $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
+
+ $dthen = first_post_date($uid,$wall);
+ if(! $dthen)
+ return array();
+
+ // If it's near the end of a long month, backup to the 28th so that in
+ // consecutive loops we'll always get a whole month difference.
+
+ if(intval(substr($dnow,8)) > 28)
+ $dnow = substr($dnow,0,8) . '28';
+ if(intval(substr($dthen,8)) > 28)
+ $dnow = substr($dthen,0,8) . '28';
+
+ $ret = array();
+ while($dnow >= $dthen) {
+ $dstart = substr($dnow,0,8) . '01';
+ $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
+ $start_month = datetime_convert('','',$dstart,'Y-m-d');
+ $end_month = datetime_convert('','',$dend,'Y-m-d');
+ $str = day_translate(datetime_convert('','',$dnow,'F Y'));
+ $ret[] = array($str,$end_month,$start_month);
+ $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
+ }
+ return $ret;
+}
+
+
+function posted_date_widget($url,$uid,$wall) {
+ $o = '';
+
+ // For former Facebook folks that left because of "timeline"
+
+ if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
+ return $o;
+
+ $ret = posted_dates($uid,$wall);
+ if(! count($ret))
+ return $o;
+
+ $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
+ '$title' => t('Archives'),
+ '$size' => ((count($ret) > 6) ? 6 : count($ret)),
+ '$url' => $url,
+ '$dates' => $ret
+ ));
+ return $o;
+}
\ No newline at end of file