]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'master' into testing
authorEvan Prodromou <evan@status.net>
Mon, 25 Jan 2010 23:13:09 +0000 (18:13 -0500)
committerEvan Prodromou <evan@status.net>
Mon, 25 Jan 2010 23:13:09 +0000 (18:13 -0500)
Conflicts:
lib/queuemanager.php

65 files changed:
EVENTS.txt
actions/apiaccountverifycredentials.php
actions/apioauthaccesstoken.php [new file with mode: 0644]
actions/apioauthauthorize.php [new file with mode: 0644]
actions/apioauthrequesttoken.php [new file with mode: 0644]
actions/apistatusesupdate.php
actions/designadminpanel.php
actions/doc.php
actions/editapplication.php [new file with mode: 0644]
actions/emailsettings.php
actions/inbox.php
actions/newapplication.php [new file with mode: 0644]
actions/oauthappssettings.php [new file with mode: 0644]
actions/oauthconnectionssettings.php [new file with mode: 0644]
actions/outbox.php
actions/replies.php
actions/showapplication.php [new file with mode: 0644]
actions/showfavorites.php
actions/showgroup.php
actions/showstream.php
actions/tag.php
actions/usergroups.php
classes/Consumer.php
classes/Memcached_DataObject.php
classes/Oauth_application.php [new file with mode: 0644]
classes/Oauth_application_user.php [new file with mode: 0644]
classes/Profile.php
classes/Token.php
classes/statusnet.ini
db/statusnet.sql
js/farbtastic/farbtastic.css [new file with mode: 0644]
js/geometa.js
js/identica-badge.js
js/util.js
lib/action.php
lib/api.php
lib/apiauth.php
lib/apioauth.php [new file with mode: 0644]
lib/apioauthstore.php [new file with mode: 0644]
lib/applicationeditform.php [new file with mode: 0644]
lib/applicationlist.php [new file with mode: 0644]
lib/connectsettingsaction.php
lib/default.php
lib/designsettings.php
lib/queuemanager.php
lib/router.php
plugins/MobileProfile/MobileProfilePlugin.php
plugins/MobileProfile/mp-screen.css
plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php
plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po [new file with mode: 0644]
plugins/PubSubHubBub/PubSubHubBubPlugin.php
scripts/console.php
scripts/queuedaemon.php
tests/oauth/README [new file with mode: 0644]
tests/oauth/exchangetokens.php [new file with mode: 0755]
tests/oauth/getrequesttoken.php [new file with mode: 0755]
tests/oauth/oauth.ini [new file with mode: 0644]
tests/oauth/verifycreds.php [new file with mode: 0755]
theme/base/css/display.css
theme/base/css/farbtastic.css [deleted file]
theme/base/css/mobile.css [deleted file]
theme/base/images/icons/icons-01.gif
theme/base/images/icons/twotone/green/key.gif [new file with mode: 0644]
theme/default/css/display.css
theme/identica/css/display.css

index 6e6afa070f56442824ee31a091edb8efd7aff3c3..1ed670697b7570506662bbf617f0754b25329cde 100644 (file)
@@ -150,6 +150,12 @@ StartAddressData: Allows the site owner to provide additional information about
 EndAddressData: At the end of <address>
 - $action: the current action
 
+StartShowSiteNotice: Before showing site notice
+- $action: the current action
+
+EndShowSiteNotice: After showing site notice
+- $action: the current action
+
 StartLoginGroupNav: Before showing the login and register navigation menu
 - $action: the current action
 
index 08b201dbffdeec72f25c0089369c92e8268ab501..1095d5162634f312772ce78e4e533dc2a2eaf00a 100644 (file)
@@ -82,4 +82,18 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction
 
     }
 
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     * 
+     * @return boolean true
+     *
+     **/
+    
+    function isReadOnly($args)
+    {
+        return true;
+    }
+    
 }
diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php
new file mode 100644 (file)
index 0000000..085ef6f
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Exchange an authorized OAuth request token for an access token
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apioauth.php';
+
+/**
+ * Exchange an authorized OAuth request token for an access token
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApiOauthAccessTokenAction extends ApiOauthAction
+{
+
+    /**
+     * Class handler.
+     *
+     * @param array $args array of arguments
+     *
+     * @return void
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $datastore   = new ApiStatusNetOAuthDataStore();
+        $server      = new OAuthServer($datastore);
+        $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+        $server->add_signature_method($hmac_method);
+
+        $atok = null;
+
+        try {
+            $req  = OAuthRequest::from_request();
+            $atok = $server->fetch_access_token($req);
+
+        } catch (OAuthException $e) {
+            common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
+            common_debug(var_export($req, true));
+            $this->outputError($e->getMessage());
+            return;
+        }
+
+        if (empty($atok)) {
+            common_debug('couldn\'t get access token.');
+            print "Token exchange failed. Has the request token been authorized?\n";
+        } else {
+            print $atok;
+        }
+    }
+
+    function outputError($msg)
+    {
+        header('HTTP/1.1 401 Unauthorized');
+        header('Content-Type: text/html; charset=utf-8');
+        print $msg . "\n";
+    }
+}
+
diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php
new file mode 100644 (file)
index 0000000..fa074c4
--- /dev/null
@@ -0,0 +1,376 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Authorize an OAuth request token
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apioauth.php';
+
+/**
+ * Authorize an OAuth request token
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApiOauthAuthorizeAction extends ApiOauthAction
+{
+    var $oauth_token;
+    var $callback;
+    var $app;
+    var $nickname;
+    var $password;
+    var $store;
+
+    /**
+     * Is this a read-only action?
+     *
+     * @return boolean false
+     */
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        common_debug("apioauthauthorize");
+
+        $this->nickname    = $this->trimmed('nickname');
+        $this->password    = $this->arg('password');
+        $this->oauth_token = $this->arg('oauth_token');
+        $this->callback    = $this->arg('oauth_callback');
+        $this->store       = new ApiStatusNetOAuthDataStore();
+        $this->app         = $this->store->getAppByRequestToken($this->oauth_token);
+
+        return true;
+    }
+
+    /**
+     * Handle input, produce output
+     *
+     * Switches on request method; either shows the form or handles its input.
+     *
+     * @param array $args $_REQUEST data
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            $this->handlePost();
+
+        } else {
+
+            // XXX: make better error messages
+
+            if (empty($this->oauth_token)) {
+
+                common_debug("No request token found.");
+
+                $this->clientError(_('Bad request.'));
+                return;
+            }
+
+            if (empty($this->app)) {
+                common_debug('No app for that token.');
+                $this->clientError(_('Bad request.'));
+                return;
+            }
+
+            $name = $this->app->name;
+            common_debug("Requesting auth for app: " . $name);
+
+            $this->showForm();
+        }
+    }
+
+    function handlePost()
+    {
+        common_debug("handlePost()");
+
+        // check 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. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        // check creds
+
+        $user = null;
+
+        if (!common_logged_in()) {
+            $user = common_check_user($this->nickname, $this->password);
+            if (empty($user)) {
+                $this->showForm(_("Invalid nickname / password!"));
+                return;
+            }
+        } else {
+            $user = common_current_user();
+        }
+
+        if ($this->arg('allow')) {
+
+            // mark the req token as authorized
+
+            $this->store->authorize_token($this->oauth_token);
+
+            // Check to see if there was a previous token associated
+            // with this user/app and kill it. If the user is doing this she
+            // probably doesn't want any old tokens anyway.
+
+            $appUser = Oauth_application_user::getByKeys($user, $this->app);
+
+            if (!empty($appUser)) {
+                $result = $appUser->delete();
+
+                if (!$result) {
+                    common_log_db_error($appUser, 'DELETE', __FILE__);
+                    throw new ServerException(_('DB error deleting OAuth app user.'));
+                    return;
+                }
+            }
+
+            // associated the authorized req token with the user and the app
+
+            $appUser = new Oauth_application_user();
+
+            $appUser->profile_id     = $user->id;
+            $appUser->application_id = $this->app->id;
+
+            // Note: do not copy the access type from the application.
+            // The access type should always be 0 when the OAuth app
+            // user record has a request token associated with it.
+            // Access type gets assigned once an access token has been
+            // granted.  The OAuth app user record then gets updated
+            // with the new access token and access type.
+
+            $appUser->token          = $this->oauth_token;
+            $appUser->created        = common_sql_now();
+
+            $result = $appUser->insert();
+
+            if (!$result) {
+                common_log_db_error($appUser, 'INSERT', __FILE__);
+                throw new ServerException(_('DB error inserting OAuth app user.'));
+                return;
+            }
+
+            // if we have a callback redirect and provide the token
+
+            // A callback specified in the app setup overrides whatever
+            // is passed in with the request.
+
+            common_debug("Req token is authorized - doing callback");
+
+            if (!empty($this->app->callback_url)) {
+                $this->callback = $this->app->callback_url;
+            }
+
+            if (!empty($this->callback)) {
+
+                // XXX: Need better way to build this redirect url.
+
+                $target_url = $this->getCallback($this->callback,
+                                                 array('oauth_token' => $this->oauth_token));
+
+                common_debug("Doing callback to $target_url");
+
+                common_redirect($target_url, 303);
+            } else {
+                common_debug("callback was empty!");
+            }
+
+            // otherwise inform the user that the rt was authorized
+
+            $this->elementStart('p');
+
+            // XXX: Do OAuth 1.0a verifier code
+
+            $this->raw(sprintf(_("The request token %s has been authorized. " .
+                                 'Please exchange it for an access token.'),
+                               $this->oauth_token));
+
+            $this->elementEnd('p');
+
+        } else if ($this->arg('deny')) {
+
+            $this->elementStart('p');
+
+            $this->raw(sprintf(_("The request token %s has been denied."),
+                               $this->oauth_token));
+
+            $this->elementEnd('p');
+        } else {
+            $this->clientError(_('Unexpected form submission.'));
+            return;
+        }
+    }
+
+    function showForm($error=null)
+    {
+        $this->error = $error;
+        $this->showPage();
+    }
+
+    function showScripts()
+    {
+        parent::showScripts();
+        if (!common_logged_in()) {
+            $this->autofocus('nickname');
+        }
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title of the page
+     */
+
+    function title()
+    {
+        return _('An application would like to connect to your account');
+    }
+
+    /**
+     * Shows the authorization form.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_apioauthauthorize',
+                                          'class' => 'form_settings',
+                                          'action' => common_local_url('apioauthauthorize')));
+        $this->elementStart('fieldset');
+        $this->element('legend', array('id' => 'apioauthauthorize_allowdeny'),
+                                 _('Allow or deny access'));
+
+        $this->hidden('token', common_session_token());
+        $this->hidden('oauth_token', $this->oauth_token);
+        $this->hidden('oauth_callback', $this->callback);
+
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->elementStart('p');
+        if (!empty($this->app->icon)) {
+            $this->element('img', array('src' => $this->app->icon));
+        }
+
+        $access = ($this->app->access_type & Oauth_application::$writeAccess) ?
+          'access and update' : 'access';
+
+        $msg = _("The application <strong>%s</strong> by <strong>%s</strong> would like " .
+                 "the ability to <strong>%s</strong> your account data.");
+
+        $this->raw(sprintf($msg,
+                           $this->app->name,
+                           $this->app->organization,
+                           $access));
+        $this->elementEnd('p');
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+
+        if (!common_logged_in()) {
+
+            $this->elementStart('fieldset');
+            $this->element('legend', null, _('Account'));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->input('nickname', _('Nickname'));
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->password('password', _('Password'));
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+
+            $this->elementEnd('fieldset');
+
+        }
+
+        $this->element('input', array('id' => 'deny_submit',
+                                      'class' => 'submit submit form_action-primary',
+                                      'name' => 'deny',
+                                      'type' => 'submit',
+                                      'value' => _('Deny')));
+
+        $this->element('input', array('id' => 'allow_submit',
+                                      'class' => 'submit submit form_action-secondary',
+                                      'name' => 'allow',
+                                      'type' => 'submit',
+                                      'value' => _('Allow')));
+
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Instructions for using the form
+     *
+     * For "remembered" logins, we make the user re-login when they
+     * try to change settings. Different instructions for this case.
+     *
+     * @return void
+     */
+
+    function getInstructions()
+    {
+        return _('Allow or deny access to your account information.');
+    }
+
+    /**
+     * A local menu
+     *
+     * Shows different login/register actions.
+     *
+     * @return void
+     */
+
+    function showLocalNav()
+    {
+    }
+
+}
diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php
new file mode 100644 (file)
index 0000000..467640b
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Get an OAuth request token
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apioauth.php';
+
+/**
+ * Get an OAuth request token
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApiOauthRequestTokenAction extends ApiOauthAction
+{
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->callback  = $this->arg('oauth_callback');
+
+        if (!empty($this->callback)) {
+            common_debug("callback: $this->callback");
+        }
+
+        return true;
+    }
+
+    /**
+     * Class handler.
+     *
+     * @param array $args array of arguments
+     *
+     * @return void
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $datastore   = new ApiStatusNetOAuthDataStore();
+        $server      = new OAuthServer($datastore);
+        $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+        $server->add_signature_method($hmac_method);
+
+        try {
+            $req   = OAuthRequest::from_request();
+            $token = $server->fetch_request_token($req);
+            print $token;
+        } catch (OAuthException $e) {
+            common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
+            header('HTTP/1.1 401 Unauthorized');
+            header('Content-Type: text/html; charset=utf-8');
+            print $e->getMessage() . "\n";
+        }
+    }
+
+}
index 9d831b9dbbc42c855dec90813c04861b0acd9029..31c9b20ce2783774927ab0f212e80627cd685eeb 100644 (file)
@@ -85,6 +85,11 @@ class ApiStatusesUpdateAction extends ApiAuthAction
         $this->lat    = $this->trimmed('lat');
         $this->lon    = $this->trimmed('long');
 
+        // try to set the source attr from OAuth app
+        if (empty($this->source)) {
+            $this->source = $this->oauth_source;
+        }
+
         if (empty($this->source) || in_array($this->source, self::$reserved_sources)) {
             $this->source = 'api';
         }
index f862aff0ebed5a11b8cf2962c2176fd51734a861..72ad6ade2a12ced8c9dadba73b51069aec2e3119 100644 (file)
@@ -289,7 +289,7 @@ class DesignadminpanelAction extends AdminPanelAction
     function showStylesheets()
     {
         parent::showStylesheets();
-        $this->cssLink('css/farbtastic.css','base','screen, projection, tv');
+        $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv');
     }
 
     /**
index 836f039d32128d9696d5bbc07b598155fd803218..25d363472a2cca2feadf3d1292c1d226c64f8bd5 100644 (file)
@@ -45,11 +45,23 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  */
 class DocAction extends Action
 {
-    var $filename;
-    var $title;
+    var $output   = null;
+    var $filename = null;
+    var $title    = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->title  = $this->trimmed('title');
+        $this->output = null;
+
+        $this->loadDoc();
+        return true;
+    }
 
     /**
-     * Class handler.
+     * Handle a request
      *
      * @param array $args array of arguments
      *
@@ -58,51 +70,51 @@ class DocAction extends Action
     function handle($args)
     {
         parent::handle($args);
-
-        $this->title  = $this->trimmed('title');
-        $this->output = null;
-
-        if (Event::handle('StartLoadDoc', array(&$this->title, &$this->output))) {
-
-            $this->filename = INSTALLDIR.'/doc-src/'.$this->title;
-            if (!file_exists($this->filename)) {
-                $this->clientError(_('No such document.'));
-                return;
-            }
-
-            $c = file_get_contents($this->filename);
-            $this->output = common_markup_to_html($c);
-
-            Event::handle('EndLoadDoc', array($this->title, &$this->output));
-        }
-
         $this->showPage();
     }
 
-    // overrrided to add entry-title class
-    function showPageTitle() {
+    /**
+     * Page title
+     *
+     * Gives the page title of the document. Override default for hAtom entry.
+     *
+     * @return void
+     */
+
+    function showPageTitle()
+    {
         $this->element('h1', array('class' => 'entry-title'), $this->title());
     }
 
-    // overrided to add hentry, and content-inner classes
+    /**
+     * Block for content.
+     *
+     * Overrides default from Action to wrap everything in an hAtom entry.
+     *
+     * @return void.
+     */
+
     function showContentBlock()
-     {
-         $this->elementStart('div', array('id' => 'content', 'class' => 'hentry'));
-         $this->showPageTitle();
-         $this->showPageNoticeBlock();
-         $this->elementStart('div', array('id' => 'content_inner',
-             'class' => 'entry-content'));
-         // show the actual content (forms, lists, whatever)
-         $this->showContent();
-         $this->elementEnd('div');
-         $this->elementEnd('div');
-     }
+    {
+        $this->elementStart('div', array('id' => 'content', 'class' => 'hentry'));
+        $this->showPageTitle();
+        $this->showPageNoticeBlock();
+        $this->elementStart('div', array('id' => 'content_inner',
+                                         'class' => 'entry-content'));
+        // show the actual content (forms, lists, whatever)
+        $this->showContent();
+        $this->elementEnd('div');
+        $this->elementEnd('div');
+    }
 
     /**
      * Display content.
      *
-     * @return nothing
+     * Shows the content of the document.
+     *
+     * @return void
      */
+
     function showContent()
     {
         $this->raw($this->output);
@@ -111,6 +123,8 @@ class DocAction extends Action
     /**
      * Page title.
      *
+     * Uses the title of the document.
+     *
      * @return page title
      */
     function title()
@@ -118,8 +132,74 @@ class DocAction extends Action
         return ucfirst($this->title);
     }
 
+    /**
+     * These pages are read-only.
+     *
+     * @param array $args unused.
+     *
+     * @return boolean read-only flag (false)
+     */
+
     function isReadOnly($args)
     {
         return true;
     }
+
+    function loadDoc()
+    {
+        if (Event::handle('StartLoadDoc', array(&$this->title, &$this->output))) {
+
+            $this->filename = $this->getFilename();
+
+            if (empty($this->filename)) {
+                throw new ClientException(sprintf(_('No such document "%s"'), $this->title), 404);
+            }
+
+            $c = file_get_contents($this->filename);
+
+            $this->output = common_markup_to_html($c);
+
+            Event::handle('EndLoadDoc', array($this->title, &$this->output));
+        }
+    }
+
+    function getFilename()
+    {
+        if (file_exists(INSTALLDIR.'/local/doc-src/'.$this->title)) {
+            $localDef = INSTALLDIR.'/local/doc-src/'.$this->title;
+        }
+
+        $local = glob(INSTALLDIR.'/local/doc-src/'.$this->title.'.*');
+
+        if (count($local) || isset($localDef)) {
+            return $this->negotiateLanguage($local, $localDef);
+        }
+
+        if (file_exists(INSTALLDIR.'/doc-src/'.$this->title)) {
+            $distDef = INSTALLDIR.'/doc-src/'.$this->title;
+        }
+
+        $dist = glob(INSTALLDIR.'/doc-src/'.$this->title.'.*');
+
+        if (count($dist) || isset($distDef)) {
+            return $this->negotiateLanguage($dist, $distDef);
+        }
+
+        return null;
+    }
+
+    function negotiateLanguage($filenames, $defaultFilename=null)
+    {
+        // XXX: do this better
+
+        $langcode = common_language();
+
+        foreach ($filenames as $filename) {
+            if (preg_match('/\.'.$langcode.'$/', $filename)) {
+                return $filename;
+            }
+        }
+
+        return $defaultFilename;
+    }
 }
diff --git a/actions/editapplication.php b/actions/editapplication.php
new file mode 100644 (file)
index 0000000..9cc3e3c
--- /dev/null
@@ -0,0 +1,264 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Edit an OAuth Application
+ *
+ * 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  Applications
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Edit the details of an OAuth application
+ *
+ * This is the form for editing an application
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class EditApplicationAction extends OwnerDesignAction
+{
+    var $msg   = null;
+    var $owner = null;
+    var $app   = null;
+
+    function title()
+    {
+        return _('Edit Application');
+    }
+
+    /**
+     * Prepare to run
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to edit an application.'));
+            return false;
+        }
+
+        $id = (int)$this->arg('id');
+
+        $this->app   = Oauth_application::staticGet($id);
+        $this->owner = User::staticGet($this->app->owner);
+        $cur         = common_current_user();
+
+        if ($cur->id != $this->owner->id) {
+            $this->clientError(_('You are not the owner of this application.'), 401);
+        }
+
+        if (!$this->app) {
+            $this->clientError(_('No such application.'));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * On GET, show the form. On POST, try to save the app.
+     *
+     * @param array $args unused
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->handlePost($args);
+        } else {
+            $this->showForm();
+        }
+    }
+
+    function handlePost($args)
+    {
+        // Workaround for PHP returning empty $_POST and $_FILES when POST
+        // length > post_max_size in php.ini
+
+        if (empty($_FILES)
+            && empty($_POST)
+            && ($_SERVER['CONTENT_LENGTH'] > 0)
+            ) {
+            $msg = _('The server was unable to handle that much POST ' .
+                     'data (%s bytes) due to its current configuration.');
+            $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
+            return;
+        }
+
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->clientError(_('There was a problem with your session token.'));
+            return;
+        }
+
+        $cur = common_current_user();
+
+        if ($this->arg('cancel')) {
+            common_redirect(common_local_url('showapplication',
+                                             array('id' => $this->app->id)), 303);
+        } elseif ($this->arg('save')) {
+            $this->trySave();
+        } else {
+            $this->clientError(_('Unexpected form submission.'));
+        }
+    }
+
+    function showForm($msg=null)
+    {
+        $this->msg = $msg;
+        $this->showPage();
+    }
+
+    function showContent()
+    {
+        $form = new ApplicationEditForm($this, $this->app);
+        $form->show();
+    }
+
+    function showPageNotice()
+    {
+        if (!empty($this->msg)) {
+            $this->element('p', 'error', $this->msg);
+        } else {
+            $this->element('p', 'instructions',
+                           _('Use this form to edit your application.'));
+        }
+    }
+
+    function trySave()
+    {
+        $name         = $this->trimmed('name');
+        $description  = $this->trimmed('description');
+        $source_url   = $this->trimmed('source_url');
+        $organization = $this->trimmed('organization');
+        $homepage     = $this->trimmed('homepage');
+        $callback_url = $this->trimmed('callback_url');
+        $type         = $this->arg('app_type');
+        $access_type  = $this->arg('default_access_type');
+
+        if (empty($name)) {
+            $this->showForm(_('Name is required.'));
+            return;
+        } elseif (mb_strlen($name) > 255) {
+            $this->showForm(_('Name is too long (max 255 chars).'));
+            return;
+        } elseif (empty($description)) {
+            $this->showForm(_('Description is required.'));
+            return;
+        } elseif (Oauth_application::descriptionTooLong($description)) {
+            $this->showForm(sprintf(
+                _('Description is too long (max %d chars).'),
+                                    Oauth_application::maxDescription()));
+            return;
+        } elseif (mb_strlen($source_url) > 255) {
+            $this->showForm(_('Source URL is too long.'));
+            return;
+        } elseif ((mb_strlen($source_url) > 0)
+                  && !Validate::uri($source_url,
+                                    array('allowed_schemes' => array('http', 'https'))))
+            {
+                $this->showForm(_('Source URL is not valid.'));
+                return;
+        } elseif (empty($organization)) {
+            $this->showForm(_('Organization is required.'));
+            return;
+        } elseif (mb_strlen($organization) > 255) {
+            $this->showForm(_('Organization is too long (max 255 chars).'));
+            return;
+        } elseif (empty($homepage)) {
+            $this->showForm(_('Organization homepage is required.'));
+            return;
+        } elseif ((mb_strlen($homepage) > 0)
+                  && !Validate::uri($homepage,
+                                    array('allowed_schemes' => array('http', 'https'))))
+            {
+                $this->showForm(_('Homepage is not a valid URL.'));
+                return;
+            } elseif (mb_strlen($callback_url) > 255) {
+                $this->showForm(_('Callback is too long.'));
+                return;
+            } elseif (mb_strlen($callback_url) > 0
+                      && !Validate::uri($source_url,
+                                        array('allowed_schemes' => array('http', 'https'))
+                                        ))
+                {
+                    $this->showForm(_('Callback URL is not valid.'));
+                    return;
+                }
+
+        $cur = common_current_user();
+
+        // Checked in prepare() above
+
+        assert(!is_null($cur));
+        assert(!is_null($this->app));
+
+        $orig = clone($this->app);
+
+        $this->app->name         = $name;
+        $this->app->description  = $description;
+        $this->app->source_url   = $source_url;
+        $this->app->organization = $organization;
+        $this->app->homepage     = $homepage;
+        $this->app->callback_url = $callback_url;
+        $this->app->type         = $type;
+
+        common_debug("access_type = $access_type");
+
+        if ($access_type == 'r') {
+            $this->app->access_type = 1;
+        } else {
+            $this->app->access_type = 3;
+        }
+
+        $result = $this->app->update($orig);
+
+        if (!$result) {
+            common_log_db_error($this->app, 'UPDATE', __FILE__);
+            $this->serverError(_('Could not update application.'));
+        }
+
+        $this->app->uploadLogo();
+
+        common_redirect(common_local_url('oauthappssettings'), 303);
+    }
+
+}
+
index bfef2970dabbe1bc4a352220009bc8976077de13..08608348cdae1f79f077df2dc9b70581996485ed 100644 (file)
@@ -130,7 +130,7 @@ class EmailsettingsAction extends AccountSettingsAction
 
        if (common_config('emailpost', 'enabled') && $user->email) {
             $this->elementStart('fieldset', array('id' => 'settings_email_incoming'));
-            $this->element('legend',_('Incoming email'));
+            $this->element('legend', null, _('Incoming email'));
             if ($user->incomingemail) {
                 $this->elementStart('p');
                 $this->element('span', 'address', $user->incomingemail);
index f605cc9e8b3edf6d246c0febc68f716bf3e49384..8330f753ff1dd4161830656171b9373be10312d1 100644 (file)
@@ -56,10 +56,10 @@ class InboxAction extends MailboxAction
     function title()
     {
         if ($this->page > 1) {
-            return sprintf(_("Inbox for %1$s - page %2$d"), $this->user->nickname,
+            return sprintf(_('Inbox for %1$s - page %2$d'), $this->user->nickname,
                 $this->page);
         } else {
-            return sprintf(_("Inbox for %s"), $this->user->nickname);
+            return sprintf(_('Inbox for %s'), $this->user->nickname);
         }
     }
 
diff --git a/actions/newapplication.php b/actions/newapplication.php
new file mode 100644 (file)
index 0000000..c499fe7
--- /dev/null
@@ -0,0 +1,277 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Register a new OAuth Application
+ *
+ * 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  Applications
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Add a new application
+ *
+ * This is the form for adding a new application
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class NewApplicationAction extends OwnerDesignAction
+{
+    var $msg;
+
+    function title()
+    {
+        return _('New Application');
+    }
+
+    /**
+     * Prepare to run
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to register an application.'));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * On GET, show the form. On POST, try to save the app.
+     *
+     * @param array $args unused
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+        $this->handlePost($args);
+        } else {
+            $this->showForm();
+        }
+    }
+
+    function handlePost($args)
+    {
+    // Workaround for PHP returning empty $_POST and $_FILES when POST
+        // length > post_max_size in php.ini
+
+        if (empty($_FILES)
+            && empty($_POST)
+            && ($_SERVER['CONTENT_LENGTH'] > 0)
+        ) {
+            $msg = _('The server was unable to handle that much POST ' .
+             'data (%s bytes) due to its current configuration.');
+            $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
+            return;
+        }
+
+    // CSRF protection
+    $token = $this->trimmed('token');
+    if (!$token || $token != common_session_token()) {
+        $this->clientError(_('There was a problem with your session token.'));
+        return;
+    }
+
+    $cur = common_current_user();
+
+    if ($this->arg('cancel')) {
+        common_redirect(common_local_url('oauthappssettings'), 303);
+    } elseif ($this->arg('save')) {
+        $this->trySave();
+    } else {
+        $this->clientError(_('Unexpected form submission.'));
+    }
+    }
+
+    function showForm($msg=null)
+    {
+        $this->msg = $msg;
+        $this->showPage();
+    }
+
+    function showContent()
+    {
+        $form = new ApplicationEditForm($this);
+        $form->show();
+    }
+
+    function showPageNotice()
+    {
+        if ($this->msg) {
+            $this->element('p', 'error', $this->msg);
+        } else {
+            $this->element('p', 'instructions',
+                           _('Use this form to register a new application.'));
+        }
+    }
+
+    function trySave()
+    {
+        $name         = $this->trimmed('name');
+        $description  = $this->trimmed('description');
+        $source_url   = $this->trimmed('source_url');
+        $organization = $this->trimmed('organization');
+        $homepage     = $this->trimmed('homepage');
+        $callback_url = $this->trimmed('callback_url');
+        $type         = $this->arg('app_type');
+        $access_type  = $this->arg('default_access_type');
+
+        if (empty($name)) {
+             $this->showForm(_('Name is required.'));
+             return;
+        } elseif (mb_strlen($name) > 255) {
+            $this->showForm(_('Name is too long (max 255 chars).'));
+            return;
+        } elseif (empty($description)) {
+            $this->showForm(_('Description is required.'));
+            return;
+        } elseif (Oauth_application::descriptionTooLong($description)) {
+            $this->showForm(sprintf(
+                _('Description is too long (max %d chars).'),
+                Oauth_application::maxDescription()));
+            return;
+        } elseif (empty($source_url)) {
+            $this->showForm(_('Source URL is required.'));
+            return;
+        } elseif ((strlen($source_url) > 0)
+            && !Validate::uri(
+                $source_url,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Source URL is not valid.'));
+            return;
+        } elseif (empty($organization)) {
+            $this->showForm(_('Organization is required.'));
+            return;
+        } elseif (mb_strlen($organization) > 255) {
+            $this->showForm(_('Organization is too long (max 255 chars).'));
+            return;
+        } elseif (empty($homepage)) {
+            $this->showForm(_('Organization homepage is required.'));
+            return;
+        } elseif ((strlen($homepage) > 0)
+            && !Validate::uri(
+                $homepage,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Homepage is not a valid URL.'));
+            return;
+        } elseif (mb_strlen($callback_url) > 255) {
+            $this->showForm(_('Callback is too long.'));
+            return;
+        } elseif (strlen($callback_url) > 0
+            && !Validate::uri(
+                $source_url,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Callback URL is not valid.'));
+            return;
+        }
+
+        $cur = common_current_user();
+
+        // Checked in prepare() above
+
+        assert(!is_null($cur));
+
+        $app = new Oauth_application();
+
+        $app->query('BEGIN');
+
+        $app->name         = $name;
+        $app->owner        = $cur->id;
+        $app->description  = $description;
+        $app->source_url   = $source_url;
+        $app->organization = $organization;
+        $app->homepage     = $homepage;
+        $app->callback_url = $callback_url;
+        $app->type         = $type;
+
+        // Yeah, I dunno why I chose bit flags. I guess so I could
+        // copy this value directly to Oauth_application_user
+        // access_type which I think does need bit flags -- Z
+
+        if ($access_type == 'r') {
+            $app->setAccessFlags(true, false);
+        } else {
+            $app->setAccessFlags(true, true);
+        }
+
+        $app->created = common_sql_now();
+
+        // generate consumer key and secret
+
+        $consumer = Consumer::generateNew();
+
+        $result = $consumer->insert();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'INSERT', __FILE__);
+            $this->serverError(_('Could not create application.'));
+        }
+
+        $app->consumer_key = $consumer->consumer_key;
+
+        $this->app_id = $app->insert();
+
+        if (!$this->app_id) {
+            common_log_db_error($app, 'INSERT', __FILE__);
+            $this->serverError(_('Could not create application.'));
+            $app->query('ROLLBACK');
+        }
+
+        $app->uploadLogo();
+
+        $app->query('COMMIT');
+
+        common_redirect(common_local_url('oauthappssettings'), 303);
+
+    }
+
+}
+
diff --git a/actions/oauthappssettings.php b/actions/oauthappssettings.php
new file mode 100644 (file)
index 0000000..6c0670b
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List the OAuth applications that a user has registered with this instance
+ *
+ * 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  Settings
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/settingsaction.php';
+require_once INSTALLDIR . '/lib/applicationlist.php';
+
+/**
+ * Show a user's registered OAuth applications
+ *
+ * @category Settings
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      SettingsAction
+ */
+
+class OauthappssettingsAction extends SettingsAction
+{
+    var $page = 0;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to list your applications.'));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('OAuth applications');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Applications you have registered');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $user = common_current_user();
+
+        $offset = ($this->page - 1) * APPS_PER_PAGE;
+        $limit  =  APPS_PER_PAGE + 1;
+
+        $application = new Oauth_application();
+        $application->owner = $user->id;
+        $application->limit($offset, $limit);
+        $application->orderBy('created DESC');
+        $application->find();
+
+        $cnt = 0;
+
+        if ($application) {
+            $al = new ApplicationList($application, $user, $this);
+            $cnt = $al->show();
+            if (0 == $cnt) {
+                $this->showEmptyListMessage();
+            }
+        }
+
+        $this->elementStart('p', array('id' => 'application_register'));
+        $this->element('a',
+            array('href' => common_local_url('newapplication'),
+                  'class' => 'more'
+            ),
+            'Register a new application');
+        $this->elementEnd('p');
+
+        $this->pagination(
+            $this->page > 1,
+            $cnt > APPS_PER_PAGE,
+            $this->page,
+            'oauthappssettings'
+        );
+    }
+
+    function showEmptyListMessage()
+    {
+        $message = sprintf(_('You have not registered any applications yet.'));
+
+        $this->elementStart('div', 'guide');
+        $this->raw(common_markup_to_html($message));
+        $this->elementEnd('div');
+    }
+
+    /**
+     * Handle posts to this form
+     *
+     * Based on the button that was pressed, muxes out to other functions
+     * to do the actual task requested.
+     *
+     * All sub-functions reload the form with a message -- success or failure.
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+    }
+
+}
diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php
new file mode 100644 (file)
index 0000000..b17729b
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List a user's OAuth connected applications
+ *
+ * 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  Settings
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/connectsettingsaction.php';
+require_once INSTALLDIR . '/lib/applicationlist.php';
+
+/**
+ * Show connected OAuth applications
+ *
+ * @category Settings
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      SettingsAction
+ */
+
+class OauthconnectionssettingsAction extends ConnectSettingsAction
+{
+
+    var $page = null;
+    var $id   = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->id = (int)$this->arg('id');
+        $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Connected Applications');
+    }
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('You have allowed the following applications to access you account.');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $user    = common_current_user();
+        $profile = $user->getProfile();
+
+        $offset = ($this->page - 1) * APPS_PER_PAGE;
+        $limit  =  APPS_PER_PAGE + 1;
+
+        $application = $profile->getApplications($offset, $limit);
+
+        $cnt == 0;
+
+        if (!empty($application)) {
+            $al = new ApplicationList($application, $user, $this, true);
+            $cnt = $al->show();
+        }
+
+        if ($cnt == 0) {
+            $this->showEmptyListMessage();
+        }
+
+        $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE,
+                          $this->page, 'connectionssettings',
+                          array('nickname' => $this->user->nickname));
+    }
+
+    /**
+     * Handle posts to this form
+     *
+     * Based on the button that was pressed, muxes out to other functions
+     * to do the actual task requested.
+     *
+     * All sub-functions reload the form with a message -- success or failure.
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('revoke')) {
+            $this->revokeAccess($this->id);
+
+            // XXX: Show some indicator to the user of what's been done.
+
+            $this->showPage();
+        } else {
+            $this->clientError(_('Unexpected form submission.'), 401);
+            return false;
+        }
+    }
+
+    function revokeAccess($appId)
+    {
+        $cur = common_current_user();
+
+        $app = Oauth_application::staticGet('id', $appId);
+
+        if (empty($app)) {
+            $this->clientError(_('No such application.'), 404);
+            return false;
+        }
+
+        $appUser = Oauth_application_user::getByKeys($cur, $app);
+
+        if (empty($appUser)) {
+            $this->clientError(_('You are not a user of that application.'), 401);
+            return false;
+        }
+
+        $orig = clone($appUser);
+        $appUser->access_type = 0;  // No access
+        $result = $appUser->update();
+
+        if (!$result) {
+            common_log_db_error($orig, 'UPDATE', __FILE__);
+            $this->clientError(_('Unable to revoke access for app: ' . $app->id));
+            return false;
+        }
+
+        $msg = 'User %s (id: %d) revoked access to app %s (id: %d)';
+        common_log(LOG_INFO, sprintf($msg, $cur->nickname,
+                                     $cur->id, $app->name, $app->id));
+
+    }
+
+    function showEmptyListMessage()
+    {
+        $message = sprintf(_('You have not authorized any applications to use your account.'));
+
+        $this->elementStart('div', 'guide');
+        $this->raw(common_markup_to_html($message));
+        $this->elementEnd('div');
+    }
+
+    function showSections()
+    {
+       $cur = common_current_user();
+
+       $this->element('h2', null, 'Developers');
+       $this->elementStart('p');
+       $this->raw(_('Developers can edit the registration settings for their applications '));
+       $this->element('a',
+           array('href' => common_local_url('oauthappssettings')),
+               'here.');
+       $this->elementEnd('p');
+    }
+
+}
index de30de018354ff5e599cb1a20f5d5126120c9872..b81d4b9d0d8d1fac1e5d27b240147b77f9155c82 100644 (file)
@@ -55,10 +55,10 @@ class OutboxAction extends MailboxAction
     function title()
     {
         if ($this->page > 1) {
-            return sprintf(_("Outbox for %1$s - page %2$d"),
+            return sprintf(_('Outbox for %1$s - page %2$d'),
                 $this->user->nickname, $page);
         } else {
-            return sprintf(_("Outbox for %s"), $this->user->nickname);
+            return sprintf(_('Outbox for %s'), $this->user->nickname);
         }
     }
 
index 2e50f1c3c4ce04f7379e8058073e5105c8da835e..164c328db3cd6e0f4749f11543254899f79e4acb 100644 (file)
@@ -124,7 +124,7 @@ class RepliesAction extends OwnerDesignAction
         if ($this->page == 1) {
             return sprintf(_("Replies to %s"), $this->user->nickname);
         } else {
-            return sprintf(_("Replies to %1$s, page %2$d"),
+            return sprintf(_('Replies to %1$s, page %2$d'),
                            $this->user->nickname,
                            $this->page);
         }
diff --git a/actions/showapplication.php b/actions/showapplication.php
new file mode 100644 (file)
index 0000000..0492063
--- /dev/null
@@ -0,0 +1,328 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show an OAuth application
+ *
+ * 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  Application
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Show an OAuth application
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ShowApplicationAction extends OwnerDesignAction
+{
+    /**
+     * Application to show
+     */
+
+    var $application = null;
+
+    /**
+     * User who owns the app
+     */
+
+    var $owner = null;
+
+    var $msg = null;
+
+    var $success = null;
+
+    /**
+     * Load attributes based on database arguments
+     *
+     * Loads all the DB stuff
+     *
+     * @param array $args $_REQUEST array
+     *
+     * @return success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $id = (int)$this->arg('id');
+
+        $this->application  = Oauth_application::staticGet($id);
+        $this->owner        = User::staticGet($this->application->owner);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to view an application.'));
+            return false;
+        }
+
+        if (empty($this->application)) {
+            $this->clientError(_('No such application.'), 404);
+            return false;
+        }
+
+        $cur = common_current_user();
+
+        if ($cur->id != $this->owner->id) {
+            $this->clientError(_('You are not the owner of this application.'), 401);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Shows info about the app
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            // CSRF protection
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->clientError(_('There was a problem with your session token.'));
+                return;
+            }
+
+            if ($this->arg('reset')) {
+                $this->resetKey();
+            }
+        } else {
+            $this->showPage();
+        }
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title of the page
+     */
+
+    function title()
+    {
+        if (!empty($this->application->name)) {
+            return 'Application: ' . $this->application->name;
+        }
+    }
+
+    function showPageNotice()
+    {
+        if (!empty($this->msg)) {
+            $this->element('div', ($this->success) ? 'success' : 'error', $this->msg);
+        }
+    }
+
+    function showContent()
+    {
+
+        $cur = common_current_user();
+
+        $consumer = $this->application->getConsumer();
+
+        $this->elementStart('div', 'entity_profile vcard');
+        $this->element('h2', null, _('Application profile'));
+        $this->elementStart('dl', 'entity_depiction');
+        $this->element('dt', null, _('Icon'));
+        $this->elementStart('dd');
+        if (!empty($this->application->icon)) {
+            $this->element('img', array('src' => $this->application->icon,
+                                        'class' => 'photo logo'));
+        }
+        $this->elementEnd('dd');
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_fn');
+        $this->element('dt', null, _('Name'));
+        $this->elementStart('dd');
+        $this->element('a', array('href' =>  $this->application->source_url,
+                                  'class' => 'url fn'),
+                            $this->application->name);
+        $this->elementEnd('dd');
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_org');
+        $this->element('dt', null, _('Organization'));
+        $this->elementStart('dd');
+        $this->element('a', array('href' =>  $this->application->homepage,
+                                  'class' => 'url'),
+                            $this->application->organization);
+        $this->elementEnd('dd');
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_note');
+        $this->element('dt', null, _('Description'));
+        $this->element('dd', 'note', $this->application->description);
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_statistics');
+        $this->element('dt', null, _('Statistics'));
+        $this->elementStart('dd');
+        $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess)
+            ? 'read-write' : 'read-only';
+        $profile = Profile::staticGet($this->application->owner);
+
+        $appUsers = new Oauth_application_user();
+        $appUsers->application_id = $this->application->id;
+        $userCnt = $appUsers->count();
+
+        $this->raw(sprintf(
+            _('created by %1$s - %2$s access by default - %3$d users'),
+              $profile->getBestName(),
+              $defaultAccess,
+              $userCnt
+            ));
+        $this->elementEnd('dd');
+        $this->elementEnd('dl');
+        $this->elementEnd('div');
+
+        $this->elementStart('div', 'entity_actions');
+        $this->element('h2', null, _('Application actions'));
+        $this->elementStart('ul');
+        $this->elementStart('li', 'entity_edit');
+        $this->element('a',
+                       array('href' => common_local_url('editapplication',
+                                                        array('id' => $this->application->id))),
+                       'Edit');
+        $this->elementEnd('li');
+
+        $this->elementStart('li', 'entity_reset_keysecret');
+        $this->elementStart('form', array(
+            'id' => 'forma_reset_key',
+            'class' => 'form_reset_key',
+            'method' => 'POST',
+            'action' => common_local_url('showapplication',
+                                         array('id' => $this->application->id))));
+
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->submit('reset', _('Reset key & secret'));
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->elementEnd('div');
+
+        $this->elementStart('div', 'entity_data');
+        $this->element('h2', null, _('Application info'));
+        $this->elementStart('dl', 'entity_consumer_key');
+        $this->element('dt', null, _('Consumer key'));
+        $this->element('dd', null, $consumer->consumer_key);
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_consumer_secret');
+        $this->element('dt', null, _('Consumer secret'));
+        $this->element('dd', null, $consumer->consumer_secret);
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_request_token_url');
+        $this->element('dt', null, _('Request token URL'));
+        $this->element('dd', null, common_local_url('apioauthrequesttoken'));
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_access_token_url');
+        $this->element('dt', null, _('Access token URL'));
+        $this->element('dd', null, common_local_url('apioauthaccesstoken'));
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_authorize_url');
+        $this->element('dt', null, _('Authorize URL'));
+        $this->element('dd', null, common_local_url('apioauthauthorize'));
+        $this->elementEnd('dl');
+
+        $this->element('p', 'note',
+            _('Note: We support hmac-sha1 signatures. We do not support the plaintext signature method.'));
+        $this->elementEnd('div');
+
+        $this->elementStart('p', array('id' => 'application_action'));
+        $this->element('a',
+            array('href' => common_local_url('oauthappssettings'),
+                  'class' => 'more'),
+                  'View your applications');
+        $this->elementEnd('p');
+    }
+
+    function resetKey()
+    {
+        $this->application->query('BEGIN');
+
+        $consumer = $this->application->getConsumer();
+        $result = $consumer->delete();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'DELETE', __FILE__);
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $consumer = Consumer::generateNew();
+
+        $result = $consumer->insert();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'INSERT', __FILE__);
+            $this->application->query('ROLLBACK');
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $orig = clone($this->application);
+        $this->application->consumer_key = $consumer->consumer_key;
+        $result = $this->application->update($orig);
+
+        if (!$result) {
+            common_log_db_error($application, 'UPDATE', __FILE__);
+            $this->application->query('ROLLBACK');
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $this->application->query('COMMIT');
+
+        $this->success = true;
+        $this->msg = ('Consumer key and secret reset.');
+        $this->showPage();
+    }
+
+}
+
index 6023f015678185bfe18c680d5a5b1458840a6912..f2d0822936bc562c9014b4b531ec2eb3d2fdaf17 100644 (file)
@@ -74,9 +74,9 @@ class ShowfavoritesAction extends OwnerDesignAction
     function title()
     {
         if ($this->page == 1) {
-            return sprintf(_("%s's favorite notices"), $this->user->nickname);
+            return sprintf(_('%s\'s favorite notices'), $this->user->nickname);
         } else {
-            return sprintf(_("%1$s's favorite notices, page %2$d"),
+            return sprintf(_('%1$s\'s favorite notices, page %2$d'),
                            $this->user->nickname,
                            $this->page);
         }
index 06ae572e8107efd72ff30d82449fae612de7bd0c..8042a4951339a26f7aebaf4ad56f2e05de3a386b 100644 (file)
@@ -79,9 +79,9 @@ class ShowgroupAction extends GroupDesignAction
         }
 
         if ($this->page == 1) {
-            return sprintf(_("%s group"), $base);
+            return sprintf(_('%s group'), $base);
         } else {
-            return sprintf(_("%1$s group, page %2$d"),
+            return sprintf(_('%1$s group, page %2$d'),
                            $base,
                            $this->page);
         }
index 75e10858d08ad2f917074c56a5ed0861bec29346..90ff67073a1bd7d120cbcae560b010e9031d12bc 100644 (file)
@@ -76,7 +76,7 @@ class ShowstreamAction extends ProfileAction
         if ($this->page == 1) {
             return $base;
         } else {
-            return sprintf(_("%1$s, page %2$d"),
+            return sprintf(_('%1$s, page %2$d'),
                            $base,
                            $this->page);
         }
index 12857236eff733e03c22a4eec98942578492555d..e91df6ea97525752b1ea7e945b5e75d64ce95ddb 100644 (file)
@@ -63,9 +63,9 @@ class TagAction extends Action
     function title()
     {
         if ($this->page == 1) {
-            return sprintf(_("Notices tagged with %s"), $this->tag);
+            return sprintf(_('Notices tagged with %s'), $this->tag);
         } else {
-            return sprintf(_("Notices tagged with %1$s, page %2$d"),
+            return sprintf(_('Notices tagged with %1$s, page %2$d'),
                            $this->tag,
                            $this->page);
         }
@@ -85,7 +85,7 @@ class TagAction extends Action
                                                array('tag' => $this->tag)),
                               sprintf(_('Notice feed for tag %s (RSS 1.0)'),
                                       $this->tag)),
-                     new Feed(Feed::RSS2,   
+                     new Feed(Feed::RSS2,
                               common_local_url('ApiTimelineTag',
                                                array('format' => 'rss',
                                                      'tag' => $this->tag)),
index 504226143280bfff9f11e03b9cf2a5e3ca96632c..97faabae65ad9cd7f02ab1c5cc0cba3d04a21fa1 100644 (file)
@@ -59,9 +59,9 @@ class UsergroupsAction extends OwnerDesignAction
     function title()
     {
         if ($this->page == 1) {
-            return sprintf(_("%s groups"), $this->user->nickname);
+            return sprintf(_('%s groups'), $this->user->nickname);
         } else {
-            return sprintf(_("%1$s groups, page %2$d"),
+            return sprintf(_('%1$s groups, page %2$d'),
                            $this->user->nickname,
                            $this->page);
         }
index d5b7b7e33ad440f04a5b16886692c11874f48d94..ad64a8491be38f16378a6a9be918da1c6ae7d0ef 100644 (file)
@@ -4,16 +4,17 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Consumer extends Memcached_DataObject 
+class Consumer extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
 
     public $__table = 'consumer';                        // table name
     public $consumer_key;                    // varchar(255)  primary_key not_null
+    public $consumer_secret;                 // varchar(255)   not_null
     public $seed;                            // char(32)   not_null
-    public $created;                         // datetime()   not_null
-    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+    public $created;                         // datetime   not_null
+    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
 
     /* Static get */
     function staticGet($k,$v=null)
@@ -21,4 +22,18 @@ class Consumer extends Memcached_DataObject
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
+
+    static function generateNew()
+    {
+        $cons = new Consumer();
+        $rand = common_good_rand(16);
+
+        $cons->seed            = $rand;
+        $cons->consumer_key    = md5(time() + $rand);
+        $cons->consumer_secret = md5(md5(time() + time() + $rand));
+        $cons->created         = common_sql_now();
+
+        return $cons;
+    }
+
 }
index 2c9dcf59539784d0123503405b63287dd02abd01..2cc6377f83f1a5b38545d4faeb6709792fbfe721 100644 (file)
@@ -66,7 +66,6 @@ class Memcached_DataObject extends DB_DataObject
         // Clear this out so we don't accidentally break global
         // state in *this* process.
         $this->_DB_resultid = null;
-
         // We don't have any local DBO refs, so clear these out.
         $this->_link_loaded = false;
     }
@@ -91,9 +90,7 @@ class Memcached_DataObject extends DB_DataObject
             unset($i);
         }
         $i = Memcached_DataObject::getcached($cls, $k, $v);
-        if ($i) {
-            return $i;
-        } else {
+        if ($i === false) { // false == cache miss
             $i = DB_DataObject::factory($cls);
             if (empty($i)) {
                 $i = false;
@@ -101,22 +98,34 @@ class Memcached_DataObject extends DB_DataObject
             }
             $result = $i->get($k, $v);
             if ($result) {
+                // Hit!
                 $i->encache();
-                return $i;
             } else {
+                // save the fact that no such row exists
+                $c = self::memcache();
+                if (!empty($c)) {
+                    $ck = self::cachekey($cls, $k, $v);
+                    $c->set($ck, null);
+                }
                 $i = false;
-                return $i;
             }
         }
+        return $i;
     }
 
-    function &pkeyGet($cls, $kv)
+    /**
+     * @fixme Should this return false on lookup fail to match staticGet?
+     */
+    function pkeyGet($cls, $kv)
     {
         $i = Memcached_DataObject::multicache($cls, $kv);
-        if ($i) {
+        if ($i !== false) { // false == cache miss
             return $i;
         } else {
-            $i = new $cls();
+            $i = DB_DataObject::factory($cls);
+            if (empty($i)) {
+                return false;
+            }
             foreach ($kv as $k => $v) {
                 $i->$k = $v;
             }
@@ -124,6 +133,11 @@ class Memcached_DataObject extends DB_DataObject
                 $i->encache();
             } else {
                 $i = null;
+                $c = self::memcache();
+                if (!empty($c)) {
+                    $ck = self::multicacheKey($cls, $kv);
+                    $c->set($ck, null);
+                }
             }
             return $i;
         }
@@ -132,6 +146,9 @@ class Memcached_DataObject extends DB_DataObject
     function insert()
     {
         $result = parent::insert();
+        if ($result) {
+            $this->encache(); // in case of cached negative lookups
+        }
         return $result;
     }
 
@@ -186,6 +203,17 @@ class Memcached_DataObject extends DB_DataObject
 
     function keyTypes()
     {
+        // ini-based classes return number-indexed arrays. handbuilt
+        // classes return column => keytype. Make this uniform.
+
+        $keys = $this->keys();
+
+        $keyskeys = array_keys($keys);
+
+        if (is_string($keyskeys[0])) {
+            return $keys;
+        }
+
         global $_DB_DATAOBJECT;
         if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
             $this->databaseStructure();
@@ -197,6 +225,7 @@ class Memcached_DataObject extends DB_DataObject
     function encache()
     {
         $c = $this->memcache();
+
         if (!$c) {
             return false;
         } else if ($this->tableName() == 'user' && is_object($this->id)) {
@@ -206,64 +235,86 @@ class Memcached_DataObject extends DB_DataObject
                        str_replace("\n", " ", $e->getTraceAsString()));
             return false;
         } else {
-            $pkey = array();
-            $pval = array();
-            $types = $this->keyTypes();
-            ksort($types);
-            foreach ($types as $key => $type) {
-                if ($type == 'K') {
-                    $pkey[] = $key;
-                    $pval[] = $this->$key;
-                } else {
-                    $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this);
-                }
-            }
-            # XXX: should work for both compound and scalar pkeys
-            $pvals = implode(',', $pval);
-            $pkeys = implode(',', $pkey);
-            $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this);
+               $keys = $this->_allCacheKeys();
+
+               foreach ($keys as $key) {
+                   $c->set($key, $this);
+               }
         }
     }
 
     function decache()
     {
         $c = $this->memcache();
+
         if (!$c) {
             return false;
-        } else {
-            $pkey = array();
-            $pval = array();
-            $types = $this->keyTypes();
-            ksort($types);
-            foreach ($types as $key => $type) {
-                if ($type == 'K') {
-                    $pkey[] = $key;
-                    $pval[] = $this->$key;
-                } else {
-                    $c->delete($this->cacheKey($this->tableName(), $key, $this->$key));
+        }
+
+        $keys = $this->_allCacheKeys();
+
+        foreach ($keys as $key) {
+            $c->delete($key, $this);
+        }
+    }
+
+    function _allCacheKeys()
+    {
+        $ckeys = array();
+
+        $types = $this->keyTypes();
+        ksort($types);
+
+        $pkey = array();
+        $pval = array();
+
+        foreach ($types as $key => $type) {
+
+            assert(!empty($key));
+
+            if ($type == 'U') {
+                if (empty($this->$key)) {
+                    continue;
                 }
+                $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key);
+            } else if ($type == 'K' || $type == 'N') {
+                $pkey[] = $key;
+                $pval[] = $this->$key;
+            } else {
+                throw new Exception("Unknown key type $key => $type for " . $this->tableName());
             }
-            # should work for both compound and scalar pkeys
-            # XXX: comma works for now but may not be safe separator for future keys
-            $pvals = implode(',', $pval);
-            $pkeys = implode(',', $pkey);
-            $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals));
         }
+
+        assert(count($pkey) > 0);
+
+        // XXX: should work for both compound and scalar pkeys
+        $pvals = implode(',', $pval);
+        $pkeys = implode(',', $pkey);
+
+        $ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals);
+
+        return $ckeys;
     }
 
     function multicache($cls, $kv)
     {
         ksort($kv);
-        $c = Memcached_DataObject::memcache();
+        $c = self::memcache();
         if (!$c) {
             return false;
         } else {
-            $pkeys = implode(',', array_keys($kv));
-            $pvals = implode(',', array_values($kv));
-            return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals));
+            return $c->get(self::multicacheKey($cls, $kv));
         }
     }
 
+    static function multicacheKey($cls, $kv)
+    {
+        ksort($kv);
+        $pkeys = implode(',', array_keys($kv));
+        $pvals = implode(',', array_values($kv));
+        return self::cacheKey($cls, $pkeys, $pvals);
+    }
+
     function getSearchEngine($table)
     {
         require_once INSTALLDIR.'/lib/search_engines.php';
@@ -298,7 +349,8 @@ class Memcached_DataObject extends DB_DataObject
         $key_part = common_keyize($cls).':'.md5($qry);
         $ckey = common_cache_key($key_part);
         $stored = $c->get($ckey);
-        if ($stored) {
+
+        if ($stored !== false) {
             return new ArrayWrapper($stored);
         }
 
@@ -313,6 +365,39 @@ class Memcached_DataObject extends DB_DataObject
         return new ArrayWrapper($cached);
     }
 
+    /**
+     * sends query to database - this is the private one that must work 
+     *   - internal functions use this rather than $this->query()
+     *
+     * Overridden to do logging.
+     *
+     * @param  string  $string
+     * @access private
+     * @return mixed none or PEAR_Error
+     */
+    function _query($string)
+    {
+        $start = microtime(true);
+        $result = parent::_query($string);
+        $delta = microtime(true) - $start;
+
+        $limit = common_config('db', 'log_slow_queries');
+        if (($limit > 0 && $delta >= $limit) || common_config('db', 'log_queries')) {
+            $clean = $this->sanitizeQuery($string);
+            common_log(LOG_DEBUG, sprintf("DB query (%0.3fs): %s", $delta, $clean));
+        }
+        return $result;
+    }
+
+    // Sanitize a query for logging
+    // @fixme don't trim spaces in string literals
+    function sanitizeQuery($string)
+    {
+        $string = preg_replace('/\s+/', ' ', $string);
+        $string = trim($string);
+        return $string;
+    }
+
     // We overload so that 'SET NAMES "utf8"' is called for
     // each connection
 
diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php
new file mode 100644 (file)
index 0000000..a6b5390
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Table Definition for oauth_application
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Oauth_application extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'oauth_application';               // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $owner;                           // int(4)   not_null
+    public $consumer_key;                    // varchar(255)   not_null
+    public $name;                            // varchar(255)   not_null
+    public $description;                     // varchar(255)
+    public $icon;                            // varchar(255)   not_null
+    public $source_url;                      // varchar(255)
+    public $organization;                    // varchar(255)
+    public $homepage;                        // varchar(255)
+    public $callback_url;                    // varchar(255)   not_null
+    public $type;                            // tinyint(1)
+    public $access_type;                     // tinyint(1)
+    public $created;                         // datetime   not_null
+    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) {
+    return Memcached_DataObject::staticGet('Oauth_application',$k,$v);
+    }
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    // Bit flags
+    public static $readAccess  = 1;
+    public static $writeAccess = 2;
+
+    public static $browser = 1;
+    public static $desktop = 2;
+
+    function getConsumer()
+    {
+        return Consumer::staticGet('consumer_key', $this->consumer_key);
+    }
+
+    static function maxDesc()
+    {
+        $desclimit = common_config('application', 'desclimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($desclimit)) {
+            $desclimit = common_config('site', 'textlimit');
+        }
+        return $desclimit;
+    }
+
+    static function descriptionTooLong($desc)
+    {
+        $desclimit = self::maxDesc();
+        return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
+    }
+
+    function setAccessFlags($read, $write)
+    {
+        if ($read) {
+            $this->access_type |= self::$readAccess;
+        } else {
+            $this->access_type &= ~self::$readAccess;
+        }
+
+        if ($write) {
+            $this->access_type |= self::$writeAccess;
+        } else {
+            $this->access_type &= ~self::$writeAccess;
+        }
+    }
+
+    function setOriginal($filename)
+    {
+        $imagefile = new ImageFile($this->id, Avatar::path($filename));
+
+        // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini?
+        // or just one and control size via CSS? --Zach
+
+        $orig = clone($this);
+        $this->icon = Avatar::url($filename);
+        common_debug(common_log_objstring($this));
+        return $this->update($orig);
+    }
+
+    static function getByConsumerKey($key)
+    {
+        if (empty($key)) {
+            return null;
+        }
+
+        $app = new Oauth_application();
+        $app->consumer_key = $key;
+        $app->limit(1);
+        $result = $app->find(true);
+
+        return empty($result) ? null : $app;
+    }
+
+    /**
+     * Handle an image upload
+     *
+     * Does all the magic for handling an image upload, and crops the
+     * image by default.
+     *
+     * @return void
+     */
+
+    function uploadLogo()
+    {
+        if ($_FILES['app_icon']['error'] ==
+            UPLOAD_ERR_OK) {
+
+            try {
+                $imagefile = ImageFile::fromUpload('app_icon');
+            } catch (Exception $e) {
+                common_debug("damn that sucks");
+                $this->showForm($e->getMessage());
+                return;
+            }
+
+            $filename = Avatar::filename($this->id,
+                                         image_type_to_extension($imagefile->type),
+                                         null,
+                                         'oauth-app-icon-'.common_timestamp());
+
+            $filepath = Avatar::path($filename);
+
+            move_uploaded_file($imagefile->filepath, $filepath);
+
+            $this->setOriginal($filename);
+        }
+    }
+
+}
diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php
new file mode 100644 (file)
index 0000000..5798628
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Table Definition for oauth_application_user
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Oauth_application_user extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'oauth_application_user';          // table name
+    public $profile_id;                      // int(4)  primary_key not_null
+    public $application_id;                  // int(4)  primary_key not_null
+    public $access_type;                     // tinyint(1)
+    public $token;                           // varchar(255)
+    public $created;                         // datetime   not_null
+    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) {
+        return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v);
+    }
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    static function getByKeys($user, $app)
+    {
+        if (empty($user) || empty($app)) {
+            return null;
+        }
+
+        $oau = new Oauth_application_user();
+
+        $oau->profile_id     = $user->id;
+        $oau->application_id = $app->id;
+        $oau->limit(1);
+
+        $result = $oau->find(true);
+
+        return empty($result) ? null : $oau;
+    }
+
+}
index 25d908dbf93c22c1a24fb41cbd5a204d2478e825..1076fb2cb3e09f8b95b1915b36f1b6bb5ffbcef6 100644 (file)
@@ -352,6 +352,31 @@ class Profile extends Memcached_DataObject
         return $profile;
     }
 
+    function getApplications($offset = 0, $limit = null)
+    {
+        $qry =
+          'SELECT a.* ' .
+          'FROM oauth_application_user u, oauth_application a ' .
+          'WHERE u.profile_id = %d ' .
+          'AND a.id = u.application_id ' .
+          'AND u.access_type > 0 ' .
+          'ORDER BY u.created DESC ';
+
+        if ($offset > 0) {
+            if (common_config('db','type') == 'pgsql') {
+                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+            } else {
+                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+            }
+        }
+
+        $application = new Oauth_application();
+
+        $cnt = $application->query(sprintf($qry, $this->id));
+
+        return $application;
+    }
+
     function subscriptionCount()
     {
         $c = common_memcache();
index 1fabd72f13a70a1b89a2f62b8bd916f4d425403c..a129d1fd110747aa945ed1f8a816f34242d40eb1 100644 (file)
@@ -4,7 +4,7 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Token extends Memcached_DataObject 
+class Token extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -14,7 +14,9 @@ class Token extends Memcached_DataObject
     public $tok;                             // char(32)  primary_key not_null
     public $secret;                          // char(32)   not_null
     public $type;                            // tinyint(1)   not_null
-    public $state;                           // tinyint(1)  
+    public $state;                           // tinyint(1)
+    public $verifier;                        // varchar(255)
+    public $verified_callback;               // varchar(255)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
index 6ce4495be0594dabf37d503c73889163699d9e22..6203650a693a626d6004908c3a5fe2d43221030a 100644 (file)
@@ -39,6 +39,7 @@ code = K
 
 [consumer]
 consumer_key = 130
+consumer_secret = 130
 seed = 130
 created = 142
 modified = 384
@@ -348,6 +349,37 @@ created = 142
 tag = K
 notice_id = K
 
+[oauth_application]
+id = 129
+owner = 129
+consumer_key = 130
+name = 130
+description = 2
+icon = 130
+source_url = 2
+organization = 2
+homepage = 2
+callback_url = 130
+type = 17
+access_type = 17
+created = 142
+modified = 384
+
+[oauth_application__keys]
+id = N
+
+[oauth_application_user]
+profile_id = 129
+application_id = 129
+access_type = 17
+token = 2
+created = 142
+modified = 384
+
+[oauth_application_user__keys]
+profile_id = K
+application_id = K
+
 [profile]
 id = 129
 nickname = 130
@@ -484,6 +516,8 @@ tok = 130
 secret = 130
 type = 145
 state = 17
+verifier = 2
+verified_callback = 2
 created = 142
 modified = 384
 
index a9ed66cb4f4d59d73c489f256403e25be0f586cf..17de4fd0d48b0852b197bf1c18f3ded8524a4ac7 100644 (file)
@@ -176,6 +176,7 @@ create table fave (
 
 create table consumer (
     consumer_key varchar(255) primary key comment 'unique identifier, root URL',
+    consumer_secret varchar(255) not null comment 'secret value',
     seed char(32) not null comment 'seed for new tokens by this consumer',
 
     created datetime not null comment 'date this record was created',
@@ -188,6 +189,8 @@ create table token (
     secret char(32) not null comment 'secret value',
     type tinyint not null default 0 comment 'request or access',
     state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used',
+    verifier varchar(255) comment 'verifier string for OAuth 1.0a',
+    verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a',
 
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
@@ -207,6 +210,33 @@ create table nonce (
     constraint primary key (consumer_key, ts, nonce)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
+create table oauth_application (
+    id integer auto_increment primary key comment 'unique identifier',
+    owner integer not null comment 'owner of the application' references profile (id),
+    consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key),
+    name varchar(255) not null comment 'name of the application',
+    description varchar(255) comment 'description of the application',
+    icon varchar(255) not null comment 'application icon',
+    source_url varchar(255) comment 'application homepage - used for source link',
+    organization varchar(255) comment 'name of the organization running the application',
+    homepage varchar(255) comment 'homepage for the organization',
+    callback_url varchar(255) comment 'url to redirect to after authentication',
+    type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop',
+    access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
+    created datetime not null comment 'date this record was created',
+    modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table oauth_application_user (
+    profile_id integer not null comment 'user of the application' references profile (id),
+    application_id integer not null comment 'id of the application' references oauth_application (id),
+    access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
+    token varchar(255) comment 'request or access token',
+    created datetime not null comment 'date this record was created',
+    modified timestamp comment 'date this record was modified',
+    constraint primary key (profile_id, application_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
 /* These are used by JanRain OpenID library */
 
 create table oid_associations (
diff --git a/js/farbtastic/farbtastic.css b/js/farbtastic/farbtastic.css
new file mode 100644 (file)
index 0000000..a88e7b8
--- /dev/null
@@ -0,0 +1,32 @@
+.farbtastic {
+  position: relative;
+}
+.farbtastic * {
+  position: absolute;
+  cursor: crosshair;
+}
+.farbtastic, .farbtastic .wheel {
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .color, .farbtastic .overlay {
+  top: 47px;
+  left: 47px;
+  width: 101px;
+  height: 101px;
+}
+.farbtastic .wheel {
+  background: url(wheel.png) no-repeat;
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .overlay {
+  background: url(mask.png) no-repeat;
+}
+.farbtastic .marker {
+  width: 17px;
+  height: 17px;
+  margin: -8px 0 0 -8px;
+  overflow: hidden; 
+  background: url(marker.png) no-repeat;
+}
index 87e3c99a16dd0c5734356570df7b1dc63394f893..bba59b4486d616b32e754b7912f8142af5b76313 100644 (file)
@@ -1,5 +1,5 @@
-// A shim to implement the W3C Geolocation API Specification using Gears
-if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){
+// A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API
+if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){
 
 // -- BEGIN GEARS_INIT
 (function() {
@@ -23,8 +23,7 @@ if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim )
       }
     } catch (e) {
       // Safari
-      if ((typeof navigator.mimeTypes != 'undefined')
-           && navigator.mimeTypes["application/x-googlegears"]) {
+      if ((typeof navigator.mimeTypes != 'undefined') && navigator.mimeTypes["application/x-googlegears"]) {
         factory = document.createElement("object");
         factory.style.display = "none";
         factory.width = 0;
@@ -64,8 +63,8 @@ var GearsGeoLocation = (function() {
         return function(position) {
             callback(position);
             self.lastPosition = position;
-        }
-    }
+        };
+    };
     
     // -- PUBLIC
     return {
@@ -96,9 +95,123 @@ var GearsGeoLocation = (function() {
     };
 });
 
-// If you have Gears installed use that
-if (window.google && google.gears) {
-    navigator.geolocation = GearsGeoLocation();
-}
+var AjaxGeoLocation = (function() {
+    // -- PRIVATE
+    var loading = false;
+    var loadGoogleLoader = function() {
+        if (!hasGoogleLoader() && !loading) {
+            loading = true;
+            var s = document.createElement('script');
+            s.src = (document.location.protocol == "https:"?"https://":"http://") + 'www.google.com/jsapi?callback=_google_loader_apiLoaded';
+            s.type = "text/javascript";
+            document.getElementsByTagName('body')[0].appendChild(s);
+        }
+    };
+    
+    var queue = [];
+    var addLocationQueue = function(callback) {
+        queue.push(callback);
+    };
+    
+    var runLocationQueue = function() {
+        if (hasGoogleLoader()) {
+            while (queue.length > 0) {
+                var call = queue.pop();
+                call();
+            }
+        }
+    };
+    
+    window['_google_loader_apiLoaded'] = function() {
+        runLocationQueue();
+    };
+    
+    var hasGoogleLoader = function() {
+        return (window['google'] && google['loader']);
+    };
+    
+    var checkGoogleLoader = function(callback) {
+        if (hasGoogleLoader()) { return true; }
+
+        addLocationQueue(callback);
+                
+        loadGoogleLoader();
+        
+        return false;
+    };
+    
+    loadGoogleLoader(); // start to load as soon as possible just in case
+    
+    // -- PUBLIC
+    return {
+        shim: true,
+        
+        type: "ClientLocation",
+        
+        lastPosition: null,
+        
+        getCurrentPosition: function(successCallback, errorCallback, options) {
+            var self = this;
+            if (!checkGoogleLoader(function() {
+                self.getCurrentPosition(successCallback, errorCallback, options);
+            })) { return; }
+            
+            if (google.loader.ClientLocation) {
+                var cl = google.loader.ClientLocation;
+                
+                var position = {
+                    coords: {
+                        latitude: cl.latitude,
+                        longitude: cl.longitude,
+                        altitude: null,
+                        accuracy: 43000, // same as Gears accuracy over wifi?
+                        altitudeAccuracy: null,
+                        heading: null,
+                        speed: null
+                    },
+                    // extra info that is outside of the bounds of the core API
+                    address: {
+                        city: cl.address.city,
+                        country: cl.address.country,
+                        country_code: cl.address.country_code,
+                        region: cl.address.region
+                    },
+                    timestamp: new Date()
+                };
+
+                successCallback(position);
+                
+                this.lastPosition = position;
+            } else if (errorCallback === "function")  {
+                errorCallback({ code: 3, message: "Using the Google ClientLocation API and it is not able to calculate a location."});
+            }
+        },
+        
+        watchPosition: function(successCallback, errorCallback, options) {
+            this.getCurrentPosition(successCallback, errorCallback, options);
+            
+            var self = this;
+            var watchId = setInterval(function() {
+                self.getCurrentPosition(successCallback, errorCallback, options);
+            }, 10000);
+            
+            return watchId;
+        },
+        
+        clearWatch: function(watchId) {
+            clearInterval(watchId);
+        },
+        
+        getPermission: function(siteName, imageUrl, extraMessage) {
+            // for now just say yes :)
+            return true;
+        }
+
+    };
+});
+
+// If you have Gears installed use that, else use Ajax ClientLocation
+navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation();
 
 })();
+}
index 8276f22a1deaae90128bcbac155671affe1d0a6e..e43d1c43c758f48996d5e18e75392d0cdbd94fe2 100644 (file)
@@ -223,7 +223,7 @@ function markupPost(raw, server) {
          },
          changeUserTo : function(el) {
             $.a.user = el.rel;
-            $.s.h.a.innerHTML = el.rev + $.a.headerText;
+            $.s.h.a.appendChild(document.createTextNode(el.rev + $.a.headerText));
             $.s.h.a.href = 'http://' + $.a.server + '/' + el.id;
             $.f.runSearch(); 
          },
index 88016bd6d06c27c30848e88a12d3f55663562972..8d52d859b774bce10992ab3a54fa0d42b5363e58 100644 (file)
@@ -205,8 +205,10 @@ var SN = { // StatusNet
                         cookieValue = JSON.parse(cookieValue);
                         NLat = $('#'+SN.C.S.NoticeLat).val(cookieValue.NLat).val();
                         NLon = $('#'+SN.C.S.NoticeLon).val(cookieValue.NLon).val();
-                        NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val();
-                        NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val();
+                        if ($('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS)) {
+                            NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val();
+                            NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val();
+                        }
                     }
                     if (cookieValue == 'disabled') {
                         NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', false).attr('checked');
@@ -301,8 +303,10 @@ var SN = { // StatusNet
 
                     $('#'+SN.C.S.NoticeLat).val(NLat);
                     $('#'+SN.C.S.NoticeLon).val(NLon);
-                    $('#'+SN.C.S.NoticeLocationNs).val(NLNS);
-                    $('#'+SN.C.S.NoticeLocationId).val(NLID);
+                    if ($('#'+SN.C.S.NoticeLocationNs)) {
+                        $('#'+SN.C.S.NoticeLocationNs).val(NLNS);
+                        $('#'+SN.C.S.NoticeLocationId).val(NLID);
+                    }
                     $('#'+SN.C.S.NoticeDataGeo).attr('checked', NDG);
                 }
             });
@@ -491,7 +495,7 @@ var SN = { // StatusNet
                 $('#'+SN.C.S.NoticeLocationId).val('');
                 $('#'+SN.C.S.NoticeDataGeo).attr('checked', false);
 
-                $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled');
+                $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetDateFromNow(30) });
             }
 
             function getJSONgeocodeURL(geocodeURL, data) {
@@ -509,7 +513,7 @@ var SN = { // StatusNet
                     }
 
                     if (typeof(location.name) == 'undefined') {
-                        NLN_text = position.coords.latitude + ';' + position.coords.longitude;
+                        NLN_text = data.lat + ';' + data.lon;
                     }
                     else {
                         NLN_text = location.name;
@@ -525,15 +529,16 @@ var SN = { // StatusNet
                     $('#'+SN.C.S.NoticeDataGeo).attr('checked', true);
 
                     var cookieValue = {
-                        'NLat': data.lat,
-                        'NLon': data.lon,
-                        'NLNS': lns,
-                        'NLID': lid,
-                        'NLN': NLN_text,
-                        'NLNU': location.url,
-                        'NDG': true
+                        NLat: data.lat,
+                        NLon: data.lon,
+                        NLNS: lns,
+                        NLID: lid,
+                        NLN: NLN_text,
+                        NLNU: location.url,
+                        NDG: true
                     };
-                    $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue));
+
+                    $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetDateFromNow(30) });
                 });
             }
 
@@ -566,9 +571,9 @@ var SN = { // StatusNet
                                         $('#'+SN.C.S.NoticeLon).val(position.coords.longitude);
 
                                         var data = {
-                                            'lat': position.coords.latitude,
-                                            'lon': position.coords.longitude,
-                                            'token': $('#token').val()
+                                            lat: position.coords.latitude,
+                                            lon: position.coords.longitude,
+                                            token: $('#token').val()
                                         };
 
                                         getJSONgeocodeURL(geocodeURL, data);
@@ -593,9 +598,9 @@ var SN = { // StatusNet
                             else {
                                 if (NLat.length > 0 && NLon.length > 0) {
                                     var data = {
-                                        'lat': NLat,
-                                        'lon': NLon,
-                                        'token': $('#token').val()
+                                        lat: NLat,
+                                        lon: NLon,
+                                        token: $('#token').val()
                                     };
 
                                     getJSONgeocodeURL(geocodeURL, data);
@@ -624,8 +629,6 @@ var SN = { // StatusNet
                     else {
                         removeNoticeDataGeo();
                     }
-
-                    $('#'+SN.C.S.NoticeDataText).focus();
                 }).change();
             }
         },
@@ -656,6 +659,13 @@ var SN = { // StatusNet
                 }
                 return false;
             });
+        },
+
+        GetDateFromNow: function(days) {
+            var date = new Date();
+            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+
+            return date;
         }
     },
 
index 171bea17c721b864b80da750dcef9bbdf07f21c6..e2427755852120fa61d1b5b8cb6576b1170dba49 100644 (file)
@@ -199,10 +199,6 @@ class Action extends HTMLOutputter // lawsuit
             if (Event::handle('StartShowStatusNetStyles', array($this)) &&
                 Event::handle('StartShowLaconicaStyles', array($this))) {
                 $this->cssLink('css/display.css',null,'screen, projection, tv');
-                if (common_config('site', 'mobile')) {
-                    // TODO: "handheld" CSS for other mobile devices
-                    $this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit
-                }
                 $this->cssLink('css/print.css','base','print');
                 Event::handle('EndShowStatusNetStyles', array($this));
                 Event::handle('EndShowLaconicaStyles', array($this));
@@ -373,7 +369,11 @@ class Action extends HTMLOutputter // lawsuit
         $this->elementStart('div', array('id' => 'header'));
         $this->showLogo();
         $this->showPrimaryNav();
-        $this->showSiteNotice();
+        if (Event::handle('StartShowSiteNotice', array($this))) {
+            $this->showSiteNotice();
+
+            Event::handle('EndShowSiteNotice', array($this));
+        }
         if (common_logged_in()) {
             $this->showNoticeForm();
         } else {
index 707e4ac21a421ee75260fc4bb950e43c7727c8af..794b14050709dddb7d9503a3cad05b829a783cce 100644 (file)
@@ -53,6 +53,9 @@ if (!defined('STATUSNET')) {
 
 class ApiAction extends Action
 {
+    const READ_ONLY  = 1;
+    const READ_WRITE = 2;
+
     var $format    = null;
     var $user      = null;
     var $auth_user = null;
@@ -62,6 +65,8 @@ class ApiAction extends Action
     var $since_id  = null;
     var $since     = null;
 
+    var $access    = self::READ_ONLY;  // read (default) or read-write
+
     /**
      * Initialization.
      *
index 7102764cbaa753489430f072522db5cd37c3ccb1..37070d212fc5c6f40e309e2a3393a934fcd79295 100644 (file)
@@ -28,7 +28,7 @@
  * @author    Evan Prodromou <evan@status.net>
  * @author    mEDI <medi@milaro.net>
  * @author    Sarven Capadisli <csarven@status.net>
- * @author    Zach Copley <zach@status.net> 
+ * @author    Zach Copley <zach@status.net>
  * @copyright 2009 StatusNet, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://status.net/
@@ -39,6 +39,7 @@ if (!defined('STATUSNET')) {
 }
 
 require_once INSTALLDIR . '/lib/api.php';
+require_once INSTALLDIR . '/lib/apioauth.php';
 
 /**
  * Actions extending this class will require auth
@@ -52,6 +53,9 @@ require_once INSTALLDIR . '/lib/api.php';
 
 class ApiAuthAction extends ApiAction
 {
+    var $access_token;
+    var $oauth_access_type;
+    var $oauth_source;
 
     /**
      * Take arguments for running, and output basic auth header if needed
@@ -67,12 +71,118 @@ class ApiAuthAction extends ApiAction
         parent::prepare($args);
 
         if ($this->requiresAuth()) {
-            $this->checkBasicAuthUser();
+
+            $this->consumer_key = $this->arg('oauth_consumer_key');
+            $this->access_token = $this->arg('oauth_token');
+
+            if (!empty($this->access_token)) {
+                $this->checkOAuthRequest();
+            } else {
+                $this->checkBasicAuthUser();
+                // By default, all basic auth users have read and write access
+
+                $this->access = self::READ_WRITE;
+            }
         }
 
         return true;
     }
 
+    function handle($args)
+    {
+        parent::handle($args);
+    }
+
+    function checkOAuthRequest()
+    {
+        common_debug("We have an OAuth request.");
+
+        $datastore   = new ApiStatusNetOAuthDataStore();
+        $server      = new OAuthServer($datastore);
+        $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+        $server->add_signature_method($hmac_method);
+
+        ApiOauthAction::cleanRequest();
+
+        try {
+
+            $req  = OAuthRequest::from_request();
+            $server->verify_request($req);
+
+            $app = Oauth_application::getByConsumerKey($this->consumer_key);
+
+            if (empty($app)) {
+
+                // this should really not happen
+                common_log(LOG_WARN,
+                           "Couldn't find the OAuth app for consumer key: $this->consumer_key");
+
+                throw new OAuthException('No application for that consumer key.');
+            }
+
+            // set the source attr
+
+            $this->oauth_source = $app->name;
+
+            $appUser = Oauth_application_user::staticGet('token',
+                                                         $this->access_token);
+
+            // XXX: check that app->id and appUser->application_id and consumer all
+            // match?
+
+            if (!empty($appUser)) {
+
+                // read or read-write
+                $this->oauth_access_type = $appUser->access_type;
+
+                // If access_type == 0 we have either a request token
+                // or a bad / revoked access token
+
+                if ($this->oauth_access_type != 0) {
+
+                    // Set the read or read-write access for the api call
+                    $this->access = ($appUser->access_type & Oauth_application::$writeAccess)
+                      ? self::READ_WRITE : self::READ_ONLY;
+
+                    if (Event::handle('StartSetApiUser', array(&$user))) {
+                        $this->auth_user = User::staticGet('id', $appUser->profile_id);
+                        Event::handle('EndSetApiUser', array($user));
+                    }
+
+                    $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " .
+                      "application '%s' (id: %d).";
+
+                    common_log(LOG_INFO, sprintf($msg,
+                                                 $this->auth_user->nickname,
+                                                 $this->auth_user->id,
+                                                 $app->name,
+                                                 $app->id));
+                    return true;
+                } else {
+                    throw new OAuthException('Bad access token.');
+                }
+            } else {
+
+                // also should not happen
+                throw new OAuthException('No user for that token.');
+        }
+
+        } catch (OAuthException $e) {
+            common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
+            common_debug(var_export($req, true));
+            $this->showOAuthError($e->getMessage());
+            exit();
+        }
+    }
+
+    function showOAuthError($msg)
+    {
+        header('HTTP/1.1 401 Unauthorized');
+        header('Content-Type: text/html; charset=utf-8');
+        print $msg . "\n";
+    }
+
     /**
      * Does this API resource require authentication?
      *
@@ -128,6 +238,7 @@ class ApiAuthAction extends ApiAction
                 exit;
             }
         }
+
         return true;
     }
 
diff --git a/lib/apioauth.php b/lib/apioauth.php
new file mode 100644 (file)
index 0000000..4cb8a67
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base action for OAuth API endpoints
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apioauthstore.php';
+
+/**
+ * Base action for API OAuth enpoints.  Clean up the
+ * the request, and possibly some other common things
+ * here.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApiOauthAction extends Action
+{
+    /**
+     * Is this a read-only action?
+     *
+     * @return boolean false
+     */
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle input, produce output
+     *
+     * Switches on request method; either shows the form or handles its input.
+     *
+     * @param array $args $_REQUEST data
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        self::cleanRequest();
+    }
+
+    static function cleanRequest()
+    {
+        // kill evil effects of magical slashing
+
+        if (get_magic_quotes_gpc() == 1) {
+            $_POST = array_map('stripslashes', $_POST);
+            $_GET = array_map('stripslashes', $_GET);
+        }
+
+        // strip out the p param added in index.php
+
+        // XXX: should we strip anything else?  Or alternatively
+        // only allow a known list of params?
+
+        unset($_GET['p']);
+        unset($_POST['p']);
+    }
+
+    function getCallback($url, $params)
+    {
+        foreach ($params as $k => $v) {
+            $url = $this->appendQueryVar($url,
+                                         OAuthUtil::urlencode_rfc3986($k),
+                                         OAuthUtil::urlencode_rfc3986($v));
+        }
+
+        return $url;
+    }
+
+    function appendQueryVar($url, $k, $v) {
+        $url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
+        $url = substr($url, 0, -1);
+        if (strpos($url, '?') === false) {
+            return ($url . '?' . $k . '=' . $v);
+        } else {
+            return ($url . '&' . $k . '=' . $v);
+        }
+    }
+
+}
diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php
new file mode 100644 (file)
index 0000000..32110d0
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR . '/lib/oauthstore.php';
+
+class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
+{
+
+    function lookup_consumer($consumer_key)
+    {
+        $con = Consumer::staticGet('consumer_key', $consumer_key);
+
+        if (!$con) {
+            return null;
+        }
+
+        return new OAuthConsumer($con->consumer_key,
+                                 $con->consumer_secret);
+    }
+
+    function getAppByRequestToken($token_key)
+    {
+        // Look up the full req tokenx
+
+        $req_token = $this->lookup_token(null,
+                                         'request',
+                                         $token_key);
+
+        if (empty($req_token)) {
+            common_debug("couldn't get request token from oauth datastore");
+            return null;
+        }
+
+        // Look up the full Token
+
+        $token = new Token();
+        $token->tok = $req_token->key;
+        $result = $token->find(true);
+
+        if (empty($result)) {
+            common_debug('Couldn\'t find req token in the token table.');
+            return null;
+        }
+
+        // Look up the app
+
+        $app = new Oauth_application();
+        $app->consumer_key = $token->consumer_key;
+        $result = $app->find(true);
+
+        if (!empty($result)) {
+            return $app;
+        } else {
+            common_debug("Couldn't find the app!");
+            return null;
+        }
+    }
+
+    function new_access_token($token, $consumer)
+    {
+        common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
+
+        $rt = new Token();
+        $rt->consumer_key = $consumer->key;
+        $rt->tok = $token->key;
+        $rt->type = 0; // request
+
+        $app = Oauth_application::getByConsumerKey($consumer->key);
+
+        if (empty($app)) {
+            common_debug("empty app!");
+        }
+
+        if ($rt->find(true) && $rt->state == 1) { // authorized
+            common_debug('request token found.', __FILE__);
+
+            // find the associated user of the app
+
+            $appUser = new Oauth_application_user();
+            $appUser->application_id = $app->id;
+            $appUser->token = $rt->tok;
+            $result = $appUser->find(true);
+
+            if (!empty($result)) {
+                common_debug("Oath app user found.");
+            } else {
+                common_debug("Oauth app user not found. app id $app->id token $rt->tok");
+                return null;
+            }
+
+            // go ahead and make the access token
+
+            $at = new Token();
+            $at->consumer_key = $consumer->key;
+            $at->tok = common_good_rand(16);
+            $at->secret = common_good_rand(16);
+            $at->type = 1; // access
+            $at->created = DB_DataObject_Cast::dateTime();
+
+            if (!$at->insert()) {
+                $e = $at->_lastError;
+                common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
+                return null;
+            } else {
+                common_debug('access token "'.$at->tok.'" inserted', __FILE__);
+                // burn the old one
+                $orig_rt = clone($rt);
+                $rt->state = 2; // used
+                if (!$rt->update($orig_rt)) {
+                    return null;
+                }
+                common_debug('request token "'.$rt->tok.'" updated', __FILE__);
+
+                // update the token from req to access for the user
+
+                $orig = clone($appUser);
+                $appUser->token = $at->tok;
+
+                // It's at this point that we change the access type
+                // to whatever the application's access is.  Request
+                // tokens should always have an access type of 0, and
+                // therefore be unuseable for making requests for
+                // protected resources.
+
+                $appUser->access_type = $app->access_type;
+
+                $result = $appUser->update($orig);
+
+                if (empty($result)) {
+                    common_debug('couldn\'t update OAuth app user.');
+                    return null;
+                }
+
+                // Okay, good
+
+                return new OAuthToken($at->tok, $at->secret);
+            }
+
+        } else {
+            return null;
+        }
+    }
+
+}
+
diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php
new file mode 100644 (file)
index 0000000..040d3bf
--- /dev/null
@@ -0,0 +1,338 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for editing an application
+ *
+ * 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  Form
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/form.php';
+
+/**
+ * Form for editing an application
+ *
+ * @category Form
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ */
+
+class ApplicationEditForm extends Form
+{
+    /**
+     * group for user to join
+     */
+
+    var $application = null;
+
+    /**
+     * Constructor
+     *
+     * @param Action     $out   output channel
+     * @param User_group $group group to join
+     */
+
+    function __construct($out=null, $application=null)
+    {
+        parent::__construct($out);
+
+        $this->application = $application;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return string ID of the form
+     */
+
+    function id()
+    {
+        if ($this->application) {
+            return 'form_application_edit-' . $this->application->id;
+        } else {
+            return 'form_application_add';
+        }
+    }
+
+    /**
+     * HTTP method used to submit the form
+     *
+     * For image data we need to send multipart/form-data
+     * so we set that here too
+     *
+     * @return string the method to use for submitting
+     */
+
+    function method()
+    {
+        $this->enctype = 'multipart/form-data';
+        return 'post';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string of the form class
+     */
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        $cur = common_current_user();
+
+        if (!empty($this->application)) {
+            return common_local_url('editapplication',
+                                    array('id' => $this->application->id));
+        } else {
+            return common_local_url('newapplication');
+        }
+    }
+
+    /**
+     * Name of the form
+     *
+     * @return void
+     */
+
+    function formLegend()
+    {
+        $this->out->element('legend', null, _('Edit application'));
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        if ($this->application) {
+            $id                = $this->application->id;
+            $icon              = $this->application->icon;
+            $name              = $this->application->name;
+            $description       = $this->application->description;
+            $source_url        = $this->application->source_url;
+            $organization      = $this->application->organization;
+            $homepage          = $this->application->homepage;
+            $callback_url      = $this->application->callback_url;
+            $this->type        = $this->application->type;
+            $this->access_type = $this->application->access_type;
+        } else {
+            $id                = '';
+            $icon              = '';
+            $name              = '';
+            $description       = '';
+            $source_url        = '';
+            $organization      = '';
+            $homepage          = '';
+            $callback_url      = '';
+            $this->type        = '';
+            $this->access_type = '';
+        }
+
+        $this->out->hidden('token', common_session_token());
+
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->out->elementStart('li', array('id' => 'application_icon'));
+
+        if (!empty($icon)) {
+            $this->out->element('img', array('src' => $icon));
+        }
+
+        $this->out->element('label', array('for' => 'app_icon'),
+                            _('Icon'));
+        $this->out->element('input', array('name' => 'app_icon',
+                                           'type' => 'file',
+                                           'id' => 'app_icon'));
+        $this->out->element('p', 'form_guide', _('Icon for this application'));
+        $this->out->element('input', array('name' => 'MAX_FILE_SIZE',
+                                           'type' => 'hidden',
+                                           'id' => 'MAX_FILE_SIZE',
+                                           'value' => ImageFile::maxFileSizeInt()));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+
+        $this->out->hidden('application_id', $id);
+
+        $this->out->input('name', _('Name'),
+                          ($this->out->arg('name')) ? $this->out->arg('name') : $name);
+
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+
+        $maxDesc = Oauth_application::maxDesc();
+        if ($maxDesc > 0) {
+            $descInstr = sprintf(_('Describe your application in %d chars'),
+                                 $maxDesc);
+        } else {
+            $descInstr = _('Describe your application');
+        }
+        $this->out->textarea('description', _('Description'),
+                        ($this->out->arg('description')) ? $this->out->arg('description') : $description,
+                             $descInstr);
+
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+        $this->out->input('source_url', _('Source URL'),
+                          ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url,
+                          _('URL of the homepage of this application'));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+        $this->out->input('organization', _('Organization'),
+                          ($this->out->arg('organization')) ? $this->out->arg('organization') : $organization,
+                          _('Organization responsible for this application'));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+        $this->out->input('homepage', _('Homepage'),
+                          ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
+                          _('URL for the homepage of the organization'));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li');
+        $this->out->input('callback_url', ('Callback URL'),
+                          ($this->out->arg('callback_url')) ? $this->out->arg('callback_url') : $callback_url,
+                          _('URL to redirect to after authentication'));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li', array('id' => 'application_types'));
+
+        $attrs = array('name' => 'app_type',
+                       'type' => 'radio',
+                       'id' => 'app_type-browser',
+                       'class' => 'radio',
+                       'value' => Oauth_application::$browser);
+
+        // Default to Browser
+
+        if ($this->application->type == Oauth_application::$browser
+            || empty($this->application->type)) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'app_type-browser',
+                                           'class' => 'radio'),
+                            _('Browser'));
+
+        $attrs = array('name' => 'app_type',
+                       'type' => 'radio',
+                       'id' => 'app_type-dekstop',
+                       'class' => 'radio',
+                       'value' => Oauth_application::$desktop);
+
+        if ($this->application->type == Oauth_application::$desktop) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'app_type-desktop',
+                                           'class' => 'radio'),
+                            _('Desktop'));
+        $this->out->element('p', 'form_guide', _('Type of application, browser or desktop'));
+        $this->out->elementEnd('li');
+
+        $this->out->elementStart('li', array('id' => 'default_access_types'));
+
+        $attrs = array('name' => 'default_access_type',
+                       'type' => 'radio',
+                       'id' => 'default_access_type-r',
+                       'class' => 'radio',
+                       'value' => 'r');
+
+        // default to read-only access
+
+        if ($this->application->access_type & Oauth_application::$readAccess
+            || empty($this->application->access_type)) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'default_access_type-ro',
+                                           'class' => 'radio'),
+                            _('Read-only'));
+
+        $attrs = array('name' => 'default_access_type',
+                       'type' => 'radio',
+                       'id' => 'default_access_type-rw',
+                       'class' => 'radio',
+                       'value' => 'rw');
+
+        if ($this->application->access_type & Oauth_application::$readAccess
+            && $this->application->access_type & Oauth_application::$writeAccess
+            ) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'default_access_type-rw',
+                                           'class' => 'radio'),
+                            _('Read-write'));
+        $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write'));
+
+        $this->out->elementEnd('li');
+
+        $this->out->elementEnd('ul');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary',
+                           'cancel', _('Cancel'));
+        $this->out->submit('save', _('Save'), 'submit form_action-secondary',
+                           'save', _('Save'));
+    }
+}
diff --git a/lib/applicationlist.php b/lib/applicationlist.php
new file mode 100644 (file)
index 0000000..3abb1f8
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Widget to show a list of OAuth applications
+ *
+ * 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  Application
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/widget.php';
+
+define('APPS_PER_PAGE', 20);
+
+/**
+ * Widget to show a list of OAuth applications
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApplicationList extends Widget
+{
+    /** Current application, application query */
+    var $application = null;
+
+    /** Owner of this list */
+    var $owner = null;
+
+    /** Action object using us. */
+    var $action = null;
+
+    function __construct($application, $owner=null, $action=null, $connections = false)
+    {
+        parent::__construct($action);
+
+        $this->application = $application;
+        $this->owner       = $owner;
+        $this->action      = $action;
+        $this->connections = $connections;
+    }
+
+    function show()
+    {
+        $this->out->elementStart('ul', 'applications');
+
+        $cnt = 0;
+
+        while ($this->application->fetch()) {
+            $cnt++;
+            if($cnt > APPS_PER_PAGE) {
+                break;
+            }
+            $this->showapplication();
+        }
+
+        $this->out->elementEnd('ul');
+
+        return $cnt;
+    }
+
+    function showApplication()
+    {
+
+        $user = common_current_user();
+
+        $this->out->elementStart('li', array('class' => 'application',
+                                             'id' => 'oauthclient-' . $this->application->id));
+
+        $this->out->elementStart('span', 'vcard author');
+        if (!$this->connections) {
+            $this->out->elementStart('a',
+                                     array('href' => common_local_url('showapplication',
+                                                                      array('id' => $this->application->id)),
+                                                                      'class' => 'url'));
+
+        } else {
+            $this->out->elementStart('a', array('href' =>  $this->application->source_url,
+                                                'class' => 'url'));
+        }
+
+        if (!empty($this->application->icon)) {
+            $this->out->element('img', array('src' => $this->application->icon,
+                                             'class' => 'photo avatar'));
+        }
+
+        $this->out->element('span', 'fn', $this->application->name);
+        $this->out->elementEnd('a');
+        $this->out->elementEnd('span');
+
+        $this->out->raw(' by ');
+
+        $this->out->element('a', array('href' => $this->application->homepage,
+                                       'class' => 'url'),
+                                 $this->application->organization);
+
+        $this->out->element('p', 'note', $this->application->description);
+        $this->out->elementEnd('li');
+
+        if ($this->connections) {
+            $appUser = Oauth_application_user::getByKeys($this->owner, $this->application);
+
+            if (empty($appUser)) {
+                common_debug("empty appUser!");
+            }
+
+            $this->out->elementStart('li');
+
+            $access = ($this->application->access_type & Oauth_application::$writeAccess)
+              ? 'read-write' : 'read-only';
+
+            $txt = 'Approved ' . common_date_string($appUser->modified) .
+              " - $access access.";
+
+            $this->out->raw($txt);
+            $this->out->elementEnd('li');
+
+            $this->out->elementStart('li', 'entity_revoke');
+            $this->out->elementStart('form', array('id' => 'form_revoke_app',
+                                                   'class' => 'form_revoke_app',
+                                                   'method' => 'POST',
+                                                   'action' =>
+                                                   common_local_url('oauthconnectionssettings')));
+            $this->out->elementStart('fieldset');
+            $this->out->hidden('id', $this->application->id);
+            $this->out->hidden('token', common_session_token());
+            $this->out->submit('revoke', _('Revoke'));
+            $this->out->elementEnd('fieldset');
+            $this->out->elementEnd('form');
+            $this->out->elementEnd('li');
+        }
+    }
+
+    /* Override this in subclasses. */
+
+    function showOwnerControls()
+    {
+        return;
+    }
+
+}
index e5fb8727ba1dc4e89ead2563215cea53fc77319c..b9c14799e03bb386eaeae1d95d2fe7e8afba2330 100644 (file)
@@ -115,6 +115,11 @@ class ConnectSettingsNav extends Widget
                   array(_('SMS'),
                         _('Updates by SMS'));
             }
+            
+            $menu['oauthconnectionssettings'] = array(
+                _('Connections'),
+                _('Authorized connected applications')
+            );
 
             foreach ($menu as $menuaction => $menudesc) {
                 $this->action->menuItem(common_local_url($menuaction),
@@ -131,4 +136,3 @@ class ConnectSettingsNav extends Widget
 
 }
 
-
index 57199b356fa6034c5612263ef69006a7d89d1829..b6ee72279d8d1c9990cb9c794589d25b5c40bf63 100644 (file)
@@ -69,7 +69,9 @@ $default =
               'db_driver' => 'DB', # XXX: JanRain libs only work with DB
               'quote_identifiers' => false,
               'type' => 'mysql',
-              'schemacheck' => 'runtime'), // 'runtime' or 'script'
+              'schemacheck' => 'runtime', // 'runtime' or 'script'
+              'log_queries' => false, // true to log all DB queries
+              'log_slow_queries' => 0), // if set, log queries taking over N seconds
         'syslog' =>
         array('appname' => 'statusnet', # for syslog
               'priority' => 'debug', # XXX: currently ignored
@@ -209,6 +211,8 @@ $default =
               'uploads' => true,
               'filecommand' => '/usr/bin/file',
               ),
+        'application' =>
+        array('desclimit' => null),
         'group' =>
         array('maxaliases' => 3,
               'desclimit' => null),
index b70ba0dfcaab6eba7e7eb913baed85f5e6abe8d0..8e44c03a9272942d16285d99d27e426894dd0401 100644 (file)
@@ -314,7 +314,7 @@ class DesignSettingsAction extends AccountSettingsAction
     function showStylesheets()
     {
         parent::showStylesheets();
-        $this->cssLink('css/farbtastic.css','base','screen, projection, tv');
+        $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv');
     }
 
     /**
index e5cf8239e9cb1ee49dd5681032995b20a8eaa412..1bc8de1f787178598ac0cfef3f238c895f546fd7 100644 (file)
@@ -225,7 +225,6 @@ abstract class QueueManager extends IoManager
             // XMPP output handlers...
             $this->connect('jabber', 'JabberQueueHandler');
             $this->connect('public', 'PublicQueueHandler');
-
             // @fixme this should get an actual queue
             //$this->connect('confirm', 'XmppConfirmHandler');
 
index 6b87ed27f61bbabe099a19380ff3c840ae8143b2..42bff277889165ec4a3654c10e43de1198a99291 100644 (file)
@@ -140,8 +140,8 @@ class Router
 
             // settings
 
-            foreach (array('profile', 'avatar', 'password', 'im',
-                           'email', 'sms', 'userdesign', 'other') as $s) {
+            foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
+                           'oauthapps', 'email', 'sms', 'userdesign', 'other') as $s) {
                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
             }
 
@@ -641,6 +641,27 @@ class Router
                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
             }
 
+            $m->connect('settings/oauthapps/show/:id',
+                array('action' => 'showapplication'),
+                array('id' => '[0-9]+')
+            );
+            $m->connect('settings/oauthapps/new',
+                array('action' => 'newapplication')
+            );
+            $m->connect('settings/oauthapps/edit/:id',
+                array('action' => 'editapplication'),
+                array('id' => '[0-9]+')
+            );
+
+            $m->connect('api/oauth/request_token',
+                        array('action' => 'apioauthrequesttoken'));
+
+            $m->connect('api/oauth/access_token',
+                        array('action' => 'apioauthaccesstoken'));
+
+            $m->connect('api/oauth/authorize',
+                        array('action' => 'apioauthauthorize'));
+
             foreach (array('subscriptions', 'subscribers') as $a) {
                 $m->connect(':nickname/'.$a.'/:tag',
                             array('action' => $a),
index 14d2500e8fdc4859d880b46f2e24204ed3291355..5c913836dccd7db6f6375e88bf24626ee596c8d2 100644 (file)
@@ -356,8 +356,6 @@ class MobileProfilePlugin extends WAP20Plugin
 
         $contentLimit = Notice::maxContent();
 
-        $form->out->inlineScript('maxLength = ' . $contentLimit . ';');
-
         if ($contentLimit > 0) {
             $form->out->element('div', array('id' => 'notice_text-count'),
                                 $contentLimit);
@@ -416,7 +414,15 @@ class MobileProfilePlugin extends WAP20Plugin
 
         return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
     }
-}
-
 
-?>
+    function onPluginVersion(&$versions)
+    {
+        $versions[] = array('name' => 'MobileProfile',
+                            'version' => STATUSNET_VERSION,
+                            'author' => 'Sarven Capadisli',
+                            'homepage' => 'http://status.net/wiki/Plugin:MobileProfile',
+                            'rawdescription' =>
+                            _m('XHTML MobileProfile output for supporting user agents.'));
+        return true;
+    }
+}
index 3eefc0c8e059b7191a89f4380251cda59c433233..04fa5fb0021054e871bfd81673b2e15dd0a580eb 100644 (file)
@@ -176,13 +176,25 @@ margin-bottom:0;
 .profile {
 padding-top:4px;
 padding-bottom:4px;
+min-height:65px;
 }
-.notice div.entry-content {
+#content .notice .entry-title {
+float:left;
+width:100%;
 margin-left:0;
-width:62.5%;
+}
+#content .notice .author .photo {
+position:static;
+float:left;
+}
+#content .notice div.entry-content {
+margin-left:0;
+width:75%;
+max-width:100%;
+min-width:0;
 }
 .notice-options {
-width:34%;
+width:43px;
 margin-right:1%;
 }
 
@@ -190,12 +202,36 @@ margin-right:1%;
 width:16px;
 height:16px;
 }
-.notice-options a,
-.notice-options input {
+.notice-options form.processing {
+background-image:none;
+}
+#wrap .notice-options form.processing input.submit {
+background-position:0 47%;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
 box-shadow:none;
 -moz-box-shadow:none;
 -webkit-box-shadow:none;
 }
+.notice .notice-options a,
+.notice .notice-options form {
+margin:-4px 0 0 0;
+}
+.notice .notice-options .form_repeat,
+.notice .notice-options .notice_delete {
+margin-top:11px;
+}
+.notice .notice-options .form_favor,
+.notice .notice-options .form_disfavor,
+.notice .notice-options .form_repeat {
+margin-right:11px;
+}
+
+.notice .notice-options .notice_delete {
+float:left;
+}
 
 .entity_profile {
 width:auto;
index c59fcca8903218c45cc03910e443d3ae3bb28d58..14d1608d3c8b25bf8311bf82dab0dd94d7e0a02d 100644 (file)
@@ -46,8 +46,9 @@ class PoweredByStatusNetPlugin extends Plugin
     function onEndAddressData($action)
     {
         $action->elementStart('span', 'poweredby');
-        $action->text(_('powered by'));
-        $action->element('a', array('href' => 'http://status.net/'), 'StatusNet');
+        $action->raw(sprintf(_m('powered by %s'),
+                     sprintf('<a href="http://status.net/">%s</a>',
+                             _m('StatusNet'))));
         $action->elementEnd('span');
 
         return true;
diff --git a/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po
new file mode 100644 (file)
index 0000000..bd39124
--- /dev/null
@@ -0,0 +1,32 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-22 15:03-0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: PoweredByStatusNetPlugin.php:49
+#, php-format
+msgid "powered by %s"
+msgstr ""
+
+#: PoweredByStatusNetPlugin.php:51
+msgid "StatusNet"
+msgstr ""
+
+#: PoweredByStatusNetPlugin.php:64
+msgid ""
+"Outputs powered by <a href=\"http://status.net/\">StatusNet</a> after site "
+"name."
+msgstr ""
index 8286cd548969648e43f552ea5af1463ffe86f7b6..a880dc8666f5bc2b2f912917bcd9d66d90f72b2e 100644 (file)
@@ -79,6 +79,21 @@ class PubSubHubBubPlugin extends Plugin
         parent::__construct();
     }
 
+    /**
+     * Check if plugin should be active; may be mass-enabled.
+     * @return boolean
+     */
+
+    function enabled()
+    {
+        if (common_config('site', 'private')) {
+            // PuSH relies on public feeds
+            return false;
+        }
+        // @fixme check for being on a private network?
+        return true;
+    }
+
     /**
      * Hooks the StartApiAtom event
      *
@@ -92,8 +107,9 @@ class PubSubHubBubPlugin extends Plugin
 
     function onStartApiAtom($action)
     {
-        $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null);
-
+        if ($this->enabled()) {
+            $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null);
+        }
         return true;
     }
 
@@ -110,9 +126,11 @@ class PubSubHubBubPlugin extends Plugin
 
     function onStartApiRss($action)
     {
-        $action->element('atom:link', array('rel' => 'hub',
-                                            'href' => $this->hub),
-                         null);
+        if ($this->enabled()) {
+            $action->element('atom:link', array('rel' => 'hub',
+                                                'href' => $this->hub),
+                             null);
+        }
         return true;
     }
 
@@ -130,6 +148,9 @@ class PubSubHubBubPlugin extends Plugin
 
     function onHandleQueuedNotice($notice)
     {
+        if (!$this->enabled()) {
+            return false;
+        }
         $publisher = new Publisher($this->hub);
 
         $feeds = array();
@@ -211,13 +232,20 @@ class PubSubHubBubPlugin extends Plugin
                                                   'format' => 'atom'));
             }
         }
-
-        foreach (array_unique($feeds) as $feed) {
-            if (!$publisher->publish_update($feed)) {
-                common_log_line(LOG_WARNING,
-                                $feed.' was not published to hub at '.
-                                $this->hub.':'.$publisher->last_response());
-            }
+        $feeds = array_unique($feeds);
+
+        ob_start();
+        $ok = $publisher->publish_update($feeds);
+        $push_last_response = ob_get_clean();
+
+        if (!$ok) {
+            common_log(LOG_WARNING,
+                       'Failure publishing ' . count($feeds) . ' feeds to hub at '.
+                       $this->hub.': '.$push_last_response);
+        } else {
+            common_log(LOG_INFO,
+                       'Published ' . count($feeds) . ' feeds to hub at '.
+                       $this->hub.': '.$push_last_response);
         }
 
         return true;
@@ -236,16 +264,21 @@ class PubSubHubBubPlugin extends Plugin
 
     function onPluginVersion(&$versions)
     {
+        $about = _m('The PubSubHubBub plugin pushes RSS/Atom updates '.
+                    'to a <a href = "'.
+                    'http://pubsubhubbub.googlecode.com/'.
+                    '">PubSubHubBub</a> hub.');
+        if (!$this->enabled()) {
+            $about = '<span class="disabled" style="color:gray">' . $about . '</span> ' .
+                     _m('(inactive on private site)');
+        }
         $versions[] = array('name' => 'PubSubHubBub',
                             'version' => STATUSNET_VERSION,
                             'author' => 'Craig Andrews',
                             'homepage' =>
                             'http://status.net/wiki/Plugin:PubSubHubBub',
                             'rawdescription' =>
-                            _m('The PubSubHubBub plugin pushes RSS/Atom updates '.
-                               'to a <a href = "'.
-                               'http://pubsubhubbub.googlecode.com/'.
-                               '">PubSubHubBub</a> hub.'));
+                            $about);
 
         return true;
     }
index 8b62a3a96783d7a7442326f33007d4f859e8d4f3..4d207c261b5a7da9f52799107dd04805e0240520 100755 (executable)
@@ -45,10 +45,12 @@ function read_input_line($prompt)
     if (CONSOLE_INTERACTIVE) {
         if (CONSOLE_READLINE) {
             $line = readline($prompt);
-            readline_add_history($line);
-            if (defined('CONSOLE_HISTORY')) {
-                // Save often; it's easy to hit fatal errors.
-                readline_write_history(CONSOLE_HISTORY);
+            if (trim($line) != '') {
+                readline_add_history($line);
+                if (defined('CONSOLE_HISTORY')) {
+                    // Save often; it's easy to hit fatal errors.
+                    readline_write_history(CONSOLE_HISTORY);
+                }
             }
             return $line;
         } else {
index a9cfda6d72e8f927ee513fe8572a26ccde00c96c..bedd14b1a3fbce867534e2c3a0eeb8e4857ce52c 100755 (executable)
@@ -21,7 +21,7 @@
 define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 
 $shortoptions = 'fi:at:';
-$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only');
+$longoptions = array('id=', 'foreground', 'all', 'threads=');
 
 /**
  * Attempts to get a count of the processors available on the current system
@@ -163,13 +163,6 @@ if (!$threads) {
 $daemonize = !(have_option('f') || have_option('--foreground'));
 $all = have_option('a') || have_option('--all');
 
-if (have_option('--skip-xmpp')) {
-    define('XMPP_EMERGENCY_FLAG', true);
-}
-if (have_option('--xmpp-only')) {
-    define('XMPP_ONLY_FLAG', true);
-}
-
 $daemon = new QueueDaemon($id, $daemonize, $threads, $all);
 $daemon->runOnce();
 
diff --git a/tests/oauth/README b/tests/oauth/README
new file mode 100644 (file)
index 0000000..dd76feb
--- /dev/null
@@ -0,0 +1,22 @@
+Some very rough test scripts for hitting up the OAuth endpoints.
+
+Note: this works best if you register an OAuth application, leaving
+the callback URL blank.
+
+Put your instance info and consumer key and secret in oauth.ini
+
+Example usage:
+--------------
+
+php getrequesttoken.php
+
+Gets a request token, token secret and a url to authorize it.  Once
+you authorize the request token you can exchange it for an access token...
+
+php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657
+
+Once you have your access token, go ahead and try a protected API
+resource:
+
+php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8
+
diff --git a/tests/oauth/exchangetokens.php b/tests/oauth/exchangetokens.php
new file mode 100755 (executable)
index 0000000..2394826
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+require_once INSTALLDIR . '/extlib/OAuth.php';
+
+$ini = parse_ini_file("oauth.ini");
+
+$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
+
+$at_endpoint = $ini['apiroot'] . $ini['access_token_url'];
+
+$shortoptions = 't:s:';
+$longoptions = array('oauth_token=', 'token_secret=');
+
+$helptext = <<<END_OF_ETOKENS_HELP
+  exchangetokens.php [options]
+  Exchange an authorized OAuth request token for an access token
+
+    -t --oauth_token       authorized request token
+    -s --token_secret      authorized request token secret
+
+END_OF_ETOKENS_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+
+$token        = null;
+$token_secret = null;
+
+if (have_option('t', 'oauth_token')) {
+    $token = get_option_value('oauth_token');
+}
+
+if (have_option('s', 'token_secret')) {
+    $token_secret = get_option_value('s', 'token_secret');
+}
+
+if (empty($token)) {
+    print "Please specify a request token.\n";
+    exit(1);
+}
+
+if (empty($token_secret)) {
+    print "Please specify a request token secret.\n";
+    exit(1);
+}
+
+$rt = new OAuthToken($token, $token_secret);
+common_debug("Exchange request token = " . var_export($rt, true));
+
+$parsed = parse_url($at_endpoint);
+$params = array();
+parse_str($parsed['query'], $params);
+
+$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+$req_req = OAuthRequest::from_consumer_and_token($test_consumer, $rt, "GET", $at_endpoint, $params);
+$req_req->sign_request($hmac_method, $test_consumer, $rt);
+
+$r = httpRequest($req_req->to_url());
+
+common_debug("Exchange request token = " . var_export($rt, true));
+common_debug("Exchange tokens URL: " . $req_req->to_url());
+
+$body = $r->getBody();
+
+$token_stuff = array();
+parse_str($body, $token_stuff);
+
+print 'Access token        : ' . $token_stuff['oauth_token'] . "\n";
+print 'Access token secret : ' . $token_stuff['oauth_token_secret'] . "\n";
+
+function httpRequest($url)
+{
+    $request = HTTPClient::start();
+
+    $request->setConfig(array(
+                             'follow_redirects' => true,
+                             'connect_timeout' => 120,
+                             'timeout' => 120,
+                             'ssl_verify_peer' => false,
+                             'ssl_verify_host' => false
+                             ));
+
+    return $request->get($url);
+}
+
diff --git a/tests/oauth/getrequesttoken.php b/tests/oauth/getrequesttoken.php
new file mode 100755 (executable)
index 0000000..fc546a0
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/extlib/OAuth.php';
+
+$ini = parse_ini_file("oauth.ini");
+
+$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
+
+$rt_endpoint = $ini['apiroot'] . $ini['request_token_url'];
+
+$parsed = parse_url($rt_endpoint);
+$params = array();
+
+parse_str($parsed['query'], $params);
+
+$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $rt_endpoint, $params);
+$req_req->sign_request($hmac_method, $test_consumer, NULL);
+
+$r = httpRequest($req_req->to_url());
+
+$body = $r->getBody();
+
+$token_stuff = array();
+parse_str($body, $token_stuff);
+
+$authurl = $ini['apiroot'] . $ini['authorize_url'] . '?oauth_token=' . $token_stuff['oauth_token'];
+
+print 'Request token        : ' . $token_stuff['oauth_token'] . "\n";
+print 'Request token secret : ' . $token_stuff['oauth_token_secret'] . "\n";
+print "Authorize URL        : $authurl\n";
+
+//var_dump($req_req);
+
+function httpRequest($url)
+{
+    $request = HTTPClient::start();
+    
+    $request->setConfig(array(
+                             'follow_redirects' => true,
+                             'connect_timeout' => 120,
+                             'timeout' => 120,
+                             'ssl_verify_peer' => false,
+                             'ssl_verify_host' => false
+                             ));
+    
+    return $request->get($url);
+}
+
diff --git a/tests/oauth/oauth.ini b/tests/oauth/oauth.ini
new file mode 100644 (file)
index 0000000..16b747f
--- /dev/null
@@ -0,0 +1,10 @@
+; Setup OAuth info here
+apiroot       = "http://YOURSTATUSNET/api"
+
+request_token_url = "/oauth/request_token"
+authorize_url     = "/oauth/authorize"
+access_token_url  = "/oauth/access_token"
+
+consumer_key  = "b748968e9bea81a53f3a3c15aa0c686f"
+consumer_secret = "5434e18cce05d9e53cdd48029a62fa41"
+
diff --git a/tests/oauth/verifycreds.php b/tests/oauth/verifycreds.php
new file mode 100755 (executable)
index 0000000..873bdb8
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+require_once INSTALLDIR . '/extlib/OAuth.php';
+
+$shortoptions = 'o:s:';
+$longoptions = array('oauth_token=', 'token_secret=');
+
+$helptext = <<<END_OF_VERIFY_HELP
+  verifycreds.php [options]
+  Use an access token to verify credentials thru the api
+
+    -o --oauth_token       access token
+    -s --token_secret      access token secret
+
+END_OF_VERIFY_HELP;
+
+$token        = null;
+$token_secret = null;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+
+if (have_option('o', 'oauth_token')) {
+    $token = get_option_value('oauth_token');
+}
+
+if (have_option('s', 'token_secret')) {
+    $token_secret = get_option_value('s', 'token_secret');
+}
+
+if (empty($token)) {
+    print "Please specify an access token.\n";
+    exit(1);
+}
+
+if (empty($token_secret)) {
+    print "Please specify an access token secret.\n";
+    exit(1);
+}
+
+$ini = parse_ini_file("oauth.ini");
+
+$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
+
+$endpoint = $ini['apiroot'] . '/account/verify_credentials.xml';
+
+print "$endpoint\n";
+
+$at = new OAuthToken($token, $token_secret);
+
+$parsed = parse_url($endpoint);
+$params = array();
+parse_str($parsed['query'], $params);
+
+$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+$req_req = OAuthRequest::from_consumer_and_token($test_consumer, $at, "GET", $endpoint, $params);
+$req_req->sign_request($hmac_method, $test_consumer, $at);
+
+$r = httpRequest($req_req->to_url());
+
+$body = $r->getBody();
+
+print "$body\n";
+
+//print $req_req->to_url() . "\n\n";
+
+function httpRequest($url)
+{
+    $request = HTTPClient::start();
+
+    $request->setConfig(array(
+                             'follow_redirects' => true,
+                             'connect_timeout' => 120,
+                             'timeout' => 120,
+                             'ssl_verify_peer' => false,
+                             'ssl_verify_host' => false
+                             ));
+
+    return $request->get($url);
+}
+
index 2e4c88dfa32e11c411a512d6c9fe13b07006c3da..84e9426c77f3dd1e046e9db8963316d32386c41d 100644 (file)
@@ -73,7 +73,7 @@ input.checkbox,
 input.radio {
 position:relative;
 top:2px;
-left:0;
+left:auto;
 border:0;
 }
 
@@ -177,7 +177,8 @@ font-weight:bold;
 #form_password_recover legend,
 #form_password_change legend,
 .form_entity_block legend,
-#form_filter_bytag legend {
+#form_filter_bytag legend,
+#apioauthauthorize_allowdeny {
 display:none;
 }
 
@@ -567,7 +568,8 @@ float:right;
 font-size:0.8em;
 }
 
-.form_notice #notice_data-geo_wrap label {
+.form_notice #notice_data-geo_wrap label,
+.form_notice #notice_data-geo_wrap input {
 position:absolute;
 top:25px;
 right:4px;
@@ -578,7 +580,7 @@ height:16px;
 display:block;
 }
 .form_notice #notice_data-geo_wrap input {
-display:none;
+visibility:hidden;
 }
 .form_notice #notice_data-geo_wrap label {
 font-weight:normal;
@@ -894,9 +896,63 @@ font-weight:normal;
 margin-right:11px;
 }
 
+/*applications*/
+.applications {
+margin-bottom:18px;
+float:left;
+width:100%;
+}
+.applications li {
+list-style-type:none;
+}
+.application img,
+#showapplication .entity_profile img,
+.form_data #application_icon img,
+#apioauthauthorize .form_data img {
+max-width:96px;
+max-height:96px;
+}
+#apioauthauthorize .form_data img {
+margin-right:18px;
+float:left;
+}
+#showapplication .entity_profile {
+width:68%;
+}
+#showapplication .entity_profile .entity_fn {
+margin-left:0;
+}
+#showapplication .entity_profile .entity_fn .fn:before,
+#showapplication .entity_profile .entity_fn .fn:after {
+content:'';
+}
+#showapplication .entity_data {
+clear:both;
+margin-bottom:18px;
+}
+#showapplication .entity_data h2 {
+display:none;
+}
+#showapplication .entity_data dl {
+margin-bottom:18px;
+}
+#showapplication .entity_data dt {
+font-weight:bold;
+}
+#showapplication .entity_data dd {
+margin-left:1.795%;
+font-family:monospace;
+font-size:1.3em;
+}
+.form_data #application_types label.radio,
+.form_data #default_access_types label.radio {
+width:14.5%;
+}
+
 /* NOTICE */
 .notice,
-.profile {
+.profile,
+.application {
 position:relative;
 padding-top:11px;
 padding-bottom:11px;
@@ -956,6 +1012,16 @@ float:left;
 #shownotice .vcard .photo {
 margin-bottom:4px;
 }
+#content .notice .author .photo {
+position:absolute;
+top:11px;
+left:0;
+float:none;
+}
+#content .notice .entry-title {
+margin-left:59px;
+}
+
 .vcard .url {
 text-decoration:none;
 }
@@ -964,13 +1030,19 @@ text-decoration:underline;
 }
 
 .notice .entry-title {
-float:left;
-width:100%;
 overflow:hidden;
 }
 .notice .entry-title.ov {
 overflow:visible;
 }
+#showstream .notice .entry-title,
+#showstream .notice div.entry-content {
+margin-left:0;
+}
+#shownotice .notice .entry-title,
+#shownotice .notice div.entry-content {
+margin-left:110px;
+}
 #shownotice .notice .entry-title {
 font-size:2.2em;
 }
@@ -1000,7 +1072,6 @@ max-width:70%;
 }
 #showstream .notice div.entry-content,
 #shownotice .notice div.entry-content {
-margin-left:0;
 max-width:79%;
 }
 
@@ -1064,6 +1135,7 @@ position:relative;
 font-size:0.95em;
 width:113px;
 float:right;
+margin-top:3px;
 margin-right:4px;
 }
 
diff --git a/theme/base/css/farbtastic.css b/theme/base/css/farbtastic.css
deleted file mode 100644 (file)
index 7efcc73..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-.farbtastic {
-  position: relative;
-}
-.farbtastic * {
-  position: absolute;
-  cursor: crosshair;
-}
-.farbtastic, .farbtastic .wheel {
-  width: 195px;
-  height: 195px;
-}
-.farbtastic .color, .farbtastic .overlay {
-  top: 47px;
-  left: 47px;
-  width: 101px;
-  height: 101px;
-}
-.farbtastic .wheel {
-  background: url(../../../js/farbtastic/wheel.png) no-repeat;
-  width: 195px;
-  height: 195px;
-}
-.farbtastic .overlay {
-  background: url(../../../js/farbtastic/mask.png) no-repeat;
-}
-.farbtastic .marker {
-  width: 17px;
-  height: 17px;
-  margin: -8px 0 0 -8px;
-  overflow: hidden; 
-  background: url(../../../js/farbtastic/marker.png) no-repeat;
-}
diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css
deleted file mode 100644 (file)
index f6c53ea..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-/** theme: base
- *
- * @package   StatusNet
- * @author    Meitar Moscovitz <meitar@maymay.net>
- * @author    Sarven Capadisli <csarven@status.net>
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-body {
-font-size:2.5em;
-}
-
-#wrap {
-width:95%;
-}
-
-#header,
-#header address,
-#anon_notice,
-#site_nav_local_views .nav,
-#form_notice,
-#form_notice .form_data li,
-#core,
-#content_inner,
-#notices_primary,
-.notice,
-.notice .entry-title,
-.notice div.entry-content,
-.notice-options,
-.notice .notice-options a,
-.pagination,
-.pagination .nav,
-.aside .section {
-float:none;
-}
-
-.notice-options .notice_reply,
-.notice-options .notice_delete,
-.notice-options .form_favor,
-.notice-options .form_disfavor {
-position:static;
-}
-
-#form_notice,
-#anon_notice,
-#footer,
-#form_notice .form_actions input.submit {
-width:auto;
-}
-
-.form_settings label {
-width:25%;
-}
-.form_settings .form_data p.form_guide {
-margin-left:26%;
-}
-
-#site_nav_global_primary {
-width:75%;
-}
-
-.entity_profile {
-width:65%;
-}
-.entity_actions {
-margin-left:0;
-}
-
-#form_notice,
-#anon_notice {
-clear:both;
-}
-
-#content,
-#aside_primary {
-width:96%;
-padding-left:2%;
-padding-right:2%;
-}
-
-#site_notice {
-position:static;
-float:right;
-clear:right;
-width:75%;
-margin-right:0;
-margin-bottom:11px;
-}
-
-.notices {
-font-size:1.5em;
-}
-
-#form_notice textarea {
-width:80%;
-height:5em;
-}
-#form_notice .form_note {
-right:20%;
-top:6em;
-}
-
-
-.vcard .photo,
-.section .vcard .photo {
-margin-right:18px;
-}
-.notice,
-.profile {
-margin-bottom:18px;
-}
-
-.notices .entry-title,
-.notices div.entry-content {
-width:90%;
-}
-.notice div.entry-content {
-margin-left:0;
-}
-
-.notice .author .photo {
-height:4.5em;
-width:4.5em;
-}
-.notice-options {
-position:absolute;
-top:0;
-right:0;
-padding-left:7%;
-width:3%;
-}
-
-.notice-options .notice_delete a {
-float:left;
-}
-.pagination .nav {
-overflow:auto;
-}
-
-#export_data {
-display:none;
-}
-
-#site_nav_local_views li {
-margin-right:4px;
-}
-#site_nav_local_views a {
-padding:18px 11px;
-}
index 06202a047b40daed0f0ceec269d9025232de4dc6..f93d33d79bce50e1e407c98d534c6cbff24d4366 100644 (file)
Binary files a/theme/base/images/icons/icons-01.gif and b/theme/base/images/icons/icons-01.gif differ
diff --git a/theme/base/images/icons/twotone/green/key.gif b/theme/base/images/icons/twotone/green/key.gif
new file mode 100644 (file)
index 0000000..ccf357a
Binary files /dev/null and b/theme/base/images/icons/twotone/green/key.gif differ
index 8a2c0117520762378b71a0838bec8b789cf5a89f..3aebb239d30c77d7204bee99a54bfe184a59cc7a 100644 (file)
@@ -129,6 +129,7 @@ color:#002FA7;
 
 .notice,
 .profile,
+.application,
 #content tbody tr {
 border-top-color:#C8D1D5;
 }
@@ -187,7 +188,8 @@ button.close,
 .entity_delete input.submit,
 .notice-options .repeated,
 .form_notice label[for=notice_data-geo],
-button.minimize {
+button.minimize,
+.form_reset_key input.submit {
 background-image:url(../../base/images/icons/icons-01.gif);
 background-repeat:no-repeat;
 background-color:transparent;
@@ -332,6 +334,9 @@ background-position: 5px -1445px;
 .entity_delete input.submit {
 background-position: 5px -1511px;
 }
+.form_reset_key input.submit {
+background-position: 5px -1973px;
+}
 
 /* NOTICES */
 .notice .attachment {
@@ -378,6 +383,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 }
 #content .notices li:hover,
+#content .applications li:hover,
 #content tbody tr:hover {
 background-color:rgba(240, 240, 240, 0.2);
 }
index 4ee48459d008763b5b8870ff98f6b409f570bedf..2818196c2091020a4bdf1175cb8cddad3c62a35c 100644 (file)
@@ -129,6 +129,7 @@ color:#002FA7;
 
 .notice,
 .profile,
+.application,
 #content tbody tr {
 border-top-color:#CEE1E9;
 }
@@ -187,7 +188,8 @@ button.close,
 .entity_delete input.submit,
 .notice-options .repeated,
 .form_notice label[for=notice_data-geo],
-button.minimize {
+button.minimize,
+.form_reset_key input.submit {
 background-image:url(../../base/images/icons/icons-01.gif);
 background-repeat:no-repeat;
 background-color:transparent;
@@ -331,6 +333,9 @@ background-position: 5px -1445px;
 .entity_delete input.submit {
 background-position: 5px -1511px;
 }
+.form_reset_key input.submit {
+background-position: 5px -1973px;
+}
 
 /* NOTICES */
 .notice .attachment {
@@ -377,6 +382,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 }
 #content .notices li:hover,
+#content .applications li:hover,
 #content tbody tr:hover {
 background-color:rgba(240, 240, 240, 0.2);
 }