diff options
Diffstat (limited to 'actions')
54 files changed, 1194 insertions, 1617 deletions
diff --git a/actions/accesstoken.php b/actions/accesstoken.php index 2a8cd1713..dcd04a1b4 100644 --- a/actions/accesstoken.php +++ b/actions/accesstoken.php @@ -1,6 +1,6 @@ <?php /** - * Access token class. + * Access token class * * PHP version 5 * @@ -32,10 +32,11 @@ if (!defined('LACONICA')) { exit(1); } +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; require_once INSTALLDIR.'/lib/omb.php'; /** - * Access token class. + * Access token class * * @category Action * @package Laconica @@ -47,28 +48,23 @@ require_once INSTALLDIR.'/lib/omb.php'; class AccesstokenAction extends Action { /** - * Class handler. + * Class handler * * @param array $args query arguments * - * @return boolean false if user doesn't exist - */ + * @return nothing + * + **/ function handle($args) { parent::handle($args); try { - common_debug('getting request from env variables', __FILE__); - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('accesstoken')); - common_debug('getting a server', __FILE__); - $server = omb_oauth_server(); - common_debug('fetching the access token', __FILE__); - $token = $server->fetch_access_token($req); - common_debug('got this token: "'.print_r($token, true).'"', __FILE__); - common_debug('printing the access token', __FILE__); - print $token; - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->writeAccessToken(); + } catch (Exception $e) { $this->serverError($e->getMessage()); } } } +?> diff --git a/actions/all.php b/actions/all.php index f06ead2a8..38aee65b6 100644 --- a/actions/all.php +++ b/actions/all.php @@ -25,11 +25,31 @@ require_once INSTALLDIR.'/lib/feedlist.php'; class AllAction extends ProfileAction { + var $notice; + function isReadOnly($args) { return true; } + function prepare($args) + { + parent::prepare($args); + $cur = common_current_user(); + + if (!empty($cur) && $cur->id == $this->user->id) { + $this->notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); + } else { + $this->notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); + } + + if($this->page > 1 && $this->notice->N == 0){ + $this->serverError(_('No such page'),$code=404); + } + + return true; + } + function handle($args) { parent::handle($args); @@ -88,7 +108,9 @@ class AllAction extends ProfileAction } } else { - $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin', + $this->user->nickname); } $this->elementStart('div', 'guide'); @@ -98,15 +120,7 @@ class AllAction extends ProfileAction function showContent() { - $cur = common_current_user(); - - if (!empty($cur) && $cur->id == $this->user->id) { - $notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); - } else { - $notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); - } - - $nl = new NoticeList($notice, $this); + $nl = new NoticeList($this->notice, $this); $cnt = $nl->show(); diff --git a/actions/allrss.php b/actions/allrss.php index 885a67f61..260667090 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -115,8 +115,8 @@ class AllrssAction extends Rss10Action 'link' => common_local_url('all', array('nickname' => $user->nickname)), - 'description' => sprintf(_('Feed for friends of %s'), - $user->nickname)); + 'description' => sprintf(_('Updates from %1$s and friends on %2$s!'), + $user->nickname, common_config('site', 'name'))); return $c; } diff --git a/actions/api.php b/actions/api.php index 99ab262ad..6d226af7e 100644 --- a/actions/api.php +++ b/actions/api.php @@ -131,6 +131,8 @@ class ApiAction extends Action 'tags/timeline', 'oembed/oembed', 'groups/show', + 'groups/timeline', + 'groups/list_all', 'groups/timeline'); static $bareauth = array('statuses/user_timeline', @@ -140,7 +142,8 @@ class ApiAction extends Action 'statuses/mentions', 'statuses/followers', 'favorites/favorites', - 'friendships/show'); + 'friendships/show', + 'groups/list_groups'); $fullname = "$this->api_action/$this->api_method"; diff --git a/actions/attachment.php b/actions/attachment.php index c6a5d0d52..f42906fd8 100644 --- a/actions/attachment.php +++ b/actions/attachment.php @@ -103,18 +103,18 @@ class AttachmentAction extends Action $this->element('link',array('rel'=>'alternate', 'type'=>'application/json+oembed', 'href'=>common_local_url( - 'api', - array('apiaction'=>'oembed','method'=>'oembed.json'), - array('url'=> + 'oembed', + array(), + array('format'=>'json', 'url'=> common_local_url('attachment', array('attachment' => $this->attachment->id)))), 'title'=>'oEmbed'),null); $this->element('link',array('rel'=>'alternate', 'type'=>'text/xml+oembed', 'href'=>common_local_url( - 'api', - array('apiaction'=>'oembed','method'=>'oembed.xml'), - array('url'=> + 'oembed', + array(), + array('format'=>'xml','url'=> common_local_url('attachment', array('attachment' => $this->attachment->id)))), 'title'=>'oEmbed'),null); diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index c2bb35a39..c45514ff6 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -382,13 +382,7 @@ class AvatarsettingsAction extends AccountSettingsAction function showStylesheets() { parent::showStylesheets(); - $jcropStyle = - common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION); - - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => $jcropStyle, - 'media' => 'screen, projection, tv')); + $this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv'); } /** @@ -402,13 +396,8 @@ class AvatarsettingsAction extends AccountSettingsAction parent::showScripts(); if ($this->mode == 'crop') { - $jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js'); - $jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js'); - - $this->element('script', array('type' => 'text/javascript', - 'src' => $jcropPack)); - $this->element('script', array('type' => 'text/javascript', - 'src' => $jcropGo)); + $this->script('js/jcrop/jquery.Jcrop.min.js'); + $this->script('js/jcrop/jquery.Jcrop.go.js'); } } } diff --git a/actions/confirmaddress.php b/actions/confirmaddress.php index 725c1f1e3..3c41a5c70 100644 --- a/actions/confirmaddress.php +++ b/actions/confirmaddress.php @@ -67,7 +67,11 @@ class ConfirmaddressAction extends Action parent::handle($args); if (!common_logged_in()) { common_set_returnto($this->selfUrl()); - common_redirect(common_local_url('login')); + if (!common_config('site', 'openidonly')) { + common_redirect(common_local_url('login')); + } else { + common_redirect(common_local_url('openidlogin')); + } return; } $code = $this->trimmed('code'); diff --git a/actions/editgroup.php b/actions/editgroup.php index 6aa6f8b11..aeeea2b63 100644 --- a/actions/editgroup.php +++ b/actions/editgroup.php @@ -196,8 +196,8 @@ class EditgroupAction extends GroupDesignAction } else if (!is_null($fullname) && mb_strlen($fullname) > 255) { $this->showForm(_('Full name is too long (max 255 chars).')); return; - } else if (!is_null($description) && mb_strlen($description) > 140) { - $this->showForm(_('description is too long (max 140 chars).')); + } else if (User_group::descriptionTooLong($description)) { + $this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription())); return; } else if (!is_null($location) && mb_strlen($location) > 255) { $this->showForm(_('Location is too long (max 255 chars).')); diff --git a/actions/emailsettings.php b/actions/emailsettings.php index 634388fdd..cdd092829 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -122,7 +122,7 @@ class EmailsettingsAction extends AccountSettingsAction } $this->elementEnd('fieldset'); - if ($user->email) { + if (common_config('emailpost', 'enabled') && $user->email) { $this->elementStart('fieldset', array('id' => 'settings_email_incoming')); $this->element('legend',_('Incoming email')); if ($user->incomingemail) { @@ -173,11 +173,13 @@ class EmailsettingsAction extends AccountSettingsAction _('Allow friends to nudge me and send me an email.'), $user->emailnotifynudge); $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('emailpost', - _('I want to post notices by email.'), - $user->emailpost); - $this->elementEnd('li'); + if (common_config('emailpost', 'enabled')) { + $this->elementStart('li'); + $this->checkbox('emailpost', + _('I want to post notices by email.'), + $user->emailpost); + $this->elementEnd('li'); + } $this->elementStart('li'); $this->checkbox('emailmicroid', _('Publish a MicroID for my email address.'), diff --git a/actions/favorited.php b/actions/favorited.php index 156c7a700..a3d1a5e20 100644 --- a/actions/favorited.php +++ b/actions/favorited.php @@ -153,7 +153,8 @@ class FavoritedAction extends Action $message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.'); } else { - $message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!'); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to add a notice to your favorites!'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } $this->elementStart('div', 'guide'); diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php index c439a9a62..5dc09e5e8 100644 --- a/actions/favoritesrss.php +++ b/actions/favoritesrss.php @@ -111,8 +111,8 @@ class FavoritesrssAction extends Rss10Action 'link' => common_local_url('showfavorites', array('nickname' => $user->nickname)), - 'description' => sprintf(_('Feed of favorite notices of %s'), - $user->nickname)); + 'description' => sprintf(_('Updates favored by %1$s on %2$s!'), + $user->nickname, common_config('site', 'name'))); return $c; } diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index 5c764aeb0..da563cb29 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Handler for remote subscription finish callback + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -15,285 +26,123 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ + **/ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/extlib/libomb/service_consumer.php'; +require_once INSTALLDIR.'/lib/omb.php'; +/** + * Handler for remote subscription finish callback + * + * When a remote user subscribes a local user, a redirect to this action is + * issued after the remote user authorized his service to subscribe. + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class FinishremotesubscribeAction extends Action { + /** + * Class handler. + * + * @param array $args query arguments + * + * @return nothing + * + **/ function handle($args) { - parent::handle($args); - if (common_logged_in()) { - $this->clientError(_('You can use the local subscription!')); - return; - } - - $omb = $_SESSION['oauth_authorization_request']; + /* Restore session data. RemotesubscribeAction should have stored + this entry. */ + $service = unserialize($_SESSION['oauth_authorization_request']); - if (!$omb) { + if (!$service) { $this->clientError(_('Not expecting this response!')); return; } - common_debug('stored request: '.print_r($omb,true), __FILE__); - - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization')); - - $token = $req->get_parameter('oauth_token'); - - # I think this is the success metric - - if ($token != $omb['token']) { - $this->clientError(_('Not authorized.')); - return; - } - - $version = $req->get_parameter('omb_version'); - - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unknown version of OMB protocol.')); - return; - } - - $nickname = $req->get_parameter('omb_listener_nickname'); - - if (!$nickname) { - $this->clientError(_('No nickname provided by remote server.')); - return; - } - - $profile_url = $req->get_parameter('omb_listener_profile'); - - if (!$profile_url) { - $this->clientError(_('No profile URL returned by server.')); - return; - } - - if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) { - $this->clientError(_('Invalid profile URL returned by server.')); - return; - } - - if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) { - $this->clientError(_('You can use the local subscription!')); - return; - } - - common_debug('listenee: "'.$omb['listenee'].'"', __FILE__); + common_debug('stored request: '. print_r($service, true), __FILE__); - $user = User::staticGet('nickname', $omb['listenee']); + /* Create user objects for both users. Do it early for request + validation. */ + $user = User::staticGet('uri', $service->getListeneeURI()); if (!$user) { - $this->clientError(_('User being listened to doesn\'t exist.')); + $this->clientError(_('User being listened to does not exist.')); return; } - $other = User::staticGet('uri', $omb['listener']); + $other = User::staticGet('uri', $service->getListenerURI()); if ($other) { $this->clientError(_('You can use the local subscription!')); return; } - $fullname = $req->get_parameter('omb_listener_fullname'); - $homepage = $req->get_parameter('omb_listener_homepage'); - $bio = $req->get_parameter('omb_listener_bio'); - $location = $req->get_parameter('omb_listener_location'); - $avatar_url = $req->get_parameter('omb_listener_avatar'); + $remote = Remote_profile::staticGet('uri', $service->getListenerURI()); - list($newtok, $newsecret) = $this->access_token($omb); + $profile = Profile::staticGet($remote->id); - if (!$newtok || !$newsecret) { - $this->clientError(_('Couldn\'t convert request tokens to access tokens.')); + if ($user->hasBlocked($profile)) { + $this->clientError(_('That user has blocked you from subscribing.')); return; } - # XXX: possible attack point; subscribe and return someone else's profile URI - - $remote = Remote_profile::staticGet('uri', $omb['listener']); - - if ($remote) { - $exists = true; - $profile = Profile::staticGet($remote->id); - $orig_remote = clone($remote); - $orig_profile = clone($profile); - # XXX: compare current postNotice and updateProfile URLs to the ones - # stored in the DB to avoid (possibly...) above attack - } else { - $exists = false; - $remote = new Remote_profile(); - $remote->uri = $omb['listener']; - $profile = new Profile(); - } - - $profile->nickname = $nickname; - $profile->profileurl = $profile_url; - - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } - - if ($exists) { - $profile->update($orig_profile); - } else { - $profile->created = DB_DataObject_Cast::dateTime(); # current time - $id = $profile->insert(); - if (!$id) { - $this->serverError(_('Error inserting new profile')); - return; - } - $remote->id = $id; - } - - if ($avatar_url) { - if (!$this->add_avatar($profile, $avatar_url)) { - $this->serverError(_('Error inserting avatar')); + /* Perform the handling itself via libomb. */ + try { + $service->finishAuthorization(); + } catch (OAuthException $e) { + if ($e->getMessage() == 'The authorized token does not equal the ' . + 'submitted token.') { + $this->clientError(_('You are not authorized.')); return; - } - } - - $remote->postnoticeurl = $omb['post_notice_url']; - $remote->updateprofileurl = $omb['update_profile_url']; - - if ($exists) { - if (!$remote->update($orig_remote)) { - $this->serverError(_('Error updating remote profile')); - return; - } - } else { - $remote->created = DB_DataObject_Cast::dateTime(); # current time - if (!$remote->insert()) { - $this->serverError(_('Error inserting remote profile')); + } else { + $this->clientError(_('Could not convert request token to ' . + 'access token.')); return; } - } - - if ($user->hasBlocked($profile)) { - $this->clientError(_('That user has blocked you from subscribing.')); + } catch (OMB_RemoteServiceException $e) { + $this->clientError(_('Remote service uses unknown version of ' . + 'OMB protocol.')); + return; + } catch (Exception $e) { + common_debug('Got exception ' . print_r($e, true), __FILE__); + $this->clientError($e->getMessage()); return; } - $sub = new Subscription(); + /* The service URLs are not accessible from datastore, so setting them + after insertion of the profile. */ + $orig_remote = clone($remote); - $sub->subscriber = $remote->id; - $sub->subscribed = $user->id; + $remote->postnoticeurl = + $service->getServiceURI(OMB_ENDPOINT_POSTNOTICE); + $remote->updateprofileurl = + $service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE); - $sub_exists = false; - - if ($sub->find(true)) { - $sub_exists = true; - $orig_sub = clone($sub); - } else { - $sub_exists = false; - $sub->created = DB_DataObject_Cast::dateTime(); # current time - } - - $sub->token = $newtok; - $sub->secret = $newsecret; - - if ($sub_exists) { - $result = $sub->update($orig_sub); - } else { - $result = $sub->insert(); - } - - if (!$result) { - common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__); - $this->clientError(_('Couldn\'t insert new subscription.')); - return; + if (!$remote->update($orig_remote)) { + $this->serverError(_('Error updating remote profile')); + return; } - # Notify user, if necessary - - mail_subscribe_notify_profile($user, $profile); - - # Clear the data + /* Clear the session data. */ unset($_SESSION['oauth_authorization_request']); - # If we show subscriptions in reverse chron order, this should - # show up close to the top of the page - + /* If we show subscriptions in reverse chronological order, the new one + should show up close to the top of the page. */ common_redirect(common_local_url('subscribers', array('nickname' => $user->nickname)), 303); } - - function add_avatar($profile, $url) - { - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - return $profile->setOriginal($filename); - } - - function access_token($omb) - { - - common_debug('starting request for access token', __FILE__); - - $con = omb_oauth_consumer(); - $tok = new OAuthToken($omb['token'], $omb['secret']); - - common_debug('using request token "'.$tok.'"', __FILE__); - - $url = $omb['access_token_url']; - - common_debug('using access token url "'.$url.'"', __FILE__); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params); - - $req->set_parameter('omb_version', OMB_VERSION_01); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, $tok); - - # We re-use this tool's fetcher, since it's pretty good - - common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__); - common_debug('posting request data "'.$req->to_postdata().'"', __FILE__); - - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); - - common_debug('got result: "'.print_r($result,true).'"', __FILE__); - - if ($result->status != 200) { - return null; - } - - parse_str($result->body, $return); - - return array($return['oauth_token'], $return['oauth_token_secret']); - } } diff --git a/actions/grouplogo.php b/actions/grouplogo.php index 8f6158dac..87c68e2a2 100644 --- a/actions/grouplogo.php +++ b/actions/grouplogo.php @@ -428,13 +428,7 @@ class GrouplogoAction extends GroupDesignAction function showStylesheets() { parent::showStylesheets(); - $jcropStyle = - common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION); - - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => $jcropStyle, - 'media' => 'screen, projection, tv')); + $this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv'); } /** @@ -448,13 +442,8 @@ class GrouplogoAction extends GroupDesignAction parent::showScripts(); if ($this->mode == 'crop') { - $jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js'); - $jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js'); - - $this->element('script', array('type' => 'text/javascript', - 'src' => $jcropPack)); - $this->element('script', array('type' => 'text/javascript', - 'src' => $jcropGo)); + $this->script('js/jcrop/jquery.Jcrop.min.js'); + $this->script('js/jcrop/jquery.Jcrop.go.js'); } } diff --git a/actions/grouprss.php b/actions/grouprss.php index 2bdcaafb2..e1e2d2018 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -132,9 +132,10 @@ class groupRssAction extends Rss10Action $c = array('url' => common_local_url('grouprss', array('nickname' => $group->nickname)), - 'title' => $group->nickname, + 'title' => sprintf(_('%s timeline'), $group->nickname), 'link' => common_local_url('showgroup', array('nickname' => $group->nickname)), - 'description' => sprintf(_('Microblog by %s group'), $group->nickname)); + 'description' => sprintf(_('Updates from members of %1$s on %2$s!'), + $group->nickname, common_config('site', 'name'))); return $c; } diff --git a/actions/groupsearch.php b/actions/groupsearch.php index c50466ce6..7437166e6 100644 --- a/actions/groupsearch.php +++ b/actions/groupsearch.php @@ -82,7 +82,8 @@ class GroupsearchAction extends SearchAction $message = _('If you can\'t find the group you\'re looking for, you can [create it](%%action.newgroup%%) yourself.'); } else { - $message = _('Why not [register an account](%%action.register%%) and [create the group](%%action.newgroup%%) yourself!'); + $message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and [create the group](%%%%action.newgroup%%%%) yourself!'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } $this->elementStart('div', 'guide'); $this->raw(common_markup_to_html($message)); diff --git a/actions/imsettings.php b/actions/imsettings.php index e0f5ede3a..70a6f37d4 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -84,6 +84,12 @@ class ImsettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('xmpp', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('IM is not available.')); + return; + } + $user = common_current_user(); $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_im', diff --git a/actions/invite.php b/actions/invite.php index 26c951ed2..bdc0d34cb 100644 --- a/actions/invite.php +++ b/actions/invite.php @@ -235,7 +235,7 @@ class InviteAction extends CurrentUserDesignAction common_root_url(), $personal, common_local_url('showstream', array('nickname' => $user->nickname)), - common_local_url('register', array('code' => $invite->code))); + common_local_url((!common_config('site', 'openidonly')) ? 'register' : 'openidlogin', array('code' => $invite->code))); mail_send($recipients, $headers, $body); } diff --git a/actions/login.php b/actions/login.php index f5a658bf5..e09fdc76b 100644 --- a/actions/login.php +++ b/actions/login.php @@ -247,7 +247,7 @@ class LoginAction extends Action return _('For security reasons, please re-enter your ' . 'user name and password ' . 'before changing your settings.'); - } else { + } else if (common_config('openid', 'enabled')) { return _('Login with your username and password. ' . 'Don\'t have a username yet? ' . '[Register](%%action.register%%) a new account.'); diff --git a/actions/newgroup.php b/actions/newgroup.php index 0289e77c2..71647d834 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -146,8 +146,8 @@ class NewgroupAction extends Action } else if (!is_null($fullname) && mb_strlen($fullname) > 255) { $this->showForm(_('Full name is too long (max 255 chars).')); return; - } else if (!is_null($description) && mb_strlen($description) > 140) { - $this->showForm(_('description is too long (max 140 chars).')); + } else if (User_group::descriptionTooLong($description)) { + $this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription())); return; } else if (!is_null($location) && mb_strlen($location) > 255) { $this->showForm(_('Location is too long (max 255 chars).')); diff --git a/actions/newmessage.php b/actions/newmessage.php index 52d4899ba..cd26e1640 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -144,9 +144,10 @@ class NewmessageAction extends Action } else { $content_shortened = common_shorten_links($this->content); - if (mb_strlen($content_shortened) > 140) { - $this->showForm(_('That\'s too long. ' . - 'Max message size is 140 chars.')); + if (Message::contentTooLong($content_shortened)) { + $this->showForm(sprintf(_('That\'s too long. ' . + 'Max message size is %d chars.'), + Message::maxContent())); return; } } diff --git a/actions/newnotice.php b/actions/newnotice.php index e254eac49..049d7c322 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -91,8 +91,8 @@ class NewnoticeAction extends Action // is losts when size is exceeded if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) { $this->clientError(sprintf(_('The server was unable to handle ' . - 'that much POST data (%s bytes) due to its current configuration.'), - $_SERVER['CONTENT_LENGTH'])); + 'that much POST data (%s bytes) due to its current configuration.'), + $_SERVER['CONTENT_LENGTH'])); } parent::handle($args); @@ -130,7 +130,7 @@ class NewnoticeAction extends Action $hint = ''; } $this->clientError(sprintf( - _('%s is not a supported filetype on this server.'), $filetype) . $hint); + _('%s is not a supported filetype on this server.'), $filetype) . $hint); } function isRespectsQuota($user) { @@ -162,9 +162,10 @@ class NewnoticeAction extends Action $this->clientError(_('No content!')); } else { $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { - $this->clientError(_('That\'s too long. '. - 'Max notice size is 140 chars.')); + if (Notice::contentTooLong($content_shortened)) { + $this->clientError(sprintf(_('That\'s too long. '. + 'Max notice size is %d chars.'), + Notice::maxContent())); } } @@ -190,37 +191,37 @@ class NewnoticeAction extends Action if (isset($_FILES['attach']['error'])) { switch ($_FILES['attach']['error']) { - case UPLOAD_ERR_NO_FILE: - // no file uploaded, nothing to do - break; + case UPLOAD_ERR_NO_FILE: + // no file uploaded, nothing to do + break; - case UPLOAD_ERR_OK: - $mimetype = $this->getUploadedFileType(); - if (!$this->isRespectsQuota($user)) { - die('clientError() should trigger an exception before reaching here.'); - } - break; + case UPLOAD_ERR_OK: + $mimetype = $this->getUploadedFileType(); + if (!$this->isRespectsQuota($user)) { + die('clientError() should trigger an exception before reaching here.'); + } + break; - case UPLOAD_ERR_INI_SIZE: - $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); + case UPLOAD_ERR_INI_SIZE: + $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); - case UPLOAD_ERR_FORM_SIZE: - $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); + case UPLOAD_ERR_FORM_SIZE: + $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); - case UPLOAD_ERR_PARTIAL: - $this->clientError(_('The uploaded file was only partially uploaded.')); + case UPLOAD_ERR_PARTIAL: + $this->clientError(_('The uploaded file was only partially uploaded.')); - case UPLOAD_ERR_NO_TMP_DIR: - $this->clientError(_('Missing a temporary folder.')); + case UPLOAD_ERR_NO_TMP_DIR: + $this->clientError(_('Missing a temporary folder.')); - case UPLOAD_ERR_CANT_WRITE: - $this->clientError(_('Failed to write file to disk.')); + case UPLOAD_ERR_CANT_WRITE: + $this->clientError(_('Failed to write file to disk.')); - case UPLOAD_ERR_EXTENSION: - $this->clientError(_('File upload stopped by extension.')); + case UPLOAD_ERR_EXTENSION: + $this->clientError(_('File upload stopped by extension.')); - default: - die('Should never reach here.'); + default: + die('Should never reach here.'); } } @@ -233,7 +234,7 @@ class NewnoticeAction extends Action $fileRecord = $this->storeFile($filename, $mimetype); $fileurl = common_local_url('attachment', - array('attachment' => $fileRecord->id)); + array('attachment' => $fileRecord->id)); // not sure this is necessary -- Zach $this->maybeAddRedir($fileRecord->id, $fileurl); @@ -241,9 +242,10 @@ class NewnoticeAction extends Action $short_fileurl = common_shorten_url($fileurl); $content_shortened .= ' ' . $short_fileurl; - if (mb_strlen($content_shortened) > 140) { + if (Notice::contentTooLong($content_shortened)) { $this->deleteFile($filename); - $this->clientError(_('Max notice size is 140 chars, including attachment URL.')); + $this->clientError(sprintf(_('Max notice size is %d chars, including attachment URL.'), + Notice::maxContent())); } // Also, not sure this is necessary -- Zach @@ -367,7 +369,7 @@ class NewnoticeAction extends Action File_to_post::processNew($filerec->id, $notice->id); $this->maybeAddRedir($filerec->id, - common_local_url('file', array('notice' => $notice->id))); + common_local_url('file', array('notice' => $notice->id))); } /** diff --git a/actions/noticesearch.php b/actions/noticesearch.php index 49b473d9e..90b3309cf 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -121,7 +121,9 @@ class NoticesearchAction extends SearchAction $message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q)); } else { - $message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q)); + $message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin', + urlencode($q)); } $this->elementStart('div', 'guide'); diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php index 2a4b2060d..045531c5a 100644 --- a/actions/noticesearchrss.php +++ b/actions/noticesearchrss.php @@ -86,9 +86,10 @@ class NoticesearchrssAction extends Rss10Action { $q = $this->trimmed('q'); $c = array('url' => common_local_url('noticesearchrss', array('q' => $q)), - 'title' => common_config('site', 'name') . sprintf(_(' Search Stream for "%s"'), $q), + 'title' => sprintf(_('Updates with "%s"'), $q), 'link' => common_local_url('noticesearch', array('q' => $q)), - 'description' => sprintf(_('All updates matching search term "%s"'), $q)); + 'description' => sprintf(_('Updates matching search term "%1$s" on %2$s!'), + $q, common_config('site', 'name'))); return $c; } diff --git a/actions/twitapioembed.php b/actions/oembed.php index 3019e5878..3e46a7262 100644 --- a/actions/twitapioembed.php +++ b/actions/oembed.php @@ -31,8 +31,6 @@ if (!defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; - /** * Oembed provider implementation * @@ -46,17 +44,13 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @link http://laconi.ca/ */ -class TwitapioembedAction extends TwitterapiAction +class OembedAction extends Action { - function oembed($args, $apidata) + function handle($args) { - parent::handle($args); - common_debug("in oembed api action"); - $this->auth_user = $apidata['user']; - $url = $args['url']; if( substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url()) ){ $path = substr($url,strlen(common_root_url())); @@ -131,8 +125,7 @@ class TwitapioembedAction extends TwitterapiAction default: $this->serverError(_("$path not supported for oembed requests"), 501); } - - switch($apidata['content-type']){ + switch($args['format']){ case 'xml': $this->init_document('xml'); $this->elementStart('oembed'); @@ -151,12 +144,11 @@ class TwitapioembedAction extends TwitterapiAction if($oembed['thumbnail_url']) $this->element('thumbnail_url',null,$oembed['thumbnail_url']); if($oembed['thumbnail_width']) $this->element('thumbnail_width',null,$oembed['thumbnail_width']); if($oembed['thumbnail_height']) $this->element('thumbnail_height',null,$oembed['thumbnail_height']); - $this->elementEnd('oembed'); $this->end_document('xml'); break; - case 'json': + case 'json': case '': $this->init_document('json'); print(json_encode($oembed)); $this->end_document('json'); @@ -164,10 +156,51 @@ class TwitapioembedAction extends TwitterapiAction default: $this->serverError(_('content type ' . $apidata['content-type'] . ' not supported'), 501); } - }else{ $this->serverError(_('Only ' . common_root_url() . ' urls over plain http please'), 404); } } -} + function init_document($type) + { + switch ($type) { + case 'xml': + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + break; + case 'json': + header('Content-Type: application/json; charset=utf-8'); + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print $callback . '('; + } + break; + default: + $this->serverError(_('Not a supported data format.'), 501); + break; + } + } + + function end_document($type='xml') + { + switch ($type) { + case 'xml': + $this->endXML(); + break; + case 'json': + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print ')'; + } + break; + default: + $this->serverError(_('Not a supported data format.'), 501); + break; + } + return; + } + +} diff --git a/actions/opensearch.php b/actions/opensearch.php index 4fe95c93b..6044568f1 100644 --- a/actions/opensearch.php +++ b/actions/opensearch.php @@ -66,7 +66,7 @@ class OpensearchAction extends Action $type = 'noticesearch'; $short_name = _('Notice Search'); } - header('Content-Type: text/html'); + header('Content-Type: application/opensearchdescription+xml'); $this->startXML(); $this->elementStart('OpenSearchDescription', array('xmlns' => 'http://a9.com/-/spec/opensearch/1.1/')); $short_name = common_config('site', 'name').' '.$short_name; diff --git a/actions/postnotice.php b/actions/postnotice.php index eb2d63b61..14152a83d 100644 --- a/actions/postnotice.php +++ b/actions/postnotice.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Handle postnotice action + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,75 +28,71 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +/** + * Handler for postnotice action + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class PostnoticeAction extends Action { + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) + { + parent::prepare($argarray); + try { + $this->checkNotice(); + } catch (Exception $e) { + $this->clientError($e->getMessage()); + return false; + } + return true; + } + function handle($args) { parent::handle($args); try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('postnotice')); - # Note: server-to-server function! - $server = omb_oauth_server(); - list($consumer, $token) = $server->verify_request($req); - if ($this->save_notice($req, $consumer, $token)) { - print "omb_version=".OMB_VERSION_01; - } - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->handlePostNotice(); + } catch (Exception $e) { $this->serverError($e->getMessage()); return; } } - function save_notice(&$req, &$consumer, &$token) + function checkNotice() { - $version = $req->get_parameter('omb_version'); - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unsupported OMB version'), 400); - return false; - } - # First, check to see - $listenee = $req->get_parameter('omb_listenee'); - $remote_profile = Remote_profile::staticGet('uri', $listenee); - if (!$remote_profile) { - $this->clientError(_('Profile unknown'), 403); - return false; - } - $sub = Subscription::staticGet('token', $token->key); - if (!$sub) { - $this->clientError(_('No such subscription'), 403); - return false; - } - $content = $req->get_parameter('omb_notice_content'); - $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { + $content = common_shorten_links($_POST['omb_notice_content']); + if (Notice::contentTooLong($content)) { $this->clientError(_('Invalid notice content'), 400); return false; } - $notice_uri = $req->get_parameter('omb_notice'); - if (!Validate::uri($notice_uri) && - !common_valid_tag($notice_uri)) { - $this->clientError(_('Invalid notice uri'), 400); - return false; - } - $notice_url = $req->get_parameter('omb_notice_url'); - if ($notice_url && !common_valid_http_url($notice_url)) { - $this->clientError(_('Invalid notice url'), 400); - return false; - } - $notice = Notice::staticGet('uri', $notice_uri); - if (!$notice) { - $notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, null, $notice_uri); - if (is_string($notice)) { - common_server_serror($notice, 500); - return false; - } - common_broadcast_notice($notice, true); + $license = $_POST['omb_notice_license']; + $site_license = common_config('license', 'url'); + if ($license && !common_compatible_license($license, $site_license)) { + throw new Exception(sprintf(_('Notice license ‘%s’ is not ' . + 'compatible with site license ‘%s’.'), + $license, $site_license)); } - return true; } } +?> diff --git a/actions/profilesettings.php b/actions/profilesettings.php index fb847680b..f429a2e51 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -109,9 +109,16 @@ class ProfilesettingsAction extends AccountSettingsAction _('URL of your homepage, blog, or profile on another site')); $this->elementEnd('li'); $this->elementStart('li'); + $maxBio = Profile::maxBio(); + if ($maxBio > 0) { + $bioInstr = sprintf(_('Describe yourself and your interests in %d chars'), + $maxBio); + } else { + $bioInstr = _('Describe yourself and your interests'); + } $this->textarea('bio', _('Bio'), ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, - _('Describe yourself and your interests in 140 chars')); + $bioInstr); $this->elementEnd('li'); $this->elementStart('li'); $this->input('location', _('Location'), @@ -189,7 +196,7 @@ class ProfilesettingsAction extends AccountSettingsAction // Some validation if (!Validate::string($nickname, array('min_length' => 1, 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { + 'format' => NICKNAME_FMT))) { $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); return; } else if (!User::allowed_nickname($nickname)) { @@ -202,8 +209,9 @@ class ProfilesettingsAction extends AccountSettingsAction } else if (!is_null($fullname) && mb_strlen($fullname) > 255) { $this->showForm(_('Full name is too long (max 255 chars).')); return; - } else if (!is_null($bio) && mb_strlen($bio) > 140) { - $this->showForm(_('Bio is too long (max 140 chars).')); + } else if (Profile::bioTooLong($bio)) { + $this->showForm(sprintf(_('Bio is too long (max %d chars).'), + Profile::maxBio())); return; } else if (!is_null($location) && mb_strlen($location) > 255) { $this->showForm(_('Location is too long (max 255 chars).')); diff --git a/actions/public.php b/actions/public.php index 322a52963..b68b2ff79 100644 --- a/actions/public.php +++ b/actions/public.php @@ -59,6 +59,7 @@ class PublicAction extends Action */ var $page = null; + var $notice; function isReadOnly($args) { @@ -84,6 +85,18 @@ class PublicAction extends Action common_set_returnto($this->selfUrl()); + $this->notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + + if (!$this->notice) { + $this->serverError(_('Could not retrieve public stream.')); + return; + } + + if($this->page > 1 && $this->notice->N == 0){ + $this->serverError(_('No such page'),$code=404); + } + return true; } @@ -165,7 +178,8 @@ class PublicAction extends Action } else { if (! (common_config('site','closed') || common_config('site','inviteonly'))) { - $message .= _('Why not [register an account](%%action.register%%) and be the first to post!'); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post!'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } } @@ -185,15 +199,7 @@ class PublicAction extends Action function showContent() { - $notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1); - - if (!$notice) { - $this->serverError(_('Could not retrieve public stream.')); - return; - } - - $nl = new NoticeList($notice, $this); + $nl = new NoticeList($this->notice, $this); $cnt = $nl->show(); @@ -220,9 +226,11 @@ class PublicAction extends Action function showAnonymousMessage() { if (! (common_config('site','closed') || common_config('site','inviteonly'))) { - $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . - 'based on the Free Software [Laconica](http://laconi.ca/) tool. ' . - '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ([Read more](%%doc.help%%))'); + $m = sprintf(_('This is %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . + 'based on the Free Software [Laconica](http://laconi.ca/) tool. ' . + '[Join now](%%%%action.%s%%%%) to share notices about yourself with friends, family, and colleagues! ' . + '([Read more](%%%%doc.help%%%%))'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } else { $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . 'based on the Free Software [Laconica](http://laconi.ca/) tool.'); diff --git a/actions/publicrss.php b/actions/publicrss.php index 7e8df9625..5c08de641 100644 --- a/actions/publicrss.php +++ b/actions/publicrss.php @@ -86,9 +86,9 @@ class PublicrssAction extends Rss10Action { $c = array( 'url' => common_local_url('publicrss') - , 'title' => sprintf(_('%s Public Stream'), common_config('site', 'name')) + , 'title' => sprintf(_('%s public timeline'), common_config('site', 'name')) , 'link' => common_local_url('public') - , 'description' => sprintf(_('All updates for %s'), common_config('site', 'name'))); + , 'description' => sprintf(_('%s updates from everyone!'), common_config('site', 'name'))); return $c; } diff --git a/actions/publictagcloud.php b/actions/publictagcloud.php index e9f33d58b..a2772869d 100644 --- a/actions/publictagcloud.php +++ b/actions/publictagcloud.php @@ -72,7 +72,8 @@ class PublictagcloudAction extends Action $message .= _('Be the first to post one!'); } else { - $message .= _('Why not [register an account](%%action.register%%) and be the first to post one!'); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post one!'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } $this->elementStart('div', 'guide'); diff --git a/actions/register.php b/actions/register.php index dd3edc4ed..aa295c097 100644 --- a/actions/register.php +++ b/actions/register.php @@ -207,8 +207,9 @@ class RegisterAction extends Action } else if (!is_null($fullname) && mb_strlen($fullname) > 255) { $this->showForm(_('Full name is too long (max 255 chars).')); return; - } else if (!is_null($bio) && mb_strlen($bio) > 140) { - $this->showForm(_('Bio is too long (max 140 chars).')); + } else if (Profile::bioTooLong($bio)) { + $this->showForm(sprintf(_('Bio is too long (max %d chars).'), + Profile::maxBio())); return; } else if (!is_null($location) && mb_strlen($location) > 255) { $this->showForm(_('Location is too long (max 255 chars).')); @@ -442,10 +443,16 @@ class RegisterAction extends Action 'or profile on another site')); $this->elementEnd('li'); $this->elementStart('li'); + $maxBio = Profile::maxBio(); + if ($maxBio > 0) { + $bioInstr = sprintf(_('Describe yourself and your interests in %d chars'), + $maxBio); + } else { + $bioInstr = _('Describe yourself and your interests'); + } $this->textarea('bio', _('Bio'), $this->trimmed('bio'), - _('Describe yourself and your '. - 'interests in 140 chars')); + $bioInstr); $this->elementEnd('li'); $this->elementStart('li'); $this->input('location', _('Location'), diff --git a/actions/remotesubscribe.php b/actions/remotesubscribe.php index e658f8d37..90499bbe2 100644 --- a/actions/remotesubscribe.php +++ b/actions/remotesubscribe.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Handler for remote subscription + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -15,11 +26,26 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ + **/ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_consumer.php'; +require_once INSTALLDIR.'/extlib/libomb/profile.php'; -require_once(INSTALLDIR.'/lib/omb.php'); +/** + * Handler for remote subscription + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class RemotesubscribeAction extends Action { @@ -36,7 +62,7 @@ class RemotesubscribeAction extends Action return false; } - $this->nickname = $this->trimmed('nickname'); + $this->nickname = $this->trimmed('nickname'); $this->profile_url = $this->trimmed('profile_url'); return true; @@ -47,7 +73,7 @@ class RemotesubscribeAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - # CSRF protection + /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { $this->showForm(_('There was a problem with your session token. '. @@ -71,11 +97,13 @@ class RemotesubscribeAction extends Action if ($this->err) { $this->element('div', 'error', $this->err); } else { - $inst = _('To subscribe, you can [login](%%action.login%%),' . - ' or [register](%%action.register%%) a new ' . - ' account. If you already have an account ' . - ' on a [compatible microblogging site](%%doc.openmublog%%), ' . - ' enter your profile URL below.'); + $inst = sprintf(_('To subscribe, you can [login](%%%%action.%s%%%%),' . + ' or [register](%%%%action.%s%%%%) a new ' . + ' account. If you already have an account ' . + ' on a [compatible microblogging site](%%doc.openmublog%%), ' . + ' enter your profile URL below.'), + (!common_config('site','openidonly')) ? 'login' : 'openidlogin', + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); $output = common_markup_to_html($inst); $this->elementStart('div', 'instructions'); $this->raw($output); @@ -90,8 +118,8 @@ class RemotesubscribeAction extends Action function showContent() { - # id = remotesubscribe conflicts with the - # button on profile page + /* The id 'remotesubscribe' conflicts with the + button on profile page. */ $this->elementStart('form', array('id' => 'form_remote_subscribe', 'method' => 'post', 'class' => 'form_settings', @@ -117,247 +145,50 @@ class RemotesubscribeAction extends Action function remoteSubscription() { - $user = $this->getUser(); - - if (!$user) { + if (!$this->nickname) { $this->showForm(_('No such user.')); return; } + $user = User::staticGet('nickname', $this->nickname); + $this->profile_url = $this->trimmed('profile_url'); if (!$this->profile_url) { - $this->showForm(_('No such user.')); + $this->showForm(_('No such user')); return; } - if (!Validate::uri($this->profile_url, array('allowed_schemes' => array('http', 'https')))) { + if (!common_valid_http_url($this->profile_url)) { $this->showForm(_('Invalid profile URL (bad format)')); return; } - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - $yadis = Auth_Yadis_Yadis::discover($this->profile_url, $fetcher); - - if (!$yadis || $yadis->failed) { - $this->showForm(_('Not a valid profile URL (no YADIS document).')); - return; - } - - # XXX: a little liberal for sites that accidentally put whitespace before the xml declaration - - $xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text)); - - if (!$xrds) { - $this->showForm(_('Not a valid profile URL (no XRDS defined).')); - return; - } - - $omb = $this->getOmb($xrds); - - if (!$omb) { - $this->showForm(_('Not a valid profile URL (incorrect services).')); - return; - } - - if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) == - common_local_url('requesttoken')) - { - $this->showForm(_('That\'s a local profile! Login to subscribe.')); + try { + $service = new OMB_Service_Consumer($this->profile_url, + common_root_url(), + omb_oauth_datastore()); + } catch (OMB_InvalidYadisException $e) { + $this->showForm(_('Not a valid profile URL (no YADIS document or ' . + 'no or invalid XRDS defined).')); return; } - if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) { - $this->showForm(_('That\'s a local profile! Login to subscribe.')); + if ($service->getServiceURI(OAUTH_ENDPOINT_REQUEST) == + common_local_url('requesttoken') || + User::staticGet('uri', $service->getRemoteUserURI())) { + $this->showForm(_('That’s a local profile! Login to subscribe.')); return; } - list($token, $secret) = $this->requestToken($omb); - - if (!$token || !$secret) { - $this->showForm(_('Couldn\'t get a request token.')); + try { + $service->requestToken(); + } catch (OMB_RemoteServiceException $e) { + $this->showForm(_('Couldn’t get a request token.')); return; } - $this->requestAuthorization($user, $omb, $token, $secret); - } - - function getUser() - { - $user = null; - if ($this->nickname) { - $user = User::staticGet('nickname', $this->nickname); - } - return $user; - } - - function getOmb($xrds) - { - static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE); - static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, - OAUTH_ENDPOINT_ACCESS); - $omb = array(); - - # XXX: the following code could probably be refactored to eliminate dupes - - $oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY); - - if (!$oauth_services) { - return null; - } - - $oauth_service = $oauth_services[0]; - - $oauth_xrd = $this->getXRD($oauth_service, $xrds); - - if (!$oauth_xrd) { - return null; - } - - if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) { - return null; - } - - $omb_services = omb_get_services($xrds, OMB_NAMESPACE); - - if (!$omb_services) { - return null; - } - - $omb_service = $omb_services[0]; - - $omb_xrd = $this->getXRD($omb_service, $xrds); - - if (!$omb_xrd) { - return null; - } - - if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) { - return null; - } - - # XXX: check that we got all the services we needed - - foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) { - if (!array_key_exists($type, $omb) || !$omb[$type]) { - return null; - } - } - - if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) { - return null; - } - - return $omb; - } - - function getXRD($main_service, $main_xrds) - { - $uri = omb_service_uri($main_service); - if (strpos($uri, "#") !== 0) { - # FIXME: more rigorous handling of external service definitions - return null; - } - $id = substr($uri, 1); - $nodes = $main_xrds->allXrdNodes; - $parser = $main_xrds->parser; - foreach ($nodes as $node) { - $attrs = $parser->attributes($node); - if (array_key_exists('xml:id', $attrs) && - $attrs['xml:id'] == $id) { - # XXX: trick the constructor into thinking this is the only node - $bogus_nodes = array($node); - return new Auth_Yadis_XRDS($parser, $bogus_nodes); - } - } - return null; - } - - function addServices($xrd, $types, &$omb) - { - foreach ($types as $type) { - $matches = omb_get_services($xrd, $type); - if ($matches) { - $omb[$type] = $matches[0]; - } else { - # no match for type - return false; - } - } - return true; - } - - function requestToken($omb) - { - $con = omb_oauth_consumer(); - - $url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, null, "POST", $url, $params); - - $listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]); - - if (!$listener) { - return null; - } - - $req->set_parameter('omb_listener', $listener); - $req->set_parameter('omb_version', OMB_VERSION_01); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, null); - - # We re-use this tool's fetcher, since it's pretty good - - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); - if ($result->status != 200) { - return null; - } - - parse_str($result->body, $return); - - return array($return['oauth_token'], $return['oauth_token_secret']); - } - - function requestAuthorization($user, $omb, $token, $secret) - { - $con = omb_oauth_consumer(); - $tok = new OAuthToken($token, $secret); - - $url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params); - - # We send over a ton of information. This lets the other - # server store info about our user, and it lets the current - # user decide if they really want to authorize the subscription. - - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname)); - $req->set_parameter('omb_listenee_nickname', $user->nickname); - $req->set_parameter('omb_listenee_license', common_config('license', 'url')); - + /* Create an OMB_Profile from $user. */ $profile = $user->getProfile(); if (!$profile) { common_log_db_error($user, 'SELECT', __FILE__); @@ -365,49 +196,16 @@ class RemotesubscribeAction extends Action return; } - if (!is_null($profile->fullname)) { - $req->set_parameter('omb_listenee_fullname', $profile->fullname); - } - if (!is_null($profile->homepage)) { - $req->set_parameter('omb_listenee_homepage', $profile->homepage); - } - if (!is_null($profile->bio)) { - $req->set_parameter('omb_listenee_bio', $profile->bio); - } - if (!is_null($profile->location)) { - $req->set_parameter('omb_listenee_location', $profile->location); - } - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $req->set_parameter('omb_listenee_avatar', $avatar->url); - } - - # XXX: add a nonce to prevent replay attacks - - $req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe')); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, $tok); - - # store all our info here - - $omb['listenee'] = $user->nickname; - $omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]); - $omb['token'] = $token; - $omb['secret'] = $secret; - # call doesn't work after bounce back so we cache; maybe serialization issue...? - $omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]); - $omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]); - $omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]); + $target_url = $service->requestAuthorization( + profile_to_omb_profile($user->uri, $profile), + common_local_url('finishremotesubscribe')); common_ensure_session(); - $_SESSION['oauth_authorization_request'] = $omb; - - # Redirect to authorization service + $_SESSION['oauth_authorization_request'] = serialize($service); - common_redirect($req->to_url(), 303); - return; + /* Redirect to the remote service for authorization. */ + common_redirect($target_url, 303); } } +?> diff --git a/actions/replies.php b/actions/replies.php index d7ed440e9..fcfc3a272 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -48,6 +48,7 @@ require_once INSTALLDIR.'/lib/feedlist.php'; class RepliesAction extends OwnerDesignAction { var $page = null; + var $notice; /** * Prepare the object @@ -84,6 +85,13 @@ class RepliesAction extends OwnerDesignAction common_set_returnto($this->selfUrl()); + $this->notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + + if($this->page > 1 && $this->notice->N == 0){ + $this->serverError(_('No such page'),$code=404); + } + return true; } @@ -159,10 +167,7 @@ class RepliesAction extends OwnerDesignAction function showContent() { - $notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1); - - $nl = new NoticeList($notice, $this); + $nl = new NoticeList($this->notice, $this); $cnt = $nl->show(); if (0 === $cnt) { @@ -187,7 +192,9 @@ class RepliesAction extends OwnerDesignAction } } else { - $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin', + $this->user->nickname); } $this->elementStart('div', 'guide'); diff --git a/actions/repliesrss.php b/actions/repliesrss.php index a87e2870d..580bb91f7 100644 --- a/actions/repliesrss.php +++ b/actions/repliesrss.php @@ -68,7 +68,8 @@ class RepliesrssAction extends Rss10Action 'link' => common_local_url('replies', array('nickname' => $user->nickname)), - 'description' => sprintf(_('Feed for replies to %s'), $user->nickname)); + 'description' => sprintf(_('Replies to %1$s on %2$s!'), + $user->nickname, common_config('site', 'name'))); return $c; } diff --git a/actions/requesttoken.php b/actions/requesttoken.php index 8d1e3f004..8328962f2 100644 --- a/actions/requesttoken.php +++ b/actions/requesttoken.php @@ -34,6 +34,7 @@ if (!defined('LACONICA')) { } require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; /** * Request token action class. @@ -49,17 +50,17 @@ class RequesttokenAction extends Action { /** * Is read only? - * + * * @return boolean false */ - function isReadOnly($args) + function isReadOnly() { return false; } - + /** * Class handler. - * + * * @param array $args array of arguments * * @return void @@ -68,14 +69,12 @@ class RequesttokenAction extends Action { parent::handle($args); try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('requesttoken')); - $server = omb_oauth_server(); - $token = $server->fetch_request_token($req); - print $token; - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->writeRequestToken(); + } catch (Exception $e) { $this->serverError($e->getMessage()); } } } - +?> diff --git a/actions/showfavorites.php b/actions/showfavorites.php index 8efe9d30a..91287cc96 100644 --- a/actions/showfavorites.php +++ b/actions/showfavorites.php @@ -114,6 +114,29 @@ class ShowfavoritesAction extends OwnerDesignAction common_set_returnto($this->selfUrl()); + $cur = common_current_user(); + + if (!empty($cur) && $cur->id == $this->user->id) { + + // Show imported/gateway notices as well as local if + // the user is looking at his own favorites + + $this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1, true); + } else { + $this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1, false); + } + + if (empty($this->notice)) { + $this->serverError(_('Could not retrieve favorite notices.')); + return; + } + + if($this->page > 1 && $this->notice->N == 0){ + $this->serverError(_('No such page'),$code=404); + } + return true; } @@ -173,7 +196,9 @@ class ShowfavoritesAction extends OwnerDesignAction } } else { - $message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to thier favorites :)'), $this->user->nickname); + $message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.%s%%%%) and then post something interesting they would add to their favorites :)'), + $this->user->nickname, + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } $this->elementStart('div', 'guide'); @@ -191,26 +216,7 @@ class ShowfavoritesAction extends OwnerDesignAction function showContent() { - $cur = common_current_user(); - - if (!empty($cur) && $cur->id == $this->user->id) { - - // Show imported/gateway notices as well as local if - // the user is looking at his own favorites - - $notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1, true); - } else { - $notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1, false); - } - - if (empty($notice)) { - $this->serverError(_('Could not retrieve favorite notices.')); - return; - } - - $nl = new NoticeList($notice, $this); + $nl = new NoticeList($this->notice, $this); $cnt = $nl->show(); if (0 == $cnt) { diff --git a/actions/showgroup.php b/actions/showgroup.php index 32ec674a9..b0cc1dbc7 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -130,8 +130,18 @@ class ShowgroupAction extends GroupDesignAction $this->group = User_group::staticGet('nickname', $nickname); if (!$this->group) { - $this->clientError(_('No such group'), 404); - return false; + $alias = Group_alias::staticGet('alias', $nickname); + if ($alias) { + $args = array('id' => $alias->group_id); + if ($this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('groupbyid', $args), 301); + return false; + } else { + $this->clientError(_('No such group'), 404); + return false; + } } common_set_returnto($this->selfUrl()); @@ -440,8 +450,9 @@ class ShowgroupAction extends GroupDesignAction $m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . 'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' . 'short messages about their life and interests. '. - '[Join now](%%%%action.register%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'), - $this->group->nickname); + '[Join now](%%%%action.%s%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'), + $this->group->nickname, + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } else { $m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . 'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' . diff --git a/actions/shownotice.php b/actions/shownotice.php index 3d7319489..82031d90d 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -103,8 +103,8 @@ class ShownoticeAction extends OwnerDesignAction $this->user = User::staticGet('id', $this->profile->id); - if (empty($this->user)) { - $this->serverError(_('Not a local notice'), 500); + if (! $this->notice->is_local) { + common_redirect($this->notice->uri); return false; } @@ -196,7 +196,7 @@ class ShownoticeAction extends OwnerDesignAction { parent::handle($args); - if ($this->notice->is_local == 0) { + if ($this->notice->is_local == Notice::REMOTE_OMB) { if (!empty($this->notice->url)) { common_redirect($this->notice->url, 301); } else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) { @@ -284,16 +284,16 @@ class ShownoticeAction extends OwnerDesignAction $this->element('link',array('rel'=>'alternate', 'type'=>'application/json+oembed', 'href'=>common_local_url( - 'api', - array('apiaction'=>'oembed','method'=>'oembed.json'), - array('url'=>$this->notice->uri)), + 'oembed', + array(), + array('format'=>'json','url'=>$this->notice->uri)), 'title'=>'oEmbed'),null); $this->element('link',array('rel'=>'alternate', 'type'=>'text/xml+oembed', 'href'=>common_local_url( - 'api', - array('apiaction'=>'oembed','method'=>'oembed.xml'), - array('url'=>$this->notice->uri)), + 'oembed', + array(), + array('format'=>'xml','url'=>$this->notice->uri)), 'title'=>'oEmbed'),null); } } diff --git a/actions/showstream.php b/actions/showstream.php index cd5d4bb70..3f603d64f 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -358,7 +358,9 @@ class ShowstreamAction extends ProfileAction } } else { - $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname); + $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'), + (!common_config('site','openidonly')) ? 'register' : 'openidlogin', + $this->user->nickname); } $this->elementStart('div', 'guide'); @@ -387,8 +389,10 @@ class ShowstreamAction extends ProfileAction if (!(common_config('site','closed') || common_config('site','inviteonly'))) { $m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . 'based on the Free Software [Laconica](http://laconi.ca/) tool. ' . - '[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'), - $this->user->nickname, $this->user->nickname); + '[Join now](%%%%action.%s%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'), + $this->user->nickname, + (!common_config('site','openidonly')) ? 'register' : 'openidlogin', + $this->user->nickname); } else { $m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' . 'based on the Free Software [Laconica](http://laconi.ca/) tool. '), diff --git a/actions/smssettings.php b/actions/smssettings.php index 922bab9a4..33b54abf6 100644 --- a/actions/smssettings.php +++ b/actions/smssettings.php @@ -80,6 +80,12 @@ class SmssettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('sms', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('SMS is not available.')); + return; + } + $user = common_current_user(); $this->elementStart('form', array('method' => 'post', diff --git a/actions/subscribers.php b/actions/subscribers.php index 66ac00fb1..404738012 100644 --- a/actions/subscribers.php +++ b/actions/subscribers.php @@ -111,7 +111,9 @@ class SubscribersAction extends GalleryAction } } else { - $message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.register%%%%) and be the first?'), $this->user->nickname); + $message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.%s%%%%) and be the first?'), + $this->user->nickname, + (!common_config('site','openidonly')) ? 'register' : 'openidlogin'); } $this->elementStart('div', 'guide'); diff --git a/actions/subscriptions.php b/actions/subscriptions.php index 42bdae10f..0724471ff 100644 --- a/actions/subscriptions.php +++ b/actions/subscriptions.php @@ -174,14 +174,26 @@ class SubscriptionsListItem extends SubscriptionListItem return; } + if (!common_config('xmpp', 'enabled') && !common_config('sms', 'enabled')) { + return; + } + $this->out->elementStart('form', array('id' => 'subedit-' . $this->profile->id, 'method' => 'post', 'class' => 'form_subscription_edit', 'action' => common_local_url('subedit'))); $this->out->hidden('token', common_session_token()); $this->out->hidden('profile', $this->profile->id); - $this->out->checkbox('jabber', _('Jabber'), $sub->jabber); - $this->out->checkbox('sms', _('SMS'), $sub->sms); + if (common_config('xmpp', 'enabled')) { + $this->out->checkbox('jabber', _('Jabber'), $sub->jabber); + } else { + $this->out->hidden('jabber', $sub->jabber); + } + if (common_config('sms', 'enabled')) { + $this->out->checkbox('sms', _('SMS'), $sub->sms); + } else { + $this->out->hidden('sms', $sub->sms); + } $this->out->submit('save', _('Save')); $this->out->elementEnd('form'); return; diff --git a/actions/tag.php b/actions/tag.php index 020399d9e..771eb2861 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -21,6 +21,9 @@ if (!defined('LACONICA')) { exit(1); } class TagAction extends Action { + + var $notice; + function prepare($args) { parent::prepare($args); @@ -42,6 +45,12 @@ class TagAction extends Action common_set_returnto($this->selfUrl()); + $this->notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1); + + if($this->page > 1 && $this->notice->N == 0){ + $this->serverError(_('No such page'),$code=404); + } + return true; } @@ -94,9 +103,7 @@ class TagAction extends Action function showContent() { - $notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1); - - $nl = new NoticeList($notice, $this); + $nl = new NoticeList($this->notice, $this); $cnt = $nl->show(); diff --git a/actions/tagrss.php b/actions/tagrss.php index f69374fca..c3c03b9cd 100644 --- a/actions/tagrss.php +++ b/actions/tagrss.php @@ -61,7 +61,8 @@ class TagrssAction extends Rss10Action $c = array('url' => common_local_url('tagrss', array('tag' => $tagname)), 'title' => $tagname, 'link' => common_local_url('tagrss', array('tag' => $tagname)), - 'description' => sprintf(_('Microblog tagged with %s'), $tagname)); + 'description' => sprintf(_('Updates tagged with %1$s on %2$s!'), + $tagname, common_config('site', 'name'))); return $c; } diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php index bd27e9d20..aac7d63b1 100644 --- a/actions/twitapidirect_messages.php +++ b/actions/twitapidirect_messages.php @@ -141,9 +141,10 @@ class Twitapidirect_messagesAction extends TwitterapiAction $code = 406, $apidata['content-type']); } else { $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { - $this->clientError(_('That\'s too long. Max message size is 140 chars.'), - $code = 406, $apidata['content-type']); + if (Message::contentTooLong($content_shortened)) { + $this->clientError(sprintf(_('That\'s too long. Max message size is %d chars.'), + Message::maxContent()), + $code = 406, $apidata['content-type']); return; } } diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php index 82604ebff..bebc07fa1 100644 --- a/actions/twitapigroups.php +++ b/actions/twitapigroups.php @@ -51,6 +51,103 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; class TwitapigroupsAction extends TwitterapiAction { + function list_groups($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $this->auth_user = $apidata['user']; + $user = $this->get_user($apidata['api_arg'], $apidata); + + if (empty($user)) { + $this->clientError('Not Found', 404, $apidata['content-type']); + return; + } + + $page = (int)$this->arg('page', 1); + $count = (int)$this->arg('count', 20); + $max_id = (int)$this->arg('max_id', 0); + $since_id = (int)$this->arg('since_id', 0); + $since = $this->arg('since'); + $group = $user->getGroups(($page-1)*$count, + $count, $since_id, $max_id, $since); + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s's groups"), $user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Groups"; + $link = common_root_url(); + $subtitle = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename); + + switch($apidata['content-type']) { + case 'xml': + $this->show_xml_groups($group); + break; + case 'rss': + $this->show_rss_groups($group, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . 'api/laconica/groups/list/' . $user->id . '.atom'; + $this->show_atom_groups($group, $title, $id, $link, + $subtitle, $selfuri); + break; + case 'json': + $this->show_json_groups($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + function list_all($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $page = (int)$this->arg('page', 1); + $count = (int)$this->arg('count', 20); + $max_id = (int)$this->arg('max_id', 0); + $since_id = (int)$this->arg('since_id', 0); + $since = $this->arg('since'); + + /* TODO: + Use the $page, $count, $max_id, $since_id, and $since parameters + */ + $group = new User_group(); + $group->orderBy('created DESC'); + $group->find(); + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s groups"), $sitename); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Groups"; + $link = common_root_url(); + $subtitle = sprintf(_("groups on %s"), $sitename); + + switch($apidata['content-type']) { + case 'xml': + $this->show_xml_groups($group); + break; + case 'rss': + $this->show_rss_groups($group, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . 'api/laconica/groups/list_all.atom'; + $this->show_atom_groups($group, $title, $id, $link, + $subtitle, $selfuri); + break; + case 'json': + $this->show_json_groups($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + function show($args, $apidata) { parent::handle($args); diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index e3d366ecc..1f3c53bef 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -242,14 +242,15 @@ class TwitapistatusesAction extends TwitterapiAction $status_shortened = common_shorten_links($status); - if (mb_strlen($status_shortened) > 140) { + if (Notice::contentTooLong($status_shortened)) { // XXX: Twitter truncates anything over 140, flags the status // as "truncated." Sending this error may screw up some clients // that assume Twitter will truncate for them. Should we just // truncate too? -- Zach - $this->clientError(_('That\'s too long. Max notice size is 140 chars.'), - $code = 406, $apidata['content-type']); + $this->clientError(sprintf(_('That\'s too long. Max notice size is %d chars.'), + Notice::maxContent()), + $code = 406, $apidata['content-type']); return; } } @@ -455,7 +456,8 @@ class TwitapistatusesAction extends TwitterapiAction function friends($args, $apidata) { parent::handle($args); - return $this->subscriptions($apidata, 'subscribed', 'subscriber'); + $includeStatuses=! (boolean) $args['lite']; + return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses); } function friendsIDs($args, $apidata) @@ -467,7 +469,8 @@ class TwitapistatusesAction extends TwitterapiAction function followers($args, $apidata) { parent::handle($args); - return $this->subscriptions($apidata, 'subscriber', 'subscribed'); + $includeStatuses=! (boolean) $args['lite']; + return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses); } function followersIDs($args, $apidata) @@ -476,7 +479,7 @@ class TwitapistatusesAction extends TwitterapiAction return $this->subscriptions($apidata, 'subscriber', 'subscribed', true); } - function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false) + function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true) { $this->auth_user = $apidata['user']; $user = $this->get_user($apidata['api_arg'], $apidata); @@ -532,26 +535,26 @@ class TwitapistatusesAction extends TwitterapiAction if ($onlyIDs) { $this->showIDs($others, $type); } else { - $this->show_profiles($others, $type); + $this->show_profiles($others, $type, $includeStatuses); } $this->end_document($type); } - function show_profiles($profiles, $type) + function show_profiles($profiles, $type, $includeStatuses) { switch ($type) { case 'xml': $this->elementStart('users', array('type' => 'array')); foreach ($profiles as $profile) { - $this->show_profile($profile); + $this->show_profile($profile,$type,null,$includeStatuses); } $this->elementEnd('users'); break; case 'json': $arrays = array(); foreach ($profiles as $profile) { - $arrays[] = $this->twitter_user_array($profile, true); + $arrays[] = $this->twitter_user_array($profile, $includeStatuses); } print json_encode($arrays); break; diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php new file mode 100644 index 000000000..b04f35327 --- /dev/null +++ b/actions/twitterauthorization.php @@ -0,0 +1,222 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Class for doing OAuth authentication against Twitter + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Twitter + * @package Laconica + * @author Zach Copely <zach@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * Class for doing OAuth authentication against Twitter + * + * Peforms the OAuth "dance" between Laconica and Twitter -- requests a token, + * authorizes it, and exchanges it for an access token. It also creates a link + * (Foreign_link) between the Laconica user and Twitter user and stores the + * access token and secret in the link. + * + * @category Twitter + * @package Laconica + * @author Zach Copley <zach@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ +class TwitterauthorizationAction extends Action +{ + /** + * Initialize class members. Looks for 'oauth_token' parameter. + * + * @param array $args misc. arguments + * + * @return boolean true + */ + function prepare($args) + { + parent::prepare($args); + + $this->oauth_token = $this->arg('oauth_token'); + + return true; + } + + /** + * Handler method + * + * @param array $args is ignored since it's now passed in in prepare() + * + * @return nothing + */ + function handle($args) + { + parent::handle($args); + + if (!common_logged_in()) { + $this->clientError(_('Not logged in.'), 403); + } + + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + + // If there's already a foreign link record, it means we already + // have an access token, and this is unecessary. So go back. + + if (isset($flink)) { + common_redirect(common_local_url('twittersettings')); + } + + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + + if (empty($this->oauth_token)) { + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } + } + + /** + * Asks Twitter for a request token, and then redirects to Twitter + * to authorize it. + * + * @return nothing + */ + function authorizeRequestToken() + { + try { + + // Get a new request token and authorize it + + $client = new TwitterOAuthClient(); + $req_tok = + $client->getRequestToken(TwitterOAuthClient::$requestTokenURL); + + // Sock the request token away in the session temporarily + + $_SESSION['twitter_request_token'] = $req_tok->key; + $_SESSION['twitter_request_token_secret'] = $req_tok->secret; + + $auth_link = $client->getAuthorizeLink($req_tok); + + } catch (TwitterOAuthClientException $e) { + $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', + $e->getCode(), $e->getMessage()); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + common_redirect($auth_link); + } + + /** + * Called when Twitter returns an authorized request token. Exchanges + * it for an access token and stores it. + * + * @return nothing + */ + function saveAccessToken() + { + + // Check to make sure Twitter returned the same request + // token we sent them + + if ($_SESSION['twitter_request_token'] != $this->oauth_token) { + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + try { + + $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], + $_SESSION['twitter_request_token_secret']); + + // Exchange the request token for an access token + + $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL); + + // Test the access token and get the user's Twitter info + + $client = new TwitterOAuthClient($atok->key, $atok->secret); + $twitter_user = $client->verifyCredentials(); + + } catch (OAuthClientException $e) { + $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s', + $e->getCode(), $e->getMessage()); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + // Save the access token and Twitter user info + + $this->saveForeignLink($atok, $twitter_user); + + // Clean up the the mess we made in the session + + unset($_SESSION['twitter_request_token']); + unset($_SESSION['twitter_request_token_secret']); + + common_redirect(common_local_url('twittersettings')); + } + + /** + * Saves a Foreign_link between Twitter user and local user, + * which includes the access token and secret. + * + * @param OAuthToken $access_token the access token to save + * @param mixed $twitter_user twitter API user object + * + * @return nothing + */ + function saveForeignLink($access_token, $twitter_user) + { + $user = common_current_user(); + + $flink = new Foreign_link(); + + $flink->user_id = $user->id; + $flink->foreign_id = $twitter_user->id; + $flink->service = TWITTER_SERVICE; + + $creds = TwitterOAuthClient::packToken($access_token); + + $flink->credentials = $creds; + $flink->created = common_sql_now(); + + // Defaults: noticesync on, everything else off + + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + if (empty($flink_id)) { + common_log_db_error($flink, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + save_twitter_user($twitter_user->id, $twitter_user->screen_name); + } + +} + diff --git a/actions/twittersettings.php b/actions/twittersettings.php index 2b742788e..0859ab9d3 100644 --- a/actions/twittersettings.php +++ b/actions/twittersettings.php @@ -34,8 +34,6 @@ if (!defined('LACONICA')) { require_once INSTALLDIR.'/lib/connectsettingsaction.php'; require_once INSTALLDIR.'/lib/twitter.php'; -define('SUBSCRIPTIONS', 80); - /** * Settings for Twitter integration * @@ -69,9 +67,8 @@ class TwittersettingsAction extends ConnectSettingsAction function getInstructions() { - return _('Add your Twitter account to automatically send '. - ' your notices to Twitter, ' . - 'and subscribe to Twitter friends already here.'); + return _('Connect your Twitter account to share your updates ' . + 'with your Twitter friends and vice-versa.'); } /** @@ -85,6 +82,12 @@ class TwittersettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('twitter', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('Twitter is not available.')); + return; + } + $user = common_current_user(); $profile = $user->getProfile(); @@ -93,7 +96,7 @@ class TwittersettingsAction extends ConnectSettingsAction $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - if ($flink) { + if (!empty($flink)) { $fuser = $flink->getForeignUser(); } @@ -102,192 +105,86 @@ class TwittersettingsAction extends ConnectSettingsAction 'class' => 'form_settings', 'action' => common_local_url('twittersettings'))); - $this->elementStart('fieldset', array('id' => 'settings_twitter_account')); - $this->element('legend', null, _('Twitter Account')); + $this->hidden('token', common_session_token()); - if ($fuser) { + + $this->elementStart('fieldset', array('id' => 'settings_twitter_account')); + + if (empty($fuser)) { $this->elementStart('ul', 'form_data'); - $this->elementStart('li', array('id' => 'settings_twitter_remove')); - $this->element('span', 'twitter_user', $fuser->nickname); - $this->element('a', array('href' => $fuser->uri), $fuser->uri); - $this->element('p', 'form_note', - _('Current verified Twitter account.')); - $this->hidden('flink_foreign_id', $flink->foreign_id); + $this->elementStart('li', array('id' => 'settings_twitter_login_button')); + $this->element('a', array('href' => common_local_url('twitterauthorization')), + 'Connect my Twitter account'); $this->elementEnd('li'); $this->elementEnd('ul'); - $this->submit('remove', _('Remove')); + + $this->elementEnd('fieldset'); } else { + $this->element('legend', null, _('Twitter account')); + $this->elementStart('p', array('id' => 'form_confirmed')); + $this->element('a', array('href' => $fuser->uri), $fuser->nickname); + $this->elementEnd('p'); + $this->element('p', 'form_note', + _('Connected Twitter account')); + + $this->submit('remove', _('Remove')); + + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences')); + + $this->element('legend', null, _('Preferences')); $this->elementStart('ul', 'form_data'); - $this->elementStart('li', array('id' => 'settings_twitter_login')); - $this->input('twitter_username', _('Twitter user name'), - ($this->arg('twitter_username')) ? - $this->arg('twitter_username') : - $profile->nickname, - _('No spaces, please.')); // hey, it's what Twitter says + $this->elementStart('li'); + $this->checkbox('noticesend', + _('Automatically send my notices to Twitter.'), + ($flink) ? + ($flink->noticesync & FOREIGN_NOTICE_SEND) : + true); $this->elementEnd('li'); $this->elementStart('li'); - $this->password('twitter_password', _('Twitter password')); - $this->elementend('li'); - $this->elementEnd('ul'); - } - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset', - array('id' => 'settings_twitter_preferences')); - $this->element('legend', null, _('Preferences')); - - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->checkbox('noticesend', - _('Automatically send my notices to Twitter.'), - ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_SEND) : - true); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('replysync', - _('Send local "@" replies to Twitter.'), - ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : - true); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('friendsync', - _('Subscribe to my Twitter friends here.'), - ($flink) ? - ($flink->friendsync & FOREIGN_FRIEND_RECV) : - false); - $this->elementEnd('li'); - - if (common_config('twitterbridge','enabled')) { + $this->checkbox('replysync', + _('Send local "@" replies to Twitter.'), + ($flink) ? + ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : + true); + $this->elementEnd('li'); $this->elementStart('li'); - $this->checkbox('noticerecv', - _('Import my Friends Timeline.'), + $this->checkbox('friendsync', + _('Subscribe to my Twitter friends here.'), ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_RECV) : + ($flink->friendsync & FOREIGN_FRIEND_RECV) : false); $this->elementEnd('li'); - } else { - // preserve setting even if bidrection bridge toggled off - if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) { - $this->hidden('noticerecv', true, 'noticerecv'); - } - } - - $this->elementEnd('ul'); - - if ($flink) { - $this->submit('save', _('Save')); - } else { - $this->submit('add', _('Add')); - } - $this->elementEnd('fieldset'); - - $this->showTwitterSubscriptions(); - - $this->elementEnd('form'); - } - - /** - * Gets some of the user's Twitter friends - * - * Gets the number of Twitter friends that are on this - * instance of Laconica. - * - * @return array array of User objects - */ - - function subscribedTwitterUsers() - { - - $current_user = common_current_user(); - - $qry = 'SELECT "user".* ' . - 'FROM subscription ' . - 'JOIN "user" ON subscription.subscribed = "user".id ' . - 'JOIN foreign_link ON foreign_link.user_id = "user".id ' . - 'WHERE subscriber = %d ' . - 'ORDER BY "user".nickname'; - - $user = new User(); - - $user->query(sprintf($qry, $current_user->id)); - - $users = array(); - - while ($user->fetch()) { - - // Don't include the user's own self-subscription - if ($user->id != $current_user->id) { - $users[] = clone($user); - } - } - - return $users; - } - - /** - * Show user's Twitter friends - * - * Gets the number of Twitter friends that are on this - * instance of Laconica, and shows their mini-avatars. - * - * @return void - */ - - function showTwitterSubscriptions() - { - - $friends = $this->subscribedTwitterUsers(); - - $friends_count = count($friends); - if ($friends_count > 0) { - $this->elementStart('div', array('id' => 'entity_subscriptions', - 'class' => 'section')); - $this->element('h2', null, _('Twitter Friends')); - $this->elementStart('ul', 'entities users xoxo'); - - for ($i = 0; $i < min($friends_count, SUBSCRIPTIONS); $i++) { + if (common_config('twitterbridge','enabled')) { + $this->elementStart('li'); + $this->checkbox('noticerecv', + _('Import my Friends Timeline.'), + ($flink) ? + ($flink->noticesync & FOREIGN_NOTICE_RECV) : + false); + $this->elementEnd('li'); - $other = Profile::staticGet($friends[$i]->id); + // preserve setting even if bidrection bridge toggled off - if (!$other) { - common_log_db_error($subs, 'SELECT', __FILE__); - continue; + if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) { + $this->hidden('noticerecv', true, 'noticerecv'); } - - $this->elementStart('li', 'vcard'); - $this->elementStart('a', array('title' => ($other->fullname) ? - $other->fullname : - $other->nickname, - 'href' => $other->profileurl, - 'class' => 'url')); - - $avatar = $other->getAvatar(AVATAR_MINI_SIZE); - - $avatar_url = ($avatar) ? - $avatar->displayUrl() : - Avatar::defaultImage(AVATAR_MINI_SIZE); - - $this->element('img', array('src' => $avatar_url, - 'width' => AVATAR_MINI_SIZE, - 'height' => AVATAR_MINI_SIZE, - 'class' => 'avatar photo', - 'alt' => ($other->fullname) ? - $other->fullname : - $other->nickname)); - - $this->element('span', 'fn nickname', $other->nickname); - $this->elementEnd('a'); - $this->elementEnd('li'); - } $this->elementEnd('ul'); - $this->elementEnd('div'); + if ($flink) { + $this->submit('save', _('Save')); + } else { + $this->submit('add', _('Add')); + } + + $this->elementEnd('fieldset'); } + + $this->elementEnd('form'); } /** @@ -303,7 +200,6 @@ class TwittersettingsAction extends ConnectSettingsAction function handlePost() { - // CSRF protection $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { @@ -314,8 +210,6 @@ class TwittersettingsAction extends ConnectSettingsAction if ($this->arg('save')) { $this->savePreferences(); - } else if ($this->arg('add')) { - $this->addTwitterAccount(); } else if ($this->arg('remove')) { $this->removeTwitterAccount(); } else { @@ -324,82 +218,6 @@ class TwittersettingsAction extends ConnectSettingsAction } /** - * Associate a Twitter account with the user's account - * - * Validates post input; verifies it against Twitter; and if - * successful stores in the database. - * - * @return void - */ - - function addTwitterAccount() - { - $screen_name = $this->trimmed('twitter_username'); - $password = $this->trimmed('twitter_password'); - $noticesend = $this->boolean('noticesend'); - $noticerecv = $this->boolean('noticerecv'); - $replysync = $this->boolean('replysync'); - $friendsync = $this->boolean('friendsync'); - - if (!Validate::string($screen_name, - array('min_length' => 1, - 'max_length' => 15, - 'format' => VALIDATE_NUM.VALIDATE_ALPHA.'_'))) { - $this->showForm(_('Username must have only numbers, '. - 'upper- and lowercase letters, '. - 'and underscore (_). 15 chars max.')); - return; - } - - if (!$this->verifyCredentials($screen_name, $password)) { - $this->showForm(_('Could not verify your Twitter credentials!')); - return; - } - - $twit_user = twitter_user_info($screen_name, $password); - - if (!$twit_user) { - $this->showForm(sprintf(_('Unable to retrieve account information '. - 'For "%s" from Twitter.'), - $screen_name)); - return; - } - - if (!save_twitter_user($twit_user->id, $screen_name)) { - $this->showForm(_('Unable to save your Twitter settings!')); - return; - } - - $user = common_current_user(); - - $flink = new Foreign_link(); - - $flink->user_id = $user->id; - $flink->foreign_id = $twit_user->id; - $flink->service = TWITTER_SERVICE; - $flink->credentials = $password; - $flink->created = common_sql_now(); - - $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync); - - $flink_id = $flink->insert(); - - if (!$flink_id) { - common_log_db_error($flink, 'INSERT', __FILE__); - $this->showForm(_('Unable to save your Twitter settings!')); - return; - } - - if ($friendsync) { - save_twitter_friends($user, $twit_user->id, $screen_name, $password); - $flink->last_friendsync = common_sql_now(); - $flink->update(); - } - - $this->showForm(_('Twitter settings saved.'), true); - } - - /** * Disassociate an existing Twitter account from this account * * @return void @@ -408,20 +226,11 @@ class TwittersettingsAction extends ConnectSettingsAction function removeTwitterAccount() { $user = common_current_user(); - - $flink = Foreign_link::getByUserID($user->id, 1); - - $flink_foreign_id = $this->arg('flink_foreign_id'); - - // Maybe an old tab open...? - if ($flink->foreign_id != $flink_foreign_id) { - $this->showForm(_('That is not your Twitter account.')); - return; - } + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); $result = $flink->delete(); - if (!$result) { + if (empty($result)) { common_log_db_error($flink, 'DELETE', __FILE__); $this->serverError(_('Couldn\'t remove Twitter user.')); return; @@ -444,32 +253,16 @@ class TwittersettingsAction extends ConnectSettingsAction $replysync = $this->boolean('replysync'); $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - $flink = Foreign_link::getByUserID($user->id, 1); - - if (!$flink) { + if (empty($flink)) { common_log_db_error($flink, 'SELECT', __FILE__); $this->showForm(_('Couldn\'t save Twitter preferences.')); return; } - $twitter_id = $flink->foreign_id; - $password = $flink->credentials; - - $fuser = $flink->getForeignUser(); - - if (!$fuser) { - common_log_db_error($fuser, 'SELECT', __FILE__); - $this->showForm(_('Couldn\'t save Twitter preferences.')); - return; - } - - $screen_name = $fuser->nickname; - $original = clone($flink); - $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync); - $result = $flink->update($original); if ($result === false) { @@ -478,45 +271,7 @@ class TwittersettingsAction extends ConnectSettingsAction return; } - if ($friendsync) { - save_twitter_friends($user, $flink->foreign_id, $screen_name, $password); - } - $this->showForm(_('Twitter preferences saved.'), true); } - /** - * Verifies a username and password against Twitter's API - * - * @param string $screen_name Twitter user name - * @param string $password Twitter password - * - * @return boolean success flag - */ - - function verifyCredentials($screen_name, $password) - { - $uri = 'http://twitter.com/account/verify_credentials.json'; - - $data = get_twitter_data($uri, $screen_name, $password); - - if (!$data) { - return false; - } - - $user = json_decode($data); - - if (!$user) { - return false; - } - - $twitter_id = $user->id; - - if ($twitter_id) { - return $twitter_id; - } - - return false; - } - } diff --git a/actions/unsubscribe.php b/actions/unsubscribe.php index 19275041a..46fbcf657 100644 --- a/actions/unsubscribe.php +++ b/actions/unsubscribe.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Unsubscribe handler + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,6 +28,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +if (!defined('LACONICA')) { + exit(1); +} + +/** + * Unsubscribe handler + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class UnsubscribeAction extends Action { @@ -31,16 +56,18 @@ class UnsubscribeAction extends Action $user = common_current_user(); if ($_SERVER['REQUEST_METHOD'] != 'POST') { - common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname))); + common_redirect(common_local_url('subscriptions', + array('nickname' => $user->nickname))); return; } - # CSRF protection + /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token. Try again, please.')); + $this->clientError(_('There was a problem with your session token. ' . + 'Try again, please.')); return; } @@ -53,7 +80,7 @@ class UnsubscribeAction extends Action $other = Profile::staticGet('id', $other_id); - if (!$other_id) { + if (!$other) { $this->clientError(_('No profile with that id.')); return; } @@ -76,8 +103,8 @@ class UnsubscribeAction extends Action $this->elementEnd('body'); $this->elementEnd('html'); } else { - common_redirect(common_local_url('subscriptions', array('nickname' => - $user->nickname)), + common_redirect(common_local_url('subscriptions', + array('nickname' => $user->nickname)), 303); } } diff --git a/actions/updateprofile.php b/actions/updateprofile.php index d8b62fb09..b020413b3 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Handle an updateprofile action + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,34 +28,34 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +/** + * Handle an updateprofile action + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class UpdateprofileAction extends Action { - - function handle($args) - { - parent::handle($args); - try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('updateprofile')); - # Note: server-to-server function! - $server = omb_oauth_server(); - list($consumer, $token) = $server->verify_request($req); - if ($this->update_profile($req, $consumer, $token)) { - header('HTTP/1.1 200 OK'); - header('Content-type: text/plain'); - print "omb_version=".OMB_VERSION_01; - } - } catch (OAuthException $e) { - $this->serverError($e->getMessage()); - return; - } - } - function update_profile($req, $consumer, $token) + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) { $version = $req->get_parameter('omb_version'); if ($version != OMB_VERSION_01) { @@ -79,7 +90,7 @@ class UpdateprofileAction extends Action $nickname = $req->get_parameter('omb_listenee_nickname'); if ($nickname && !Validate::string($nickname, array('min_length' => 1, 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { + 'format' => NICKNAME_FMT))) { $this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.')); return false; } @@ -88,96 +99,20 @@ class UpdateprofileAction extends Action $this->clientError(sprintf(_("Invalid license URL '%s'"), $license)); return false; } - $profile_url = $req->get_parameter('omb_listenee_profile'); - if ($profile_url && !common_valid_http_url($profile_url)) { - $this->clientError(sprintf(_("Invalid profile URL '%s'."), $profile_url)); - return false; - } - # optional stuff - $fullname = $req->get_parameter('omb_listenee_fullname'); - if ($fullname && mb_strlen($fullname) > 255) { - $this->clientError(_("Full name is too long (max 255 chars).")); - return false; - } - $homepage = $req->get_parameter('omb_listenee_homepage'); - if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) { - $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage)); - return false; - } - $bio = $req->get_parameter('omb_listenee_bio'); - if ($bio && mb_strlen($bio) > 140) { - $this->clientError(_("Bio is too long (max 140 chars).")); - return false; - } - $location = $req->get_parameter('omb_listenee_location'); - if ($location && mb_strlen($location) > 255) { - $this->clientError(_("Location is too long (max 255 chars).")); - return false; - } - $avatar = $req->get_parameter('omb_listenee_avatar'); - if ($avatar) { - if (!common_valid_http_url($avatar) || strlen($avatar) > 255) { - $this->clientError(sprintf(_("Invalid avatar URL '%s'"), $avatar)); - return false; - } - $size = @getimagesize($avatar); - if (!$size) { - $this->clientError(sprintf(_("Can't read avatar URL '%s'"), $avatar)); - return false; - } - if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) { - $this->clientError(sprintf(_("Wrong size image at '%s'"), $avatar)); - return false; - } - if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, - IMAGETYPE_PNG))) { - $this->clientError(sprintf(_("Wrong image type for '%s'"), $avatar)); - return false; - } - } - - $orig_profile = clone($profile); + return true; + } - /* Use values even if they are an empty string. Parsing an empty string in - updateProfile is the specified way of clearing a parameter in OMB. */ - if (!is_null($nickname)) { - $profile->nickname = $nickname; - } - if (!is_null($profile_url)) { - $profile->profileurl = $profile_url; - } - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } + function handle($args) + { + parent::handle($args); - if (!$profile->update($orig_profile)) { - $this->serverError(_('Could not save new profile info'), 500); - return false; - } else { - if ($avatar) { - $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); - copy($avatar, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - if (!$profile->setOriginal($filename)) { - $this->serverError(_('Could not save avatar info'), 500); - return false; - } - } - return true; + try { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->handleUpdateProfile(); + } catch (Exception $e) { + $this->serverError($e->getMessage()); + return; } } -} +}
\ No newline at end of file diff --git a/actions/userauthorization.php b/actions/userauthorization.php index 8dc2c808d..3e7be9747 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -1,5 +1,16 @@ <?php -/* +/** + * Let the user authorize a remote subscription request + * + * PHP version 5 + * + * @category Action + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Robin Millette <millette@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,9 +28,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +require_once INSTALLDIR.'/extlib/libomb/profile.php'; define('TIMESTAMP_THRESHOLD', 300); class UserauthorizationAction extends Action @@ -32,42 +47,62 @@ class UserauthorizationAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - # CSRF protection + /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { - $params = $this->getStoredParams(); - $this->showForm($params, _('There was a problem with your session token. '. - 'Try again, please.')); + $srv = $this->getStoredParams(); + $this->showForm($srv->getRemoteUser(), _('There was a problem ' . + 'with your session token. Try again, ' . + 'please.')); return; } - # We've shown the form, now post user's choice + /* We've shown the form, now post user's choice. */ $this->sendAuthorization(); } else { if (!common_logged_in()) { - # Go log in, and then come back + /* Go log in, and then come back. */ common_set_returnto($_SERVER['REQUEST_URI']); - common_redirect(common_local_url('login')); + if (!common_config('site', 'openidonly')) { + common_redirect(common_local_url('login')); + } else { + common_redirect(common_local_url('openidlogin')); + } + return; + } + + $user = common_current_user(); + $profile = $user->getProfile(); + if (!$profile) { + common_log_db_error($user, 'SELECT', __FILE__); + $this->serverError(_('User without matching profile')); return; } + /* TODO: If no token is passed the user should get a prompt to enter + it according to OAuth Core 1.0. */ try { - $this->validateRequest(); - $this->storeParams($_GET); - $this->showForm($_GET); - } catch (OAuthException $e) { + $this->validateOmb(); + $srv = new OMB_Service_Provider( + profile_to_omb_profile($user->uri, $profile), + omb_oauth_datastore()); + + $remote_user = $srv->handleUserAuth(); + } catch (Exception $e) { $this->clearParams(); $this->clientError($e->getMessage()); return; } + $this->storeParams($srv); + $this->showForm($remote_user); } } function showForm($params, $error=null) { $this->params = $params; - $this->error = $error; + $this->error = $error; $this->showPage(); } @@ -79,23 +114,24 @@ class UserauthorizationAction extends Action function showPageNotice() { $this->element('p', null, _('Please check these details to make sure '. - 'that you want to subscribe to this user\'s notices. '. - 'If you didn\'t just ask to subscribe to someone\'s notices, '. - 'click "Reject".')); + 'that you want to subscribe to this ' . + 'user’s notices. If you didn’t just ask ' . + 'to subscribe to someone’s notices, '. + 'click “Reject”.')); } function showContent() { $params = $this->params; - $nickname = $params['omb_listenee_nickname']; - $profile = $params['omb_listenee_profile']; - $license = $params['omb_listenee_license']; - $fullname = $params['omb_listenee_fullname']; - $homepage = $params['omb_listenee_homepage']; - $bio = $params['omb_listenee_bio']; - $location = $params['omb_listenee_location']; - $avatar = $params['omb_listenee_avatar']; + $nickname = $params->getNickname(); + $profile = $params->getProfileURL(); + $license = $params->getLicenseURL(); + $fullname = $params->getFullname(); + $homepage = $params->getHomepage(); + $bio = $params->getBio(); + $location = $params->getLocation(); + $avatar = $params->getAvatarURL(); $this->elementStart('div', array('class' => 'profile')); $this->elementStart('div', 'entity_profile vcard'); @@ -172,11 +208,14 @@ class UserauthorizationAction extends Action 'id' => 'userauthorization', 'class' => 'form_user_authorization', 'name' => 'userauthorization', - 'action' => common_local_url('userauthorization'))); + 'action' => common_local_url( + 'userauthorization'))); $this->hidden('token', common_session_token()); - $this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user')); - $this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription')); + $this->submit('accept', _('Accept'), 'submit accept', null, + _('Subscribe to this user')); + $this->submit('reject', _('Reject'), 'submit reject', null, + _('Reject this subscription')); $this->elementEnd('form'); $this->elementEnd('li'); $this->elementEnd('ul'); @@ -186,191 +225,27 @@ class UserauthorizationAction extends Action function sendAuthorization() { - $params = $this->getStoredParams(); + $srv = $this->getStoredParams(); - if (!$params) { + if (is_null($srv)) { $this->clientError(_('No authorization request!')); return; } - $callback = $params['oauth_callback']; - - if ($this->arg('accept')) { - if (!$this->authorizeToken($params)) { - $this->clientError(_('Error authorizing token')); - } - if (!$this->saveRemoteProfile($params)) { - $this->clientError(_('Error saving remote profile')); - } - if (!$callback) { - $this->showAcceptMessage($params['oauth_token']); - } else { - $newparams = array(); - $newparams['oauth_token'] = $params['oauth_token']; - $newparams['omb_version'] = OMB_VERSION_01; - $user = User::staticGet('uri', $params['omb_listener']); - $profile = $user->getProfile(); - if (!$profile) { - common_log_db_error($user, 'SELECT', __FILE__); - $this->serverError(_('User without matching profile')); - return; - } - $newparams['omb_listener_nickname'] = $user->nickname; - $newparams['omb_listener_profile'] = common_local_url('showstream', - array('nickname' => $user->nickname)); - if (!is_null($profile->fullname)) { - $newparams['omb_listener_fullname'] = $profile->fullname; - } - if (!is_null($profile->homepage)) { - $newparams['omb_listener_homepage'] = $profile->homepage; - } - if (!is_null($profile->bio)) { - $newparams['omb_listener_bio'] = $profile->bio; - } - if (!is_null($profile->location)) { - $newparams['omb_listener_location'] = $profile->location; - } - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $newparams['omb_listener_avatar'] = $avatar->url; - } - $parts = array(); - foreach ($newparams as $k => $v) { - $parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v); - } - $query_string = implode('&', $parts); - $parsed = parse_url($callback); - $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string; - common_redirect($url, 303); - } - } else { - if (!$callback) { - $this->showRejectMessage(); - } else { - # XXX: not 100% sure how to signal failure... just redirect without token? - common_redirect($callback, 303); - } - } - } - - function authorizeToken(&$params) - { - $token_field = $params['oauth_token']; - $rt = new Token(); - $rt->tok = $token_field; - $rt->type = 0; - $rt->state = 0; - if ($rt->find(true)) { - $orig_rt = clone($rt); - $rt->state = 1; # Authorized but not used - if ($rt->update($orig_rt)) { - return true; - } - } - return false; - } - - # XXX: refactor with similar code in finishremotesubscribe.php - - function saveRemoteProfile(&$params) - { - # FIXME: we should really do this when the consumer comes - # back for an access token. If they never do, we've got stuff in a - # weird state. - - $nickname = $params['omb_listenee_nickname']; - $fullname = $params['omb_listenee_fullname']; - $profile_url = $params['omb_listenee_profile']; - $homepage = $params['omb_listenee_homepage']; - $bio = $params['omb_listenee_bio']; - $location = $params['omb_listenee_location']; - $avatar_url = $params['omb_listenee_avatar']; - - $listenee = $params['omb_listenee']; - $remote = Remote_profile::staticGet('uri', $listenee); - - if ($remote) { - $exists = true; - $profile = Profile::staticGet($remote->id); - $orig_remote = clone($remote); - $orig_profile = clone($profile); - } else { - $exists = false; - $remote = new Remote_profile(); - $remote->uri = $listenee; - $profile = new Profile(); - } - - $profile->nickname = $nickname; - $profile->profileurl = $profile_url; - - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } - - if ($exists) { - $profile->update($orig_profile); - } else { - $profile->created = DB_DataObject_Cast::dateTime(); # current time - $id = $profile->insert(); - if (!$id) { - return false; - } - $remote->id = $id; + $accepted = $this->arg('accept'); + try { + list($val, $token) = $srv->continueUserAuth($accepted); + } catch (Exception $e) { + $this->clientError($e->getMessage()); + return; } - - if ($exists) { - if (!$remote->update($orig_remote)) { - return false; - } + if ($val !== false) { + common_redirect($val, 303); + } elseif ($accepted) { + $this->showAcceptMessage($token); } else { - $remote->created = DB_DataObject_Cast::dateTime(); # current time - if (!$remote->insert()) { - return false; - } - } - - if ($avatar_url) { - if (!$this->addAvatar($profile, $avatar_url)) { - return false; - } - } - - $user = common_current_user(); - - $sub = new Subscription(); - $sub->subscriber = $user->id; - $sub->subscribed = $remote->id; - $sub->token = $params['oauth_token']; # NOTE: request token, not valid for use! - $sub->created = DB_DataObject_Cast::dateTime(); # current time - - if (!$sub->insert()) { - return false; + $this->showRejectMessage(); } - - return true; - } - - function addAvatar($profile, $url) - { - $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); - copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - return $profile->setOriginal($filename); } function showAcceptMessage($tok) @@ -378,26 +253,28 @@ class UserauthorizationAction extends Action common_show_header(_('Subscription authorized')); $this->element('p', null, _('The subscription has been authorized, but no '. - 'callback URL was passed. Check with the site\'s instructions for '. - 'details on how to authorize the subscription. Your subscription token is:')); + 'callback URL was passed. Check with the site’s ' . + 'instructions for details on how to authorize the ' . + 'subscription. Your subscription token is:')); $this->element('blockquote', 'token', $tok); common_show_footer(); } - function showRejectMessage($tok) + function showRejectMessage() { common_show_header(_('Subscription rejected')); $this->element('p', null, _('The subscription has been rejected, but no '. - 'callback URL was passed. Check with the site\'s instructions for '. - 'details on how to fully reject the subscription.')); + 'callback URL was passed. Check with the site’s ' . + 'instructions for details on how to fully reject ' . + 'the subscription.')); common_show_footer(); } function storeParams($params) { common_ensure_session(); - $_SESSION['userauthorizationparams'] = $params; + $_SESSION['userauthorizationparams'] = serialize($params); } function clearParams() @@ -409,138 +286,74 @@ class UserauthorizationAction extends Action function getStoredParams() { common_ensure_session(); - $params = $_SESSION['userauthorizationparams']; + $params = unserialize($_SESSION['userauthorizationparams']); return $params; } - # Throws an OAuthException if anything goes wrong - - function validateRequest() - { - /* Find token. - TODO: If no token is passed the user should get a prompt to enter it - according to OAuth Core 1.0 */ - $t = new Token(); - $t->tok = $_GET['oauth_token']; - $t->type = 0; - if (!$t->find(true)) { - throw new OAuthException("Invalid request token: " . $_GET['oauth_token']); - } - - $this->validateOmb(); - return true; - } - function validateOmb() { - foreach (array('omb_version', 'omb_listener', 'omb_listenee', - 'omb_listenee_profile', 'omb_listenee_nickname', - 'omb_listenee_license') as $param) - { - if (!isset($_GET[$param]) || is_null($_GET[$param])) { - throw new OAuthException("Required parameter '$param' not found"); - } - } - # Now, OMB stuff - $version = $_GET['omb_version']; - if ($version != OMB_VERSION_01) { - throw new OAuthException("OpenMicroBlogging version '$version' not supported"); - } $listener = $_GET['omb_listener']; + $listenee = $_GET['omb_listenee']; + $nickname = $_GET['omb_listenee_nickname']; + $profile = $_GET['omb_listenee_profile']; + $user = User::staticGet('uri', $listener); if (!$user) { - throw new OAuthException("Listener URI '$listener' not found here"); - } - $cur = common_current_user(); - if ($cur->id != $user->id) { - throw new OAuthException("Can't add for another user!"); - } - $listenee = $_GET['omb_listenee']; - if (!Validate::uri($listenee) && - !common_valid_tag($listenee)) { - throw new OAuthException("Listenee URI '$listenee' not a recognizable URI"); + throw new Exception(sprintf(_('Listener URI ‘%s’ not found here'), + $listener)); } + if (strlen($listenee) > 255) { - throw new OAuthException("Listenee URI '$listenee' too long"); + throw new Exception(sprintf(_('Listenee URI ‘%s’ is too long.'), + $listenee)); } $other = User::staticGet('uri', $listenee); if ($other) { - throw new OAuthException("Listenee URI '$listenee' is local user"); + throw new Exception(sprintf(_('Listenee URI ‘%s’ is a local user.'), + $listenee)); } $remote = Remote_profile::staticGet('uri', $listenee); if ($remote) { - $sub = new Subscription(); + $sub = new Subscription(); $sub->subscriber = $user->id; $sub->subscribed = $remote->id; if ($sub->find(true)) { - throw new OAuthException("Already subscribed to user!"); + throw new Exception('You are already subscribed to this user.'); } } - $nickname = $_GET['omb_listenee_nickname']; - if (!Validate::string($nickname, array('min_length' => 1, - 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { - throw new OAuthException('Nickname must have only letters and numbers and no spaces.'); - } - $profile = $_GET['omb_listenee_profile']; - if (!common_valid_http_url($profile)) { - throw new OAuthException("Invalid profile URL '$profile'."); - } - if ($profile == common_local_url('showstream', array('nickname' => $nickname))) { - throw new OAuthException("Profile URL '$profile' is for a local user."); - } + if ($profile == common_profile_url($nickname)) { + throw new Exception(sprintf(_('Profile URL ‘%s’ is for a local user.'), + $profile)); - $license = $_GET['omb_listenee_license']; - if (!common_valid_http_url($license)) { - throw new OAuthException("Invalid license URL '$license'."); } + + $license = $_GET['omb_listenee_license']; $site_license = common_config('license', 'url'); if (!common_compatible_license($license, $site_license)) { - throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'."); - } - # optional stuff - $fullname = $_GET['omb_listenee_fullname']; - if ($fullname && mb_strlen($fullname) > 255) { - throw new OAuthException("Full name '$fullname' too long."); - } - $homepage = $_GET['omb_listenee_homepage']; - if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) { - throw new OAuthException("Invalid homepage '$homepage'"); - } - $bio = $_GET['omb_listenee_bio']; - if ($bio && mb_strlen($bio) > 140) { - throw new OAuthException("Bio too long '$bio'"); - } - $location = $_GET['omb_listenee_location']; - if ($location && mb_strlen($location) > 255) { - throw new OAuthException("Location too long '$location'"); + throw new Exception(sprintf(_('Listenee stream license ‘%s’ is not ' . + 'compatible with site license ‘%s’.'), + $license, $site_license)); } + $avatar = $_GET['omb_listenee_avatar']; if ($avatar) { if (!common_valid_http_url($avatar) || strlen($avatar) > 255) { - throw new OAuthException("Invalid avatar URL '$avatar'"); + throw new Exception(sprintf(_('Avatar URL ‘%s’ is not valid.'), + $avatar)); } $size = @getimagesize($avatar); if (!$size) { - throw new OAuthException("Can't read avatar URL '$avatar'"); - } - if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) { - throw new OAuthException("Wrong size image at '$avatar'"); + throw new Exception(sprintf(_('Can’t read avatar URL ‘%s’.'), + $avatar)); } if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) { - throw new OAuthException("Wrong image type for '$avatar'"); + throw new Exception(sprintf(_('Wrong image type for avatar URL '. + '‘%s’.'), $avatar)); } } - $callback = $_GET['oauth_callback']; - if ($callback && !common_valid_http_url($callback)) { - throw new OAuthException("Invalid callback URL '$callback'"); - } - if ($callback && $callback == common_local_url('finishremotesubscribe')) { - throw new OAuthException("Callback URL '$callback' is for local site."); - } } } diff --git a/actions/userrss.php b/actions/userrss.php index 8a940865f..a9f3fd5f8 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -88,9 +88,10 @@ class UserrssAction extends Rss10Action $c = array('url' => common_local_url('userrss', array('nickname' => $user->nickname)), - 'title' => $user->nickname, + 'title' => sprintf(_('%s timeline'), $user->nickname), 'link' => $profile->profileurl, - 'description' => sprintf(_('Microblog by %s'), $user->nickname)); + 'description' => sprintf(_('Updates from %1$s on %2$s!'), + $user->nickname, common_config('site', 'name'))); return $c; } diff --git a/actions/xrds.php b/actions/xrds.php index 3c7521884..b3aa8df8e 100644 --- a/actions/xrds.php +++ b/actions/xrds.php @@ -34,6 +34,8 @@ if (!defined('LACONICA')) { } require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php'; /** * XRDS for OpenMicroBlogging @@ -52,7 +54,7 @@ class XrdsAction extends Action * * @return boolean true */ - function isReadOnly($args) + function isReadOnly() { return true; } @@ -85,89 +87,31 @@ class XrdsAction extends Action */ function showXrds($user) { - header('Content-Type: application/xrds+xml'); - $this->startXML(); - $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds')); + $srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri, + $user->getProfile())); + /* Use libomb’s default XRDS Writer. */ + $xrds_writer = null; + $srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer); + } +} - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'xml:id' => 'oauth', - 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OAUTH_ENDPOINT_REQUEST, - common_local_url('requesttoken'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1), - $user->uri); - $this->showService(OAUTH_ENDPOINT_AUTHORIZE, - common_local_url('userauthorization'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->showService(OAUTH_ENDPOINT_ACCESS, - common_local_url('accesstoken'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->showService(OAUTH_ENDPOINT_RESOURCE, - null, - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->elementEnd('XRD'); +class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper +{ + protected $urls; - // XXX: decide whether to include user's ID/nickname in postNotice URL - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'xml:id' => 'omb', - 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OMB_ENDPOINT_POSTNOTICE, - common_local_url('postnotice')); - $this->showService(OMB_ENDPOINT_UPDATEPROFILE, - common_local_url('updateprofile')); - $this->elementEnd('XRD'); - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OAUTH_DISCOVERY, - '#oauth'); - $this->showService(OMB_NAMESPACE, - '#omb'); - $this->elementEnd('XRD'); - $this->elementEnd('XRDS'); - $this->endXML(); + public function __construct() + { + $this->urls = array( + OAUTH_ENDPOINT_REQUEST => 'requesttoken', + OAUTH_ENDPOINT_AUTHORIZE => 'userauthorization', + OAUTH_ENDPOINT_ACCESS => 'accesstoken', + OMB_ENDPOINT_POSTNOTICE => 'postnotice', + OMB_ENDPOINT_UPDATEPROFILE => 'updateprofile'); } - /** - * Show service. - * - * @param string $type XRDS type - * @param string $uri URI - * @param array $params type parameters, null by default - * @param array $sigs type signatures, null by default - * @param string $localId local ID, null by default - * - * @return void - */ - function showService($type, $uri, $params=null, $sigs=null, $localId=null) + public function getURL($action) { - $this->elementStart('Service'); - if ($uri) { - $this->element('URI', null, $uri); - } - $this->element('Type', null, $type); - if ($params) { - foreach ($params as $param) { - $this->element('Type', null, $param); - } - } - if ($sigs) { - foreach ($sigs as $sig) { - $this->element('Type', null, $sig); - } - } - if ($localId) { - $this->element('LocalID', null, $localId); - } - $this->elementEnd('Service'); + return common_local_url($this->urls[$action]); } } - +?> |