Commit 6fa0bea7 authored by Brion Vibber's avatar Brion Vibber

Merge branch '0.9.x' into testing

parents 7c3f820f 6455461c
......@@ -39,7 +39,6 @@ require_once INSTALLDIR.'/lib/apibareauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/
class ApiAtomServiceAction extends ApiBareAuthAction
{
/**
......@@ -50,13 +49,13 @@ class ApiAtomServiceAction extends ApiBareAuthAction
* @return boolean success flag
*
*/
function prepare($args)
{
parent::prepare($args);
$this->user = $this->getTargetUser($this->arg('id'));
if (empty($this->user)) {
// TRANS: Client error displayed when making an Atom API request for an unknown user.
$this->clientError(_('No such user.'), 404, $this->format);
return;
}
......@@ -71,7 +70,6 @@ class ApiAtomServiceAction extends ApiBareAuthAction
*
* @return void
*/
function handle($args)
{
parent::handle($args);
......@@ -83,13 +81,15 @@ class ApiAtomServiceAction extends ApiBareAuthAction
'xmlns:atom' => 'http://www.w3.org/2005/Atom',
'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/'));
$this->elementStart('workspace');
$this->element('atom:title', null, _('Main'));
// TRANS: Title for Atom feed.
$this->element('atom:title', null, _m('ATOM','Main'));
$this->elementStart('collection',
array('href' => common_local_url('ApiTimelineUser',
array('id' => $this->user->id,
'format' => 'atom'))));
$this->element('atom:title',
null,
// TRANS: Title for Atom feed. %s is a user nickname.
sprintf(_("%s timeline"),
$this->user->nickname));
$this->element('accept', null, 'application/atom+xml;type=entry');
......@@ -100,6 +100,7 @@ class ApiAtomServiceAction extends ApiBareAuthAction
array('subscriber' => $this->user->id))));
$this->element('atom:title',
null,
// TRANS: Title for Atom feed with a user's subscriptions. %s is a user nickname.
sprintf(_("%s subscriptions"),
$this->user->nickname));
$this->element('accept', null, 'application/atom+xml;type=entry');
......@@ -110,6 +111,7 @@ class ApiAtomServiceAction extends ApiBareAuthAction
array('profile' => $this->user->id))));
$this->element('atom:title',
null,
// TRANS: Title for Atom feed with a user's favorite notices. %s is a user nickname.
sprintf(_("%s favorites"),
$this->user->nickname));
$this->element('accept', null, 'application/atom+xml;type=entry');
......@@ -120,6 +122,7 @@ class ApiAtomServiceAction extends ApiBareAuthAction
array('profile' => $this->user->id))));
$this->element('atom:title',
null,
// TRANS: Title for Atom feed with a user's memberships. %s is a user nickname.
sprintf(_("%s memberships"),
$this->user->nickname));
$this->element('accept', null, 'application/atom+xml;type=entry');
......
......@@ -92,6 +92,7 @@ class ApiBlockCreateAction extends ApiAuthAction
}
if (empty($this->user) || empty($this->other)) {
// TRANS: Client error displayed when trying to block a non-existing user or a user from another site.
$this->clientError(_('No such user.'), 404, $this->format);
return;
}
......
......@@ -114,6 +114,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
$this->deleteNotice();
break;
default:
// TRANS: Client error displayed calling an unsupported HTTP error in API status show.
$this->clientError(_('HTTP method not supported.'), 405);
return;
}
......@@ -138,6 +139,8 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
$this->showSingleAtomStatus($this->notice);
break;
default:
// TRANS: Exception thrown requesting an unsupported notice output format.
// TRANS: %s is the requested output format.
throw new Exception(sprintf(_("Unsupported format: %s"), $this->format));
}
} else {
......@@ -171,7 +174,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
*
* @return boolean true
*/
function isReadOnly($args)
{
return ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD');
......@@ -220,6 +223,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
function deleteNotice()
{
if ($this->format != 'atom') {
// TRANS: Client error displayed when trying to delete a notice not using the Atom format.
$this->clientError(_("Can only delete using the Atom format."));
return;
}
......@@ -227,7 +231,8 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
if (empty($this->auth_user) ||
($this->notice->profile_id != $this->auth_user->id &&
!$this->auth_user->hasRight(Right::DELETEOTHERSNOTICE))) {
$this->clientError(_('Can\'t delete this notice.'), 403);
// TRANS: Client error displayed when a user has no rights to delete notices of other users.
$this->clientError(_('Cannot delete this notice.'), 403);
return;
}
......@@ -240,6 +245,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
header('HTTP/1.1 200 OK');
header('Content-Type: text/plain');
// TRANS: Confirmation of notice deletion in API. %d is the ID (number) of the deleted notice.
print(sprintf(_('Deleted notice %d'), $this->notice->id));
print("\n");
}
......
......@@ -241,7 +241,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
*
* @return boolean true
*/
function isReadOnly($args)
{
return ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD');
......@@ -307,11 +307,13 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$xml = trim(file_get_contents('php://input'));
if (empty($xml)) {
// TRANS: Client error displayed attempting to post an empty API notice.
$this->clientError(_('Atom post must not be empty.'));
}
$dom = DOMDocument::loadXML($xml);
if (!$dom) {
// TRANS: Client error displayed attempting to post an API that is not well-formed XML.
$this->clientError(_('Atom post must be well-formed XML.'));
}
......@@ -375,6 +377,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
} else {
// @fixme fetch from $sourceUrl?
// TRANS: Client error displayed when posting a notice without content through the API.
// TRANS: %d is the notice ID (number).
$this->clientError(sprintf(_('No content for notice %d.'),
$note->id));
return;
......@@ -434,7 +437,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$options['groups'][] = $uri;
} else {
// @fixme: hook for discovery here
common_log(LOG_WARNING, sprintf(_('AtomPub post with unknown attention URI %s'), $uri));
common_log(LOG_WARNING, sprintf('AtomPub post with unknown attention URI %s', $uri));
}
}
}
......
......@@ -4,7 +4,7 @@
* Copyright (C) 2010, StatusNet, Inc.
*
* Feed of ActivityStreams 'favorite' actions
*
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
......@@ -46,7 +46,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class AtompubfavoritefeedAction extends ApiAuthAction
{
private $_profile = null;
......@@ -59,7 +58,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
......@@ -67,7 +65,8 @@ class AtompubfavoritefeedAction extends ApiAuthAction
$this->_profile = Profile::staticGet('id', $this->trimmed('profile'));
if (empty($this->_profile)) {
throw new ClientException(_('No such profile'), 404);
// TRANS: Client exception thrown when requesting a favorite feed for a non-existing profile.
throw new ClientException(_('No such profile.'), 404);
}
$offset = ($this->page-1) * $this->count;
......@@ -76,7 +75,7 @@ class AtompubfavoritefeedAction extends ApiAuthAction
$this->_faves = Fave::byProfile($this->_profile->id,
$offset,
$limit);
return true;
}
......@@ -87,7 +86,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return void
*/
function handle($argarray=null)
{
parent::handle($argarray);
......@@ -101,6 +99,7 @@ class AtompubfavoritefeedAction extends ApiAuthAction
$this->addFavorite();
break;
default:
// TRANS: Client exception thrown when using an unsupported HTTP method.
throw new ClientException(_('HTTP method not supported.'), 405);
return;
}
......@@ -113,7 +112,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return void
*/
function showFeed()
{
header('Content-Type: application/atom+xml; charset=utf-8');
......@@ -139,21 +137,25 @@ class AtompubfavoritefeedAction extends ApiAuthAction
$feed->addAuthor($this->_profile->getBestName(),
$this->_profile->getURI());
// TRANS: Title for Atom favorites feed.
// TRANS: %s is a user nickname.
$feed->setTitle(sprintf(_("%s favorites"),
$this->_profile->getBestName()));
$feed->setSubtitle(sprintf(_("Notices %s has favorited to on %s"),
// TRANS: Subtitle for Atom favorites feed.
// TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename.
$feed->setSubtitle(sprintf(_("Notices %1$s has favorited on %2$s"),
$this->_profile->getBestName(),
common_config('site', 'name')));
$feed->addLink(common_local_url('showfavorites',
array('nickname' =>
array('nickname' =>
$this->_profile->nickname)));
$feed->addLink($url,
array('rel' => 'self',
'type' => 'application/atom+xml'));
// If there's more...
if ($this->page > 1) {
......@@ -162,9 +164,9 @@ class AtompubfavoritefeedAction extends ApiAuthAction
'type' => 'application/atom+xml'));
$feed->addLink(common_local_url('AtomPubFavoriteFeed',
array('profile' =>
array('profile' =>
$this->_profile->id),
array('page' =>
array('page' =>
$this->page - 1)),
array('rel' => 'prev',
'type' => 'application/atom+xml'));
......@@ -205,17 +207,17 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return void
*/
function addFavorite()
{
// XXX: Refactor this; all the same for atompub
if (empty($this->auth_user) ||
$this->auth_user->id != $this->_profile->id) {
throw new ClientException(_("Can't add someone else's".
" subscription"), 403);
// TRANS: Client exception thrown when trying to set a favorite for another user.
throw new ClientException(_("Cannot add someone else's".
" subscription."), 403);
}
$xml = file_get_contents('php://input');
$dom = DOMDocument::loadXML($xml);
......@@ -234,9 +236,8 @@ class AtompubfavoritefeedAction extends ApiAuthAction
if (Event::handle('StartAtomPubNewActivity', array(&$activity))) {
if ($activity->verb != ActivityVerb::FAVORITE) {
// TRANS: Client error displayed when not using the POST verb.
// TRANS: Do not translate POST.
throw new ClientException(_('Can only handle Favorite activities.'));
// TRANS: Client exception thrown when trying use an incorrect activity verb for the Atom pub method.
throw new ClientException(_('Can only handle favorite activities.'));
return;
}
......@@ -245,6 +246,7 @@ class AtompubfavoritefeedAction extends ApiAuthAction
if (!in_array($note->type, array(ActivityObject::NOTE,
ActivityObject::BLOGENTRY,
ActivityObject::STATUS))) {
// TRANS: Client exception thrown when trying favorite an object that is not a notice.
throw new ClientException(_('Can only fave notices.'));
return;
}
......@@ -253,6 +255,7 @@ class AtompubfavoritefeedAction extends ApiAuthAction
if (empty($notice)) {
// XXX: import from listed URL or something
// TRANS: Client exception thrown when trying favorite a notice without content.
throw new ClientException(_('Unknown note.'));
}
......@@ -260,6 +263,7 @@ class AtompubfavoritefeedAction extends ApiAuthAction
'notice_id' => $notice->id));
if (!empty($old)) {
// TRANS: Client exception thrown when trying favorite an already favorited notice.
throw new ClientException(_('Already a favorite.'));
}
......@@ -296,7 +300,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return boolean is read only action?
*/
function isReadOnly($args)
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......@@ -328,7 +331,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return string etag http header
*/
function etag()
{
return null;
......@@ -339,7 +341,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return boolean true if delete, else false
*/
function requiresAuth()
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......@@ -359,7 +360,6 @@ class AtompubfavoritefeedAction extends ApiAuthAction
*
* @return void
*/
function notify($fave, $notice, $user)
{
$other = User::staticGet('id', $notice->profile_id);
......
......@@ -4,7 +4,7 @@
* Copyright (C) 2010, StatusNet, Inc.
*
* Feed of group memberships for a user, in ActivityStreams format
*
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
......@@ -46,7 +46,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class AtompubmembershipfeedAction extends ApiAuthAction
{
private $_profile = null;
......@@ -59,7 +58,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
......@@ -67,8 +65,9 @@ class AtompubmembershipfeedAction extends ApiAuthAction
$profileId = $this->trimmed('profile');
$this->_profile = Profile::staticGet('id', $profileId);
if (empty($this->_profile)) {
// TRANS: Client exception.
throw new ClientException(_('No such profile.'), 404);
}
......@@ -78,7 +77,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
$this->_memberships = Group_member::byMember($this->_profile->id,
$offset,
$limit);
return true;
}
......@@ -89,7 +88,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return void
*/
function handle($argarray=null)
{
parent::handle($argarray);
......@@ -103,6 +101,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
$this->addMembership();
break;
default:
// TRANS: Client exception thrown when using an unsupported HTTP method.
throw new ClientException(_('HTTP method not supported.'), 405);
return;
}
......@@ -115,7 +114,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return void
*/
function showFeed()
{
header('Content-Type: application/atom+xml; charset=utf-8');
......@@ -141,21 +139,25 @@ class AtompubmembershipfeedAction extends ApiAuthAction
$feed->addAuthor($this->_profile->getBestName(),
$this->_profile->getURI());
// TRANS: Title for group membership feed.
// TRANS: %s is a username.
$feed->setTitle(sprintf(_("%s group memberships"),
$this->_profile->getBestName()));
$feed->setSubtitle(sprintf(_("Groups %s is a member of on %s"),
// TRANS: Subtitle for group membership feed.
// TRANS: %1$s is a username, %2$s is the StatusNet sitename.
$feed->setSubtitle(sprintf(_("Groups %1$s is a member of on %2$s"),
$this->_profile->getBestName(),
common_config('site', 'name')));
$feed->addLink(common_local_url('usergroups',
array('nickname' =>
array('nickname' =>
$this->_profile->nickname)));
$feed->addLink($url,
array('rel' => 'self',
'type' => 'application/atom+xml'));
// If there's more...
if ($this->page > 1) {
......@@ -164,9 +166,9 @@ class AtompubmembershipfeedAction extends ApiAuthAction
'type' => 'application/atom+xml'));
$feed->addLink(common_local_url('AtomPubMembershipFeed',
array('profile' =>
array('profile' =>
$this->_profile->id),
array('page' =>
array('page' =>
$this->page - 1)),
array('rel' => 'prev',
'type' => 'application/atom+xml'));
......@@ -207,17 +209,17 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return void
*/
function addMembership()
{
// XXX: Refactor this; all the same for atompub
if (empty($this->auth_user) ||
$this->auth_user->id != $this->_profile->id) {
throw new ClientException(_("Can't add someone else's".
// TRANS: Client exception thrown when trying subscribe someone else to a group.
throw new ClientException(_("Cannot add someone else's".
" membership"), 403);
}
$xml = file_get_contents('php://input');
$dom = DOMDocument::loadXML($xml);
......@@ -234,25 +236,26 @@ class AtompubmembershipfeedAction extends ApiAuthAction
$membership = null;
if (Event::handle('StartAtomPubNewActivity', array(&$activity))) {
if ($activity->verb != ActivityVerb::JOIN) {
// TRANS: Client error displayed when not using the POST verb.
// TRANS: Do not translate POST.
throw new ClientException(_('Can only handle Join activities.'));
throw new ClientException(_('Can only handle join activities.'));
return;
}
$groupObj = $activity->objects[0];
if ($groupObj->type != ActivityObject::GROUP) {
// TRANS: Client exception thrown when trying favorite an object that is not a notice.
throw new ClientException(_('Can only fave notices.'));
return;
}
$group = User_group::staticGet('uri', $groupObj->id);
if (empty($group)) {
// XXX: import from listed URL or something
// TRANS: Client exception thrown when trying to subscribe to a non-existing group.
throw new ClientException(_('Unknown group.'));
}
......@@ -260,6 +263,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
'group_id' => $group->id));
if (!empty($old)) {
// TRANS: Client exception thrown when trying to subscribe to an already subscribed group.
throw new ClientException(_('Already a member.'));
}
......@@ -267,6 +271,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
if (Group_block::isBlocked($group, $profile)) {
// XXX: import from listed URL or something
// TRANS: Client exception thrown when trying to subscribe to group while blocked from that group.
throw new ClientException(_('Blocked by admin.'));
}
......@@ -299,7 +304,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return boolean is read only action?
*/
function isReadOnly($args)
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......@@ -331,7 +335,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return string etag http header
*/
function etag()
{
return null;
......@@ -342,7 +345,6 @@ class AtompubmembershipfeedAction extends ApiAuthAction
*
* @return boolean true if delete, else false
*/
function requiresAuth()
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......
......@@ -4,7 +4,7 @@
* Copyright (C) 2010, StatusNet, Inc.
*
* Show a single favorite in Atom Activity Streams format
*
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
......@@ -48,7 +48,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class AtompubshowfavoriteAction extends ApiAuthAction
{
private $_profile = null;
......@@ -62,7 +61,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
......@@ -73,12 +71,14 @@ class AtompubshowfavoriteAction extends ApiAuthAction
$this->_profile = Profile::staticGet('id', $profileId);
if (empty($this->_profile)) {
// TRANS: Client exception.
throw new ClientException(_('No such profile.'), 404);
}
$this->_notice = Notice::staticGet('id', $noticeId);
if (empty($this->_notice)) {
// TRANS: Client exception thrown when referencing a non-existing notice.
throw new ClientException(_('No such notice.'), 404);
}
......@@ -86,6 +86,7 @@ class AtompubshowfavoriteAction extends ApiAuthAction
'notice_id' => $noticeId));
if (empty($this->_fave)) {
// TRANS: Client exception thrown when referencing a non-existing favorite.
throw new ClientException(_('No such favorite.'), 404);
}
......@@ -99,7 +100,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return void
*/
function handle($argarray=null)
{
parent::handle($argarray);
......@@ -113,6 +113,7 @@ class AtompubshowfavoriteAction extends ApiAuthAction
$this->deleteFave();
break;
default:
// TRANS: Client exception thrown using an unsupported HTTP method.
throw new ClientException(_('HTTP method not supported.'),
405);
}
......@@ -121,10 +122,9 @@ class AtompubshowfavoriteAction extends ApiAuthAction
/**
* Show a single favorite, in ActivityStreams format
*
*
* @return void
*/
function showFave()
{
$activity = $this->_fave->asActivity();
......@@ -140,15 +140,15 @@ class AtompubshowfavoriteAction extends ApiAuthAction
/**
* Delete the favorite
*
*
* @return void
*/
function deleteFave()
{
if (empty($this->auth_user) ||
$this->auth_user->id != $this->_profile->id) {
throw new ClientException(_("Can't delete someone else's".
// TRANS: Client exception thrown when trying to remove a favorite notice of another user.
throw new ClientException(_("Cannot delete someone else's".
" favorite"), 403);
}
......@@ -166,7 +166,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return boolean is read only action?
*/
function isReadOnly($args)
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......@@ -184,7 +183,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return string last modified http header
*/
function lastModified()
{
return max(strtotime($this->_profile->modified),
......@@ -199,7 +197,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return string etag http header
*/
function etag()
{
$mtime = strtotime($this->_fave->modified);
......@@ -215,7 +212,6 @@ class AtompubshowfavoriteAction extends ApiAuthAction
*
* @return boolean true if delete, else false
*/
function requiresAuth()
{
if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
......
......@@ -4,7 +4,7 @@
* Copyright (C) 2010, StatusNet, Inc.
*
* Show a single membership as an Activity Streams entry
*
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
......@@ -46,7 +46,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class AtompubshowmembershipAction extends ApiAuthAction
{
private $_profile = null;
......@@ -60,7 +59,6 @@ class AtompubshowmembershipAction extends ApiAuthAction
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
......@@ -68,8 +66,9 @@ class AtompubshowmembershipAction extends ApiAuthAction
$profileId = $this->trimmed('profile');
$this->_profile = Profile::staticGet('id', $profileId);
if (empty($this->_profile)) {
// TRANS: Client exception.
throw new ClientException(_('No such profile.'), 404);
}
......@@ -78,6 +77,7 @@ class AtompubshowmembershipAction extends ApiAuthAction
$this->_group = User_group::staticGet('id', $groupId);
if (empty($this->_group)) {
// TRANS: Client exception thrown when referencing a non-existing group.
throw new ClientException(_('No such group'), 404);
}
......@@ -87,6 +87,7 @@ class AtompubshowmembershipAction extends ApiAuthAction
$this->_membership = Group_member::pkeyGet($kv);
if (empty($this->_membership)) {
// TRANS: Client exception thrown when trying to show membership of a non-subscribed group
throw new ClientException(_('Not a member'), 404);
}
......@@ -100,7 +101,6 @@ class AtompubshowmembershipAction extends ApiAuthAction
*
* @return void
*/
function handle($argarray=null)
{